935 lines
31 KiB
Markdown
935 lines
31 KiB
Markdown
# 互動式單字查詢功能設計規格書
|
||
|
||
## 1. 功能概述
|
||
|
||
### 1.1 目標
|
||
實現低成本、高效率的互動式單字查詢系統,讓用戶能夠點擊句子中的任何單字即時查看詳細意思,同時智能識別片語和俚語。
|
||
|
||
### 1.2 核心優勢
|
||
- **成本效益**:一次 API 調用,多次查詢零成本
|
||
- **即時響應**:點擊查詢無延遲
|
||
- **智能識別**:片語/俚語優先顯示和警告
|
||
- **用戶友善**:視覺化高亮和直觀操作
|
||
|
||
## 2. 系統架構設計
|
||
|
||
### 2.1 整體流程
|
||
|
||
```
|
||
用戶輸入句子 → AI 預分析 → 高價值標記 → 分析結果快取 → 互動式顯示 → 點擊查詢
|
||
↓ ↓ ↓ ↓ ↓ ↓
|
||
50字限制 一次API調用 識別學習價值 24小時快取 可點擊文字 智能計費
|
||
(扣除1次) 重要詞彙 存儲詳情 不同顏色 高價值免費
|
||
低價值收費
|
||
```
|
||
|
||
### 2.2 API 架構
|
||
|
||
#### 2.2.1 句子分析 API
|
||
```
|
||
POST /api/ai/analyze-sentence
|
||
Content-Type: application/json
|
||
|
||
Request:
|
||
{
|
||
"inputText": "He brought this thing up during our meeting and no one agreed.",
|
||
"userId": "uuid", // 用於使用次數統計
|
||
"forceRefresh": false, // 是否強制重新分析
|
||
"analysisMode": "full" // full: 完整分析並標記高價值詞彙
|
||
}
|
||
|
||
Response:
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"analysisId": "uuid",
|
||
"sentenceMeaning": {
|
||
"translation": "他在我們的會議中提出了這件事,但沒有人同意。",
|
||
"explanation": "這句話表達了在會議中有人提出某個議題或想法,但得不到其他與會者的認同。"
|
||
},
|
||
"grammarCorrection": {
|
||
"hasErrors": false, // 是否有語法錯誤
|
||
"originalText": "He brought this thing up during our meeting and no one agreed.",
|
||
"correctedText": null, // 如無錯誤則為null
|
||
"corrections": [] // 錯誤修正列表
|
||
},
|
||
"wordAnalysis": {
|
||
"brought": {
|
||
"word": "brought",
|
||
"translation": "帶來、提出",
|
||
"definition": "Past tense of bring; to take or carry something to a place",
|
||
"partOfSpeech": "verb",
|
||
"pronunciation": {
|
||
"ipa": "/brɔːt/",
|
||
"us": "/brɔːt/",
|
||
"uk": "/brɔːt/"
|
||
},
|
||
"synonyms": ["carried", "took", "delivered"],
|
||
"antonyms": ["removed", "took away"],
|
||
"isPhrase": true,
|
||
"isHighValue": true, // 高學習價值標記
|
||
"learningPriority": "high", // high, medium, low
|
||
"phraseInfo": {
|
||
"phrase": "bring up",
|
||
"meaning": "提出(話題)、養育",
|
||
"warning": "在這個句子中,\"brought up\" 是片語,意思是\"提出話題\",而不是單純的\"帶來\"",
|
||
"colorCode": "#F59E0B" // 片語顏色代碼
|
||
},
|
||
"examples": {
|
||
"original": "He brought this thing up during our meeting",
|
||
"originalTranslation": "他在會議中提出了這件事",
|
||
"generated": "She brought up an interesting point",
|
||
"generatedTranslation": "她提出了一個有趣的觀點",
|
||
"imageUrl": "/images/examples/bring_up.png",
|
||
"audioUrl": "/audio/examples/bring_up.mp3"
|
||
},
|
||
"difficultyLevel": "B1"
|
||
}
|
||
},
|
||
"finalAnalysisText": "He brought this thing up during our meeting and no one agreed.", // 最終用於學習的文本(修正後)
|
||
"highValueWords": ["brought", "up", "meeting"], // 高價值詞彙列表
|
||
"phrasesDetected": [
|
||
{
|
||
"phrase": "bring up",
|
||
"words": ["brought", "up"],
|
||
"colorCode": "#F59E0B"
|
||
}
|
||
],
|
||
"usageStatistics": {
|
||
"remainingAnalyses": 4,
|
||
"resetTime": "2025-09-17T12:48:00Z",
|
||
"costIncurred": 1 // 本次分析扣除次數
|
||
},
|
||
"cachedUntil": "2025-09-18T09:48:00Z"
|
||
},
|
||
"message": "Sentence analyzed successfully"
|
||
}
|
||
```
|
||
|
||
#### 2.2.2 單字點擊查詢 API
|
||
```
|
||
POST /api/ai/query-word
|
||
Content-Type: application/json
|
||
|
||
Request:
|
||
{
|
||
"word": "thing",
|
||
"sentence": "He brought this thing up during our meeting and no one agreed.",
|
||
"analysisId": "uuid", // 來自預分析結果
|
||
"userId": "uuid"
|
||
}
|
||
|
||
Response:
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"word": "thing",
|
||
"isHighValue": false, // 非高價值詞彙
|
||
"wasPreAnalyzed": false, // 未在預分析中
|
||
"costIncurred": 1, // 扣除1次使用次數
|
||
"analysis": {
|
||
// 完整詞彙分析資料
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2.2.3 快取管理 API
|
||
```
|
||
GET /api/ai/analysis-cache/{inputTextHash}
|
||
DELETE /api/ai/analysis-cache/{analysisId}
|
||
```
|
||
|
||
### 2.3 資料庫設計
|
||
|
||
#### 2.3.1 句子分析快取表
|
||
```sql
|
||
CREATE TABLE SentenceAnalysisCache (
|
||
Id UNIQUEIDENTIFIER PRIMARY KEY,
|
||
InputTextHash NVARCHAR(64) NOT NULL, -- SHA256 hash
|
||
InputText NVARCHAR(1000) NOT NULL,
|
||
CorrectedText NVARCHAR(1000), -- 修正後的文本
|
||
HasGrammarErrors BIT DEFAULT 0, -- 是否有語法錯誤
|
||
GrammarCorrections NVARCHAR(MAX), -- JSON 格式,語法修正詳情
|
||
AnalysisResult NVARCHAR(MAX) NOT NULL, -- JSON 格式
|
||
HighValueWords NVARCHAR(MAX) NOT NULL, -- JSON 格式,高價值詞彙列表
|
||
PhrasesDetected NVARCHAR(MAX), -- JSON 格式,檢測到的片語
|
||
CreatedAt DATETIME2 NOT NULL,
|
||
ExpiresAt DATETIME2 NOT NULL,
|
||
AccessCount INT DEFAULT 0,
|
||
LastAccessedAt DATETIME2
|
||
);
|
||
|
||
CREATE INDEX IX_SentenceAnalysisCache_Hash ON SentenceAnalysisCache(InputTextHash);
|
||
CREATE INDEX IX_SentenceAnalysisCache_Expires ON SentenceAnalysisCache(ExpiresAt);
|
||
```
|
||
|
||
#### 2.3.2 使用統計表
|
||
```sql
|
||
CREATE TABLE WordQueryUsageStats (
|
||
Id UNIQUEIDENTIFIER PRIMARY KEY,
|
||
UserId UNIQUEIDENTIFIER NOT NULL,
|
||
Date DATE NOT NULL,
|
||
SentenceAnalysisCount INT DEFAULT 0, -- 句子分析次數
|
||
HighValueWordClicks INT DEFAULT 0, -- 高價值詞彙點擊(免費)
|
||
LowValueWordClicks INT DEFAULT 0, -- 低價值詞彙點擊(收費)
|
||
TotalApiCalls INT DEFAULT 0, -- 總 API 調用次數
|
||
UniqueWordsQueried INT DEFAULT 0,
|
||
CreatedAt DATETIME2 NOT NULL,
|
||
UpdatedAt DATETIME2 NOT NULL
|
||
);
|
||
|
||
CREATE UNIQUE INDEX IX_WordQueryUsageStats_UserDate ON WordQueryUsageStats(UserId, Date);
|
||
```
|
||
|
||
## 3. 前端組件設計
|
||
|
||
### 3.1 主要組件架構
|
||
|
||
```
|
||
WordQueryPage
|
||
├── SentenceInputForm // 句子輸入表單
|
||
├── AnalysisLoadingState // 分析中狀態
|
||
├── GrammarCorrectionPanel // 語法修正面板
|
||
│ ├── ErrorHighlight // 錯誤標記顯示
|
||
│ ├── CorrectionSuggestion // 修正建議
|
||
│ └── UserChoiceButtons // 用戶選擇按鈕
|
||
├── InteractiveTextDisplay // 互動式文字顯示
|
||
│ ├── ClickableWord // 可點擊單字
|
||
│ └── WordInfoPopup // 單字資訊彈窗
|
||
├── UsageStatistics // 使用統計顯示
|
||
└── ActionButtons // 操作按鈕組
|
||
```
|
||
|
||
### 3.2 組件詳細設計
|
||
|
||
#### 3.2.1 GrammarCorrectionPanel 組件
|
||
```typescript
|
||
interface GrammarCorrection {
|
||
hasErrors: boolean;
|
||
originalText: string;
|
||
correctedText: string | null;
|
||
corrections: Array<{
|
||
position: { start: number; end: number };
|
||
errorType: string;
|
||
original: string;
|
||
corrected: string;
|
||
reason: string;
|
||
severity: 'high' | 'medium' | 'low';
|
||
}>;
|
||
confidenceScore: number;
|
||
}
|
||
|
||
interface GrammarCorrectionPanelProps {
|
||
correction: GrammarCorrection;
|
||
onAcceptCorrection: () => void;
|
||
onRejectCorrection: () => void;
|
||
onManualEdit: (text: string) => void;
|
||
}
|
||
|
||
const GrammarCorrectionPanel: React.FC<GrammarCorrectionPanelProps> = ({
|
||
correction,
|
||
onAcceptCorrection,
|
||
onRejectCorrection,
|
||
onManualEdit
|
||
}) => {
|
||
// 錯誤高亮顯示
|
||
// 修正建議卡片
|
||
// 修正原因說明
|
||
// 用戶選擇按鈕
|
||
};
|
||
```
|
||
|
||
#### 3.2.2 SentenceInputForm 組件
|
||
```typescript
|
||
interface SentenceInputFormProps {
|
||
maxLength: number; // 300 for manual input
|
||
onSubmit: (text: string) => void;
|
||
onModeChange: (mode: 'manual' | 'screenshot') => void;
|
||
disabled: boolean;
|
||
remainingAnalyses: number;
|
||
}
|
||
|
||
const SentenceInputForm: React.FC<SentenceInputFormProps> = ({
|
||
maxLength,
|
||
onSubmit,
|
||
onModeChange,
|
||
disabled,
|
||
remainingAnalyses
|
||
}) => {
|
||
// 即時字數統計
|
||
// 300字限制阻擋
|
||
// 模式切換UI
|
||
// 示例句子填入
|
||
// 分析按鈕狀態
|
||
};
|
||
```
|
||
|
||
#### 3.2.3 InteractiveTextDisplay 組件
|
||
```typescript
|
||
interface WordAnalysis {
|
||
word: string;
|
||
translation: string;
|
||
definition: string;
|
||
partOfSpeech: string;
|
||
pronunciation: {
|
||
ipa: string;
|
||
us: string;
|
||
uk: string;
|
||
};
|
||
synonyms: string[];
|
||
antonyms: string[];
|
||
isPhrase: boolean;
|
||
isHighValue: boolean; // 高學習價值標記
|
||
learningPriority: 'high' | 'medium' | 'low'; // 學習優先級
|
||
phraseInfo?: {
|
||
phrase: string;
|
||
meaning: string;
|
||
warning: string;
|
||
colorCode: string; // 片語顏色代碼
|
||
};
|
||
examples: {
|
||
original: string;
|
||
originalTranslation: string;
|
||
generated: string;
|
||
generatedTranslation: string;
|
||
imageUrl?: string;
|
||
audioUrl?: string;
|
||
};
|
||
difficultyLevel: string;
|
||
}
|
||
|
||
interface InteractiveTextDisplayProps {
|
||
text: string;
|
||
analysis: Record<string, WordAnalysis>;
|
||
onWordClick: (word: string, analysis: WordAnalysis) => void;
|
||
}
|
||
```
|
||
|
||
#### 3.2.4 WordInfoPopup 組件
|
||
```typescript
|
||
interface WordInfoPopupProps {
|
||
word: string;
|
||
analysis: WordAnalysis;
|
||
position: { x: number; y: number };
|
||
onClose: () => void;
|
||
onPlayAudio: (audioUrl: string) => void;
|
||
}
|
||
|
||
const WordInfoPopup: React.FC<WordInfoPopupProps> = ({
|
||
word,
|
||
analysis,
|
||
position,
|
||
onClose,
|
||
onPlayAudio
|
||
}) => {
|
||
// 片語警告顯示
|
||
// 發音播放按鈕
|
||
// 例句圖片顯示
|
||
// 同義詞/反義詞標籤
|
||
// 難度等級標示
|
||
};
|
||
```
|
||
|
||
## 4. 用戶介面設計
|
||
|
||
### 4.1 頁面佈局
|
||
|
||
#### 4.1.1 輸入階段
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ DramaLing │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ AI 智能生成詞卡 - 互動式單字查詢 │
|
||
│ │
|
||
│ ┌─ 原始例句類型 ──────────────────────────────────┐ │
|
||
│ │ [✍️ 手動輸入] [📷 影劇截圖] (訂閱功能) │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─ 輸入英文文本 ──────────────────────────────────┐ │
|
||
│ │ ┌─────────────────────────────────────────────┐ │ │
|
||
│ │ │ 輸入英文句子(最多50字)... │ │ │
|
||
│ │ │ │ │ │
|
||
│ │ └─────────────────────────────────────────────┘ │ │
|
||
│ │ 最多 50 字元 • 目前:0 字元 │ │
|
||
│ │ │ │
|
||
│ │ 💡 示例句子: │ │
|
||
│ │ [點擊使用示例:He brought this thing up...] │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ 🔍 分析句子(點擊查詢單字) │ │
|
||
│ └─────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ 免費用戶:已使用 0/5 次 (3小時內) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 4.1.2 分析結果階段
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ ← 返回 句子分析結果 │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ ┌─ 原始句子 ──────────────────────────────────────┐ │
|
||
│ │ He brought this thing up during our meeting. │ │
|
||
│ │ │ │
|
||
│ │ 整句意思: │ │
|
||
│ │ 他在我們的會議中提出了這件事,但沒有人同意... │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─ 點擊查詢單字意思 ──────────────────────────────┐ │
|
||
│ │ 💡 使用說明:點擊下方句子中的任何單字,可以立即 │ │
|
||
│ │ 查看詳細意思。黃色背景表示該單字屬於片語或俚語。 │ │
|
||
│ │ │ │
|
||
│ │ ╔═══════════════════════════════════════════╗ │ │
|
||
│ │ ║ He [brought] this [thing] [up] during ║ │ │
|
||
│ │ ║ our [meeting] and no one [agreed]. ║ │ │
|
||
│ │ ╚═══════════════════════════════════════════╝ │ │
|
||
│ │ // brought 和 up 有黃色背景 │ │
|
||
│ │ // 其他單字有藍色下劃線 │ │
|
||
│ └───────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────────┐ │
|
||
│ │ [🔄 分析新句子] [📖 生成詞卡] │ │
|
||
│ └─────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 4.1.3 單字彈窗設計
|
||
```
|
||
┌─ brought ─────────────────── × ┐
|
||
│ │
|
||
│ ⚠️ 注意:這個單字屬於片語 │
|
||
│ 片語:bring up │
|
||
│ 意思:提出(話題)、養育 │
|
||
│ │
|
||
│ verb | /brɔːt/ | 🔊 │
|
||
│ │
|
||
│ 翻譯:帶來、提出 │
|
||
│ │
|
||
│ 定義:Past tense of bring; │
|
||
│ to take or carry something │
|
||
│ │
|
||
│ 同義詞:[carried] [took] │
|
||
│ 反義詞:[removed] │
|
||
│ │
|
||
│ 例句: │
|
||
│ • 原始:He brought this... │
|
||
│ 翻譯:他提出了這件事... │
|
||
│ • 生成:She brought up... │
|
||
│ 翻譯:她提出了一個... │
|
||
│ [📷 查看例句圖] [🔊 播放] │
|
||
│ │
|
||
│ 難度:B1 │
|
||
└───────────────────────────────┘
|
||
```
|
||
|
||
### 4.2 視覺設計規範
|
||
|
||
#### 4.2.1 顏色系統
|
||
```css
|
||
/* 主色彩 */
|
||
--primary-blue: #3B82F6;
|
||
--primary-blue-hover: #2563EB;
|
||
--primary-blue-light: #DBEAFE;
|
||
|
||
/* 單字價值顏色系統 */
|
||
--word-high-phrase: #F59E0B; /* 高價值片語 */
|
||
--word-high-single: #10B981; /* 高價值單字 */
|
||
--word-normal: #3B82F6; /* 普通單字 */
|
||
--word-hover: #1E40AF; /* 懸停狀態 */
|
||
|
||
/* 背景顏色 */
|
||
--word-high-phrase-bg: #FEF3C7; /* 高價值片語背景 */
|
||
--word-high-single-bg: #ECFDF5; /* 高價值單字背景 */
|
||
--word-normal-bg: transparent; /* 普通單字背景 */
|
||
--word-hover-bg: #DBEAFE; /* 懸停背景 */
|
||
|
||
/* 邊框顏色 */
|
||
--border-high-phrase: #F59E0B; /* 高價值片語邊框 */
|
||
--border-high-single: #10B981; /* 高價值單字邊框 */
|
||
--border-normal: #3B82F6; /* 普通單字邊框 */
|
||
|
||
/* 狀態顏色 */
|
||
--success: #10B981;
|
||
--warning: #F59E0B;
|
||
--error: #EF4444;
|
||
--info: #3B82F6;
|
||
--premium: #8B5CF6; /* 付費功能 */
|
||
```
|
||
|
||
#### 4.2.2 互動效果
|
||
```css
|
||
/* 可點擊單字基礎樣式 */
|
||
.clickable-word {
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
padding: 2px 4px;
|
||
border-radius: 4px;
|
||
margin: 0 1px;
|
||
position: relative;
|
||
}
|
||
|
||
/* 高價值片語樣式 */
|
||
.clickable-word.high-value.phrase {
|
||
background-color: var(--word-high-phrase-bg);
|
||
border: 2px solid var(--border-high-phrase);
|
||
}
|
||
|
||
.clickable-word.high-value.phrase::after {
|
||
content: "⭐";
|
||
position: absolute;
|
||
top: -8px;
|
||
right: -8px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 高價值單字樣式 */
|
||
.clickable-word.high-value.single {
|
||
background-color: var(--word-high-single-bg);
|
||
border: 2px solid var(--border-high-single);
|
||
}
|
||
|
||
.clickable-word.high-value.single::after {
|
||
content: "⭐";
|
||
position: absolute;
|
||
top: -8px;
|
||
right: -8px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 普通單字樣式 */
|
||
.clickable-word.normal {
|
||
border-bottom: 1px solid var(--border-normal);
|
||
}
|
||
|
||
/* 懸停效果 */
|
||
.clickable-word:hover {
|
||
background-color: var(--word-hover-bg);
|
||
transform: translateY(-1px);
|
||
}
|
||
```
|
||
|
||
#### 4.2.3 彈窗動畫
|
||
```css
|
||
/* 彈窗進入動畫 */
|
||
@keyframes popup-enter {
|
||
from {
|
||
opacity: 0;
|
||
transform: translate(-50%, -100%) scale(0.9);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translate(-50%, -100%) scale(1);
|
||
}
|
||
}
|
||
|
||
.word-popup {
|
||
animation: popup-enter 0.2s ease-out;
|
||
}
|
||
```
|
||
|
||
## 5. 技術實現細節
|
||
|
||
### 5.1 前端狀態管理
|
||
|
||
#### 5.1.1 Zustand Store 設計
|
||
```typescript
|
||
interface WordQueryStore {
|
||
// 分析狀態
|
||
isAnalyzing: boolean;
|
||
analysisResult: SentenceAnalysis | null;
|
||
analysisError: string | null;
|
||
|
||
// 互動狀態
|
||
selectedWord: string | null;
|
||
popupPosition: { x: number; y: number } | null;
|
||
|
||
// 使用統計
|
||
usageStats: {
|
||
remainingAnalyses: number;
|
||
resetTime: string;
|
||
};
|
||
|
||
// 快取
|
||
analysisCache: Map<string, SentenceAnalysis>;
|
||
|
||
// Actions
|
||
analyzeSentence: (text: string) => Promise<void>;
|
||
selectWord: (word: string, position: { x: number; y: number }) => void;
|
||
closeWordPopup: () => void;
|
||
clearCache: () => void;
|
||
}
|
||
```
|
||
|
||
#### 5.1.2 快取策略
|
||
```typescript
|
||
// 本地快取實現
|
||
class AnalysisCache {
|
||
private cache = new Map<string, CacheItem>();
|
||
|
||
get(textHash: string): SentenceAnalysis | null {
|
||
const item = this.cache.get(textHash);
|
||
if (!item || this.isExpired(item)) {
|
||
this.cache.delete(textHash);
|
||
return null;
|
||
}
|
||
return item.data;
|
||
}
|
||
|
||
set(textHash: string, data: SentenceAnalysis, ttl: number): void {
|
||
this.cache.set(textHash, {
|
||
data,
|
||
expiresAt: Date.now() + ttl
|
||
});
|
||
}
|
||
|
||
private isExpired(item: CacheItem): boolean {
|
||
return Date.now() > item.expiresAt;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 後端實現細節
|
||
|
||
#### 5.2.1 句子分析服務
|
||
```csharp
|
||
public class SentenceAnalysisService : ISentenceAnalysisService
|
||
{
|
||
private readonly IGeminiService _geminiService;
|
||
private readonly IAnalysisCacheService _cacheService;
|
||
private readonly IUsageTrackingService _usageService;
|
||
|
||
public async Task<SentenceAnalysisResult> AnalyzeSentenceAsync(
|
||
string inputText,
|
||
Guid userId,
|
||
bool forceRefresh = false)
|
||
{
|
||
// 1. 檢查使用限制
|
||
await _usageService.CheckUsageLimitAsync(userId);
|
||
|
||
// 2. 檢查快取
|
||
var textHash = GenerateTextHash(inputText);
|
||
if (!forceRefresh)
|
||
{
|
||
var cached = await _cacheService.GetAsync(textHash);
|
||
if (cached != null)
|
||
{
|
||
await _usageService.RecordCacheHitAsync(userId);
|
||
return cached;
|
||
}
|
||
}
|
||
|
||
// 3. AI 分析
|
||
var analysis = await _geminiService.AnalyzeSentenceAsync(inputText);
|
||
|
||
// 4. 存入快取
|
||
await _cacheService.SetAsync(textHash, analysis, TimeSpan.FromHours(24));
|
||
|
||
// 5. 記錄使用
|
||
await _usageService.RecordAnalysisAsync(userId);
|
||
|
||
return analysis;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 5.2.2 Gemini Prompt 設計
|
||
```csharp
|
||
private const string SENTENCE_ANALYSIS_PROMPT = @"
|
||
請分析以下英文句子,先檢查語法錯誤並修正,然後提供完整的單字和片語解析:
|
||
|
||
句子:{inputText}
|
||
|
||
請按照以下 JSON 格式回應:
|
||
|
||
{
|
||
""grammarCorrection"": {
|
||
""hasErrors"": true/false,
|
||
""originalText"": ""原始輸入句子"",
|
||
""correctedText"": ""修正後句子"" // 如無錯誤則與原始相同,
|
||
""corrections"": [
|
||
{
|
||
""position"": {""start"": 2, ""end"": 4},
|
||
""errorType"": ""tense_mismatch"",
|
||
""original"": ""錯誤詞彙"",
|
||
""corrected"": ""修正詞彙"",
|
||
""reason"": ""修正原因說明"",
|
||
""severity"": ""high/medium/low""
|
||
}
|
||
],
|
||
""confidenceScore"": 0.95
|
||
},
|
||
""sentenceMeaning"": {
|
||
""translation"": ""整句的繁體中文意思"",
|
||
""explanation"": ""詳細解釋""
|
||
},
|
||
""finalAnalysisText"": ""用於後續分析的最終文本(修正後)"",
|
||
""wordAnalysis"": {
|
||
""單字原形"": {
|
||
""word"": ""單字原形"",
|
||
""translation"": ""繁體中文翻譯"",
|
||
""definition"": ""英文定義(A1-A2程度)"",
|
||
""partOfSpeech"": ""詞性(noun/verb/adjective/adverb/pronoun/preposition/conjunction/interjection)"",
|
||
""pronunciation"": {
|
||
""ipa"": ""IPA音標"",
|
||
""us"": ""美式音標"",
|
||
""uk"": ""英式音標""
|
||
},
|
||
""synonyms"": [""同義詞1"", ""同義詞2"", ""同義詞3""],
|
||
""antonyms"": [""反義詞1"", ""反義詞2""],
|
||
""isPhrase"": true/false,
|
||
""phraseInfo"": {
|
||
""phrase"": ""完整片語"",
|
||
""meaning"": ""片語意思"",
|
||
""warning"": ""警告說明""
|
||
},
|
||
""examples"": {
|
||
""original"": ""來自原句的例句"",
|
||
""originalTranslation"": ""原句例句翻譯"",
|
||
""generated"": ""AI生成的新例句"",
|
||
""generatedTranslation"": ""新例句翻譯""
|
||
},
|
||
""difficultyLevel"": ""CEFR等級(A1/A2/B1/B2/C1/C2)""
|
||
}
|
||
}
|
||
}
|
||
|
||
分析要求:
|
||
1. **首要任務:語法檢查和修正**
|
||
- 檢測語法、拼寫、時態、介詞、詞序錯誤
|
||
- 提供修正建議和詳細說明
|
||
- 後續分析基於修正後的句子進行
|
||
- 保持原句意思不變
|
||
|
||
2. 識別所有有意義的單字(忽略 a, an, the 等功能詞)
|
||
|
||
3. **重點:標記高學習價值詞彙**
|
||
- 片語和俚語:isHighValue: true, learningPriority: "high"
|
||
- 中級以上單字(B1+):isHighValue: true, learningPriority: "high"
|
||
- 專業術語:isHighValue: true, learningPriority: "medium"
|
||
- 基礎功能詞:isHighValue: false, learningPriority: "low"
|
||
|
||
4. 特別注意片語和俚語,設定 isPhrase: true
|
||
5. 為片語提供警告說明和顏色代碼
|
||
6. 英文定義保持在 A1-A2 程度
|
||
7. 提供實用的同義詞和反義詞(如適用)
|
||
8. 例句要清楚展示單字用法
|
||
9. 準確標記 CEFR 難度等級
|
||
10. **優先處理高價值詞彙**:為高價值詞彙生成完整內容詳情
|
||
";
|
||
```
|
||
|
||
## 6. 性能優化策略
|
||
|
||
### 6.1 前端優化
|
||
|
||
#### 6.1.1 組件懶加載
|
||
```typescript
|
||
// 懶加載重型組件
|
||
const WordInfoPopup = lazy(() => import('./WordInfoPopup'));
|
||
const ExampleImageViewer = lazy(() => import('./ExampleImageViewer'));
|
||
|
||
// 使用 Suspense 包裝
|
||
<Suspense fallback={<LoadingSpinner />}>
|
||
<WordInfoPopup {...props} />
|
||
</Suspense>
|
||
```
|
||
|
||
#### 6.1.2 虛擬化長文本
|
||
```typescript
|
||
// 對於長句子使用虛擬化渲染
|
||
const VirtualizedText = ({ words, analysis, onWordClick }) => {
|
||
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 });
|
||
|
||
return (
|
||
<div className="virtual-text-container">
|
||
{words.slice(visibleRange.start, visibleRange.end).map((word, index) => (
|
||
<ClickableWord
|
||
key={index}
|
||
word={word}
|
||
analysis={analysis[word]}
|
||
onClick={onWordClick}
|
||
/>
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 6.2 後端優化
|
||
|
||
#### 6.2.1 快取層次設計
|
||
```
|
||
L1: 記憶體快取 (Redis) - 1小時 TTL
|
||
L2: 資料庫快取 (SQLite) - 24小時 TTL
|
||
L3: 磁碟快取 (File System) - 7天 TTL
|
||
```
|
||
|
||
#### 6.2.2 批量分析優化
|
||
```csharp
|
||
// 批量處理多個句子
|
||
public async Task<List<SentenceAnalysisResult>> AnalyzeMultipleSentencesAsync(
|
||
List<string> sentences,
|
||
Guid userId)
|
||
{
|
||
// 1. 批量檢查快取
|
||
var cacheResults = await _cacheService.GetMultipleAsync(
|
||
sentences.Select(GenerateTextHash)
|
||
);
|
||
|
||
// 2. 只分析未快取的句子
|
||
var uncachedSentences = sentences
|
||
.Where((s, i) => cacheResults[i] == null)
|
||
.ToList();
|
||
|
||
// 3. 批量調用 AI API
|
||
var newAnalyses = await _geminiService.AnalyzeMultipleSentencesAsync(
|
||
uncachedSentences
|
||
);
|
||
|
||
// 4. 合併結果
|
||
return MergeResults(cacheResults, newAnalyses);
|
||
}
|
||
```
|
||
|
||
## 7. 測試策略
|
||
|
||
### 7.1 單元測試
|
||
|
||
#### 7.1.1 前端組件測試
|
||
```typescript
|
||
describe('ClickableText Component', () => {
|
||
it('should highlight phrase words correctly', () => {
|
||
const analysis = {
|
||
'brought': { isPhrase: true, /* ... */ },
|
||
'up': { isPhrase: true, /* ... */ }
|
||
};
|
||
|
||
render(<ClickableText text="He brought this up" analysis={analysis} />);
|
||
|
||
expect(screen.getByText('brought')).toHaveClass('phrase');
|
||
expect(screen.getByText('up')).toHaveClass('phrase');
|
||
});
|
||
|
||
it('should show word popup on click', async () => {
|
||
const mockOnClick = jest.fn();
|
||
render(<ClickableText onWordClick={mockOnClick} />);
|
||
|
||
fireEvent.click(screen.getByText('brought'));
|
||
|
||
expect(mockOnClick).toHaveBeenCalledWith('brought', expect.any(Object));
|
||
});
|
||
});
|
||
```
|
||
|
||
#### 7.1.2 後端服務測試
|
||
```csharp
|
||
[Test]
|
||
public async Task AnalyzeSentence_ShouldReturnCachedResult_WhenCacheExists()
|
||
{
|
||
// Arrange
|
||
var inputText = "Test sentence";
|
||
var userId = Guid.NewGuid();
|
||
var cachedResult = new SentenceAnalysisResult();
|
||
|
||
_cacheService.Setup(c => c.GetAsync(It.IsAny<string>()))
|
||
.ReturnsAsync(cachedResult);
|
||
|
||
// Act
|
||
var result = await _analysisService.AnalyzeSentenceAsync(inputText, userId);
|
||
|
||
// Assert
|
||
Assert.AreEqual(cachedResult, result);
|
||
_geminiService.Verify(g => g.AnalyzeSentenceAsync(It.IsAny<string>()),
|
||
Times.Never);
|
||
}
|
||
```
|
||
|
||
### 7.2 整合測試
|
||
|
||
#### 7.2.1 E2E 測試流程
|
||
```typescript
|
||
describe('Word Query Flow', () => {
|
||
it('should complete full analysis and query flow', async () => {
|
||
// 1. 輸入句子
|
||
await page.fill('[data-testid=sentence-input]', 'He brought this up');
|
||
await page.click('[data-testid=analyze-button]');
|
||
|
||
// 2. 等待分析完成
|
||
await page.waitForSelector('[data-testid=interactive-text]');
|
||
|
||
// 3. 點擊單字
|
||
await page.click('[data-testid=word-brought]');
|
||
|
||
// 4. 驗證彈窗顯示
|
||
await expect(page.locator('[data-testid=word-popup]')).toBeVisible();
|
||
await expect(page.locator('[data-testid=phrase-warning]')).toBeVisible();
|
||
|
||
// 5. 播放發音
|
||
await page.click('[data-testid=play-pronunciation]');
|
||
|
||
// 6. 關閉彈窗
|
||
await page.click('[data-testid=close-popup]');
|
||
await expect(page.locator('[data-testid=word-popup]')).toBeHidden();
|
||
});
|
||
});
|
||
```
|
||
|
||
## 8. 部署和監控
|
||
|
||
### 8.1 部署策略
|
||
|
||
#### 8.1.1 前端部署
|
||
- **平台**:Vercel
|
||
- **環境變量**:API_BASE_URL, CACHE_TTL
|
||
- **CDN**:自動優化靜態資源
|
||
- **快取策略**:分析結果本地存儲 24 小時
|
||
|
||
#### 8.1.2 後端部署
|
||
- **平台**:Azure App Service / AWS Lambda
|
||
- **資料庫**:Azure SQL Database / AWS RDS
|
||
- **快取**:Azure Redis Cache / AWS ElastiCache
|
||
- **檔案存儲**:Azure Blob Storage / AWS S3
|
||
|
||
### 8.2 監控指標
|
||
|
||
#### 8.2.1 業務指標
|
||
- 句子分析成功率
|
||
- 平均分析響應時間
|
||
- 快取命中率
|
||
- 用戶使用次數分佈
|
||
- 單字點擊熱度排行
|
||
|
||
#### 8.2.2 技術指標
|
||
- API 響應時間 (P95 < 200ms)
|
||
- Gemini API 調用延遲
|
||
- 快取效能指標
|
||
- 錯誤率 (< 1%)
|
||
- 系統可用性 (> 99.9%)
|
||
|
||
#### 8.2.3 成本監控
|
||
- Gemini API 調用次數和費用
|
||
- 快取存儲成本
|
||
- CDN 流量費用
|
||
- 基礎設施總成本
|
||
|
||
## 9. 未來擴展計劃
|
||
|
||
### 9.1 功能增強
|
||
- **多語言支持**:支援其他語言的句子分析
|
||
- **語音輸入**:整合語音識別進行句子輸入
|
||
- **個人化推薦**:基於用戶查詢歷史推薦相關詞彙
|
||
- **社交分享**:分享有趣的句子分析結果
|
||
|
||
### 9.2 技術升級
|
||
- **AI 模型本地化**:部署本地 LLM 降低外部依賴
|
||
- **即時協作**:多用戶同時查詢同一句子
|
||
- **離線支持**:PWA 實現離線查詢基礎詞彙
|
||
- **效能優化**:WebAssembly 加速文本處理
|
||
|
||
### 9.3 商業化功能
|
||
- **高級分析**:更深度的語法和語義分析
|
||
- **專業詞典**:整合專業領域詞典
|
||
- **學習追蹤**:詳細的學習進度和成效分析
|
||
- **導師模式**:AI 導師指導詞彙學習 |