feat: 新增例句圖生成後端開發計劃
基於當前 ASP.NET Core 架構分析,制定完整的兩階段圖片生成系統開發計劃: **架構分析**: - ✅ 已具備:Gemini 整合、EF Core、JWT 認證、快取服務 - ❌ 需新增:Replicate API、流程編排器、儲存抽象層 **開發規劃** (6-8週): - Phase 1: 資料庫 Schema 擴展和基礎配置 - Phase 2: Gemini 描述生成和 Replicate 圖片生成服務 - Phase 3: API 端點和兩階段流程編排器 - Phase 4: 快取優化和成本控制系統 **技術細節**: - 具體的 C# 程式碼範例和檔案結構 - 完整的環境配置和 NuGet 套件需求 - 測試策略和部署檢查清單 - 與現有架構的整合方案 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
502e7f920b
commit
179cbc6258
|
|
@ -0,0 +1,700 @@
|
|||
# 例句圖生成功能後端開發計劃
|
||||
|
||||
## 📋 當前架構評估
|
||||
|
||||
### ✅ 已具備的基礎架構
|
||||
- **ASP.NET Core 8.0** + EF Core 8.0 + SQLite
|
||||
- **Gemini AI 整合** (`GeminiService.cs` 已實現)
|
||||
- **依賴注入架構** 完整配置
|
||||
- **JWT 認證機制** 已建立
|
||||
- **錯誤處理中介軟體** 已實現
|
||||
- **快取服務** (`HybridCacheService`) 可重用
|
||||
|
||||
### ❌ 需要新增的組件
|
||||
- **Replicate API 整合服務**
|
||||
- **兩階段流程編排器**
|
||||
- **圖片儲存抽象層**
|
||||
- **資料庫 Schema 擴展**
|
||||
- **新的 API 端點**
|
||||
|
||||
## 🎯 開發目標
|
||||
|
||||
基於現有架構,實現 **Gemini + Replicate 兩階段例句圖生成系統**,預估開發時間 **6-8 週**。
|
||||
|
||||
---
|
||||
|
||||
## 📅 Phase 1: 基礎架構擴展 (Week 1-2)
|
||||
|
||||
### Week 1: 資料庫 Schema 擴展
|
||||
|
||||
#### 1.1 新增資料表 Migration
|
||||
```bash
|
||||
dotnet ef migrations add AddImageGenerationTables
|
||||
```
|
||||
|
||||
**需要新增的表格**:
|
||||
- `example_images` (例句圖片表)
|
||||
- `flashcard_example_images` (關聯表)
|
||||
- `image_generation_requests` (生成請求追蹤表)
|
||||
|
||||
#### 1.2 實體模型建立
|
||||
**檔案位置**: `/Models/Entities/`
|
||||
|
||||
```csharp
|
||||
// ExampleImage.cs
|
||||
public class ExampleImage
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string? AltText { get; set; }
|
||||
public string? GeminiPrompt { get; set; }
|
||||
public string? GeminiDescription { get; set; }
|
||||
public string? ReplicatePrompt { get; set; }
|
||||
public string ReplicateModel { get; set; }
|
||||
public decimal? GeminiCost { get; set; }
|
||||
public decimal? ReplicateCost { get; set; }
|
||||
public decimal? TotalGenerationCost { get; set; }
|
||||
// ... 其他欄位參考 PRD
|
||||
}
|
||||
|
||||
// ImageGenerationRequest.cs
|
||||
public class ImageGenerationRequest
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid FlashcardId { get; set; }
|
||||
public string OverallStatus { get; set; } // pending/description_generating/image_generating/completed/failed
|
||||
public string GeminiStatus { get; set; }
|
||||
public string ReplicateStatus { get; set; }
|
||||
// ... 兩階段追蹤欄位
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 DbContext 更新
|
||||
**檔案**: `/Data/DramaLingDbContext.cs`
|
||||
```csharp
|
||||
public DbSet<ExampleImage> ExampleImages { get; set; }
|
||||
public DbSet<ImageGenerationRequest> ImageGenerationRequests { get; set; }
|
||||
public DbSet<FlashcardExampleImage> FlashcardExampleImages { get; set; }
|
||||
|
||||
// 在 OnModelCreating 中配置關聯
|
||||
```
|
||||
|
||||
### Week 2: 配置和基礎服務
|
||||
|
||||
#### 2.1 Replicate 配置選項
|
||||
**檔案**: `/Models/Configuration/ReplicateOptions.cs`
|
||||
```csharp
|
||||
public class ReplicateOptions
|
||||
{
|
||||
public const string SectionName = "Replicate";
|
||||
|
||||
[Required]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
public string BaseUrl { get; set; } = "https://api.replicate.com/v1";
|
||||
|
||||
[Range(1, 300)]
|
||||
public int TimeoutSeconds { get; set; } = 180;
|
||||
|
||||
public Dictionary<string, ModelConfig> Models { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ModelConfig
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public decimal CostPerGeneration { get; set; }
|
||||
public int DefaultWidth { get; set; } = 512;
|
||||
public int DefaultHeight { get; set; } = 512;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 儲存抽象層介面定義
|
||||
**檔案**: `/Services/Storage/IImageStorageService.cs`
|
||||
```csharp
|
||||
public interface IImageStorageService
|
||||
{
|
||||
Task<string> SaveImageAsync(Stream imageStream, string fileName);
|
||||
Task<string> GetImageUrlAsync(string imagePath);
|
||||
Task<bool> DeleteImageAsync(string imagePath);
|
||||
Task<StorageInfo> GetStorageInfoAsync();
|
||||
}
|
||||
|
||||
public class LocalImageStorageService : IImageStorageService
|
||||
{
|
||||
// 開發環境實現
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 Program.cs 服務註冊更新
|
||||
```csharp
|
||||
// 新增 Replicate 配置
|
||||
builder.Services.Configure<ReplicateOptions>(
|
||||
builder.Configuration.GetSection(ReplicateOptions.SectionName));
|
||||
builder.Services.AddSingleton<IValidateOptions<ReplicateOptions>, ReplicateOptionsValidator>();
|
||||
|
||||
// 新增圖片生成服務
|
||||
builder.Services.AddHttpClient<IReplicateImageGenerationService, ReplicateImageGenerationService>();
|
||||
builder.Services.AddScoped<IGeminiImageDescriptionService, GeminiImageDescriptionService>();
|
||||
builder.Services.AddScoped<IImageGenerationOrchestrator, ImageGenerationOrchestrator>();
|
||||
|
||||
// 新增儲存服務
|
||||
builder.Services.AddScoped<IImageStorageService>(provider =>
|
||||
{
|
||||
var config = provider.GetRequiredService<IConfiguration>();
|
||||
return ImageStorageFactory.Create(config, provider.GetRequiredService<ILogger<IImageStorageService>>());
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Phase 2: 核心服務實現 (Week 3-4)
|
||||
|
||||
### Week 3: Gemini 描述生成服務
|
||||
|
||||
#### 3.1 擴展現有 GeminiService
|
||||
**檔案**: `/Services/AI/GeminiImageDescriptionService.cs`
|
||||
|
||||
```csharp
|
||||
public class GeminiImageDescriptionService : IGeminiImageDescriptionService
|
||||
{
|
||||
private readonly GeminiService _geminiService; // 重用現有服務
|
||||
private readonly ILogger<GeminiImageDescriptionService> _logger;
|
||||
|
||||
public async Task<ImageDescriptionResult> GenerateDescriptionAsync(
|
||||
Flashcard flashcard,
|
||||
GenerationOptions options)
|
||||
{
|
||||
var prompt = BuildImageDescriptionPrompt(flashcard, options);
|
||||
|
||||
// 重用現有的 GeminiService.CallGeminiAPIAsync()
|
||||
var response = await _geminiService.CallGeminiAPIAsync(prompt);
|
||||
|
||||
return new ImageDescriptionResult
|
||||
{
|
||||
Success = true,
|
||||
Description = ExtractDescription(response),
|
||||
OptimizedPrompt = OptimizeForReplicate(response, options),
|
||||
Cost = CalculateCost(prompt),
|
||||
ProcessingTimeMs = stopwatch.ElapsedMilliseconds
|
||||
};
|
||||
}
|
||||
|
||||
private string BuildImageDescriptionPrompt(Flashcard flashcard, GenerationOptions options)
|
||||
{
|
||||
return $@"Generate a detailed image description for English learning flashcard.
|
||||
|
||||
Word: {flashcard.Word}
|
||||
Translation: {flashcard.Translation}
|
||||
Example: {flashcard.Example}
|
||||
Difficulty: {flashcard.DifficultyLevel}
|
||||
Style: {options.Style}
|
||||
|
||||
Create a vivid, educational scene description that clearly illustrates the word's meaning.
|
||||
Return only the image description, no additional text.";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 資料模型和 DTOs
|
||||
**檔案**: `/Models/DTOs/ImageGenerationDto.cs`
|
||||
```csharp
|
||||
public class ImageDescriptionResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? OptimizedPrompt { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public int ProcessingTimeMs { get; set; }
|
||||
public string? Error { get; set; }
|
||||
}
|
||||
|
||||
public class GenerationOptions
|
||||
{
|
||||
public string Style { get; set; } = "realistic";
|
||||
public int Width { get; set; } = 512;
|
||||
public int Height { get; set; } = 512;
|
||||
public string ReplicateModel { get; set; } = "flux-1-dev";
|
||||
public bool UseCache { get; set; } = true;
|
||||
public int TimeoutMinutes { get; set; } = 5;
|
||||
}
|
||||
```
|
||||
|
||||
### Week 4: Replicate 圖片生成服務
|
||||
|
||||
#### 4.1 Replicate API 整合
|
||||
**檔案**: `/Services/AI/ReplicateImageGenerationService.cs`
|
||||
|
||||
```csharp
|
||||
public class ReplicateImageGenerationService : IReplicateImageGenerationService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ReplicateOptions _options;
|
||||
private readonly ILogger<ReplicateImageGenerationService> _logger;
|
||||
|
||||
public async Task<ImageGenerationResult> GenerateImageAsync(
|
||||
string prompt,
|
||||
string model,
|
||||
GenerationOptions options)
|
||||
{
|
||||
// 1. 啟動 Replicate 預測
|
||||
var prediction = await StartPredictionAsync(prompt, model, options);
|
||||
|
||||
// 2. 輪詢檢查生成狀態
|
||||
var result = await WaitForCompletionAsync(prediction.Id, options.TimeoutMinutes);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ReplicatePrediction> StartPredictionAsync(
|
||||
string prompt,
|
||||
string model,
|
||||
GenerationOptions options)
|
||||
{
|
||||
var requestBody = BuildModelRequest(prompt, model, options);
|
||||
|
||||
var response = await _httpClient.PostAsync(
|
||||
$"{_options.BaseUrl}/predictions",
|
||||
new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json"));
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<ReplicatePrediction>(json);
|
||||
}
|
||||
|
||||
private async Task<ImageGenerationResult> WaitForCompletionAsync(
|
||||
string predictionId,
|
||||
int timeoutMinutes)
|
||||
{
|
||||
var timeout = TimeSpan.FromMinutes(timeoutMinutes);
|
||||
var pollInterval = TimeSpan.FromSeconds(2);
|
||||
var startTime = DateTime.UtcNow;
|
||||
|
||||
while (DateTime.UtcNow - startTime < timeout)
|
||||
{
|
||||
var status = await GetPredictionStatusAsync(predictionId);
|
||||
|
||||
switch (status.Status)
|
||||
{
|
||||
case "succeeded":
|
||||
return new ImageGenerationResult
|
||||
{
|
||||
Success = true,
|
||||
ImageUrl = status.Output?.FirstOrDefault()?.ToString(),
|
||||
ProcessingTimeMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds,
|
||||
Cost = CalculateCost(status)
|
||||
};
|
||||
|
||||
case "failed":
|
||||
return new ImageGenerationResult
|
||||
{
|
||||
Success = false,
|
||||
Error = status.Error?.ToString() ?? "Generation failed"
|
||||
};
|
||||
|
||||
case "processing":
|
||||
await Task.Delay(pollInterval);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageGenerationResult
|
||||
{
|
||||
Success = false,
|
||||
Error = "Generation timeout"
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Phase 3: API 端點和流程編排 (Week 5-6)
|
||||
|
||||
### Week 5: 兩階段流程編排器
|
||||
|
||||
#### 5.1 核心編排器
|
||||
**檔案**: `/Services/ImageGenerationOrchestrator.cs`
|
||||
|
||||
```csharp
|
||||
public class ImageGenerationOrchestrator : IImageGenerationOrchestrator
|
||||
{
|
||||
private readonly IGeminiImageDescriptionService _geminiService;
|
||||
private readonly IReplicateImageGenerationService _replicateService;
|
||||
private readonly IImageStorageService _storageService;
|
||||
private readonly DramaLingDbContext _dbContext;
|
||||
|
||||
public async Task<GenerationRequestResult> StartGenerationAsync(
|
||||
Guid flashcardId,
|
||||
GenerationRequest request)
|
||||
{
|
||||
// 1. 建立追蹤記錄
|
||||
var generationRequest = new ImageGenerationRequest
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = request.UserId,
|
||||
FlashcardId = flashcardId,
|
||||
OverallStatus = "pending",
|
||||
GeminiStatus = "pending",
|
||||
ReplicateStatus = "pending",
|
||||
OriginalRequest = JsonSerializer.Serialize(request),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_dbContext.ImageGenerationRequests.Add(generationRequest);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// 2. 後台執行兩階段生成
|
||||
_ = Task.Run(async () => await ExecuteGenerationPipelineAsync(generationRequest));
|
||||
|
||||
return new GenerationRequestResult
|
||||
{
|
||||
RequestId = generationRequest.Id,
|
||||
Status = "pending",
|
||||
EstimatedTimeMinutes = 3
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ExecuteGenerationPipelineAsync(ImageGenerationRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 第一階段:Gemini 描述生成
|
||||
await UpdateRequestStatusAsync(request.Id, "description_generating");
|
||||
|
||||
var flashcard = await _dbContext.Flashcards.FindAsync(request.FlashcardId);
|
||||
var options = JsonSerializer.Deserialize<GenerationOptions>(request.OriginalRequest);
|
||||
|
||||
var descriptionResult = await _geminiService.GenerateDescriptionAsync(flashcard, options);
|
||||
|
||||
if (!descriptionResult.Success)
|
||||
{
|
||||
await MarkRequestAsFailedAsync(request.Id, "gemini", descriptionResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 Gemini 結果
|
||||
await UpdateGeminiResultAsync(request.Id, descriptionResult);
|
||||
|
||||
// 第二階段:Replicate 圖片生成
|
||||
await UpdateRequestStatusAsync(request.Id, "image_generating");
|
||||
|
||||
var imageResult = await _replicateService.GenerateImageAsync(
|
||||
descriptionResult.OptimizedPrompt,
|
||||
options.ReplicateModel,
|
||||
options);
|
||||
|
||||
if (!imageResult.Success)
|
||||
{
|
||||
await MarkRequestAsFailedAsync(request.Id, "replicate", imageResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 儲存圖片和完成請求
|
||||
var savedImage = await SaveGeneratedImageAsync(request, descriptionResult, imageResult);
|
||||
await CompleteRequestAsync(request.Id, savedImage.Id);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Generation pipeline failed for request {RequestId}", request.Id);
|
||||
await MarkRequestAsFailedAsync(request.Id, "system", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Week 6: API 控制器實現
|
||||
|
||||
#### 6.1 新增圖片生成控制器
|
||||
**檔案**: `/Controllers/ImageGenerationController.cs`
|
||||
|
||||
```csharp
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ImageGenerationController : ControllerBase
|
||||
{
|
||||
private readonly IImageGenerationOrchestrator _orchestrator;
|
||||
private readonly DramaLingDbContext _dbContext;
|
||||
|
||||
[HttpPost("flashcards/{flashcardId}/generate")]
|
||||
public async Task<IActionResult> GenerateImage(
|
||||
Guid flashcardId,
|
||||
[FromBody] GenerationRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId(); // 從 JWT 取得
|
||||
request.UserId = userId;
|
||||
|
||||
var result = await _orchestrator.StartGenerationAsync(flashcardId, request);
|
||||
|
||||
return Ok(new { success = true, data = result });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to start image generation for flashcard {FlashcardId}", flashcardId);
|
||||
return BadRequest(new { success = false, error = "Failed to start generation" });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("requests/{requestId}/status")]
|
||||
public async Task<IActionResult> GetGenerationStatus(Guid requestId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = await _dbContext.ImageGenerationRequests
|
||||
.FirstOrDefaultAsync(r => r.Id == requestId);
|
||||
|
||||
if (request == null)
|
||||
return NotFound(new { success = false, error = "Request not found" });
|
||||
|
||||
var response = BuildStatusResponse(request);
|
||||
return Ok(new { success = true, data = response });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get status for request {RequestId}", requestId);
|
||||
return BadRequest(new { success = false, error = "Failed to get status" });
|
||||
}
|
||||
}
|
||||
|
||||
private object BuildStatusResponse(ImageGenerationRequest request)
|
||||
{
|
||||
return new
|
||||
{
|
||||
requestId = request.Id,
|
||||
overallStatus = request.OverallStatus,
|
||||
stages = new
|
||||
{
|
||||
gemini = new
|
||||
{
|
||||
status = request.GeminiStatus,
|
||||
startedAt = request.GeminiStartedAt,
|
||||
completedAt = request.GeminiCompletedAt,
|
||||
processingTimeMs = request.GeminiProcessingTimeMs,
|
||||
cost = request.GeminiCost,
|
||||
generatedDescription = request.GeneratedDescription
|
||||
},
|
||||
replicate = new
|
||||
{
|
||||
status = request.ReplicateStatus,
|
||||
startedAt = request.ReplicateStartedAt,
|
||||
completedAt = request.ReplicateCompletedAt,
|
||||
processingTimeMs = request.ReplicateProcessingTimeMs,
|
||||
cost = request.ReplicateCost
|
||||
}
|
||||
},
|
||||
totalCost = request.TotalCost,
|
||||
completedAt = request.CompletedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Phase 4: 快取和優化 (Week 7-8)
|
||||
|
||||
### Week 7: 兩階段快取實現
|
||||
|
||||
#### 7.1 擴展現有快取服務
|
||||
**檔案**: `/Services/Caching/ImageGenerationCacheService.cs`
|
||||
|
||||
```csharp
|
||||
public class ImageGenerationCacheService : IImageGenerationCacheService
|
||||
{
|
||||
private readonly ICacheService _cacheService; // 重用現有快取
|
||||
private readonly DramaLingDbContext _dbContext;
|
||||
|
||||
public async Task<string?> GetCachedDescriptionAsync(
|
||||
Flashcard flashcard,
|
||||
GenerationOptions options)
|
||||
{
|
||||
// 1. 完全匹配快取
|
||||
var cacheKey = $"desc:{flashcard.Id}:{options.GetHashCode()}";
|
||||
var cached = await _cacheService.GetAsync<string>(cacheKey);
|
||||
if (cached != null) return cached;
|
||||
|
||||
// 2. 語意匹配 (資料庫查詢)
|
||||
var similarDesc = await FindSimilarDescriptionAsync(flashcard, options);
|
||||
if (similarDesc != null)
|
||||
{
|
||||
// 快取相似結果
|
||||
await _cacheService.SetAsync(cacheKey, similarDesc, TimeSpan.FromHours(1));
|
||||
return similarDesc;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<string?> GetCachedImageAsync(string optimizedPrompt)
|
||||
{
|
||||
var promptHash = ComputeHash(optimizedPrompt);
|
||||
var cacheKey = $"img:{promptHash}";
|
||||
|
||||
return await _cacheService.GetAsync<string>(cacheKey);
|
||||
}
|
||||
|
||||
public async Task CacheDescriptionAsync(
|
||||
Flashcard flashcard,
|
||||
GenerationOptions options,
|
||||
string description)
|
||||
{
|
||||
var cacheKey = $"desc:{flashcard.Id}:{options.GetHashCode()}";
|
||||
await _cacheService.SetAsync(cacheKey, description, TimeSpan.FromHours(24));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Week 8: 成本控制和監控
|
||||
|
||||
#### 8.1 積分系統整合
|
||||
**檔案**: `/Services/CreditManagementService.cs`
|
||||
|
||||
```csharp
|
||||
public class CreditManagementService : ICreditManagementService
|
||||
{
|
||||
public async Task<bool> HasSufficientCreditsAsync(Guid userId, decimal requiredCredits)
|
||||
{
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
return user.Credits >= requiredCredits;
|
||||
}
|
||||
|
||||
public async Task<bool> DeductCreditsAsync(Guid userId, decimal amount, string description)
|
||||
{
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
if (user.Credits < amount) return false;
|
||||
|
||||
user.Credits -= amount;
|
||||
|
||||
// 記錄積分使用
|
||||
_dbContext.CreditTransactions.Add(new CreditTransaction
|
||||
{
|
||||
UserId = userId,
|
||||
Amount = -amount,
|
||||
Description = description,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 環境配置檔案
|
||||
|
||||
### appsettings.Development.json
|
||||
```json
|
||||
{
|
||||
"Gemini": {
|
||||
"ApiKey": "YOUR_GEMINI_API_KEY",
|
||||
"TimeoutSeconds": 30,
|
||||
"Model": "gemini-1.5-flash"
|
||||
},
|
||||
"Replicate": {
|
||||
"ApiKey": "YOUR_REPLICATE_API_KEY",
|
||||
"TimeoutSeconds": 180,
|
||||
"Models": {
|
||||
"flux-1-dev": {
|
||||
"Version": "dev",
|
||||
"CostPerGeneration": 0.05,
|
||||
"DefaultWidth": 512,
|
||||
"DefaultHeight": 512
|
||||
},
|
||||
"stable-diffusion-xl": {
|
||||
"Version": "39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b",
|
||||
"CostPerGeneration": 0.04
|
||||
}
|
||||
}
|
||||
},
|
||||
"ImageStorage": {
|
||||
"Provider": "Local",
|
||||
"Local": {
|
||||
"BasePath": "wwwroot/images/examples",
|
||||
"BaseUrl": "https://localhost:5008/images/examples"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 測試策略
|
||||
|
||||
### 單元測試優先級
|
||||
1. **GeminiImageDescriptionService** - 描述生成邏輯
|
||||
2. **ReplicateImageGenerationService** - API 整合
|
||||
3. **ImageGenerationOrchestrator** - 流程編排
|
||||
4. **ImageGenerationCacheService** - 快取邏輯
|
||||
|
||||
### 整合測試
|
||||
1. **完整兩階段生成流程**
|
||||
2. **錯誤處理和重試機制**
|
||||
3. **成本計算和積分扣款**
|
||||
|
||||
---
|
||||
|
||||
## 📦 NuGet 套件需求
|
||||
|
||||
需要新增到 `DramaLing.Api.csproj`:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.0" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署檢查清單
|
||||
|
||||
### 開發環境啟動
|
||||
1. ✅ 資料庫 Migration 執行
|
||||
2. ✅ Gemini API Key 配置
|
||||
3. ✅ Replicate API Key 配置
|
||||
4. ✅ 本地圖片存儲目錄建立
|
||||
5. ✅ 服務註冊檢查
|
||||
|
||||
### 測試驗證
|
||||
1. ✅ Gemini 描述生成測試
|
||||
2. ✅ Replicate 圖片生成測試
|
||||
3. ✅ 完整流程端到端測試
|
||||
4. ✅ 錯誤處理測試
|
||||
5. ✅ 積分扣款測試
|
||||
|
||||
---
|
||||
|
||||
## ⏱️ 時程總結
|
||||
|
||||
| Phase | 時間 | 主要任務 | 可交付成果 |
|
||||
|-------|------|----------|-----------|
|
||||
| Phase 1 | Week 1-2 | 基礎架構擴展 | 資料庫 Schema、配置、基礎服務 |
|
||||
| Phase 2 | Week 3-4 | 核心服務實現 | Gemini 和 Replicate 服務 |
|
||||
| Phase 3 | Week 5-6 | API 和編排器 | 完整的 API 端點和流程 |
|
||||
| Phase 4 | Week 7-8 | 優化和監控 | 快取、成本控制、監控 |
|
||||
|
||||
**總時程**: 6-8 週
|
||||
**風險緩衝**: +1-2 週 (Replicate API 整合複雜度)
|
||||
|
||||
---
|
||||
|
||||
## 📚 參考文檔
|
||||
|
||||
- [例句圖生成功能 PRD](./EXAMPLE_IMAGE_GENERATION_PRD.md)
|
||||
- [後端架構詳細說明](./docs/04_technical/backend-architecture.md)
|
||||
- [系統架構總覽](./docs/04_technical/system-architecture.md)
|
||||
- [Replicate API 文檔](https://replicate.com/docs/reference/http)
|
||||
- [Gemini API 文檔](https://cloud.google.com/ai-platform/generative-ai/docs)
|
||||
|
||||
---
|
||||
|
||||
**文檔版本**: v1.0
|
||||
**建立日期**: 2025-09-24
|
||||
**預估完成**: 2025-11-19
|
||||
**負責團隊**: 後端開發團隊
|
||||
Loading…
Reference in New Issue