dramaling-vocab-learning/AI生成功能前後端串接規格.md

1115 lines
31 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生成功能前後端串接規格
## 📋 **文件資訊**
- **文件名稱**: 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<br/>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,
includePhraseDetection: true,
includeExamples: true
}
})
})
const result = await response.json()
// 處理API回應...
}
```
#### **後端處理實現**
```csharp
// 位置: backend/Controllers/AIController.cs:32-105
[HttpPost("analyze-sentence")]
public async Task<ActionResult<SentenceAnalysisResponse>> 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,
"includePhraseDetection": 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",
"isPhrase": false,
"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",
"isPhrase": true,
"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,
"phrases": 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 isPhrase = getWordProperty(wordAnalysis, 'isPhrase')
if (isPhrase) 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, phraseCount = 0
Object.entries(sentenceAnalysis).forEach(([, wordData]: [string, any]) => {
const isPhrase = wordData?.isPhrase || wordData?.IsPhrase
const difficultyLevel = wordData?.difficultyLevel || 'A1'
if (isPhrase) {
phraseCount++
} else {
const userIndex = getLevelIndex(userLevel)
const wordIndex = getLevelIndex(difficultyLevel)
if (userIndex > wordIndex) {
simpleCount++
} else if (userIndex === wordIndex) {
moderateCount++
} else {
difficultCount++
}
}
})
return { simpleCount, moderateCount, difficultCount, phraseCount }
}, [sentenceAnalysis])
```
---
## 🏗️ **後端處理架構**
### **Gemini API整合**
#### **AI分析服務實現**
```csharp
// 位置: backend/Services/GeminiService.cs:33-56
public async Task<SentenceAnalysisData> 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)
- 如果是慣用語,設置 isPhrase: true
- 提供IPA發音標記
- 包含同義詞
- 提供適當的例句和翻譯
重要回應必須是有效的JSON格式不要包含任何其他文字。";
}
```
### **緩存機制整合**
#### **分析結果緩存**
```csharp
// 位置: backend/Services/AnalysisCacheService.cs:33-60
public async Task<SentenceAnalysisCache?> 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 (
<div className="relative">
<div className="text-lg" style={{lineHeight: '2.5'}}>
{words.map((word, index) => {
if (word.trim() === '' || /^[.,!?;:\s]+$/.test(word)) {
return <span key={index}>{word}</span>
}
const className = getWordClass(word)
const icon = getWordIcon(word)
return (
<span
key={index}
className={className}
onClick={(e) => handleWordClick(word, e)}
>
{word}
{icon}
</span>
)
})}
</div>
<VocabPopup />
</div>
)
```
### **統計卡片展示**
#### **四張統計卡片實現**
```tsx
// 位置: frontend/app/generate/page.tsx:581-605
{vocabularyStats && (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-6">
{/* 簡單詞彙卡片 */}
<div className="bg-gray-50 border border-dashed border-gray-300 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-gray-600 mb-1">{vocabularyStats.simpleCount}</div>
<div className="text-gray-600 text-xs sm:text-sm font-medium">太簡單啦</div>
</div>
{/* 適中詞彙卡片 */}
<div className="bg-green-50 border border-green-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-green-700 mb-1">{vocabularyStats.moderateCount}</div>
<div className="text-green-700 text-xs sm:text-sm font-medium">重點學習</div>
</div>
{/* 艱難詞彙卡片 */}
<div className="bg-orange-50 border border-orange-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-orange-700 mb-1">{vocabularyStats.difficultCount}</div>
<div className="text-orange-700 text-xs sm:text-sm font-medium">有點挑戰</div>
</div>
{/* 慣用語卡片 */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-blue-700 mb-1">{vocabularyStats.phraseCount}</div>
<div className="text-blue-700 text-xs sm:text-sm font-medium">慣用語</div>
</div>
</div>
)}
```
---
## 🔒 **認證與安全串接**
### **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<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
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<OkObjectResult>(response.Result);
var analysisResponse = Assert.IsType<SentenceAnalysisResponse>(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<DramaLingDbContext>(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<ActionResult<BatchAnalysisResponse>> BatchAnalyze(
[FromBody] BatchAnalysisRequest request)
{
var results = new List<SentenceAnalysisData>();
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
**下次檢查**: 功能更新時