fix: 修复资产页素材库引用不可查看 + 重新编辑素材泄漏
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m52s
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:
parent
177a9c7dec
commit
0b770340c8
@ -254,6 +254,27 @@ export function PromptInput() {
|
|||||||
if (!el) return;
|
if (!el) return;
|
||||||
setPrompt(el.textContent || '');
|
setPrompt(el.textContent || '');
|
||||||
setEditorHtml(el.innerHTML);
|
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]);
|
}, [setPrompt, setEditorHtml]);
|
||||||
|
|
||||||
// Remove orphaned mention spans when a reference is deleted
|
// Remove orphaned mention spans when a reference is deleted
|
||||||
|
|||||||
@ -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 {
|
function assetVideoToTask(v: AssetVideo): GenerationTask {
|
||||||
const references = (v.reference_urls || []).map((ref, i) => ({
|
const references = (v.reference_urls || []).map((ref, i) => {
|
||||||
id: `ref_${v.task_id}_${i}`,
|
const url = ref.url || '';
|
||||||
type: (ref.type || 'image') as 'image' | 'video',
|
const assetRef = isAssetUrl(url);
|
||||||
previewUrl: ref.url,
|
return {
|
||||||
label: ref.label || `素材${i + 1}`,
|
id: `ref_${v.task_id}_${i}`,
|
||||||
role: ref.role,
|
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 {
|
return {
|
||||||
id: String(v.id),
|
id: String(v.id),
|
||||||
taskId: v.task_id,
|
taskId: v.task_id,
|
||||||
|
|||||||
@ -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 {
|
function assetVideoToTask(v: AssetVideo): GenerationTask {
|
||||||
const references = (v.reference_urls || []).map((ref, i) => ({
|
const references = (v.reference_urls || []).map((ref, i) => {
|
||||||
id: `ref_${v.task_id}_${i}`,
|
const url = ref.url || '';
|
||||||
type: (ref.type || 'image') as 'image' | 'video',
|
const assetRef = isAssetUrl(url);
|
||||||
previewUrl: ref.url,
|
return {
|
||||||
label: ref.label || `素材${i + 1}`,
|
id: `ref_${v.task_id}_${i}`,
|
||||||
role: ref.role,
|
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 {
|
return {
|
||||||
id: String(v.id),
|
id: String(v.id),
|
||||||
taskId: v.task_id,
|
taskId: v.task_id,
|
||||||
|
|||||||
@ -618,14 +618,16 @@ export const useGenerationStore = create<GenerationState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (task.mode === 'universal') {
|
if (task.mode === 'universal') {
|
||||||
// task.references only contains file refs (assets filtered in backendToFrontend)
|
// Only include direct file refs — asset library refs are tracked via assetMentions
|
||||||
const references: UploadedFile[] = task.references.map((r) => ({
|
const references: UploadedFile[] = task.references
|
||||||
id: r.id,
|
.filter((r) => !r.isAssetRef)
|
||||||
type: r.type,
|
.map((r) => ({
|
||||||
previewUrl: r.previewUrl,
|
id: r.id,
|
||||||
label: r.label,
|
type: r.type,
|
||||||
tosUrl: r.previewUrl,
|
previewUrl: r.previewUrl,
|
||||||
}));
|
label: r.label,
|
||||||
|
tosUrl: r.previewUrl,
|
||||||
|
}));
|
||||||
|
|
||||||
// Always use plain text prompt for reEdit — let PromptInput's rebuildMentionSpans
|
// Always use plain text prompt for reEdit — let PromptInput's rebuildMentionSpans
|
||||||
// reconstruct tags from references + assetMentions (avoids dead blob: URLs)
|
// 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
|
// For regeneration, we need to re-submit with the same TOS URLs
|
||||||
// Set up the input bar state, then call addTask
|
// Only include direct file refs — asset library refs go via assetMentions fallback
|
||||||
const references: UploadedFile[] = task.references.map((r) => ({
|
const references: UploadedFile[] = task.references
|
||||||
id: r.id,
|
.filter((r) => !r.isAssetRef)
|
||||||
type: r.type,
|
.map((r) => ({
|
||||||
previewUrl: r.previewUrl,
|
id: r.id,
|
||||||
label: r.label,
|
type: r.type,
|
||||||
tosUrl: r.previewUrl,
|
previewUrl: r.previewUrl,
|
||||||
}));
|
label: r.label,
|
||||||
|
tosUrl: r.previewUrl,
|
||||||
|
}));
|
||||||
|
|
||||||
useInputBarStore.setState({
|
useInputBarStore.setState({
|
||||||
prompt: task.prompt,
|
prompt: task.prompt,
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export interface BackendTask {
|
|||||||
result_url: string;
|
result_url: string;
|
||||||
thumbnail_url: string;
|
thumbnail_url: string;
|
||||||
error_message: 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;
|
is_favorited: boolean;
|
||||||
seed: number;
|
seed: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@ -407,7 +407,7 @@ export interface AssetVideo {
|
|||||||
seconds_consumed: number;
|
seconds_consumed: number;
|
||||||
cost_amount?: number;
|
cost_amount?: number;
|
||||||
aspect_ratio: string;
|
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;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user