"use client" import { useState, useRef, useEffect } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { ScrollArea } from "@/components/ui/scroll-area" import { MessageCircle, X, Send, Bot, User, Loader2 } from "lucide-react" import { generateSystemPrompt } from "@/lib/ai-knowledge-base" interface Message { id: string text: string sender: "user" | "bot" timestamp: Date quickQuestions?: string[] } const GEMINI_API_KEY = process.env.NEXT_PUBLIC_GEMINI_API_KEY || "AIzaSyAN3pEJr_Vn2xkCidGZAq9eQqsMVvpj8g4" const GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent" const systemPrompt = generateSystemPrompt() export function ChatBot() { const [isOpen, setIsOpen] = useState(false) const [messages, setMessages] = useState([ { id: "1", text: "您好!我是AI助手,很高興為您服務。我可以協助您了解競賽管理系統的使用方法,包括後台管理和前台功能。請問有什麼可以幫助您的嗎?", sender: "bot", timestamp: new Date(), quickQuestions: [ "如何註冊參賽團隊?", "怎麼提交作品?", "如何創建新競賽?", "怎麼管理評審團?" ] } ]) const [inputValue, setInputValue] = useState("") const [isTyping, setIsTyping] = useState(false) const [isLoading, setIsLoading] = useState(false) const messagesEndRef = useRef(null) const inputRef = useRef(null) const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) } useEffect(() => { scrollToBottom() }, [messages]) // 清理 Markdown 格式和過長文字 const cleanResponse = (text: string): string => { return text // 移除 Markdown 格式 .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/#{1,6}\s/g, '') .replace(/^- /g, '• ') .replace(/^\d+\.\s/g, '') // 移除多餘的空行 .replace(/\n\s*\n\s*\n/g, '\n\n') // 限制文字長度,如果太長就截斷並添加省略號 .slice(0, 300) .trim() } const callGeminiAPI = async (userMessage: string): Promise => { try { // 構建對話歷史,只保留最近的幾條對話 const recentMessages = messages .filter(msg => msg.sender === "user") .slice(-5) // 只保留最近5條用戶消息 .map(msg => msg.text) // 構建完整的對話內容 const conversationHistory = recentMessages.length > 0 ? `之前的對話:\n${recentMessages.map(msg => `用戶:${msg}`).join('\n')}\n\n` : '' const fullPrompt = `${systemPrompt}\n\n${conversationHistory}用戶:${userMessage}` const response = await fetch(`${GEMINI_API_URL}?key=${GEMINI_API_KEY}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contents: [{ parts: [{ text: fullPrompt }] }], generationConfig: { maxOutputTokens: 300, temperature: 0.7, topP: 0.8, topK: 10 } }) }) if (!response.ok) { const errorText = await response.text() console.error("Gemini API Error:", response.status, errorText) throw new Error(`API request failed: ${response.status} - ${errorText}`) } const data = await response.json() if (!data.candidates || !data.candidates[0] || !data.candidates[0].content) { console.error("Invalid Gemini API response:", data) throw new Error("Invalid API response format") } const rawResponse = data.candidates[0].content.parts[0].text || "抱歉,我現在無法回答您的問題,請稍後再試。" return cleanResponse(rawResponse) } catch (error) { console.error("Gemini API error:", error) // 根據錯誤類型提供不同的錯誤信息 if (error instanceof Error) { if (error.message.includes('401') || error.message.includes('403')) { return "API 密鑰無效,請聯繫管理員檢查配置。" } else if (error.message.includes('429')) { return "API 請求過於頻繁,請稍後再試。" } else if (error.message.includes('500')) { return "AI 服務暫時不可用,請稍後再試。" } } return "抱歉,我現在無法連接到AI服務,請檢查網路連接或稍後再試。" } } // 根據用戶問題生成相關的快速問題 const generateQuickQuestions = (userQuestion: string): string[] => { const question = userQuestion.toLowerCase() // 前台相關問題 if (question.includes('註冊') || question.includes('團隊') || question.includes('報名')) { return [ "如何提交作品?", "怎麼查看競賽詳情?", "如何收藏應用?", "怎麼查看我的參賽記錄?" ] } if (question.includes('作品') || question.includes('提交') || question.includes('應用')) { return [ "如何修改作品信息?", "怎麼查看作品狀態?", "如何刪除作品?", "怎麼查看作品評價?" ] } if (question.includes('投票') || question.includes('排行榜') || question.includes('評分')) { return [ "如何查看排行榜?", "怎麼收藏喜歡的應用?", "如何對應用進行評論?", "怎麼查看應用詳情?" ] } if (question.includes('個人') || question.includes('資料') || question.includes('設置')) { return [ "如何修改個人資料?", "怎麼查看我的收藏?", "如何修改密碼?", "怎麼查看通知?" ] } // 後台管理相關問題 if (question.includes('競賽') || question.includes('創建') || question.includes('管理')) { return [ "如何編輯競賽信息?", "怎麼設定評分標準?", "如何管理參賽團隊?", "怎麼設定獎項?" ] } if (question.includes('評審') || question.includes('評分') || question.includes('評委')) { return [ "如何新增評審成員?", "怎麼設定評審權限?", "如何查看評分結果?", "怎麼生成評審連結?" ] } if (question.includes('應用') || question.includes('app') || question.includes('作品管理')) { return [ "如何審核應用?", "怎麼管理應用狀態?", "如何查看應用統計?", "怎麼處理應用舉報?" ] } if (question.includes('用戶') || question.includes('成員') || question.includes('邀請')) { return [ "如何邀請新用戶?", "怎麼管理用戶角色?", "如何查看用戶統計?", "怎麼處理用戶問題?" ] } // 技術支持相關問題 if (question.includes('忘記') || question.includes('密碼') || question.includes('登入')) { return [ "如何重設密碼?", "怎麼修改個人資料?", "如何聯繫管理員?", "怎麼查看使用說明?" ] } if (question.includes('錯誤') || question.includes('問題') || question.includes('無法')) { return [ "如何聯繫技術支持?", "怎麼查看常見問題?", "如何回報問題?", "怎麼查看系統狀態?" ] } // 通用問題 return [ "如何註冊參賽團隊?", "怎麼提交作品?", "如何創建新競賽?", "怎麼管理評審團?" ] } const handleSendMessage = async (text: string) => { if (!text.trim() || isLoading) return const userMessage: Message = { id: Date.now().toString(), text: text.trim(), sender: "user", timestamp: new Date() } setMessages(prev => [...prev, userMessage]) setInputValue("") setIsTyping(true) setIsLoading(true) try { const aiResponse = await callGeminiAPI(text) const botMessage: Message = { id: (Date.now() + 1).toString(), text: aiResponse, sender: "bot", timestamp: new Date(), quickQuestions: generateQuickQuestions(text) } setMessages(prev => [...prev, botMessage]) } catch (error) { const errorMessage: Message = { id: (Date.now() + 1).toString(), text: "抱歉,發生錯誤,請稍後再試。", sender: "bot", timestamp: new Date() } setMessages(prev => [...prev, errorMessage]) } finally { setIsTyping(false) setIsLoading(false) } } const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault() handleSendMessage(inputValue) } } return ( <> {/* 浮動按鈕 */} {/* 聊天對話框 */} {isOpen && (
AI 助手 在線 {/* 訊息區域 */}
{messages.map((message) => (
{message.sender === "user" ? : }
{message.text} {/* 快速問題按鈕 */} {message.sender === "bot" && message.quickQuestions && message.quickQuestions.length > 0 && (
您可能還想問:
{message.quickQuestions.map((question, index) => ( ))}
)}
))} {isTyping && (
AI 正在思考...
)}
{/* 輸入區域 */}
setInputValue(e.target.value)} onKeyPress={handleKeyPress} placeholder="輸入您的問題..." className="w-full" disabled={isLoading} />
)} ) }