18 KiB
18 KiB
🗃️ 查詢歷史快取系統 - 功能規格計劃
專案: DramaLing 英語學習平台 功能: 查詢歷史記錄與智能快取系統 文檔版本: v1.0 建立日期: 2025-01-18 核心概念: 將技術快取包裝為用戶查詢歷史,提升體驗透明度
🎯 核心設計理念
從「快取機制」到「查詢歷史」
| 技術實現 | 用戶概念 | 實際意義 |
|---|---|---|
| Cache Hit | 查詢過的句子 | "您之前查詢過這個句子" |
| Cache Miss | 新句子查詢 | "正在為您分析新句子..." |
| Word Cache | 查詢過的詞彙 | "您之前查詢過這個詞彙" |
| API Call | 即時查詢 | "正在為您查詢詞彙資訊..." |
使用者場景
場景1: 句子查詢
用戶輸入: "Hello world"
第1次: "正在分析..." (3-5秒) → 存入查詢歷史
第2次: "您之前查詢過,立即顯示" (<200ms)
場景2: 詞彙查詢
句子: "The apple"
點擊 "The": "正在查詢..." → 存入詞彙查詢歷史
新句子: "The orange"
點擊 "The": "您之前查詢過,立即顯示" → 從歷史載入
📋 技術規格設計
🎯 A. 句子查詢歷史系統
A1. 當前實現改造
現有: SentenceAnalysisCache (技術導向命名)
改為: 保持技術實現,改變用戶訊息
API 回應訊息改造
檔案: /backend/DramaLing.Api/Controllers/AIController.cs:547
// 當前 (技術導向)
return Ok(new {
Success = true,
Data = cachedResult,
Message = "句子分析完成(快取)", // ❌ 技術術語
Cached = true,
CacheHit = true
});
// 改為 (用戶導向)
return Ok(new {
Success = true,
Data = cachedResult,
Message = "您之前查詢過這個句子,立即為您顯示結果", // ✅ 用戶友善
FromHistory = true, // ✅ 更直觀的欄位名
QueryDate = cachedAnalysis.CreatedAt,
TimesQueried = cachedAnalysis.AccessCount
});
A2. 前端顯示改造
檔案: /frontend/app/generate/page.tsx
// 查詢歷史狀態顯示
{queryStatus && (
<div className={`inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium ${
queryStatus.fromHistory
? 'bg-purple-100 text-purple-800'
: 'bg-blue-100 text-blue-800'
}`}>
{queryStatus.fromHistory ? (
<>
<span className="mr-2">🗃️</span>
<span>查詢歷史 (第{queryStatus.timesQueried}次)</span>
<span className="ml-2 text-xs text-purple-600">
首次查詢: {formatDate(queryStatus.queryDate)}
</span>
</>
) : (
<>
<span className="mr-2">🔍</span>
<span>新句子分析中...</span>
</>
)}
</div>
)}
🎯 B. 詞彙查詢歷史系統
B1. 新增詞彙查詢快取表
-- 用戶詞彙查詢歷史表
CREATE TABLE UserVocabularyQueryHistory (
Id UNIQUEIDENTIFIER PRIMARY KEY,
UserId UNIQUEIDENTIFIER NOT NULL, -- 用戶ID (未來用戶系統)
Word NVARCHAR(100) NOT NULL, -- 查詢的詞彙
WordLowercase NVARCHAR(100) NOT NULL, -- 小寫版本 (查詢鍵)
-- 查詢結果快取
AnalysisResult NVARCHAR(MAX) NOT NULL, -- JSON 格式的分析結果
Translation NVARCHAR(200) NOT NULL, -- 快速存取的翻譯
Definition NVARCHAR(500) NOT NULL, -- 快速存取的定義
-- 查詢上下文
FirstQueriedInSentence NVARCHAR(1000), -- 首次查詢時的句子語境
LastQueriedInSentence NVARCHAR(1000), -- 最後查詢時的句子語境
-- 查詢歷史統計
FirstQueriedAt DATETIME2 NOT NULL, -- 首次查詢時間
LastQueriedAt DATETIME2 NOT NULL, -- 最後查詢時間
QueryCount INT DEFAULT 1, -- 查詢次數
-- 系統欄位
CreatedAt DATETIME2 NOT NULL,
UpdatedAt DATETIME2 NOT NULL,
-- 索引優化
INDEX IX_UserVocabularyQueryHistory_UserId_Word (UserId, WordLowercase),
INDEX IX_UserVocabularyQueryHistory_LastQueriedAt (LastQueriedAt),
-- 暫時不設定外鍵,因為用戶系統還未完全實現
-- FOREIGN KEY (UserId) REFERENCES Users(Id)
);
B2. 詞彙查詢服務重構
檔案: /backend/DramaLing.Api/Services/VocabularyQueryService.cs
public interface IVocabularyQueryService
{
Task<VocabularyQueryResponse> QueryWordAsync(string word, string sentence, Guid? userId = null);
Task<List<UserVocabularyQueryHistory>> GetUserQueryHistoryAsync(Guid userId, int limit = 50);
}
public class VocabularyQueryService : IVocabularyQueryService
{
private readonly DramaLingDbContext _context;
private readonly IGeminiService _geminiService;
private readonly ILogger<VocabularyQueryService> _logger;
public async Task<VocabularyQueryResponse> QueryWordAsync(string word, string sentence, Guid? userId = null)
{
var wordLower = word.ToLower();
var mockUserId = userId ?? Guid.Parse("00000000-0000-0000-0000-000000000001"); // 模擬用戶
// 1. 檢查用戶的詞彙查詢歷史
var queryHistory = await _context.UserVocabularyQueryHistory
.FirstOrDefaultAsync(h => h.UserId == mockUserId && h.WordLowercase == wordLower);
if (queryHistory != null)
{
// 更新查詢統計
queryHistory.LastQueriedAt = DateTime.UtcNow;
queryHistory.LastQueriedInSentence = sentence;
queryHistory.QueryCount++;
await _context.SaveChangesAsync();
// 返回歷史查詢結果
var historicalAnalysis = JsonSerializer.Deserialize<object>(queryHistory.AnalysisResult);
return new VocabularyQueryResponse
{
Success = true,
Data = new
{
Word = word,
Analysis = historicalAnalysis,
QueryHistory = new
{
IsFromHistory = true,
FirstQueriedAt = queryHistory.FirstQueriedAt,
QueryCount = queryHistory.QueryCount,
DaysSinceFirstQuery = (DateTime.UtcNow - queryHistory.FirstQueriedAt).Days,
FirstContext = queryHistory.FirstQueriedInSentence,
CurrentContext = sentence
}
},
Message = $"您之前查詢過 \"{word}\",這是第{queryHistory.QueryCount}次查詢"
};
}
// 2. 新詞彙查詢 - 調用 AI
var aiAnalysis = await AnalyzeWordWithAI(word, sentence);
// 3. 存入查詢歷史
var newHistory = new UserVocabularyQueryHistory
{
Id = Guid.NewGuid(),
UserId = mockUserId,
Word = word,
WordLowercase = wordLower,
AnalysisResult = JsonSerializer.Serialize(aiAnalysis),
Translation = aiAnalysis.Translation,
Definition = aiAnalysis.Definition,
FirstQueriedInSentence = sentence,
LastQueriedInSentence = sentence,
FirstQueriedAt = DateTime.UtcNow,
LastQueriedAt = DateTime.UtcNow,
QueryCount = 1,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
_context.UserVocabularyQueryHistory.Add(newHistory);
await _context.SaveChangesAsync();
return new VocabularyQueryResponse
{
Success = true,
Data = new
{
Word = word,
Analysis = aiAnalysis,
QueryHistory = new
{
IsFromHistory = false,
IsNewQuery = true,
FirstQueriedAt = DateTime.UtcNow,
QueryCount = 1,
Context = sentence
}
},
Message = $"首次查詢 \"{word}\",已加入您的查詢歷史"
};
}
private async Task<object> AnalyzeWordWithAI(string word, string sentence)
{
try
{
// 🚀 這裡應該是真實的 AI 調用,不是模擬
var prompt = $@"
請分析單字 ""{word}"" 在句子 ""{sentence}"" 中的詳細資訊:
單字: {word}
語境: {sentence}
請以JSON格式回應:
{{
""word"": ""{word}"",
""translation"": ""繁體中文翻譯"",
""definition"": ""英文定義"",
""partOfSpeech"": ""詞性"",
""pronunciation"": ""IPA音標"",
""difficultyLevel"": ""CEFR等級"",
""contextMeaning"": ""在此句子中的具體含義"",
""isHighValue"": false,
""examples"": [""例句1"", ""例句2""]
}}
";
var response = await _geminiService.CallGeminiApiAsync(prompt);
return ParseVocabularyAnalysisResponse(response);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "AI vocabulary analysis failed, using fallback data");
// 回退到基本資料
return new
{
word = word,
translation = $"{word} 的翻譯",
definition = $"Definition of {word}",
partOfSpeech = "unknown",
pronunciation = $"/{word}/",
difficultyLevel = "unknown",
contextMeaning = $"在句子 \"{sentence}\" 中的含義",
isHighValue = false,
examples = new string[0]
};
}
}
}
🎯 C. API 端點重構
C1. 更新現有端點
檔案: /backend/DramaLing.Api/Controllers/AIController.cs
句子分析端點保持不變
POST /api/ai/analyze-sentence
只修改回應訊息,讓用戶理解是查詢歷史
詞彙查詢端點整合歷史服務
[HttpPost("query-word")]
[AllowAnonymous]
public async Task<ActionResult> QueryWord([FromBody] QueryWordRequest request)
{
try
{
// 使用新的查詢歷史服務
var result = await _vocabularyQueryService.QueryWordAsync(
request.Word,
request.Sentence,
userId: null // 暫時使用模擬用戶
);
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in vocabulary query");
return StatusCode(500, new
{
Success = false,
Error = "詞彙查詢失敗",
Details = ex.Message
});
}
}
🎯 D. 前端查詢歷史整合
D1. ClickableTextV2 組件改造
檔案: /frontend/components/ClickableTextV2.tsx
// 修改詞彙查詢成功的處理
if (result.success && result.data?.analysis) {
// 顯示查詢歷史資訊
const queryHistory = result.data.queryHistory;
if (queryHistory.isFromHistory) {
console.log(`📚 從查詢歷史載入: ${word} (第${queryHistory.queryCount}次查詢)`);
} else {
console.log(`🔍 新詞彙查詢: ${word} (已加入查詢歷史)`);
}
// 將新的分析資料通知父組件
onNewWordAnalysis?.(word, {
...result.data.analysis,
queryHistory: queryHistory // 附帶查詢歷史資訊
});
// 顯示分析結果
setPopupPosition(position);
setSelectedWord(word);
onWordClick?.(word, result.data.analysis);
}
D2. 詞彙彈窗增加歷史資訊
// 在詞彙彈窗中顯示查詢歷史
function VocabularyPopup({ word, analysis, queryHistory }: Props) {
return (
<div className="vocabulary-popup bg-white border rounded-lg shadow-lg p-4 w-80">
{/* 詞彙基本資訊 */}
<div className="word-basic-info mb-3">
<h3 className="text-lg font-bold">{word}</h3>
<p className="text-gray-600">{analysis.pronunciation}</p>
<p className="text-blue-600 font-medium">{analysis.translation}</p>
<p className="text-gray-700 text-sm mt-1">{analysis.definition}</p>
</div>
{/* 查詢歷史資訊 */}
{queryHistory && (
<div className="query-history bg-gray-50 p-3 rounded-lg">
<h4 className="font-semibold text-xs text-gray-700 mb-2 flex items-center">
<span className="mr-1">🗃️</span>
查詢歷史
</h4>
{queryHistory.isFromHistory ? (
<div className="text-xs text-gray-600 space-y-1">
<div className="flex justify-between">
<span>查詢次數:</span>
<span className="font-medium">{queryHistory.queryCount} 次</span>
</div>
<div className="flex justify-between">
<span>首次查詢:</span>
<span className="font-medium">{formatDate(queryHistory.firstQueriedAt)}</span>
</div>
{queryHistory.firstContext !== queryHistory.currentContext && (
<div className="mt-2 p-2 bg-blue-50 rounded text-xs">
<p className="text-blue-700">
<strong>首次語境:</strong> {queryHistory.firstContext}
</p>
<p className="text-blue-700 mt-1">
<strong>當前語境:</strong> {queryHistory.currentContext}
</p>
</div>
)}
</div>
) : (
<div className="text-xs text-green-600">
✨ 首次查詢,已加入您的查詢歷史
</div>
)}
</div>
)}
</div>
);
}
🎯 E. 用戶介面語言優化
E1. 訊息文案改造
| 情況 | 技術訊息 | 用戶友善訊息 |
|---|---|---|
| 快取命中 | "句子分析完成(快取)" | "您之前查詢過這個句子,立即為您顯示結果" |
| 新查詢 | "AI句子分析完成" | "新句子分析完成,已加入您的查詢歷史" |
| 詞彙快取 | "高價值詞彙查詢完成(免費)" | "您之前查詢過這個詞彙 (第N次查詢)" |
| 詞彙新查詢 | "低價值詞彙查詢完成" | "首次查詢此詞彙,已加入查詢歷史" |
E2. 載入狀態文案
// 分析中的狀態提示
const getLoadingMessage = (type: 'sentence' | 'vocabulary', isNew: boolean) => {
if (type === 'sentence') {
return isNew
? "🔍 正在分析新句子,約需 3-5 秒..."
: "📚 從查詢歷史載入...";
} else {
return isNew
? "🤖 正在查詢詞彙資訊..."
: "🗃️ 從查詢歷史載入...";
}
};
🛠️ 實施計劃
📋 Phase 1: 後端查詢歷史服務 (1-2天)
1.1 建立詞彙查詢歷史表
# 建立 Entity Framework 遷移
dotnet ef migrations add AddUserVocabularyQueryHistory
dotnet ef database update
1.2 建立查詢歷史服務
- 新增
VocabularyQueryService.cs - 實現真實的 AI 詞彙查詢 (替換模擬)
- 整合查詢歷史記錄功能
1.3 修改現有 API 回應訊息
- 將技術術語改為用戶友善語言
- 新增查詢歷史相關欄位
- 保持 API 結構相容性
📋 Phase 2: 前端查詢歷史整合 (2-3天)
2.1 更新 ClickableTextV2 組件
- 整合查詢歷史資訊顯示
- 優化詞彙彈窗包含歷史資訊
- 改善視覺提示系統
2.2 修改 generate 頁面
- 更新查詢狀態顯示
- 改善載入狀態文案
- 新增查詢歷史統計
2.3 訊息文案全面優化
- 替換所有技術術語
- 採用用戶友善的描述
- 增加情境化的提示
📋 Phase 3: 查詢歷史頁面 (3-4天)
3.1 建立查詢歷史頁面
// 新頁面: /frontend/app/query-history/page.tsx
- 顯示所有查詢過的句子
- 顯示所有查詢過的詞彙
- 提供搜尋和篩選功能
- 支援重新查詢功能
3.2 導航整合
- 在主導航中新增「查詢歷史」
- 在 generate 頁面新增快速連結
- 在詞彙彈窗中新增「查看完整歷史」
📊 與現有快取系統的關係
保持底層技術優勢
- ✅ 效能優化: 繼續享受快取帶來的速度提升
- ✅ 成本控制: 避免重複的 AI API 調用
- ✅ 系統穩定性: 保持現有的錯誤處理機制
改善用戶認知
- 🔄 概念轉換: 從「快取」到「查詢歷史」
- 📊 透明化: 讓用戶了解系統行為
- 🎯 價值感知: 用戶看到查詢的累積價值
技術實現不變,體驗大幅提升
底層: 仍然是高效的快取機制
表層: 包裝為有意義的查詢歷史體驗
結果: 技術效益 + 用戶體驗雙贏
🎯 預期效果
用戶體驗轉變
- 舊: "為什麼這個查詢這麼快?"
- 新: "我之前查詢過這個詞彙,這是第3次遇到"
系統感知轉變
- 舊: 神秘的黑盒子系統
- 新: 透明的查詢歷史助手
價值感知轉變
- 舊: 一次性工具
- 新: 個人化查詢資料庫
📋 成功指標
定量指標
- 歷史查看率: >60% 用戶注意到查詢歷史資訊
- 重複查詢滿意度: >80% 用戶對快速載入感到滿意
- 功能理解度: >90% 用戶理解為什麼有些查詢很快
定性指標
- 透明感: 用戶明白系統行為邏輯
- 積累感: 用戶感受到查詢的累積價值
- 信任感: 用戶信任系統會記住他們的查詢
© 2025 DramaLing Development Team 設計理念: 技術服務於用戶體驗,快取包裝為查詢歷史 核心價值: 讓用戶感受到每次查詢的累積意義
我覺得快取機制不太貼切,
具體應該改成歷史紀錄的概念
使用者查完某個原始例句後
就會存成紀錄
如果在查詢非高價值的詞彙,因為還沒有紀錄所以就會再去問ad
然後再存到紀錄中\
這不是學習歷史
使用者也沒有儲存詞彙
那只是查詢的歷史而已
請你設計這個功能
寫成功能規格計劃再根目錄