18 KiB
18 KiB
AI生成畫面前端程式碼規格
📋 概述
本文件詳細說明DramaLing AI生成功能的前端程式碼架構、API調用、資料流程,以及如何理解和維護相關程式碼。
🏗️ 檔案架構圖
1. 核心檔案結構
frontend/
├── app/generate/
│ └── page.tsx # 🎯 主分析頁面
├── components/
│ ├── ClickableTextV2.tsx # 🔍 可點擊詞彙組件
│ ├── Navigation.tsx # 🧭 導航組件
│ └── ProtectedRoute.tsx # 🔒 路由保護組件
└── lib/services/
└── flashcards.ts # 💾 詞卡服務層
2. 依賴關係圖
page.tsx
├── imports Navigation.tsx
├── imports ProtectedRoute.tsx
├── imports ClickableTextV2.tsx
└── imports flashcardsService
🔄 API調用架構
1. 主分析頁面 (/app/generate/page.tsx)
調用的API端點:
POST /api/ai/analyze-sentence
調用位置:
// 第40行 - handleAnalyzeSentence函數
const response = await fetch('http://localhost:5000/api/ai/analyze-sentence', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
},
body: JSON.stringify({
inputText: textInput,
userLevel: userLevel, // 個人化重點學習範圍
analysisMode: 'full'
})
})
API回傳資料格式:
{
"success": true,
"data": {
"analysisId": "guid",
"userLevel": "A2",
"highValueCriteria": "B1-B2",
"wordAnalysis": {
"bonus": {
"word": "bonus",
"translation": "獎金",
"definition": "額外給予的金錢",
"partOfSpeech": "noun",
"pronunciation": "/ˈboʊnəs/",
"isHighValue": true,
"difficultyLevel": "B1",
"synonyms": ["reward", "incentive"],
"example": "She received a year-end bonus.",
"exampleTranslation": "她獲得了年終獎金。"
}
},
"sentenceMeaning": {
"translation": "公司提供了獎金。"
},
"grammarCorrection": { /*...*/ },
"highValueWords": ["bonus", "offered"]
}
}
2. 可點擊詞彙組件 (/components/ClickableTextV2.tsx)
調用的API端點:
POST /api/ai/query-word
調用位置有兩處:
位置1: handleCostConfirm函數 (第245行)
const response = await fetch('http://localhost:5000/api/ai/query-word', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
word: showCostConfirm.word,
sentence: text,
analysisId: null
})
})
位置2: queryWordWithAI函數 (第303行)
const response = await fetch('http://localhost:5000/api/ai/query-word', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
word: word,
sentence: text,
analysisId: null
})
})
觸發條件:
- 用戶點擊詞彙時,如果該詞彙不在
analysis物件中 - 用戶確認付費查詢詞彙時
3. 詞卡服務 (/lib/services/flashcards.ts)
調用的API端點:
POST /api/flashcards // 創建詞卡
GET /api/flashcards // 查詢詞卡
GET /api/cardsets // 查詢詞卡組
調用方式:
// 透過flashcardsService.createFlashcard()間接調用
await this.makeRequest<ApiResponse<Flashcard>>('/flashcards', {
method: 'POST',
body: JSON.stringify(data),
});
📊 資料流程架構
1. 完整用戶操作流程
graph TD
A[用戶輸入句子] --> B[點擊分析按鈕]
B --> C[調用 analyze-sentence API]
C --> D[接收完整詞彙分析資料]
D --> E[顯示可點擊文字]
E --> F[用戶點擊詞彙]
F --> G{詞彙在analysis中?}
G -->|是| H[直接顯示Portal彈窗]
G -->|否| I[調用 query-word API]
I --> J[覆蓋原有資料]
J --> K[顯示Portal彈窗]
H --> L[點擊保存詞卡]
K --> L
L --> M[調用 flashcards API]
2. 狀態管理流程
// 主頁面狀態
const [sentenceAnalysis, setSentenceAnalysis] = useState<any>(null) // 完整詞彙分析
const [sentenceMeaning, setSentenceMeaning] = useState('') // 句子翻譯
const [grammarCorrection, setGrammarCorrection] = useState<any>(null) // 語法修正
const [finalText, setFinalText] = useState('') // 最終文本
// ClickableTextV2狀態
const [selectedWord, setSelectedWord] = useState<string | null>(null) // 選中詞彙
const [popupPosition, setPopupPosition] = useState({...}) // 彈窗位置
const [mounted, setMounted] = useState(false) // Portal渲染狀態
3. 資料傳遞路徑
API回應 → setSentenceAnalysis → analysis prop → ClickableTextV2 → Portal彈窗
🎯 組件職責分析
1. /app/generate/page.tsx - 主分析頁面
核心職責:
- 🎯 句子分析觸發器 - 調用AI分析API
- 📊 資料狀態管理 - 管理分析結果和UI狀態
- 🎨 UI佈局控制 - 控制分析前/後的畫面切換
- 🔧 個人化設定 - 取得用戶程度設定
關鍵函數:
handleAnalyzeSentence() // 句子分析主函數
handleSaveWord() // 詞彙儲存函數
handleAcceptCorrection() // 語法修正處理
API依賴:
POST /api/ai/analyze-sentence- 句子分析flashcardsService.createFlashcard()- 詞卡儲存
2. /components/ClickableTextV2.tsx - 可點擊詞彙組件
核心職責:
- 🖱️ 詞彙互動處理 - 處理詞彙點擊事件
- 🎨 Portal彈窗管理 - 使用React Portal渲染彈窗
- 🔍 詞彙資料查找 - 在analysis中查找或即時查詢
- 💾 詞卡儲存整合 - 提供儲存到詞卡功能
關鍵函數:
handleWordClick() // 詞彙點擊處理
queryWordWithAI() // 即時詞彙查詢
getWordProperty() // 智能屬性讀取
VocabPopup() // Portal彈窗組件
API依賴:
POST /api/ai/query-word- 即時詞彙查詢
⚠️ 已知問題:
- 使用
query-wordAPI覆蓋了analyze-sentence的完整資料 - 導致例句和其他資料遺失
3. /components/Navigation.tsx - 導航組件
核心職責:
- 🧭 頁面導航 - 提供網站主要頁面連結
- 👤 用戶狀態顯示 - 顯示登入狀態
- ⚙️ 設定頁面入口 - 連結到用戶程度設定
API依賴:無直接API調用
4. /lib/services/flashcards.ts - 詞卡服務層
核心職責:
- 💾 詞卡CRUD操作 - 創建、讀取、更新、刪除詞卡
- 🗂️ 詞卡組管理 - 管理詞卡分類
- 🔒 API認證處理 - 自動添加JWT Token
API端點封裝:
/api/flashcards // 詞卡CRUD
/api/cardsets // 詞卡組管理
/api/cardsets/ensure-default // 確保預設詞卡組
🔍 如何分析程式碼中的API調用
1. 搜索技巧
在VS Code或終端中:
# 搜索API調用
grep -r "fetch(" frontend/
grep -r "api/" frontend/
grep -r "localhost:5000" frontend/
# 搜索特定API端點
grep -r "analyze-sentence" frontend/
grep -r "query-word" frontend/
grep -r "flashcards" frontend/
在瀏覽器開發者工具中:
- Network面板 - 查看實際API調用
- Console面板 - 查看調試輸出
- Application面板 - 查看localStorage資料
2. 程式碼閱讀要點
識別API調用的關鍵字:
// 直接API調用
fetch('http://localhost:5000/api/...')
await fetch(...)
// 服務層調用
flashcardsService.createFlashcard()
flashcardsService.getFlashcards()
// 其他HTTP客戶端
axios.post(...)
找到觸發條件:
// 用戶事件觸發
onClick={handleAnalyzeSentence}
onClick={(e) => handleWordClick(word, e)}
// 狀態變化觸發
useEffect(() => { /* API調用 */ }, [dependency])
3. 資料流追蹤
API回應到狀態:
const result = await response.json()
setSentenceAnalysis(result.data.WordAnalysis) // 儲存到狀態
狀態到組件:
<ClickableTextV2
analysis={sentenceAnalysis} // 傳遞給子組件
onSaveWord={handleSaveWord} // 回調函數
/>
🚨 當前架構問題分析
1. API調用衝突問題
問題描述:
- 主頁面 調用
analyze-sentenceAPI → 取得完整詞彙資料(包含例句) - 詞彙組件 調用
query-wordAPI → 取得簡化資料(無例句) - 結果 → 好資料被壞資料覆蓋
程式碼位置:
// ✅ 正確的API (page.tsx:40)
POST /api/ai/analyze-sentence → 完整資料
// ❌ 問題的API (ClickableTextV2.tsx:245, 303)
POST /api/ai/query-word → 簡化資料
觸發條件:
// ClickableTextV2.tsx:221
if (wordAnalysis) {
// 使用預存資料 ✅
} else {
// 調用 query-word API ❌
await queryWordWithAI(cleanWord, position)
}
2. 資料不一致問題
analyze-sentence 回傳:
{
"example": "She received a year-end bonus for her hard work.",
"exampleTranslation": "她因為努力工作獲得了年終獎金。",
"synonyms": ["reward", "incentive", "extra pay"]
}
query-word 回傳:
{
"example": null,
"exampleTranslation": null,
"synonyms": []
}
🎨 UI組件架構
1. Portal彈窗系統
技術實現:
import { createPortal } from 'react-dom'
const VocabPopup = () => {
if (!selectedWord || !analysis?.[selectedWord] || !mounted) return null
return createPortal(
<div className="fixed z-50 bg-white rounded-xl shadow-lg w-96">
{/* 彈窗內容 */}
</div>,
document.body // 渲染到body,避免CSS繼承
)
}
設計優勢:
- 完全脫離父級CSS繼承
- 響應式定位系統
- 詞卡風格一致性
2. 個人化標記系統
詞彙分類邏輯:
const getWordClass = (word: string) => {
const wordAnalysis = analysis?.[cleanWord]
const isHighValue = getWordProperty(wordAnalysis, 'isHighValue')
if (isHighValue) {
return "bg-green-100 border-green-400 hover:bg-green-200" // 重點學習
} else {
return "bg-blue-100 border-blue-300 hover:bg-blue-200" // 普通詞彙
}
}
視覺效果:
- 重點學習詞彙 → 綠色邊框 + ⭐ 標記
- 普通詞彙 → 藍色邊框
- 未分析詞彙 → 灰色虛線邊框
📊 狀態管理架構
1. 主頁面狀態流
// 分析階段
[textInput] → handleAnalyzeSentence() → [sentenceAnalysis]
↓
[sentenceMeaning]
↓
[grammarCorrection]
// 顯示階段
[sentenceAnalysis] → ClickableTextV2 → Portal彈窗
2. 詞彙組件狀態流
// 點擊階段
handleWordClick() → [selectedWord] + [popupPosition]
↓
VocabPopup() Portal渲染
// 儲存階段
handleSaveWord() → flashcardsService.createFlashcard()
3. 個人化設定流
localStorage.getItem('userEnglishLevel') → API請求 → 個人化結果
🔧 關鍵技術實現
1. Portal彈窗技術
為什麼使用Portal:
// ❌ 舊方式 - CSS繼承問題
<div className="relative">
<div className="text-lg">可點擊文字</div>
<div className="fixed popup">彈窗</div> // 會繼承text-lg
</div>
// ✅ Portal方式 - 完全隔離
<div className="relative">
<div className="text-lg">可點擊文字</div>
</div>
{createPortal(
<div className="fixed popup">彈窗</div>, // 渲染到body,不繼承
document.body
)}
2. 智能屬性讀取
解決大小寫不一致:
const getWordProperty = (wordData: any, propName: string) => {
const variations = [
propName, // 原始
propName.toLowerCase(), // 小寫
propName.charAt(0).toUpperCase() + propName.slice(1) // 首字母大寫
];
for (const variation of variations) {
if (wordData[variation] !== undefined) {
return wordData[variation];
}
}
}
3. 個人化重點學習範圍
前端整合:
// 讀取用戶程度
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2';
// 傳遞給API
body: JSON.stringify({
inputText: textInput,
userLevel: userLevel, // 個人化參數
analysisMode: 'full'
})
// 顯示重點學習範圍
const getTargetRange = (level: string) => {
const ranges = {
'A1': 'A2-B1', 'A2': 'B1-B2', 'B1': 'B2-C1',
'B2': 'C1-C2', 'C1': 'C2', 'C2': 'C2'
};
return ranges[level] || 'B1-B2';
};
🛠️ 開發維護指南
1. 如何添加新的API調用
步驟:
- 選擇調用位置 - 頁面組件或服務層
- 定義請求格式 - TypeScript介面
- 處理回應資料 - 錯誤處理和狀態更新
- 更新UI狀態 - 觸發重新渲染
範例:
// 1. 定義介面
interface NewApiRequest {
input: string;
options: object;
}
// 2. API調用
const callNewApi = async (data: NewApiRequest) => {
try {
const response = await fetch('/api/new-endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
const result = await response.json();
// 3. 更新狀態
setNewData(result.data);
}
} catch (error) {
console.error('API調用失敗:', error);
}
};
2. 如何修改詞彙顯示邏輯
修改位置:
// 詞彙分類邏輯
ClickableTextV2.tsx → getWordClass() 函數
// 彈窗內容
ClickableTextV2.tsx → VocabPopup() 組件
// 屬性讀取
ClickableTextV2.tsx → getWordProperty() 函數
3. 如何添加新的詞彙屬性
步驟:
- 後端API - 確保API回傳新屬性
- 前端介面 - 更新TypeScript介面
- 屬性讀取 - 在
getWordProperty中處理 - UI顯示 - 在Portal彈窗中顯示
🔍 問題診斷指南
1. API調用問題
檢查步驟:
// 1. 檢查Network面板
// 瀏覽器 → F12 → Network → 查看API調用
// 2. 檢查Console輸出
console.log('API回應:', result);
// 3. 檢查回應格式
console.log('詞彙資料:', result.data.WordAnalysis?.bonus);
常見問題:
- API端點錯誤 - 檢查URL是否正確
- 請求格式錯誤 - 檢查Content-Type和body
- 認證問題 - 檢查JWT Token
2. 資料顯示問題
檢查步驟:
// 1. 檢查狀態
console.log('sentenceAnalysis:', sentenceAnalysis);
// 2. 檢查組件接收
console.log('analysis prop:', analysis);
// 3. 檢查屬性讀取
console.log('getWordProperty結果:', getWordProperty(wordData, 'example'));
3. Portal彈窗問題
檢查步驟:
// 1. 檢查Portal渲染條件
console.log('selectedWord:', selectedWord);
console.log('mounted:', mounted);
// 2. 檢查彈窗位置
console.log('popupPosition:', popupPosition);
// 3. 檢查CSS樣式
// 瀏覽器 → F12 → Elements → 檢查Portal元素
🚀 最佳實踐建議
1. API調用
- ✅ 統一使用服務層 - 避免直接在組件中調用API
- ✅ 錯誤處理 - 每個API調用都要有try-catch
- ✅ loading狀態 - 提供用戶反饋
- ✅ 快取策略 - 避免重複調用相同API
2. 狀態管理
- ✅ 單一資料來源 - 避免狀態重複
- ✅ 明確的狀態型別 - 使用TypeScript介面
- ✅ 適當的狀態粒度 - 不要過度細分或合併
3. 組件設計
- ✅ 職責單一 - 每個組件專注一個功能
- ✅ Props介面 - 明確定義組件輸入
- ✅ 可重用性 - 組件應該可以在多處使用
📝 未來改進方向
1. 統一API策略
- 合併
analyze-sentence和query-word的功能 - 建立統一的詞彙分析端點
- 減少API調用複雜度
2. 效能優化
- 實現詞彙分析結果快取
- 減少不必要的API調用
- 優化Portal渲染效能
3. 用戶體驗提升
- 添加載入動畫
- 優化錯誤處理和用戶提示
- 增強響應式設計
文件版本: v1.0 建立日期: 2025-09-21 維護團隊: DramaLing開發團隊
📞 技術支援
如需修改或擴展AI生成功能,請參考本規格文件的相關章節,並遵循最佳實踐建議進行開發。