fix: 修复资产页素材库引用不可查看 + 重新编辑素材泄漏
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m52s

1. AdminAssetsPage/TeamAssetsPage: asset:// 协议 URL 改用 thumb_url 显示缩略图
2. generation.ts reEdit/regenerate: 过滤 isAssetRef,素材库引用不混入 references 数组
3. PromptInput extractText: 实时同步 assetMentions store,删除 @标签后不再残留旧数据

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209 2026-04-13 18:33:08 +08:00
parent 177a9c7dec
commit 0b770340c8
5 changed files with 75 additions and 32 deletions

View File

@ -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<string, unknown>[] = [];
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

View File

@ -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,

View File

@ -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,

View File

@ -618,14 +618,16 @@ export const useGenerationStore = create<GenerationState>((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<GenerationState>((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,

View File

@ -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;
}