feat: 改造云智能体桌面编辑与试用工作区

This commit is contained in:
2026-09-12 17:39:31 +08:00
parent 6b92591129
commit dca6deab29
16 changed files with 623 additions and 222 deletions

View File

@@ -63,11 +63,13 @@ it('returns to the last selected thread on remount and requires restoring an arc
return base(operation, input);
});
const first = render(<CloudChat slug={slug} />);
fireEvent.click(screen.getByRole('button', { name: '查看历史对话' }));
await waitFor(() => expect(screen.getByLabelText('历史对话')).toHaveValue('older'));
fireEvent.change(screen.getByLabelText('历史对话'), { target: { value: 'newest' } });
await waitFor(() => expect(recent.thread_id).toBe('newest'));
first.unmount();
const second = render(<CloudChat slug={slug} />);
fireEvent.click(screen.getByRole('button', { name: '查看历史对话' }));
await waitFor(() => expect(screen.getByLabelText('历史对话')).toHaveValue('newest'));
second.unmount();
recent = { ...recent, thread_id: 'older' };
@@ -241,6 +243,7 @@ it('returns to a newly created conversation from history without remounting the
await waitFor(() => expect(screen.getByLabelText('消息')).not.toBeDisabled());
fireEvent.change(screen.getByLabelText('消息'), { target: { value: '新对话主题' } });
fireEvent.click(screen.getByRole('button', { name: '发送', exact: true }));
fireEvent.click(screen.getByRole('button', { name: '查看历史对话' }));
await waitFor(() => expect(screen.getByLabelText('历史对话')).toBeEnabled());
expect(screen.getByRole('option', { name: '新对话主题 · 已完成' })).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '新对话' }));
@@ -280,3 +283,69 @@ it('creates an attachment conversation only when the selected file is confirmed'
expect(api.call.mock.calls.filter(call => call[0] === 'createThread')).toHaveLength(1);
await waitFor(() => expect(screen.getByText('附件与产物 · 本条消息附带 1 个文件')).toBeVisible());
});
it('pins uncertain preview retries to the original request and revision after a draft save', async () => {
const base = api.call.getMockImplementation()!;
let attempts = 0;
api.call.mockImplementation(async (operation, input) => {
if (operation === 'preview') {
if (++attempts === 1) throw new Error('响应丢失');
return { request_id: input.request_id, thread_id: 'thread-1', run_id: 'run-1', status: 'completed', version: 'draft:1' };
}
if (operation === 'history') return { ...history, run: { ...run, status: 'completed', version: 'draft:1' }, messages: [{ id: 1, role: 'assistant', content: '第一版回答' }] };
return base(operation, input);
});
const view = render(<CloudChat slug={slug} previewRevision={1} />);
await waitFor(() => expect(screen.getByLabelText('消息')).toBeEnabled());
fireEvent.change(screen.getByLabelText('消息'), { target: { value: '原来的问题' } });
fireEvent.click(screen.getByRole('button', { name: '发送', exact: true }));
await screen.findByRole('button', { name: '重试本次发送' });
view.rerender(<CloudChat slug={slug} previewRevision={2} />);
expect(screen.getByRole('button', { name: '用最新草稿试用' })).toBeDisabled();
fireEvent.click(screen.getByRole('button', { name: '重试本次发送' }));
await screen.findByText('第一版回答');
const submissions = api.call.mock.calls.filter(call => call[0] === 'preview');
expect(submissions).toHaveLength(2);
expect(submissions[0][1]).toEqual(submissions[1][1]);
expect(submissions[1][1]).toMatchObject({ expected_revision: 1, query: '原来的问题' });
fireEvent.click(screen.getByRole('button', { name: '用最新草稿试用' }));
expect(screen.queryByText('第一版回答')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '查看历史对话' }));
fireEvent.change(screen.getByLabelText('历史对话'), { target: { value: 'thread-1' } });
await screen.findByText('第一版回答');
expect(screen.getByText('草稿预览 · 修订 1')).toBeVisible();
});
it('uses Enter to send while preserving Shift+Enter and Chinese IME composition', async () => {
const base = api.call.getMockImplementation()!;
api.call.mockImplementation(async (operation, input) => operation === 'preview'
? { request_id: input.request_id, thread_id: 'thread-1', status: 'completed' } : base(operation, input));
render(<CloudChat slug={slug} previewRevision={3} />);
const input = screen.getByLabelText('消息');
await waitFor(() => expect(input).toBeEnabled());
fireEvent.change(input, { target: { value: '整理我的思路' } });
fireEvent.keyDown(input, { key: 'Enter', shiftKey: true });
fireEvent.keyDown(input, { key: 'Enter', isComposing: true });
fireEvent.keyDown(input, { key: 'Enter', keyCode: 229 });
expect(api.call.mock.calls.some(call => call[0] === 'preview')).toBe(false);
fireEvent.keyDown(input, { key: 'Enter' });
await waitFor(() => expect(api.call).toHaveBeenCalledWith('preview', expect.objectContaining({ expected_revision: 3, query: '整理我的思路' })));
});
it('does not mark a retained hidden conversation as read until it is shown', async () => {
const base = api.call.getMockImplementation()!;
api.call.mockImplementation(async (operation, input) => {
if (operation === 'threads') return { threads: [{ thread_id: 'thread-1', mode: 'published' }], next_offset: null };
if (operation === 'history') return { ...history, run: { ...run, status: 'completed' }, messages: [{ id: 1, role: 'assistant', content: '后台的新回答' }] };
if (operation === 'viewed') return { viewed: true };
return base(operation, input);
});
const view = render(<CloudChat slug={slug} activeView={false} />);
await screen.findByText('后台的新回答');
expect(api.call.mock.calls.some(call => call[0] === 'viewed')).toBe(false);
expect(api.remember).not.toHaveBeenCalled();
view.rerender(<CloudChat slug={slug} activeView />);
await waitFor(() => expect(api.call).toHaveBeenCalledWith('viewed', { thread_id: 'thread-1', run_id: 'run-1' }));
});