diff --git a/README.md b/README.md
index 6121f3e..bf37293 100755
--- a/README.md
+++ b/README.md
@@ -133,6 +133,8 @@ npm run dev
## 架构
+
+
### 生成管线
基于 Eino `compose.Graph` 的四阶段管线,支持质检重试与降级输出:
@@ -161,6 +163,8 @@ graph TD
C --> D["三段式规范提示词
【主题】【风格】【技术】"]
```
+
+
### 预设风格键
| 分类 | 键名 | 可选值示例 |
@@ -172,6 +176,10 @@ graph TD
| 光照 | `lighting` | bright, dim, dramatic, ambient, neon |
| 情绪 | `mood` | cheerful, dark, mysterious, epic, calm |
+
+
+详细架构说明见 [docs/_index.md](docs/_index.md)。
+
## API
接口统一前缀 `/api/v1/`,统一响应格式:
diff --git a/assets/backend-architecture.png b/assets/backend-architecture.png
new file mode 100644
index 0000000..dccff6f
Binary files /dev/null and b/assets/backend-architecture.png differ
diff --git a/assets/frontend-architecture.png b/assets/frontend-architecture.png
new file mode 100644
index 0000000..9a02526
Binary files /dev/null and b/assets/frontend-architecture.png differ
diff --git a/assets/global-architecture.png b/assets/global-architecture.png
new file mode 100644
index 0000000..23aef82
Binary files /dev/null and b/assets/global-architecture.png differ
diff --git a/backend/internal/handler/generate.go b/backend/internal/handler/generate.go
index 0aa9bce..a47eacd 100755
--- a/backend/internal/handler/generate.go
+++ b/backend/internal/handler/generate.go
@@ -308,14 +308,18 @@ func getTaskDBID(ctx context.Context, taskID string) uint {
// RecentAssetItem 首页素材展示项。
type RecentAssetItem struct {
- Key string `json:"key"`
- URL string `json:"url"`
- Format string `json:"format"`
- Prompt string `json:"prompt"`
- AssetType string `json:"assetType"`
- MetaType string `json:"metaType"` // frame / spritesheet / preview
- TaskID string `json:"taskId"`
- CreatedAt string `json:"createdAt"`
+ Key string `json:"key"`
+ URL string `json:"url"`
+ Format string `json:"format"`
+ Prompt string `json:"prompt"`
+ AssetType string `json:"assetType"`
+ MetaType string `json:"metaType"`
+ TaskID string `json:"taskId"`
+ ProjectID string `json:"projectId"`
+ ProjectName string `json:"projectName"`
+ Width int `json:"width"`
+ Height int `json:"height"`
+ CreatedAt string `json:"createdAt"`
}
// GetRecentAssets 获取最近生成的素材列表(已完成任务的全部素材)。
@@ -323,23 +327,31 @@ func GetRecentAssets(c *gin.Context) {
limit := 60
var dbRows []struct {
- Key string
- URL string
- Format string
- Prompt string
- AssetType string
- MetaType string
- TaskID string
- ProjectID uint
- CreatedAt string
+ Key string
+ URL string
+ Format string
+ Prompt string
+ AssetType string
+ MetaType string
+ TaskID string
+ ProjectID uint
+ ProjectName string
+ Width int
+ Height int
+ CreatedAt string
}
db.GetDB().WithContext(c.Request.Context()).
Raw(`SELECT a.key, a.url, a.format, t.prompt, t.asset_type as asset_type,
COALESCE(json_extract(a.metadata, '$.type'), 'frame') as meta_type,
- t.external_id as task_id, t.project_id, t.created_at as created_at
+ t.external_id as task_id, t.project_id,
+ p.name as project_name,
+ CAST(COALESCE(json_extract(t.metadata, '$.frameWidth'), '0') AS INTEGER) as width,
+ CAST(COALESCE(json_extract(t.metadata, '$.frameHeight'), '0') AS INTEGER) as height,
+ t.created_at as created_at
FROM assets a
INNER JOIN tasks t ON a.task_id = t.id
+ INNER JOIN projects p ON t.project_id = p.id
WHERE t.status = 'completed'
ORDER BY a.created_at DESC
LIMIT ?`, limit).
@@ -348,14 +360,18 @@ func GetRecentAssets(c *gin.Context) {
result := make([]RecentAssetItem, len(dbRows))
for i, r := range dbRows {
result[i] = RecentAssetItem{
- Key: r.Key,
- URL: storageSvc.GetSignedURL(r.Key),
- Format: r.Format,
- Prompt: r.Prompt,
- AssetType: r.AssetType,
- MetaType: r.MetaType,
- TaskID: r.TaskID,
- CreatedAt: r.CreatedAt,
+ Key: r.Key,
+ URL: storageSvc.GetSignedURL(r.Key),
+ Format: r.Format,
+ Prompt: r.Prompt,
+ AssetType: r.AssetType,
+ MetaType: r.MetaType,
+ TaskID: r.TaskID,
+ ProjectID: fmt.Sprintf("%d", r.ProjectID),
+ ProjectName: r.ProjectName,
+ Width: r.Width,
+ Height: r.Height,
+ CreatedAt: r.CreatedAt,
}
}
diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts
index d4ee998..3bf0c14 100755
--- a/frontend/src/api/types.ts
+++ b/frontend/src/api/types.ts
@@ -107,6 +107,10 @@ export interface RecentAssetItem {
assetType: string
metaType: string
taskId: string
+ projectId: string
+ projectName: string
+ width: number
+ height: number
createdAt: string
}
diff --git a/frontend/src/components/AssetGallery.tsx b/frontend/src/components/AssetGallery.tsx
index 621a489..695e1ec 100644
--- a/frontend/src/components/AssetGallery.tsx
+++ b/frontend/src/components/AssetGallery.tsx
@@ -1,4 +1,5 @@
import { useEffect, useState, useMemo } from 'react'
+import { createPortal } from 'react-dom'
import { getRecentAssets } from '../api/generate'
import type { RecentAssetItem } from '../api/types'
@@ -37,10 +38,118 @@ function matchCategory(item: RecentAssetItem, cat: string): boolean {
}
}
+function DetailModal({ item, onClose }: { item: RecentAssetItem; onClose: () => void }) {
+ const downloadUrl = `/api/v1/assets/download?key=${encodeURIComponent(item.key)}`
+
+ return createPortal(
+