dramaling-vocab-learning/docs/02_design/interactive-word-query-spec.md

31 KiB
Raw Blame History

互動式單字查詢功能設計規格書

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 句子分析快取表

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 使用統計表

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 組件

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 組件

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 組件

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 組件

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 顏色系統

/* 主色彩 */
--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 互動效果

/* 可點擊單字基礎樣式 */
.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 彈窗動畫

/* 彈窗進入動畫 */
@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 設計

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 快取策略

// 本地快取實現
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 句子分析服務

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 設計

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 組件懶加載

// 懶加載重型組件
const WordInfoPopup = lazy(() => import('./WordInfoPopup'));
const ExampleImageViewer = lazy(() => import('./ExampleImageViewer'));

// 使用 Suspense 包裝
<Suspense fallback={<LoadingSpinner />}>
  <WordInfoPopup {...props} />
</Suspense>

6.1.2 虛擬化長文本

// 對於長句子使用虛擬化渲染
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 批量分析優化

// 批量處理多個句子
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 前端組件測試

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 後端服務測試

[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 測試流程

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 導師指導詞彙學習