dramaling-vocab-learning/AI生成畫面前端程式碼規格.md

695 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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端點**
```typescript
POST /api/ai/analyze-sentence
```
#### **調用位置**
```typescript
// 第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回傳資料格式**
```json
{
"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端點**
```typescript
POST /api/ai/query-word
```
#### **調用位置有兩處**
##### **位置1: handleCostConfirm函數 (第245行)**
```typescript
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行)**
```typescript
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端點**
```typescript
POST /api/flashcards // 創建詞卡
GET /api/flashcards // 查詢詞卡
GET /api/cardsets // 查詢詞卡組
```
#### **調用方式**
```typescript
// 透過flashcardsService.createFlashcard()間接調用
await this.makeRequest<ApiResponse<Flashcard>>('/flashcards', {
method: 'POST',
body: JSON.stringify(data),
});
```
---
## 📊 **資料流程架構**
### **1. 完整用戶操作流程**
```mermaid
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. 狀態管理流程**
```typescript
// 主頁面狀態
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佈局控制** - 控制分析前/後的畫面切換
- 🔧 **個人化設定** - 取得用戶程度設定
#### **關鍵函數**
```typescript
handleAnalyzeSentence() // 句子分析主函數
handleSaveWord() // 詞彙儲存函數
handleAcceptCorrection() // 語法修正處理
```
#### **API依賴**
- `POST /api/ai/analyze-sentence` - 句子分析
- `flashcardsService.createFlashcard()` - 詞卡儲存
### **2. `/components/ClickableTextV2.tsx` - 可點擊詞彙組件**
#### **核心職責**
- 🖱️ **詞彙互動處理** - 處理詞彙點擊事件
- 🎨 **Portal彈窗管理** - 使用React Portal渲染彈窗
- 🔍 **詞彙資料查找** - 在analysis中查找或即時查詢
- 💾 **詞卡儲存整合** - 提供儲存到詞卡功能
#### **關鍵函數**
```typescript
handleWordClick() // 詞彙點擊處理
queryWordWithAI() // 即時詞彙查詢
getWordProperty() // 智能屬性讀取
VocabPopup() // Portal彈窗組件
```
#### **API依賴**
- `POST /api/ai/query-word` - 即時詞彙查詢
#### **⚠️ 已知問題**
- 使用`query-word` API覆蓋了`analyze-sentence`的完整資料
- 導致例句和其他資料遺失
### **3. `/components/Navigation.tsx` - 導航組件**
#### **核心職責**
- 🧭 **頁面導航** - 提供網站主要頁面連結
- 👤 **用戶狀態顯示** - 顯示登入狀態
- ⚙️ **設定頁面入口** - 連結到用戶程度設定
#### **API依賴**無直接API調用
### **4. `/lib/services/flashcards.ts` - 詞卡服務層**
#### **核心職責**
- 💾 **詞卡CRUD操作** - 創建、讀取、更新、刪除詞卡
- 🗂️ **詞卡組管理** - 管理詞卡分類
- 🔒 **API認證處理** - 自動添加JWT Token
#### **API端點封裝**
```typescript
/api/flashcards // 詞卡CRUD
/api/cardsets // 詞卡組管理
/api/cardsets/ensure-default // 確保預設詞卡組
```
---
## 🔍 **如何分析程式碼中的API調用**
### **1. 搜索技巧**
#### **在VS Code或終端中**
```bash
# 搜索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/
```
#### **在瀏覽器開發者工具中**
1. **Network面板** - 查看實際API調用
2. **Console面板** - 查看調試輸出
3. **Application面板** - 查看localStorage資料
### **2. 程式碼閱讀要點**
#### **識別API調用的關鍵字**
```typescript
// 直接API調用
fetch('http://localhost:5000/api/...')
await fetch(...)
// 服務層調用
flashcardsService.createFlashcard()
flashcardsService.getFlashcards()
// 其他HTTP客戶端
axios.post(...)
```
#### **找到觸發條件**
```typescript
// 用戶事件觸發
onClick={handleAnalyzeSentence}
onClick={(e) => handleWordClick(word, e)}
// 狀態變化觸發
useEffect(() => { /* API調用 */ }, [dependency])
```
### **3. 資料流追蹤**
#### **API回應到狀態**
```typescript
const result = await response.json()
setSentenceAnalysis(result.data.WordAnalysis) // 儲存到狀態
```
#### **狀態到組件**
```typescript
<ClickableTextV2
analysis={sentenceAnalysis} // 傳遞給子組件
onSaveWord={handleSaveWord} // 回調函數
/>
```
---
## 🚨 **當前架構問題分析**
### **1. API調用衝突問題**
#### **問題描述**
- **主頁面** 調用 `analyze-sentence` API → 取得完整詞彙資料(包含例句)
- **詞彙組件** 調用 `query-word` API → 取得簡化資料(無例句)
- **結果** → 好資料被壞資料覆蓋
#### **程式碼位置**
```typescript
// ✅ 正確的API (page.tsx:40)
POST /api/ai/analyze-sentence 完整資料
// ❌ 問題的API (ClickableTextV2.tsx:245, 303)
POST /api/ai/query-word 簡化資料
```
#### **觸發條件**
```typescript
// ClickableTextV2.tsx:221
if (wordAnalysis) {
// 使用預存資料 ✅
} else {
// 調用 query-word API ❌
await queryWordWithAI(cleanWord, position)
}
```
### **2. 資料不一致問題**
#### **analyze-sentence 回傳**
```json
{
"example": "She received a year-end bonus for her hard work.",
"exampleTranslation": "她因為努力工作獲得了年終獎金。",
"synonyms": ["reward", "incentive", "extra pay"]
}
```
#### **query-word 回傳**
```json
{
"example": null,
"exampleTranslation": null,
"synonyms": []
}
```
---
## 🎨 **UI組件架構**
### **1. Portal彈窗系統**
#### **技術實現**
```typescript
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. 個人化標記系統**
#### **詞彙分類邏輯**
```typescript
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. 主頁面狀態流**
```typescript
// 分析階段
[textInput] handleAnalyzeSentence() [sentenceAnalysis]
[sentenceMeaning]
[grammarCorrection]
// 顯示階段
[sentenceAnalysis] ClickableTextV2 Portal彈窗
```
### **2. 詞彙組件狀態流**
```typescript
// 點擊階段
handleWordClick() [selectedWord] + [popupPosition]
VocabPopup() Portal渲染
// 儲存階段
handleSaveWord() flashcardsService.createFlashcard()
```
### **3. 個人化設定流**
```typescript
localStorage.getItem('userEnglishLevel') API請求 個人化結果
```
---
## 🔧 **關鍵技術實現**
### **1. Portal彈窗技術**
#### **為什麼使用Portal**
```typescript
// ❌ 舊方式 - 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. 智能屬性讀取**
#### **解決大小寫不一致**
```typescript
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. 個人化重點學習範圍**
#### **前端整合**
```typescript
// 讀取用戶程度
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調用**
#### **步驟**
1. **選擇調用位置** - 頁面組件或服務層
2. **定義請求格式** - TypeScript介面
3. **處理回應資料** - 錯誤處理和狀態更新
4. **更新UI狀態** - 觸發重新渲染
#### **範例**
```typescript
// 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. 如何修改詞彙顯示邏輯**
#### **修改位置**
```typescript
// 詞彙分類邏輯
ClickableTextV2.tsx getWordClass() 函數
// 彈窗內容
ClickableTextV2.tsx VocabPopup() 組件
// 屬性讀取
ClickableTextV2.tsx getWordProperty() 函數
```
### **3. 如何添加新的詞彙屬性**
#### **步驟**
1. **後端API** - 確保API回傳新屬性
2. **前端介面** - 更新TypeScript介面
3. **屬性讀取** - 在`getWordProperty`中處理
4. **UI顯示** - 在Portal彈窗中顯示
---
## 🔍 **問題診斷指南**
### **1. API調用問題**
#### **檢查步驟**
```typescript
// 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. 資料顯示問題**
#### **檢查步驟**
```typescript
// 1. 檢查狀態
console.log('sentenceAnalysis:', sentenceAnalysis);
// 2. 檢查組件接收
console.log('analysis prop:', analysis);
// 3. 檢查屬性讀取
console.log('getWordProperty結果:', getWordProperty(wordData, 'example'));
```
### **3. Portal彈窗問題**
#### **檢查步驟**
```typescript
// 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生成功能請參考本規格文件的相關章節並遵循最佳實踐建議進行開發。