新增 logo、優化 AI 問答
This commit is contained in:
@@ -152,8 +152,12 @@ export default function CompetitionPage() {
|
|||||||
返回主頁
|
返回主頁
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
|
<div className="h-8 rounded-lg flex items-center justify-center">
|
||||||
<Trophy className="w-5 h-5 text-white" />
|
<img
|
||||||
|
src="/logo.png"
|
||||||
|
alt="強茂集團 AI 展示平台"
|
||||||
|
className="h-8 object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold text-gray-900">競賽專區</h1>
|
<h1 className="text-xl font-bold text-gray-900">競賽專區</h1>
|
||||||
|
@@ -15,6 +15,13 @@ export default function RootLayout({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="zh-TW">
|
<html lang="zh-TW">
|
||||||
|
<head>
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
|
<link rel="icon" href="/ai-cloud.png" type="image/png" />
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" href="/ai-cloud.png" />
|
||||||
|
<meta name="theme-color" content="#3b82f6" />
|
||||||
|
</head>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<CompetitionProvider>
|
<CompetitionProvider>
|
||||||
@@ -29,5 +36,15 @@ export default function RootLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
generator: 'v0.dev'
|
title: '強茂集團 AI 展示平台',
|
||||||
};
|
description: '企業內部 AI 應用展示與競賽管理系統',
|
||||||
|
generator: 'v0.dev',
|
||||||
|
icons: {
|
||||||
|
icon: [
|
||||||
|
{ url: '/favicon.ico', sizes: 'any' },
|
||||||
|
{ url: '/ai-cloud.png', type: 'image/png' }
|
||||||
|
],
|
||||||
|
shortcut: '/favicon.ico',
|
||||||
|
apple: '/ai-cloud.png',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@@ -479,8 +479,12 @@ export default function AIShowcasePlatform() {
|
|||||||
<div className="flex items-center justify-between h-16">
|
<div className="flex items-center justify-between h-16">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="flex items-center space-x-2 cursor-pointer" onClick={() => setShowCompetition(false)}>
|
<div className="flex items-center space-x-2 cursor-pointer" onClick={() => setShowCompetition(false)}>
|
||||||
<div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
|
<div className="h-8 rounded-lg flex items-center justify-center">
|
||||||
<Brain className="w-5 h-5 text-white" />
|
<img
|
||||||
|
src="/logo.png"
|
||||||
|
alt="強茂集團 AI 展示平台"
|
||||||
|
className="h-8 object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold text-gray-900">強茂集團 AI 展示平台</h1>
|
<h1 className="text-xl font-bold text-gray-900">強茂集團 AI 展示平台</h1>
|
||||||
|
@@ -176,8 +176,12 @@ export default function RegisterPage() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<div className="flex items-center justify-center space-x-2 mb-4">
|
<div className="flex items-center justify-center space-x-2 mb-4">
|
||||||
<div className="w-10 h-10 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
|
<div className="h-10 rounded-lg flex items-center justify-center">
|
||||||
<Brain className="w-6 h-6 text-white" />
|
<img
|
||||||
|
src="/logo.png"
|
||||||
|
alt="強茂集團 AI 展示平台"
|
||||||
|
className="h-10 object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">強茂集團 AI 展示平台</h1>
|
<h1 className="text-2xl font-bold text-gray-900">強茂集團 AI 展示平台</h1>
|
||||||
|
@@ -264,8 +264,12 @@ export function AdminLayout({ children, currentPage, onPageChange }: AdminLayout
|
|||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="p-4 border-b">
|
<div className="p-4 border-b">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
|
<div className="h-8 rounded-lg flex items-center justify-center">
|
||||||
<Settings className="w-5 h-5 text-white" />
|
<img
|
||||||
|
src="/logo.png"
|
||||||
|
alt="強茂集團 AI 展示平台"
|
||||||
|
className="h-8 object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{sidebarOpen && (
|
{sidebarOpen && (
|
||||||
<div>
|
<div>
|
||||||
|
@@ -25,8 +25,8 @@ interface Message {
|
|||||||
quickQuestions?: string[]
|
quickQuestions?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEEPSEEK_API_KEY = process.env.NEXT_PUBLIC_DEEPSEEK_API_KEY || "sk-30cac9533e5b451fa1e277fe34a7f64b"
|
const GEMINI_API_KEY = process.env.NEXT_PUBLIC_GEMINI_API_KEY || "AIzaSyAN3pEJr_Vn2xkCidGZAq9eQqsMVvpj8g4"
|
||||||
const DEEPSEEK_API_URL = process.env.NEXT_PUBLIC_DEEPSEEK_API_URL || "https://api.deepseek.com/v1/chat/completions"
|
const GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent"
|
||||||
|
|
||||||
const systemPrompt = generateSystemPrompt()
|
const systemPrompt = generateSystemPrompt()
|
||||||
|
|
||||||
@@ -77,63 +77,62 @@ export function ChatBot() {
|
|||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
const callDeepSeekAPI = async (userMessage: string): Promise<string> => {
|
const callGeminiAPI = async (userMessage: string): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
// 構建對話歷史,只保留最近的幾條對話
|
// 構建對話歷史,只保留最近的幾條對話
|
||||||
const recentMessages = messages
|
const recentMessages = messages
|
||||||
.filter(msg => msg.sender === "user")
|
.filter(msg => msg.sender === "user")
|
||||||
.slice(-5) // 只保留最近5條用戶消息
|
.slice(-5) // 只保留最近5條用戶消息
|
||||||
.map(msg => ({
|
.map(msg => msg.text)
|
||||||
role: "user" as const,
|
|
||||||
content: msg.text
|
|
||||||
}))
|
|
||||||
|
|
||||||
const response = await fetch(DEEPSEEK_API_URL, {
|
// 構建完整的對話內容
|
||||||
|
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",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
"Authorization": `Bearer ${DEEPSEEK_API_KEY}`
|
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: "deepseek-chat",
|
contents: [{
|
||||||
messages: [
|
parts: [{
|
||||||
{
|
text: fullPrompt
|
||||||
role: "system",
|
}]
|
||||||
content: systemPrompt
|
}],
|
||||||
},
|
generationConfig: {
|
||||||
...recentMessages,
|
maxOutputTokens: 300,
|
||||||
{
|
temperature: 0.7,
|
||||||
role: "user",
|
topP: 0.8,
|
||||||
content: userMessage
|
topK: 10
|
||||||
}
|
}
|
||||||
],
|
|
||||||
max_tokens: 300,
|
|
||||||
temperature: 0.7,
|
|
||||||
stream: false
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text()
|
const errorText = await response.text()
|
||||||
console.error("API Error:", response.status, errorText)
|
console.error("Gemini API Error:", response.status, errorText)
|
||||||
throw new Error(`API request failed: ${response.status} - ${errorText}`)
|
throw new Error(`API request failed: ${response.status} - ${errorText}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
if (!data.candidates || !data.candidates[0] || !data.candidates[0].content) {
|
||||||
console.error("Invalid API response:", data)
|
console.error("Invalid Gemini API response:", data)
|
||||||
throw new Error("Invalid API response format")
|
throw new Error("Invalid API response format")
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawResponse = data.choices[0].message.content || "抱歉,我現在無法回答您的問題,請稍後再試。"
|
const rawResponse = data.candidates[0].content.parts[0].text || "抱歉,我現在無法回答您的問題,請稍後再試。"
|
||||||
return cleanResponse(rawResponse)
|
return cleanResponse(rawResponse)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("DeepSeek API error:", error)
|
console.error("Gemini API error:", error)
|
||||||
|
|
||||||
// 根據錯誤類型提供不同的錯誤信息
|
// 根據錯誤類型提供不同的錯誤信息
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
if (error.message.includes('401')) {
|
if (error.message.includes('401') || error.message.includes('403')) {
|
||||||
return "API 密鑰無效,請聯繫管理員檢查配置。"
|
return "API 密鑰無效,請聯繫管理員檢查配置。"
|
||||||
} else if (error.message.includes('429')) {
|
} else if (error.message.includes('429')) {
|
||||||
return "API 請求過於頻繁,請稍後再試。"
|
return "API 請求過於頻繁,請稍後再試。"
|
||||||
@@ -261,7 +260,7 @@ export function ChatBot() {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const aiResponse = await callDeepSeekAPI(text)
|
const aiResponse = await callGeminiAPI(text)
|
||||||
|
|
||||||
const botMessage: Message = {
|
const botMessage: Message = {
|
||||||
id: (Date.now() + 1).toString(),
|
id: (Date.now() + 1).toString(),
|
||||||
|
@@ -133,14 +133,20 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
const competitionsData = await competitionsResponse.json()
|
const competitionsData = await competitionsResponse.json()
|
||||||
console.log('📋 競賽API回應:', competitionsData)
|
console.log('📋 競賽API回應:', competitionsData)
|
||||||
|
|
||||||
if (competitionsData.success && competitionsData.data) {
|
if (competitionsData.success) {
|
||||||
// 確保每個競賽都有judges屬性
|
if (competitionsData.data && competitionsData.data.length > 0) {
|
||||||
const competitionsWithJudges = competitionsData.data.map((comp: any) => ({
|
// 確保每個競賽都有judges屬性
|
||||||
...comp,
|
const competitionsWithJudges = competitionsData.data.map((comp: any) => ({
|
||||||
judges: comp.judges || []
|
...comp,
|
||||||
}))
|
judges: comp.judges || []
|
||||||
setCompetitions(competitionsWithJudges)
|
}))
|
||||||
console.log('✅ 競賽數據載入成功:', competitionsWithJudges.length, '個競賽')
|
setCompetitions(competitionsWithJudges)
|
||||||
|
console.log('✅ 競賽數據載入成功:', competitionsWithJudges.length, '個競賽')
|
||||||
|
} else {
|
||||||
|
// 沒有競賽資料是正常情況,不報錯
|
||||||
|
setCompetitions([])
|
||||||
|
console.log('ℹ️ 暫無競賽數據')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 競賽數據載入失敗:', competitionsData.message)
|
console.error('❌ 競賽數據載入失敗:', competitionsData.message)
|
||||||
// 設置空數組以避免undefined錯誤
|
// 設置空數組以避免undefined錯誤
|
||||||
@@ -152,14 +158,20 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
const currentData = await currentResponse.json()
|
const currentData = await currentResponse.json()
|
||||||
console.log('🏆 當前競賽API回應:', currentData)
|
console.log('🏆 當前競賽API回應:', currentData)
|
||||||
|
|
||||||
if (currentData.success && currentData.data) {
|
if (currentData.success) {
|
||||||
// 確保當前競賽也有judges屬性
|
if (currentData.data) {
|
||||||
const currentCompetitionWithJudges = {
|
// 確保當前競賽也有judges屬性
|
||||||
...currentData.data,
|
const currentCompetitionWithJudges = {
|
||||||
judges: currentData.data.judges || []
|
...currentData.data,
|
||||||
|
judges: currentData.data.judges || []
|
||||||
|
}
|
||||||
|
setCurrentCompetition(currentCompetitionWithJudges)
|
||||||
|
console.log('✅ 當前競賽載入成功:', currentCompetitionWithJudges.name)
|
||||||
|
} else {
|
||||||
|
// 沒有當前競賽是正常情況,不報錯
|
||||||
|
setCurrentCompetition(null)
|
||||||
|
console.log('ℹ️ 暫無當前競賽')
|
||||||
}
|
}
|
||||||
setCurrentCompetition(currentCompetitionWithJudges)
|
|
||||||
console.log('✅ 當前競賽載入成功:', currentCompetitionWithJudges.name)
|
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 當前競賽載入失敗:', currentData.message)
|
console.error('❌ 當前競賽載入失敗:', currentData.message)
|
||||||
}
|
}
|
||||||
@@ -170,9 +182,14 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
const judgesData = await judgesResponse.json()
|
const judgesData = await judgesResponse.json()
|
||||||
console.log('評審API回應:', judgesData)
|
console.log('評審API回應:', judgesData)
|
||||||
|
|
||||||
if (judgesData.success && judgesData.data) {
|
if (judgesData.success) {
|
||||||
setJudges(judgesData.data)
|
if (judgesData.data && judgesData.data.length > 0) {
|
||||||
console.log('✅ 評審數據載入成功:', judgesData.data.length, '個評審')
|
setJudges(judgesData.data)
|
||||||
|
console.log('✅ 評審數據載入成功:', judgesData.data.length, '個評審')
|
||||||
|
} else {
|
||||||
|
setJudges([])
|
||||||
|
console.log('ℹ️ 暫無評審數據')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ 評審數據載入失敗:', judgesData.message)
|
console.error('❌ 評審數據載入失敗:', judgesData.message)
|
||||||
setJudges([])
|
setJudges([])
|
||||||
|
@@ -28,7 +28,12 @@ DB_CONNECTION_TIMEOUT=5000
|
|||||||
DB_RETRY_ATTEMPTS=3
|
DB_RETRY_ATTEMPTS=3
|
||||||
DB_RETRY_DELAY=2000
|
DB_RETRY_DELAY=2000
|
||||||
|
|
||||||
# DeepSeek API 配置
|
# ===== AI API 配置 =====
|
||||||
|
# Gemini API 配置 (主要使用)
|
||||||
|
NEXT_PUBLIC_GEMINI_API_KEY=AIzaSyAN3pEJr_Vn2xkCidGZAq9eQqsMVvpj8g4
|
||||||
|
NEXT_PUBLIC_GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent
|
||||||
|
|
||||||
|
# DeepSeek API 配置 (備用)
|
||||||
NEXT_PUBLIC_DEEPSEEK_API_KEY=your_deepseek_api_key_here
|
NEXT_PUBLIC_DEEPSEEK_API_KEY=your_deepseek_api_key_here
|
||||||
NEXT_PUBLIC_DEEPSEEK_API_URL=https://api.deepseek.com/v1/chat/completions
|
NEXT_PUBLIC_DEEPSEEK_API_URL=https://api.deepseek.com/v1/chat/completions
|
||||||
|
|
||||||
|
@@ -194,13 +194,13 @@ export const platformKnowledge = {
|
|||||||
|
|
||||||
// 常見問題
|
// 常見問題
|
||||||
faq: {
|
faq: {
|
||||||
"忘記密碼怎麼辦": "點擊登入頁面的「忘記密碼」連結,輸入註冊時的電子郵件,系統會發送重設密碼的連結到您的信箱。",
|
"忘記密碼怎麼辦": "1. 點擊登入頁面「忘記密碼」\n2. 輸入註冊電子郵件\n3. 檢查信箱重設連結\n4. 按連結重設密碼",
|
||||||
"如何修改個人資料": "登入後點擊右上角頭像,選擇「個人資料」,即可修改姓名、部門、頭像等信息。",
|
"如何修改個人資料": "1. 登入後點擊右上角頭像\n2. 選擇「個人資料」\n3. 修改姓名、部門、頭像\n4. 保存更改",
|
||||||
"為什麼看不到某些競賽": "可能是因為競賽尚未開始、已結束,或者您沒有參與權限。請聯繫管理員確認。",
|
"為什麼看不到某些競賽": "可能原因:\n• 競賽尚未開始\n• 競賽已結束\n• 沒有參與權限\n請聯繫管理員確認",
|
||||||
"評分什麼時候會公布": "評分結果會在競賽結束後由管理員統一公布,請關注平台通知。",
|
"評分什麼時候會公布": "評分公布時間:\n• 競賽結束後\n• 由管理員統一公布\n• 關注平台通知",
|
||||||
"如何聯繫管理員": "可以通過平台內的通知系統或直接發送郵件給管理員。",
|
"如何聯繫管理員": "聯繫方式:\n• 平台內通知系統\n• 直接發送郵件\n• 查看管理員聯絡資訊",
|
||||||
"作品提交後可以修改嗎": "作品提交後無法修改,請在提交前仔細檢查所有信息。",
|
"作品提交後可以修改嗎": "作品提交規則:\n• 提交後無法修改\n• 請提交前仔細檢查\n• 確認所有信息正確",
|
||||||
"如何查看我的參賽記錄": "登入後進入「我的競賽」頁面,可以查看所有參賽記錄和狀態。"
|
"如何查看我的參賽記錄": "查看步驟:\n1. 登入平台\n2. 進入「我的競賽」\n3. 查看參賽記錄和狀態"
|
||||||
},
|
},
|
||||||
|
|
||||||
// 技術信息
|
// 技術信息
|
||||||
@@ -235,13 +235,15 @@ ${Object.entries(platformKnowledge.modules.backend).map(([key, value]) =>
|
|||||||
|
|
||||||
回答指南:
|
回答指南:
|
||||||
1. 用友善、專業的語氣回答用戶問題
|
1. 用友善、專業的語氣回答用戶問題
|
||||||
2. 提供具體的操作步驟和實用建議
|
2. 回答要簡潔明瞭,優先使用條列式格式
|
||||||
3. 回答要簡潔明瞭,避免過長的文字
|
3. 操作步驟用數字編號,每個步驟一行
|
||||||
4. 如果問題涉及具體操作,請提供詳細步驟
|
4. 重要信息用簡短要點列出
|
||||||
5. 如果不知道答案,請誠實說明並建議聯繫管理員
|
5. 避免長段落文字,多用換行分段
|
||||||
6. 不要使用任何 Markdown 格式,只使用純文字回答
|
6. 如果不知道答案,請誠實說明並建議聯繫管理員
|
||||||
7. 不要使用 **、*、#、- 等符號
|
7. 不要使用任何 Markdown 格式,只使用純文字回答
|
||||||
8. 回答長度控制在 200 字以內
|
8. 不要使用 **、*、#、- 等符號
|
||||||
|
9. 回答長度控制在 150 字以內
|
||||||
|
10. 優先提供實用的操作步驟和要點
|
||||||
|
|
||||||
常見問題快速回答:
|
常見問題快速回答:
|
||||||
${Object.entries(platformKnowledge.faq).map(([question, answer]) =>
|
${Object.entries(platformKnowledge.faq).map(([question, answer]) =>
|
||||||
|
BIN
public/ai-cloud.png
Normal file
BIN
public/ai-cloud.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Reference in New Issue
Block a user