feat: 修復 AI 生成同義詞完整保存功能
- 添加 Synonyms 屬性到 Flashcard 實體模型並配置 DbContext - 執行 FixSynonymsColumn migration 在資料庫中添加 synonyms 欄位 - 更新前後端 CreateFlashcardRequest DTO 支援同義詞傳輸 - 修復前端 generate page 包含 AI 生成的同義詞資料 - 添加前端安全 JSON 解析,正確顯示同義詞標籤 - 修復完整資料流程:AI 分析 → 前端處理 → API 傳輸 → 資料庫儲存 現在 AI 生成的同義詞不再被浪費,完整保存並在詞卡詳細頁面顯示。 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3b6b52c0d4
commit
f08d798aa4
|
|
@ -62,6 +62,7 @@ public class FlashcardsController : BaseController
|
|||
f.Example,
|
||||
f.ExampleTranslation,
|
||||
f.IsFavorite,
|
||||
f.Synonyms,
|
||||
DifficultyLevelNumeric = f.DifficultyLevelNumeric,
|
||||
CEFR = CEFRHelper.ToString(f.DifficultyLevelNumeric),
|
||||
f.CreatedAt,
|
||||
|
|
@ -117,6 +118,7 @@ public class FlashcardsController : BaseController
|
|||
Pronunciation = request.Pronunciation,
|
||||
Example = request.Example,
|
||||
ExampleTranslation = request.ExampleTranslation,
|
||||
Synonyms = request.Synonyms, // 儲存 AI 生成的同義詞
|
||||
DifficultyLevelNumeric = CEFRHelper.ToNumeric(request.CEFR ?? "A0"),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
|
|
@ -168,6 +170,7 @@ public class FlashcardsController : BaseController
|
|||
flashcard.Example,
|
||||
flashcard.ExampleTranslation,
|
||||
flashcard.IsFavorite,
|
||||
flashcard.Synonyms,
|
||||
DifficultyLevelNumeric = flashcard.DifficultyLevelNumeric,
|
||||
CEFR = CEFRHelper.ToString(flashcard.DifficultyLevelNumeric),
|
||||
flashcard.CreatedAt,
|
||||
|
|
@ -402,5 +405,6 @@ public class CreateFlashcardRequest
|
|||
public string Pronunciation { get; set; } = string.Empty;
|
||||
public string Example { get; set; } = string.Empty;
|
||||
public string? ExampleTranslation { get; set; }
|
||||
public string? Synonyms { get; set; } // AI 生成的同義詞 (JSON 字串)
|
||||
public string? CEFR { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -129,6 +129,7 @@ public class DramaLingDbContext : DbContext
|
|||
flashcardEntity.Property(f => f.Pronunciation).HasColumnName("pronunciation");
|
||||
flashcardEntity.Property(f => f.Example).HasColumnName("example");
|
||||
flashcardEntity.Property(f => f.ExampleTranslation).HasColumnName("example_translation");
|
||||
flashcardEntity.Property(f => f.Synonyms).HasColumnName("synonyms");
|
||||
// 已刪除的復習相關屬性配置
|
||||
// EasinessFactor, IntervalDays, NextReviewDate, MasteryLevel,
|
||||
// TimesReviewed, TimesCorrect, LastReviewedAt 已移除
|
||||
|
|
|
|||
1346
backend/DramaLing.Api/Migrations/20251007093605_FixSynonymsColumn.Designer.cs
generated
Normal file
1346
backend/DramaLing.Api/Migrations/20251007093605_FixSynonymsColumn.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DramaLing.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixSynonymsColumn : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "synonyms",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
maxLength: 2000,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "synonyms",
|
||||
table: "flashcards");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -348,6 +348,11 @@ namespace DramaLing.Api.Migrations
|
|||
.HasColumnType("TEXT")
|
||||
.HasColumnName("pronunciation");
|
||||
|
||||
b.Property<string>("Synonyms")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("synonyms");
|
||||
|
||||
b.Property<string>("Translation")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ public class Flashcard
|
|||
|
||||
public string? ExampleTranslation { get; set; }
|
||||
|
||||
[MaxLength(2000)]
|
||||
public string? Synonyms { get; set; }
|
||||
|
||||
// 基本狀態
|
||||
public bool IsFavorite { get; set; } = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ function GenerateContent() {
|
|||
partOfSpeech: analysis.partOfSpeech || analysis.PartOfSpeech || 'noun',
|
||||
example: analysis.example || `Example sentence with ${word}.`, // 使用分析結果的例句
|
||||
exampleTranslation: analysis.exampleTranslation,
|
||||
synonyms: analysis.synonyms ? JSON.stringify(analysis.synonyms) : undefined, // 轉換為 JSON 字串
|
||||
cefr: cefrValue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,23 @@ export const FlashcardContentBlocks: React.FC<FlashcardContentBlocksProps> = ({
|
|||
generationProgress,
|
||||
onGenerateImage
|
||||
}) => {
|
||||
// 安全解析同義詞 JSON 字串
|
||||
const parseSynonyms = (synonymsData: any): string[] => {
|
||||
if (!synonymsData) return []
|
||||
if (Array.isArray(synonymsData)) return synonymsData
|
||||
if (typeof synonymsData === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(synonymsData)
|
||||
return Array.isArray(parsed) ? parsed : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const synonymsList = parseSynonyms((flashcard as any).synonyms)
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
{/* 翻譯區塊 */}
|
||||
|
|
@ -173,11 +190,11 @@ export const FlashcardContentBlocks: React.FC<FlashcardContentBlocksProps> = ({
|
|||
</div>
|
||||
|
||||
{/* 同義詞區塊 */}
|
||||
{(flashcard as any).synonyms && (flashcard as any).synonyms.length > 0 && (
|
||||
{synonymsList.length > 0 && (
|
||||
<div className="bg-purple-50 rounded-lg p-4 border border-purple-200">
|
||||
<h3 className="font-semibold text-purple-900 mb-3 text-left">同義詞</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(flashcard as any).synonyms.map((synonym: string, index: number) => (
|
||||
{synonymsList.map((synonym: string, index: number) => (
|
||||
<span
|
||||
key={index}
|
||||
className="bg-white text-purple-700 px-3 py-1 rounded-full text-sm border border-purple-200 font-medium"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ export const SearchControls: React.FC<SearchControlsProps> = ({
|
|||
className="text-sm border border-gray-300 rounded-md px-3 py-1 focus:ring-2 focus:ring-primary focus:border-primary"
|
||||
>
|
||||
<option value="createdAt">創建時間</option>
|
||||
<option value="masteryLevel">掌握程度</option>
|
||||
<option value="word">字母順序</option>
|
||||
<option value="cefr">CEFR等級</option>
|
||||
<option value="timesReviewed">複習次數</option>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export interface CreateFlashcardRequest {
|
|||
partOfSpeech: string;
|
||||
example: string;
|
||||
exampleTranslation?: string;
|
||||
synonyms?: string; // AI 生成的同義詞 (JSON 字串格式)
|
||||
cefr?: string; // A1, A2, B1, B2, C1, C2
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue