fix(design): connect video preparation and playable history

This commit is contained in:
brother7 committed 2026-09-16 15:42:39 +08:00
1 parent 4321c77613
commit 1acca836fe
21 files changed
+900 -115

No files matched your search

+104
View File
@@ -0,0 +1,104 @@
import type {
DesignAsset,
DesignSpecificationValues,
DesignUserFieldOperation,
DesignWorkspace,
} from '../../../shared/image-workspace';
import {
insertReferenceToken,
promptReferencesImage,
} from './reference-tokens';
type ReferenceValue = DesignSpecificationValues['references'][number];
function createReferenceId(): string {
const random = globalThis.crypto?.randomUUID?.()
?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
return `reference-${random}`.slice(0, 64);
}
export function creatorPromptForWorkspace(workspace: DesignWorkspace): string {
const values = workspace.form.specification.values;
return values.content.concept?.trim()
|| values.content.narrative?.trim()
|| [
...values.content.subjects.map((subject) => subject.description.trim()),
values.content.action?.trim(),
values.content.environment?.trim(),
values.content.emotional_tone?.trim(),
].filter((value): value is string => Boolean(value)).join(',');
}
/**
* Build the explicit field changes for using an existing image as a video's
* first frame. The asset is only bound to the design; it is never copied or
* deleted, and existing reference rows stay in place so @图片 numbers remain
* stable.
*/
export function createVideoFirstFrameOperations(
workspace: DesignWorkspace,
asset: DesignAsset,
promptOverride?: string,
): { operations: DesignUserFieldOperation[]; nextPrompt: string; referenceIndex: number } {
if (asset.mediaType !== 'image') {
throw new Error('视频开始画面必须使用图片');
}
const values = workspace.form.specification.values;
const references = values.references;
const currentFirstFrameIndex = references.findIndex((reference) => (
reference.role === 'first_frame'
|| reference.asset_id === values.video.first_frame_asset_id
));
const selectedReferenceIndex = references.findIndex((reference) => (
reference.asset_id === asset.assetId
));
let nextReferences: ReferenceValue[];
let referenceIndex: number;
if (selectedReferenceIndex >= 0) {
referenceIndex = selectedReferenceIndex;
nextReferences = references.map((reference, index) => {
if (index === referenceIndex) return { ...reference, role: 'first_frame' };
if (index === currentFirstFrameIndex && index !== referenceIndex) {
return { ...reference, role: 'inspiration' };
}
return reference;
});
} else if (currentFirstFrameIndex >= 0) {
referenceIndex = currentFirstFrameIndex;
nextReferences = references.map((reference, index) => (
index === referenceIndex
? { ...reference, asset_id: asset.assetId, asset_revision: null, role: 'first_frame' }
: reference
));
} else {
referenceIndex = references.length;
nextReferences = [
...references,
{
id: createReferenceId(),
asset_id: asset.assetId,
asset_revision: null,
role: 'first_frame',
preserve: [],
adapt: [],
do_not_copy: [],
reviewed_observations: [],
},
];
}
const prompt = promptOverride ?? creatorPromptForWorkspace(workspace);
const inserted = promptReferencesImage(prompt, referenceIndex)
? { value: prompt }
: insertReferenceToken(prompt, referenceIndex, null);
const operations: DesignUserFieldOperation[] = [
{ kind: 'set', path: 'references', value: nextReferences },
{ kind: 'set', path: 'video.first_frame_asset_id', value: asset.assetId },
];
if (inserted.value.trim() !== prompt.trim()) {
operations.push({ kind: 'set', path: 'content.concept', value: inserted.value.trim() });
}
return { operations, nextPrompt: inserted.value.trim(), referenceIndex };
}