diff --git a/web/src/components/PromptInput.tsx b/web/src/components/PromptInput.tsx index b949cd7..5b66454 100644 --- a/web/src/components/PromptInput.tsx +++ b/web/src/components/PromptInput.tsx @@ -254,6 +254,27 @@ export function PromptInput() { if (!el) return; setPrompt(el.textContent || ''); setEditorHtml(el.innerHTML); + // Sync assetMentions from DOM — prevents stale refs after deleting @mention spans + const mentions: Record[] = []; + el.querySelectorAll('[data-ref-type="asset"]').forEach((span) => { + const s = span as HTMLElement; + if (s.dataset.assetId) { + mentions.push({ + assetId: s.dataset.assetId, + label: s.dataset.assetName || s.textContent?.replace('@', '') || '', + thumbUrl: s.dataset.thumbUrl || '', + assetType: s.dataset.assetType || 'Image', + duration: parseFloat(s.dataset.duration || '0'), + }); + } else if (s.dataset.assetGroupId) { + mentions.push({ + groupId: s.dataset.assetGroupId, + label: s.dataset.groupName || s.textContent?.replace('@', '') || '', + thumbUrl: s.dataset.thumbUrl || '', + }); + } + }); + useInputBarStore.setState({ assetMentions: mentions }); }, [setPrompt, setEditorHtml]); // Remove orphaned mention spans when a reference is deleted diff --git a/web/src/pages/AdminAssetsPage.tsx b/web/src/pages/AdminAssetsPage.tsx index 5574be3..0e2a45d 100644 --- a/web/src/pages/AdminAssetsPage.tsx +++ b/web/src/pages/AdminAssetsPage.tsx @@ -31,14 +31,23 @@ function VideoThumbnail({ video, onClick }: { video: AssetVideo; onClick: () => ); } +function isAssetUrl(url: string): boolean { + return url.startsWith('asset://') || url.startsWith('Asset://'); +} + function assetVideoToTask(v: AssetVideo): GenerationTask { - const references = (v.reference_urls || []).map((ref, i) => ({ - id: `ref_${v.task_id}_${i}`, - type: (ref.type || 'image') as 'image' | 'video', - previewUrl: ref.url, - label: ref.label || `素材${i + 1}`, - role: ref.role, - })); + const references = (v.reference_urls || []).map((ref, i) => { + const url = ref.url || ''; + const assetRef = isAssetUrl(url); + return { + id: `ref_${v.task_id}_${i}`, + type: (ref.type || 'image') as 'image' | 'video' | 'audio', + previewUrl: assetRef ? (ref.thumb_url || '') : url, + label: ref.label || `素材${i + 1}`, + role: ref.role, + isAssetRef: assetRef || undefined, + }; + }); return { id: String(v.id), taskId: v.task_id, diff --git a/web/src/pages/TeamAssetsPage.tsx b/web/src/pages/TeamAssetsPage.tsx index 5560c71..9c37006 100644 --- a/web/src/pages/TeamAssetsPage.tsx +++ b/web/src/pages/TeamAssetsPage.tsx @@ -31,14 +31,23 @@ function VideoThumbnail({ video, onClick }: { video: AssetVideo; onClick: () => ); } +function isAssetUrl(url: string): boolean { + return url.startsWith('asset://') || url.startsWith('Asset://'); +} + function assetVideoToTask(v: AssetVideo): GenerationTask { - const references = (v.reference_urls || []).map((ref, i) => ({ - id: `ref_${v.task_id}_${i}`, - type: (ref.type || 'image') as 'image' | 'video', - previewUrl: ref.url, - label: ref.label || `素材${i + 1}`, - role: ref.role, - })); + const references = (v.reference_urls || []).map((ref, i) => { + const url = ref.url || ''; + const assetRef = isAssetUrl(url); + return { + id: `ref_${v.task_id}_${i}`, + type: (ref.type || 'image') as 'image' | 'video' | 'audio', + previewUrl: assetRef ? (ref.thumb_url || '') : url, + label: ref.label || `素材${i + 1}`, + role: ref.role, + isAssetRef: assetRef || undefined, + }; + }); return { id: String(v.id), taskId: v.task_id, diff --git a/web/src/store/generation.ts b/web/src/store/generation.ts index fb75e0f..4c6d4b4 100644 --- a/web/src/store/generation.ts +++ b/web/src/store/generation.ts @@ -618,14 +618,16 @@ export const useGenerationStore = create((set, get) => ({ } if (task.mode === 'universal') { - // task.references only contains file refs (assets filtered in backendToFrontend) - const references: UploadedFile[] = task.references.map((r) => ({ - id: r.id, - type: r.type, - previewUrl: r.previewUrl, - label: r.label, - tosUrl: r.previewUrl, - })); + // Only include direct file refs — asset library refs are tracked via assetMentions + const references: UploadedFile[] = task.references + .filter((r) => !r.isAssetRef) + .map((r) => ({ + id: r.id, + type: r.type, + previewUrl: r.previewUrl, + label: r.label, + tosUrl: r.previewUrl, + })); // Always use plain text prompt for reEdit — let PromptInput's rebuildMentionSpans // reconstruct tags from references + assetMentions (avoids dead blob: URLs) @@ -669,14 +671,16 @@ export const useGenerationStore = create((set, get) => ({ } // For regeneration, we need to re-submit with the same TOS URLs - // Set up the input bar state, then call addTask - const references: UploadedFile[] = task.references.map((r) => ({ - id: r.id, - type: r.type, - previewUrl: r.previewUrl, - label: r.label, - tosUrl: r.previewUrl, - })); + // Only include direct file refs — asset library refs go via assetMentions fallback + const references: UploadedFile[] = task.references + .filter((r) => !r.isAssetRef) + .map((r) => ({ + id: r.id, + type: r.type, + previewUrl: r.previewUrl, + label: r.label, + tosUrl: r.previewUrl, + })); useInputBarStore.setState({ prompt: task.prompt, diff --git a/web/src/types/index.ts b/web/src/types/index.ts index ecbe496..9b6e6d0 100644 --- a/web/src/types/index.ts +++ b/web/src/types/index.ts @@ -76,7 +76,7 @@ export interface BackendTask { result_url: string; thumbnail_url: string; error_message: string; - reference_urls: { url: string; type: string; role: string; label: string }[]; + reference_urls: { url: string; type: string; role: string; label: string; thumb_url?: string }[]; is_favorited: boolean; seed: number; created_at: string; @@ -407,7 +407,7 @@ export interface AssetVideo { seconds_consumed: number; cost_amount?: number; aspect_ratio: string; - reference_urls?: { url: string; type: string; role: string; label: string }[]; + reference_urls?: { url: string; type: string; role: string; label: string; thumb_url?: string }[]; created_at: string; }