fix(design): connect video preparation and playable history
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
/** Offline connectivity diagnostic, intentionally red; not in the normal unit suite.
|
||||
/** Offline connectivity diagnostic; not in the normal unit suite.
|
||||
* Actual React controls + Zustand commands -> stdin bridge -> actual Python
|
||||
* Specification kernel / production capability builder / compiler. Upload and
|
||||
* transport are simulated. No live gateway, reasoner, paid task or provider.
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
|
||||
import { YouthCreationCard } from '@/pages/ImageCanvas/YouthCreationCard';
|
||||
import { DesignPlanHistory } from '@/pages/ImageCanvas/DesignPlanHistory';
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ImageWorkspaceApiError, submitImageWorkspaceCommand, uploadImageWorkspaceAsset,
|
||||
} from '@/lib/image-workspace';
|
||||
import {
|
||||
designFormFixture, designQuoteFixture, designWorkspaceFixture,
|
||||
designCapabilitiesFixture, designFormFixture, designQuoteFixture, designWorkspaceFixture,
|
||||
} from '../../tests/fixtures/design-workspace-v2';
|
||||
import type {
|
||||
DesignAsset, DesignCompilationIssue, DesignSpecification, DesignUserFieldOperation,
|
||||
@@ -32,12 +32,16 @@ vi.mock('@/stores/auth', () => ({
|
||||
useAuthStore: { getState: () => ({ isAuthenticated: () => true }) },
|
||||
}));
|
||||
|
||||
function serverStep(specification: DesignSpecification | null, operations: DesignUserFieldOperation[]) {
|
||||
function serverStep(
|
||||
specification: DesignSpecification | null,
|
||||
inputKind: 'direct_edit' | 'prepare_generation',
|
||||
operations: DesignUserFieldOperation[] = [],
|
||||
) {
|
||||
const result = spawnSync(process.env.DESIGN_DIAGNOSTIC_PYTHON!, [
|
||||
'-X', 'utf8', '-m', 'scripts.diagnostics.design_media_blockers', '--json-step',
|
||||
], {
|
||||
cwd: process.env.DESIGN_DIAGNOSTIC_SERVER,
|
||||
input: JSON.stringify({ specification, operations }), encoding: 'utf8',
|
||||
input: JSON.stringify({ specification, input_kind: inputKind, operations }), encoding: 'utf8',
|
||||
});
|
||||
if (result.status !== 0) throw new Error(result.stderr || String(result.error));
|
||||
return JSON.parse(result.stdout) as {
|
||||
@@ -55,7 +59,12 @@ const asset: DesignAsset = {
|
||||
function LiveCard() {
|
||||
const workspace = useImageWorkspaceStore((state) => state.workspace)!;
|
||||
const blockers = useImageWorkspaceStore((state) => state.quoteBlockers);
|
||||
return <YouthCreationCard workspace={workspace} quoteBlockers={blockers} generationAvailable />;
|
||||
return <YouthCreationCard
|
||||
workspace={workspace}
|
||||
quoteBlockers={blockers}
|
||||
generationAvailable
|
||||
generationOptions={designCapabilitiesFixture.generationOptions}
|
||||
/>;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -64,11 +73,8 @@ beforeEach(() => {
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
it.each([
|
||||
{ label: 'current UI payload', complete: false },
|
||||
{ label: 'control with a complete semantic video draft supplied', complete: true },
|
||||
])('reaches video confirmation: $label', async ({ complete }) => {
|
||||
const initial = serverStep(null, [
|
||||
it('reaches video confirmation through the real prepare-generation bridge', async () => {
|
||||
const initial = serverStep(null, 'direct_edit', [
|
||||
{ kind: 'set', path: 'intent.media', value: 'image' },
|
||||
{ kind: 'set', path: 'content.concept', value: '沙漠里的小动物围圈玩游戏,天上的云在飘。' },
|
||||
{ kind: 'set', path: 'output.aspect_ratio', value: '9:16' },
|
||||
@@ -84,21 +90,24 @@ it.each([
|
||||
vi.mocked(uploadImageWorkspaceAsset).mockResolvedValue(asset);
|
||||
vi.mocked(submitImageWorkspaceCommand).mockImplementation(async (command) => {
|
||||
commands.push(command.kind === 'apply_input' ? command.input.kind : command.kind);
|
||||
const operations = command.kind === 'apply_input' && command.input.kind === 'direct_edit'
|
||||
? command.input.operations : [];
|
||||
const step = serverStep(workspace.form.specification, operations);
|
||||
if (command.kind === 'request_quote' && !step.ready) {
|
||||
useImageWorkspaceStore.setState({ quoteBlockers: step.blockers });
|
||||
throw new ImageWorkspaceApiError(409, 'design_quote_blocked', 'offline blocked');
|
||||
}
|
||||
const inputKind = command.kind === 'apply_input' ? command.input.kind : 'direct_edit';
|
||||
const operations = command.kind === 'apply_input' && (
|
||||
command.input.kind === 'direct_edit' || command.input.kind === 'prepare_generation'
|
||||
) ? command.input.operations ?? [] : [];
|
||||
const step = serverStep(workspace.form.specification, inputKind, operations);
|
||||
workspace = {
|
||||
...workspace, assets: operations.some((operation) => operation.path === 'references')
|
||||
...workspace,
|
||||
assets: operations.some((operation) => operation.path === 'references')
|
||||
? [asset] : workspace.assets,
|
||||
workspace: { ...workspace.workspace, workspaceViewRevision: workspace.workspace.workspaceViewRevision + 1 },
|
||||
form: { ...workspace.form, specification: step.specification,
|
||||
specificationRevision: workspace.form.specificationRevision + 1,
|
||||
directionRevision: workspace.form.directionRevision + 1 },
|
||||
};
|
||||
useImageWorkspaceStore.setState({ workspace, quoteBlockers: step.blockers });
|
||||
if (!step.ready && command.kind === 'request_quote') {
|
||||
throw new ImageWorkspaceApiError(409, 'design_quote_blocked', 'offline blocked');
|
||||
}
|
||||
if (command.kind === 'request_quote') {
|
||||
const quote = designQuoteFixture();
|
||||
workspace.form.activeQuotes = [{ ...quote, medium: 'video',
|
||||
@@ -114,22 +123,15 @@ it.each([
|
||||
fireEvent.change(screen.getByLabelText('选择参考图片'), {
|
||||
target: { files: [new File(['offline-upload'], 'start.png', { type: 'image/png' })] },
|
||||
});
|
||||
await screen.findByText('已引用');
|
||||
if (complete) {
|
||||
// Control input, not a claim that today's Agent/UI performs this transition.
|
||||
const reference = workspace.form.specification.values.references[0];
|
||||
const step = serverStep(workspace.form.specification, [
|
||||
{ kind: 'set', path: 'references', value: [{ ...reference, role: 'first_frame' }] },
|
||||
{ kind: 'set', path: 'video.shots', value: [{ id: 'one', story_beat: '小动物围圈游戏,云缓慢飘过。' }] },
|
||||
]);
|
||||
workspace = { ...workspace, form: { ...workspace.form, specification: step.specification } };
|
||||
act(() => useImageWorkspaceStore.setState({ workspace }));
|
||||
}
|
||||
await screen.findByText('已选用');
|
||||
expect(useImageWorkspaceStore.getState().workspace?.form.specification.values.video.first_frame_asset_id).toBe('image-1');
|
||||
expect(useImageWorkspaceStore.getState().workspace?.form.specification.values.video.shots.length).toBe(1);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '准备制作方案' })).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole('button', { name: '准备制作方案' }));
|
||||
await waitFor(() => expect(commands).toContain('request_quote'));
|
||||
console.log('FLOW_COMMANDS', commands);
|
||||
console.log('FLOW_BLOCKERS', useImageWorkspaceStore.getState().quoteBlockers.map((item) => item.code));
|
||||
expect(commands.filter((kind) => kind === 'prepare_generation')).toHaveLength(3);
|
||||
expect(useImageWorkspaceStore.getState().quoteBlockers).toEqual([]);
|
||||
expect(screen.getByRole('button', { name: '确认并开始制作' })).toBeEnabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '确认并开始制作' }));
|
||||
|
||||
Reference in New Issue
Block a user