Merge remote-tracking branch 'origin/main'
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

# Conflicts:
#	.project-docs/20-architecture/module-map.md
#	src/pages/ImageCanvas/DesignConversationPane.tsx
#	tests/unit/image-canvas-page.test.tsx
This commit is contained in:
inman committed 2026-09-07 18:40:36 +08:00
commit 64db6abbfb
17 files changed
+2437 -105

No files matched your search

+181
View File
@@ -69,6 +69,7 @@ function prepareWorkspace(overrides = {}) {
pendingOperations: {},
fieldDrafts: {},
chatDraft: '',
assistantActivities: {},
assistantStreams: {},
quoteBlockers: [],
error: null,
@@ -129,6 +130,37 @@ describe('AI Design Canvas page', () => {
expect(screen.queryByText(/规格版本|编译器|生成策略|字段决策/)).not.toBeInTheDocument();
});
it('does not disable the current composer for another Workspace command', () => {
prepareWorkspace();
useImageWorkspaceStore.setState({
chatDraft: '当前项目的新想法',
pendingOperations: {
'operation-workspace-2': {
id: 'operation-workspace-2',
label: '发送创作想法',
command: {
kind: 'apply_input',
workspaceId: 'workspace-2',
sessionId: 'session-2',
expectedDirectionRevision: 2,
clientOperationId: 'operation-workspace-2',
input: { kind: 'chat', message: '另一个项目还在处理' },
},
status: 'submitting',
error: null,
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
},
});
render(<ImageCanvas />);
expect(screen.getByRole('textbox', { name: '告诉 AI 你想创作什么' })).toBeEnabled();
expect(screen.getByRole('button', { name: '发送' })).toBeEnabled();
});
it('places desktop production history in a full-height right rail', () => {
setViewport(true);
prepareWorkspace();
@@ -194,6 +226,7 @@ describe('AI Design Canvas page', () => {
error: null,
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
},
});
@@ -231,6 +264,152 @@ describe('AI Design Canvas page', () => {
expect(within(conversation).getByTestId('youth-creation-card')).toBeInTheDocument();
});
it('keeps each pending reply beside the user message that started it', () => {
prepareWorkspace({ turns: [] });
useImageWorkspaceStore.setState({
pendingOperations: {
'operation-chat-1': {
id: 'operation-chat-1',
label: '发送创作想法',
command: {
kind: 'apply_input',
workspaceId: 'workspace-1',
sessionId: 'session-1',
expectedDirectionRevision: 4,
clientOperationId: 'operation-chat-1',
input: { kind: 'chat', message: '先画一只小熊' },
},
status: 'unknown',
error: '结果尚未确认',
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
'operation-chat-2': {
id: 'operation-chat-2',
label: '发送创作想法',
command: {
kind: 'apply_input',
workspaceId: 'workspace-1',
sessionId: 'session-1',
expectedDirectionRevision: 4,
clientOperationId: 'operation-chat-2',
input: { kind: 'chat', message: '再补充一只小兔子' },
},
status: 'submitting',
error: null,
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
},
assistantStreams: {
'operation-chat-1': '小熊会站在月亮旁边。',
},
});
render(<ImageCanvas />);
const conversation = screen.getByTestId('image-workspace-conversation');
const firstMessage = within(conversation).getByTestId('pending-design-chat-operation-chat-1');
const firstReply = within(conversation)
.getByTestId('streaming-design-assistant-operation-chat-1');
const secondMessage = within(conversation).getByTestId('pending-design-chat-operation-chat-2');
expect(firstMessage.compareDocumentPosition(firstReply) & Node.DOCUMENT_POSITION_FOLLOWING)
.toBeTruthy();
expect(firstReply.compareDocumentPosition(secondMessage) & Node.DOCUMENT_POSITION_FOLLOWING)
.toBeTruthy();
});
it('shows public AI activity under the submitted message and collapses it beside the final turn', async () => {
const message = '画一只在月球踢球的熊猫';
const { workspace } = prepareWorkspace({ turns: [] });
useImageWorkspaceStore.setState({
pendingOperations: {
'operation-chat-1': {
id: 'operation-chat-1',
label: '发送创作想法',
command: {
kind: 'apply_input',
workspaceId: 'workspace-1',
sessionId: 'session-1',
expectedDirectionRevision: 4,
clientOperationId: 'operation-chat-1',
input: { kind: 'chat', message },
},
status: 'submitting',
error: null,
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
},
assistantActivities: {
'operation-chat-1': {
workspaceId: 'workspace-1',
directionId: 'direction-1',
status: 'active',
turnId: null,
steps: [
{ stage: 'understanding', message: '正在听懂你刚补充的内容…' },
{ stage: 'reviewing_context', message: '正在把它和前面的设计想法放在一起看…' },
],
},
},
});
render(<ImageCanvas />);
const conversation = screen.getByTestId('image-workspace-conversation');
const pending = within(conversation).getByTestId('pending-design-chat-operation-chat-1');
const activity = within(conversation).getByTestId('design-assistant-activity-operation-chat-1');
expect(pending.compareDocumentPosition(activity) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(within(activity).getByRole('button', { name: /AI 处理过程/ }))
.toHaveAttribute('aria-expanded', 'true');
expect(within(activity).getByText('正在听懂你刚补充的内容…')).toBeInTheDocument();
expect(within(activity).getAllByText('正在把它和前面的设计想法放在一起看…'))
.not.toHaveLength(0);
expect(within(activity).queryByText(/思考过程|推理内容/)).not.toBeInTheDocument();
act(() => {
useImageWorkspaceStore.setState({
workspace: {
...workspace,
turns: [{
turnId: 'turn-2',
rawTurnSequence: 2,
userMessage: message,
assistantMessage: '这个画面很有趣。你想让它更像漫画还是电影?',
}],
},
pendingOperations: {},
assistantActivities: {
'operation-chat-1': {
workspaceId: 'workspace-1',
directionId: 'direction-1',
status: 'completed',
turnId: 'turn-2',
steps: [
{ stage: 'understanding', message: '正在听懂你刚补充的内容…' },
{ stage: 'reviewing_context', message: '正在把它和前面的设计想法放在一起看…' },
],
},
},
});
});
const completedActivity = within(conversation)
.getByTestId('design-assistant-activity-operation-chat-1');
await waitFor(() => {
expect(within(completedActivity).getByRole('button', { name: /AI 处理过程/ }))
.toHaveAttribute('aria-expanded', 'false');
});
expect(within(completedActivity).queryByText('正在听懂你刚补充的内容…'))
.not.toBeInTheDocument();
fireEvent.click(within(completedActivity).getByRole('button', { name: /AI 处理过程/ }));
expect(within(completedActivity).getByText('正在听懂你刚补充的内容…')).toBeInTheDocument();
});
it('requests a Quote from the inline plan without starting a paid task', async () => {
const { actions } = prepareWorkspace();
const quotedWorkspace = designWorkspaceFixture({
@@ -273,6 +452,7 @@ describe('AI Design Canvas page', () => {
error: null,
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
},
});
@@ -413,6 +593,7 @@ describe('AI Design Canvas page', () => {
error: '网络已断开',
clearDraftPaths: [],
clearChatDraft: false,
baseRawTurnSequence: null,
},
},
});