diff --git a/apps/api/app/api/routes/asset_diagnosis.py b/apps/api/app/api/routes/asset_diagnosis.py
index 9b1ce607e..26a8c6456 100644
--- a/apps/api/app/api/routes/asset_diagnosis.py
+++ b/apps/api/app/api/routes/asset_diagnosis.py
@@ -45,6 +45,9 @@ def _build_diagnosis(workspace_id: str, project_id: str, assets: list[Asset]) ->
problem_assets = [asset for asset in assets if asset.status in {AssetStatus.ERROR, AssetStatus.UPLOADING, AssetStatus.PROCESSING}]
unclassified_assets = [asset for asset in ready_assets if asset.classification_status.value in {"pending", "failed"}]
risky_assets = [asset for asset in ready_assets if asset.quality_score is not None and asset.quality_score < 60]
+ used_assets = [asset for asset in ready_assets if int(asset.metadata.get("generation_use_count") or 0) > 0]
+ unused_assets = [asset for asset in ready_assets if int(asset.metadata.get("generation_use_count") or 0) == 0]
+ pending_review_assets = [asset for asset in ready_assets if asset.metadata.get("review_status") == "pending_review"]
total_duration = round(sum(float(asset.duration or 0) for asset in video_assets), 2)
estimated_video_count = max(0, min(len(video_assets), int(total_duration // 5) if total_duration else len(video_assets)))
@@ -124,6 +127,9 @@ def _build_diagnosis(workspace_id: str, project_id: str, assets: list[Asset]) ->
AssetSmartViewItem(key="needs_attention", label="慎用素材", count=len(problem_assets) + len(risky_assets), description="导入未完成、失败或质量分偏低的素材"),
AssetSmartViewItem(key="unclassified", label="未分类素材", count=len(unclassified_assets), description="尚未完成分类或分类失败的 ready 素材"),
AssetSmartViewItem(key="recent", label="最近上传", count=min(len(assets), 10), description="最近进入素材库的素材,可用于快速复核"),
+ AssetSmartViewItem(key="unused", label="未使用素材", count=len(unused_assets), description="尚未参与生成的 ready 素材"),
+ AssetSmartViewItem(key="used", label="已使用素材", count=len(used_assets), description="已经参与过生成的素材"),
+ AssetSmartViewItem(key="pending_review", label="待复核素材", count=len(pending_review_assets), description="生成后待人工复核的素材"),
AssetSmartViewItem(key="voice", label="配音素材", count=len(voice_assets), description="可用于后续配音/旁白工作流的素材"),
]
@@ -139,6 +145,9 @@ def _build_diagnosis(workspace_id: str, project_id: str, assets: list[Asset]) ->
voice_assets=len(voice_assets),
total_duration_seconds=total_duration,
estimated_video_count=estimated_video_count,
+ used_assets=len(used_assets),
+ unused_assets=len(unused_assets),
+ pending_review_assets=len(pending_review_assets),
smart_views=smart_views,
gaps=gaps,
)
diff --git a/apps/api/app/schemas/asset_diagnosis.py b/apps/api/app/schemas/asset_diagnosis.py
index a167ed556..95b8684a8 100644
--- a/apps/api/app/schemas/asset_diagnosis.py
+++ b/apps/api/app/schemas/asset_diagnosis.py
@@ -27,5 +27,8 @@ class ProjectAssetDiagnosisResponse(BaseModel):
voice_assets: int
total_duration_seconds: float
estimated_video_count: int
+ used_assets: int
+ unused_assets: int
+ pending_review_assets: int
smart_views: list[AssetSmartViewItem]
gaps: list[AssetGapItem]
diff --git a/apps/web/src/api/assets.ts b/apps/web/src/api/assets.ts
index 249f3c2e7..e8e8961fc 100644
--- a/apps/web/src/api/assets.ts
+++ b/apps/web/src/api/assets.ts
@@ -62,6 +62,9 @@ export interface ProjectAssetDiagnosis {
voice_assets: number;
total_duration_seconds: number;
estimated_video_count: number;
+ used_assets: number;
+ unused_assets: number;
+ pending_review_assets: number;
smart_views: Array<{ key: string; label: string; count: number; description: string }>;
gaps: Array<{ key: string; severity: 'critical' | 'warning' | 'info'; message: string; recommendation: string }>;
}
diff --git a/apps/web/src/pages/workspace/ProjectAssets.tsx b/apps/web/src/pages/workspace/ProjectAssets.tsx
index 0903e3db8..28a63ec20 100644
--- a/apps/web/src/pages/workspace/ProjectAssets.tsx
+++ b/apps/web/src/pages/workspace/ProjectAssets.tsx
@@ -290,6 +290,12 @@ const ProjectAssets: React.FC = () => {
items = items.filter((item) => !item.metadata?.classification || item.classification_status === 'failed');
} else if (smartViewFilter === 'voice') {
items = items.filter((item) => item.mime_type?.startsWith('audio'));
+ } else if (smartViewFilter === 'unused') {
+ items = items.filter((item) => item.status === 'ready' && Number(item.metadata?.generation_use_count || 0) === 0);
+ } else if (smartViewFilter === 'used') {
+ items = items.filter((item) => Number(item.metadata?.generation_use_count || 0) > 0);
+ } else if (smartViewFilter === 'pending_review') {
+ items = items.filter((item) => item.metadata?.review_status === 'pending_review');
}
if (classificationFilter !== 'all') {
@@ -505,6 +511,9 @@ const ProjectAssets: React.FC = () => {
{ label: '慎用素材', value: 'needs_attention' },
{ label: '未分类素材', value: 'unclassified' },
{ label: '最近上传', value: 'recent' },
+ { label: '未使用素材', value: 'unused' },
+ { label: '已使用素材', value: 'used' },
+ { label: '待复核素材', value: 'pending_review' },
{ label: '配音素材', value: 'voice' },
]}
onChange={setSmartViewFilter}
@@ -608,6 +617,9 @@ const ProjectAssets: React.FC = () => {