添加 AI 设计图生图选图交互

This commit is contained in:
2026-08-12 20:45:24 +08:00
parent 3a80625fe2
commit 809364ecbe
4 changed files with 312 additions and 57 deletions

View File

@@ -711,11 +711,152 @@ describe('ImageCanvas Workspace-first design experience', () => {
expect(sendImageWorkspaceMessageMock).not.toHaveBeenCalled();
});
it('keeps the historical video picker behavior when the brief medium is not decided yet', async () => {
fetchImageWorkspaceConversationMock.mockResolvedValueOnce({
...conversationFixture(),
phase: 'shaping',
brief: {
version: 2,
status: 'draft',
medium: null,
summary: '',
ready: false,
missingDecision: '请选择首帧来源',
},
messages: [
...conversationFixture().messages,
{
id: 'message-undecided-source',
role: 'assistant',
kind: 'choice',
text: '请选择视频首帧来源。',
quickReplies: ['从作品列表选择图片'],
generationQuote: null,
turnRevision: 2,
createdAt: '2026-08-12T09:00:00Z',
},
],
turnRevision: 2,
} satisfies DesignConversation);
render(<MemoryRouter><ImageCanvas /></MemoryRouter>);
fireEvent.click(await screen.findByRole('button', { name: '从作品列表选择图片' }));
const picker = screen.getByRole('dialog', { name: '选择视频首帧' });
const selectButton = within(picker).getByRole('button', { name: '选择此图片' });
await waitFor(() => expect(selectButton).toBeEnabled());
fireEvent.click(selectButton);
await waitFor(() => expect(sendImageWorkspaceMessageMock).toHaveBeenCalledWith(
'workspace-cloud',
'conversation-cloud',
2,
'使用作品图片作为视频首帧',
expect.stringMatching(/^turn-/),
['asset-one'],
));
expect(screen.queryByRole('dialog', { name: '选择视频首帧' }))
.not.toBeInTheDocument();
});
it('opens the image reference picker and submits a successful work Asset id', async () => {
fetchImageWorkspaceConversationMock.mockResolvedValueOnce({
...conversationFixture(),
phase: 'shaping',
brief: {
version: 2,
status: 'draft',
medium: 'image',
summary: '参考海洋公益海报生成新图',
ready: false,
missingDecision: '请选择参考图来源',
},
messages: [
...conversationFixture().messages,
{
id: 'message-image-source',
role: 'assistant',
kind: 'choice',
text: '请选择图生图参考图。',
quickReplies: ['从作品列表选择图片'],
generationQuote: null,
turnRevision: 2,
createdAt: '2026-08-12T09:00:00Z',
},
],
turnRevision: 2,
} satisfies DesignConversation);
render(<MemoryRouter><ImageCanvas /></MemoryRouter>);
fireEvent.click(await screen.findByRole('button', { name: '从作品列表选择图片' }));
const picker = screen.getByRole('dialog', { name: '选择图生图参考图' });
expect(within(picker).getByText(/作为图生图参考/)).toBeInTheDocument();
const selectButton = within(picker).getByRole('button', { name: '选择此图片' });
await waitFor(() => expect(selectButton).toBeEnabled());
fireEvent.click(selectButton);
await waitFor(() => expect(sendImageWorkspaceMessageMock).toHaveBeenCalledWith(
'workspace-cloud',
'conversation-cloud',
2,
'使用作品图片作为图生图参考图',
expect.stringMatching(/^turn-/),
['asset-one'],
));
expect(screen.queryByRole('dialog', { name: '选择图生图参考图' }))
.not.toBeInTheDocument();
});
it('uploads a local image reference and submits its Asset id to the Agent', async () => {
fetchImageWorkspaceConversationMock.mockResolvedValueOnce({
...conversationFixture(),
messages: [
...conversationFixture().messages,
{
id: 'message-image-source',
role: 'assistant',
kind: 'choice',
text: '请选择图生图参考图。',
quickReplies: ['从作品列表选择图片'],
generationQuote: null,
turnRevision: 2,
createdAt: '2026-08-12T09:00:00Z',
},
],
turnRevision: 2,
} satisfies DesignConversation);
render(<MemoryRouter><ImageCanvas /></MemoryRouter>);
fireEvent.click(await screen.findByRole('button', { name: '从作品列表选择图片' }));
const picker = screen.getByRole('dialog', { name: '选择图生图参考图' });
const file = new File(['image-bytes'], 'reference.png', { type: 'image/png' });
fireEvent.change(within(picker).getByLabelText('上传本地图片'), {
target: { files: [file] },
});
await waitFor(() => expect(uploadImageWorkspaceAssetMock)
.toHaveBeenCalledWith('workspace-cloud', file));
await waitFor(() => expect(sendImageWorkspaceMessageMock).toHaveBeenCalledWith(
'workspace-cloud',
'conversation-cloud',
2,
'使用上传图片作为图生图参考图',
expect.stringMatching(/^turn-/),
['asset-uploaded'],
));
await waitFor(() => expect(
screen.queryByRole('dialog', { name: '选择图生图参考图' }),
).not.toBeInTheDocument());
});
it('submits a successful work Asset id when it is selected as the video first frame', async () => {
const pendingSelection = deferred<DesignConversation>();
sendImageWorkspaceMessageMock.mockReturnValueOnce(pendingSelection.promise);
fetchImageWorkspaceConversationMock.mockResolvedValueOnce({
...conversationFixture(),
brief: {
...conversationFixture().brief,
medium: 'video',
},
turnRevision: 2,
messages: [
...conversationFixture().messages,
@@ -759,6 +900,10 @@ describe('ImageCanvas Workspace-first design experience', () => {
it('uploads a local first-frame image and submits its Asset id to the Agent', async () => {
fetchImageWorkspaceConversationMock.mockResolvedValueOnce({
...conversationFixture(),
brief: {
...conversationFixture().brief,
medium: 'video',
},
messages: [
...conversationFixture().messages,
{
@@ -793,6 +938,9 @@ describe('ImageCanvas Workspace-first design experience', () => {
expect.stringMatching(/^turn-/),
['asset-uploaded'],
));
await waitFor(() => expect(
screen.queryByRole('dialog', { name: '选择视频首帧' }),
).not.toBeInTheDocument());
});
it('renders policy-blocked generation tasks with a user-facing design message', async () => {