# AI生成功能前後端串接規格 ## 📋 **文件資訊** - **文件名稱**: AI生成功能前後端串接規格 - **版本**: v1.0 - **建立日期**: 2025-01-25 - **最後更新**: 2025-01-25 - **負責團隊**: DramaLing全端開發團隊 --- ## 🎯 **串接架構概述** ### **系統架構圖** ``` ┌─────────────────┐ HTTP/JSON ┌──────────────────┐ Gemini API ┌─────────────────┐ │ │ Request │ │ Request │ │ │ Frontend │ ──────────────► │ Backend API │ ──────────────► │ Google Gemini │ │ (Next.js) │ │ (.NET Core) │ │ AI Service │ │ Port 3000 │ ◄────────────── │ Port 5008 │ ◄────────────── │ │ │ │ Response │ │ Response │ │ └─────────────────┘ └──────────────────┘ └─────────────────┘ │ │ │ │ ▼ ▼ ┌─────────────────┐ ┌──────────────────┐ │ Local Storage │ │ SQLite Database │ │ - auth_token │ │ - Cache │ │ - user_level │ │ - Usage Stats │ └─────────────────┘ └──────────────────┘ ``` --- ## 🔄 **API串接流程** ### **完整用戶操作流程** ```mermaid sequenceDiagram participant U as 用戶 participant F as 前端(3000) participant B as 後端(5008) participant G as Gemini API participant C as Cache participant D as Database U->>F: 1. 輸入英文句子 U->>F: 2. 點擊「分析句子」 F->>F: 3. 驗證輸入(≤300字符) F->>F: 4. 讀取userLevel F->>B: 5. POST /api/ai/analyze-sentence Note over F,B: Content-Type: application/json
Body: {inputText, userLevel, options} B->>B: 6. 輸入驗證 B->>C: 7. 檢查快取 alt 快取命中 C->>B: 8a. 返回快取結果 B->>F: 9a. 返回分析結果 else 快取未命中 B->>G: 8b. 調用Gemini API G->>B: 9b. AI分析結果 B->>B: 10. 解析和處理 B->>C: 11. 儲存快取 B->>D: 12. 記錄使用統計 B->>F: 13. 返回分析結果 end F->>F: 14. 處理API回應 F->>F: 15. 渲染詞彙標記 F->>U: 16. 顯示分析結果 U->>F: 17. 點擊詞彙 F->>F: 18. 顯示詞彙彈窗 U->>F: 19. 保存詞卡 F->>B: 20. POST /api/flashcards B->>D: 21. 儲存詞卡 B->>F: 22. 返回成功狀態 F->>U: 23. 顯示成功提示 ``` --- ## 📡 **API端點詳細串接** ### **1. 句子分析API** #### **前端請求實現** ```typescript // 位置: frontend/app/generate/page.tsx:65-87 const handleAnalyzeSentence = async () => { const userLevel = localStorage.getItem('userEnglishLevel') || 'A2' const response = await fetch('http://localhost:5008/api/ai/analyze-sentence', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inputText: textInput, userLevel: userLevel, analysisMode: 'full', options: { includeGrammarCheck: true, includeVocabularyAnalysis: true, includeTranslation: true, includeIdiomDetection: true, includeExamples: true } }) }) const result = await response.json() // 處理API回應... } ``` #### **後端處理實現** ```csharp // 位置: backend/Controllers/AIController.cs:32-105 [HttpPost("analyze-sentence")] public async Task> AnalyzeSentence( [FromBody] SentenceAnalysisRequest request) { // 1. 輸入驗證 if (!ModelState.IsValid) return BadRequest(...) // 2. 檢查快取 var cachedResult = await _cacheService.GetCachedAnalysisAsync(request.InputText); if (cachedResult != null) return Ok(...) // 3. 調用Gemini AI var analysisData = await _geminiService.AnalyzeSentenceAsync( request.InputText, request.UserLevel, options); // 4. 快取結果 await _cacheService.SetCachedAnalysisAsync(request.InputText, analysisData, TimeSpan.FromHours(24)); // 5. 返回結果 return Ok(new SentenceAnalysisResponse { Success = true, Data = analysisData }); } ``` --- ## 💾 **數據格式串接** ### **前端到後端請求格式** #### **SentenceAnalysisRequest** ```json { "inputText": "She just join the team, so let's cut her some slack until she get used to the workflow.", "userLevel": "A2", "analysisMode": "full", "options": { "includeGrammarCheck": true, "includeVocabularyAnalysis": true, "includeTranslation": true, "includeIdiomDetection": true, "includeExamples": true } } ``` ### **後端到前端回應格式** #### **SentenceAnalysisResponse** ```json { "success": true, "processingTime": 2.34, "data": { "analysisId": "uuid-string", "originalText": "She just join the team, so let's cut her some slack until she get used to the workflow.", "grammarCorrection": { "hasErrors": true, "correctedText": "She just joined the team, so let's cut her some slack until she gets used to the workflow.", "corrections": [ { "error": "join", "correction": "joined", "type": "時態錯誤", "explanation": "第三人稱單數過去式應使用 'joined'" }, { "error": "get", "correction": "gets", "type": "時態錯誤", "explanation": "第三人稱單數現在式應使用 'gets'" } ] }, "sentenceMeaning": "她剛加入團隊,所以讓我們對她寬容一點,直到她習慣工作流程。", "vocabularyAnalysis": { "she": { "word": "she", "translation": "她", "definition": "female person pronoun", "partOfSpeech": "pronoun", "pronunciation": "/ʃiː/", "difficultyLevel": "A1", "frequency": "very_high", "synonyms": ["her"], "example": "She is a teacher.", "exampleTranslation": "她是一名老師。", "tags": ["basic", "pronoun"] }, "cut someone some slack": { "word": "cut someone some slack", "translation": "對某人寬容一點", "definition": "to be more lenient or forgiving with someone", "partOfSpeech": "idiom", "pronunciation": "/kʌt ˈsʌmwʌn sʌm slæk/", "difficultyLevel": "B2", "frequency": "medium", "synonyms": ["be lenient", "be forgiving", "give leeway"], "example": "Cut him some slack, he's new here.", "exampleTranslation": "對他寬容一點,他是新來的。", "tags": ["idiom", "workplace", "tolerance"] } }, "statistics": { "totalWords": 16, "uniqueWords": 15, "simpleWords": 8, "moderateWords": 4, "difficultWords": 3, "idioms": 1, "averageDifficulty": "A2" }, "metadata": { "analysisModel": "gemini-pro", "analysisVersion": "1.0", "processingDate": "2025-01-25T10:30:00Z", "userLevel": "A2" } } } ``` --- ## 🔧 **前端處理邏輯** ### **API回應處理** #### **數據解析和狀態更新** ```typescript // 位置: frontend/app/generate/page.tsx:101-124 const apiData = result.data // 設定分析結果 setSentenceAnalysis(apiData.vocabularyAnalysis || {}) setSentenceMeaning(apiData.sentenceMeaning || '') // 處理語法修正 if (apiData.grammarCorrection) { setGrammarCorrection({ hasErrors: apiData.grammarCorrection.hasErrors, originalText: textInput, correctedText: apiData.grammarCorrection.correctedText || textInput, corrections: apiData.grammarCorrection.corrections || [] }) // 使用修正後的文本作為最終文本 setFinalText(apiData.grammarCorrection.correctedText || textInput) } else { setFinalText(textInput) } setShowAnalysisView(true) ``` ### **詞彙標記渲染** #### **CEFR等級比較邏輯** ```typescript // 位置: frontend/components/ClickableTextV2.tsx:107-129 const getWordClass = useCallback((word: string) => { const wordAnalysis = findWordAnalysis(word) const baseClass = "cursor-pointer transition-all duration-200 rounded relative mx-0.5 px-1 py-0.5" if (!wordAnalysis) return "" // 慣用語不在句子中顯示標記,統一在慣用語區域展示 const difficultyLevel = getWordProperty(wordAnalysis, 'difficultyLevel') || 'A1' const userLevel = typeof window !== 'undefined' ? localStorage.getItem('userEnglishLevel') || 'A2' : 'A2' const userIndex = getLevelIndex(userLevel) const wordIndex = getLevelIndex(difficultyLevel) if (userIndex > wordIndex) { return `${baseClass} bg-gray-50 border border-dashed border-gray-300 hover:bg-gray-100 hover:border-gray-400 text-gray-600 opacity-80` } else if (userIndex === wordIndex) { return `${baseClass} bg-green-50 border border-green-200 hover:bg-green-100 hover:shadow-lg transform hover:-translate-y-0.5 text-green-700 font-medium` } else { return `${baseClass} bg-orange-50 border border-orange-200 hover:bg-orange-100 hover:shadow-lg transform hover:-translate-y-0.5 text-orange-700 font-medium` } }, [findWordAnalysis, getWordProperty, getLevelIndex]) ``` ### **統計卡片計算** #### **詞彙分類統計** ```typescript // 位置: frontend/app/generate/page.tsx:353-383 const vocabularyStats = useMemo(() => { if (!sentenceAnalysis) return null const userLevel = localStorage.getItem('userEnglishLevel') || 'A2' let simpleCount = 0, moderateCount = 0, difficultCount = 0, idiomCount = 0 Object.entries(sentenceAnalysis).forEach(([, wordData]: [string, any]) => { // 慣用語由獨立的 idioms 陣列處理,不在 vocabularyAnalysis 中 const difficultyLevel = wordData?.difficultyLevel || 'A1' // 所有 vocabularyAnalysis 中的詞彙都是一般詞彙,無慣用語 const userIndex = getLevelIndex(userLevel) const wordIndex = getLevelIndex(difficultyLevel) if (userIndex > wordIndex) { simpleCount++ } else if (userIndex === wordIndex) { moderateCount++ } else { difficultCount++ } } }) // idiomCount 由獨立的 idioms 陣列長度計算 return { simpleCount, moderateCount, difficultCount, idiomCount: sentenceAnalysis.idioms?.length || 0 } }, [sentenceAnalysis]) ``` --- ## 🏗️ **後端處理架構** ### **Gemini API整合** #### **AI分析服務實現** ```csharp // 位置: backend/Services/GeminiService.cs:33-56 public async Task AnalyzeSentenceAsync(string inputText, string userLevel, AnalysisOptions options) { var startTime = DateTime.UtcNow; try { _logger.LogInformation("Starting sentence analysis for text: {Text}, UserLevel: {UserLevel}", inputText.Substring(0, Math.Min(50, inputText.Length)), userLevel); var prompt = BuildAnalysisPrompt(inputText, userLevel, options); var response = await CallGeminiAPI(prompt); var analysisData = ParseGeminiResponse(response, inputText, userLevel); var processingTime = (DateTime.UtcNow - startTime).TotalSeconds; analysisData.Metadata.ProcessingDate = DateTime.UtcNow; _logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime); return analysisData; } catch (Exception ex) { _logger.LogError(ex, "Error analyzing sentence: {Text}", inputText); throw; } } ``` #### **智能Prompt構建** ```csharp // 位置: backend/Services/GeminiService.cs:58-105 private string BuildAnalysisPrompt(string inputText, string userLevel, AnalysisOptions options) { var userIndex = Array.IndexOf(_cefrLevels, userLevel); var targetLevels = GetTargetLevels(userIndex); return $@" 請分析以下英文句子並以JSON格式回應: 句子: ""{inputText}"" 學習者程度: {userLevel} 請提供完整的分析,包含: 1. 語法檢查:檢查是否有語法錯誤,如有則提供修正建議 2. 詞彙分析:分析句子中每個有意義的詞彙 3. 中文翻譯:提供自然流暢的繁體中文翻譯 4. 慣用語識別:識別句子中的慣用語和片語 詞彙分析要求: - 為每個詞彙標註CEFR等級 (A1-C2) - 慣用語單獨放在 idioms 陣列中,不在 vocabularyAnalysis 中 - 提供IPA發音標記 - 包含同義詞 - 提供適當的例句和翻譯 重要:回應必須是有效的JSON格式,不要包含任何其他文字。"; } ``` ### **緩存機制整合** #### **分析結果緩存** ```csharp // 位置: backend/Services/AnalysisCacheService.cs:33-60 public async Task GetCachedAnalysisAsync(string inputText) { try { var textHash = GenerateTextHash(inputText); var cached = await _context.SentenceAnalysisCache .FirstOrDefaultAsync(c => c.InputTextHash == textHash && c.ExpiresAt > DateTime.UtcNow); if (cached != null) { // 更新訪問統計 cached.AccessCount++; cached.LastAccessedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Cache hit for text hash: {TextHash}", textHash); return cached; } _logger.LogInformation("Cache miss for text hash: {TextHash}", textHash); return null; } catch (Exception ex) { _logger.LogError(ex, "Error getting cached analysis for text: {InputText}", inputText); return null; } } ``` --- ## 🎨 **前端UI串接** ### **詞彙標記渲染** #### **ClickableTextV2組件** ```typescript // 位置: frontend/components/ClickableTextV2.tsx:281-299 return (
{words.map((word, index) => { if (word.trim() === '' || /^[.,!?;:\s]+$/.test(word)) { return {word} } const className = getWordClass(word) const icon = getWordIcon(word) return ( handleWordClick(word, e)} > {word} {icon} ) })}
) ``` ### **統計卡片展示** #### **四張統計卡片實現** ```tsx // 位置: frontend/app/generate/page.tsx:581-605 {vocabularyStats && (
{/* 簡單詞彙卡片 */}
{vocabularyStats.simpleCount}
太簡單啦
{/* 適中詞彙卡片 */}
{vocabularyStats.moderateCount}
重點學習
{/* 艱難詞彙卡片 */}
{vocabularyStats.difficultCount}
有點挑戰
{/* 慣用語卡片 */}
{vocabularyStats.idiomCount}
慣用語
)} ``` --- ## 🔒 **認證與安全串接** ### **JWT Token處理** #### **前端Token管理** ```typescript // 位置: frontend/lib/services/auth.ts:77-81 if (response.success && response.data?.token) { // Store token in localStorage localStorage.setItem('auth_token', response.data.token); localStorage.setItem('user_data', JSON.stringify(response.data.user)); } // 位置: frontend/lib/services/flashcards.ts:72-82 private async makeRequest(endpoint: string, options: RequestInit = {}): Promise { const token = this.getAuthToken(); const url = `${API_BASE_URL}/api${endpoint}`; const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...(token && { 'Authorization': `Bearer ${token}` }), ...options.headers, }, ...options, }); } ``` #### **後端認證驗證** ```csharp // 位置: backend/Controllers/AIController.cs:123-135 private string GetCurrentUserId() { var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier) ?? User.FindFirst("sub") ?? User.FindFirst("user_id"); if (userIdClaim?.Value == null) { throw new UnauthorizedAccessException("用戶ID未找到"); } return userIdClaim.Value; } ``` --- ## 🔄 **錯誤處理串接** ### **前端錯誤處理** #### **API錯誤捕獲** ```typescript // 位置: frontend/app/generate/page.tsx:89-99 if (!response.ok) { const errorData = await response.json() throw new Error(errorData.error?.message || `API請求失敗: ${response.status}`) } const result = await response.json() if (!result.success || !result.data) { throw new Error('API回應格式錯誤') } ``` #### **優雅錯誤回退** ```typescript // 位置: frontend/app/generate/page.tsx:125-137 } catch (error) { console.error('Error in sentence analysis:', error) setGrammarCorrection({ hasErrors: true, originalText: textInput, correctedText: textInput, corrections: [] }) setSentenceMeaning('分析過程中發生錯誤,請稍後再試。') setFinalText(textInput) setShowAnalysisView(true) } ``` ### **後端錯誤處理** #### **結構化錯誤回應** ```csharp // 位置: backend/Controllers/AIController.cs:137-155 private ApiErrorResponse CreateErrorResponse(string code, string message, object? details, string requestId) { var suggestions = GetSuggestionsForError(code); return new ApiErrorResponse { Success = false, Error = new ApiError { Code = code, Message = message, Details = details, Suggestions = suggestions }, RequestId = requestId, Timestamp = DateTime.UtcNow }; } ``` --- ## 🚀 **性能優化串接** ### **前端性能優化** #### **React記憶化** ```typescript // 統計計算優化 const vocabularyStats = useMemo(() => { // 計算邏輯... }, [sentenceAnalysis]) // 詞彙分類函數記憶化 const getWordClass = useCallback((word: string) => { // 分類邏輯... }, [findWordAnalysis, getWordProperty, getLevelIndex]) // 彈窗定位計算優化 const calculatePopupPosition = useCallback((rect: DOMRect) => { // 位置計算邏輯... }, []) ``` ### **後端性能優化** #### **快取策略** ```csharp // 快取鍵生成: 文本內容 + 用戶等級 var cacheKey = $"{request.InputText}_{request.UserLevel}"; // 快取過期時間: 24小時 await _cacheService.SetCachedAnalysisAsync(cacheKey, analysisData, TimeSpan.FromHours(24)); // 智能快取清理 [BackgroundService] public class CacheCleanupService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await _cacheService.CleanExpiredCacheAsync(); await Task.Delay(TimeSpan.FromHours(1), stoppingToken); } } } ``` --- ## 🔧 **配置管理串接** ### **環境配置** #### **前端環境設定** ```typescript // API基礎URL配置 const API_BASE_URL = 'http://localhost:5008'; // 開發環境 // const API_BASE_URL = 'https://api.dramaling.com'; // 生產環境 // 字符限制配置 const MAX_MANUAL_INPUT_LENGTH = 300; // CEFR等級配置 const CEFR_LEVELS = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] as const; ``` #### **後端配置管理** ```csharp // User Secrets配置 dotnet user-secrets set "AI:GeminiApiKey" "your-real-gemini-api-key" // appsettings.json配置 { "Gemini": { "ApiKey": "fallback-key" // 僅作為後備 }, "ConnectionStrings": { "DefaultConnection": "Data Source=dramaling_test.db" } } // 環境變數配置 Environment.GetEnvironmentVariable("GEMINI_API_KEY") // 優先級最高 ``` --- ## 📊 **數據流向分析** ### **請求數據流** ``` 1. 用戶輸入 → 前端驗證 → API請求構建 2. HTTP POST → 後端接收 → 模型驗證 3. 快取檢查 → AI服務調用 → 結果處理 4. 數據序列化 → HTTP回應 → 前端接收 5. JSON解析 → 狀態更新 → UI重新渲染 ``` ### **回應數據流** ``` 1. Gemini API → JSON回應 → 後端解析 2. 數據轉換 → DTO映射 → 統計計算 3. 快取儲存 → 結構化回應 → 前端接收 4. 狀態分派 → 組件更新 → 詞彙標記 5. 彈窗渲染 → 統計卡片 → 用戶互動 ``` --- ## 🧪 **測試串接規格** ### **前端測試** #### **單元測試** ```typescript // 測試API調用 describe('handleAnalyzeSentence', () => { it('should call API with correct parameters', async () => { // Mock fetch global.fetch = jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockApiResponse), }) ); await handleAnalyzeSentence(); expect(fetch).toHaveBeenCalledWith( 'http://localhost:5008/api/ai/analyze-sentence', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inputText: 'test sentence', userLevel: 'A2', analysisMode: 'full' }) } ); }); }); ``` #### **整合測試** ```typescript // 測試完整流程 describe('AI Analysis Integration', () => { it('should complete full analysis workflow', async () => { // 1. 輸入文本 fireEvent.change(screen.getByRole('textbox'), { target: { value: 'She just join the team' } }); // 2. 點擊分析按鈕 fireEvent.click(screen.getByText('🔍 分析句子')); // 3. 等待API回應 await waitFor(() => { expect(screen.getByText('語法修正建議')).toBeInTheDocument(); }); // 4. 驗證詞彙標記 expect(screen.getByText('she')).toHaveClass('bg-gray-50'); expect(screen.getByText('join')).toHaveClass('bg-orange-50'); }); }); ``` ### **後端測試** #### **API端點測試** ```csharp [Test] public async Task AnalyzeSentence_WithValidInput_ReturnsSuccessResponse() { // Arrange var request = new SentenceAnalysisRequest { InputText = "She just join the team", UserLevel = "A2", AnalysisMode = "full" }; // Act var response = await _controller.AnalyzeSentence(request); // Assert var okResult = Assert.IsType(response.Result); var analysisResponse = Assert.IsType(okResult.Value); Assert.True(analysisResponse.Success); Assert.NotNull(analysisResponse.Data); Assert.NotEmpty(analysisResponse.Data.VocabularyAnalysis); } ``` #### **Gemini服務測試** ```csharp [Test] public async Task GeminiService_WithValidApiKey_CallsRealAPI() { // Arrange var service = new GeminiService(_httpClient, _configuration, _logger); // Act var result = await service.AnalyzeSentenceAsync("test sentence", "A2", new AnalysisOptions()); // Assert Assert.NotNull(result); Assert.NotEmpty(result.VocabularyAnalysis); Assert.Equal("gemini-pro", result.Metadata.AnalysisModel); } ``` --- ## 🔧 **開發環境串接** ### **啟動順序** #### **開發環境啟動腳本** ```bash # 1. 啟動後端 (Port 5008) cd backend/DramaLing.Api dotnet run # 2. 啟動前端 (Port 3000) cd frontend npm run dev # 3. 設置Gemini API Key dotnet user-secrets set "AI:GeminiApiKey" "your-real-api-key" ``` #### **健康檢查** ```bash # 檢查後端健康狀態 curl http://localhost:5008/health curl http://localhost:5008/api/ai/health # 檢查前端運行狀態 curl http://localhost:3000 ``` ### **調試工具** #### **前端調試** ```typescript // 在瀏覽器Console中 console.log('API回應:', result.data); console.log('詞彙分析:', sentenceAnalysis); console.log('統計資料:', vocabularyStats); // Network面板查看 // 檢查API請求和回應內容 // 驗證請求頭和載荷格式 ``` #### **後端調試** ```csharp // 日誌輸出 _logger.LogInformation("Processing sentence analysis request {RequestId} for user {UserId}", requestId, userId); _logger.LogInformation("Gemini API response: {Response}", response); _logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime); // SQL查詢調試 // 檢查Entity Framework查詢日誌 // 監控數據庫性能指標 ``` --- ## 📈 **監控與分析** ### **性能監控** #### **前端性能指標** ```typescript // 性能測量 const startTime = performance.now(); await handleAnalyzeSentence(); const endTime = performance.now(); console.log(`分析耗時: ${endTime - startTime}ms`); // 記憶體使用監控 const observer = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { console.log('Performance:', entry.name, entry.duration); }); }); observer.observe({ entryTypes: ['measure'] }); ``` #### **後端性能指標** ```csharp // 請求處理時間 var stopwatch = Stopwatch.StartNew(); // ... 處理邏輯 stopwatch.Stop(); _logger.LogInformation("Request processed in {ElapsedMs}ms", stopwatch.ElapsedMilliseconds); // 資料庫查詢性能 services.AddDbContext(options => { options.UseSqlite(connectionString) .EnableSensitiveDataLogging() .LogTo(Console.WriteLine, LogLevel.Information); }); ``` --- ## 🔮 **未來擴展串接** ### **批次分析支援** #### **前端批次請求** ```typescript interface BatchAnalysisRequest { sentences: string[]; userLevel: string; analysisMode: string; } const handleBatchAnalysis = async (sentences: string[]) => { const response = await fetch('/api/ai/batch-analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sentences, userLevel: getUserLevel(), analysisMode: 'full' }) }); }; ``` #### **後端批次處理** ```csharp [HttpPost("batch-analyze")] public async Task> BatchAnalyze( [FromBody] BatchAnalysisRequest request) { var results = new List(); foreach (var sentence in request.Sentences) { var analysis = await _geminiService.AnalyzeSentenceAsync( sentence, request.UserLevel, request.Options); results.Add(analysis); } return Ok(new BatchAnalysisResponse { Results = results }); } ``` ### **即時分析支援** #### **WebSocket連接** ```typescript // 前端WebSocket客戶端 const ws = new WebSocket('ws://localhost:5008/ws/analysis'); ws.onmessage = (event) => { const partialResult = JSON.parse(event.data); updateAnalysisProgress(partialResult); }; // 即時分析請求 ws.send(JSON.stringify({ type: 'analyze', inputText: textInput, userLevel: userLevel })); ``` #### **SignalR整合** ```csharp // 後端SignalR Hub public class AnalysisHub : Hub { public async Task StartAnalysis(string inputText, string userLevel) { await Clients.Caller.SendAsync("AnalysisStarted"); // 分段回傳結果 await Clients.Caller.SendAsync("GrammarCheckComplete", grammarResult); await Clients.Caller.SendAsync("VocabularyAnalysisComplete", vocabResult); await Clients.Caller.SendAsync("AnalysisComplete", finalResult); } } ``` --- ## 📋 **串接檢查清單** ### **開發階段檢查** #### **前端檢查項目** - [ ] API端點URL正確配置 - [ ] 請求格式符合後端期望 - [ ] 錯誤處理涵蓋所有情況 - [ ] 認證Token正確傳遞 - [ ] 響應數據正確解析 - [ ] UI狀態正確更新 - [ ] 性能優化已實施 #### **後端檢查項目** - [ ] API端點路由正確設置 - [ ] 輸入驗證完整實現 - [ ] Gemini API正確調用 - [ ] 數據模型匹配前端需求 - [ ] 錯誤處理完善 - [ ] 緩存機制有效 - [ ] 日誌記錄詳細 ### **生產環境檢查** #### **安全性檢查** - [ ] API Key安全存儲 - [ ] JWT認證正確實施 - [ ] CORS策略適當配置 - [ ] 輸入驗證防止注入 - [ ] 錯誤訊息不洩露敏感資訊 #### **效能檢查** - [ ] API回應時間 < 5秒 - [ ] 快取命中率 > 70% - [ ] 記憶體使用穩定 - [ ] 資料庫查詢優化 - [ ] 並發處理正常 --- ## 📞 **故障排除指南** ### **常見串接問題** #### **CORS錯誤** ``` 錯誤: Access to fetch blocked by CORS policy 解決: 檢查後端CORS設定是否包含前端域名 ``` #### **認證失敗** ``` 錯誤: 401 Unauthorized 解決: 檢查JWT Token是否有效、格式是否正確 ``` #### **數據格式不匹配** ``` 錯誤: Cannot read property 'vocabularyAnalysis' of undefined 解決: 檢查後端回應格式是否符合前端期望 ``` #### **API超時** ``` 錯誤: Network timeout 解決: 檢查Gemini API是否可達、增加超時設定 ``` ### **調試步驟** 1. **檢查網路連通性**: `curl http://localhost:5008/health` 2. **驗證API端點**: `curl -X POST http://localhost:5008/api/ai/analyze-sentence` 3. **檢查前端日誌**: 瀏覽器開發者工具Console 4. **檢查後端日誌**: dotnet應用程式輸出 5. **驗證數據庫**: 檢查SQLite文件和表結構 --- **文件版本**: v1.0 **對應實現**: main分支 commit 03c1756 **最後驗證**: 2025-01-25 **下次檢查**: 功能更新時