feat(agents): move conversation attachments into a header side panel
This commit is contained in:
1 parent
e9f204776a
commit
0d54690dd6
10 files changed
+589
-98
No files matched your search
@@ -42,6 +42,11 @@ beforeEach(() => {
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
async function chooseFileFromMenu(button = screen.getByRole('button', { name: '添加文件或图片' })) {
|
||||
fireEvent.keyDown(button, { key: 'ArrowDown' });
|
||||
fireEvent.click(await screen.findByRole('menuitem', { name: '添加文件或图片', exact: true }));
|
||||
}
|
||||
|
||||
it('recovers a server queue and cancels it after remount without resubmitting messages', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
let cancelled = false;
|
||||
@@ -383,12 +388,12 @@ it('creates an attachment conversation only after a native pick and deselects th
|
||||
render(<CloudChat slug={slug} />);
|
||||
const add = screen.getByRole('button', { name: '添加文件或图片' });
|
||||
await waitFor(() => expect(add).toBeEnabled());
|
||||
fireEvent.click(add);
|
||||
await chooseFileFromMenu(add);
|
||||
await waitFor(() => expect(api.upload).toHaveBeenCalledTimes(1));
|
||||
await waitFor(() => expect(add).toBeEnabled());
|
||||
expect(api.call.mock.calls.some(call => call[0] === 'createThread')).toBe(false);
|
||||
expect(screen.queryByLabelText('历史对话')).not.toBeInTheDocument();
|
||||
fireEvent.click(add);
|
||||
await chooseFileFromMenu(add);
|
||||
await waitFor(() => expect(api.call).toHaveBeenCalledWith('confirmAttachment', {
|
||||
thread_id: 'thread-1', attachments: [{ object_name: 'temporary/notes.txt', file_type: 'txt' }],
|
||||
}));
|
||||
@@ -400,8 +405,15 @@ it('creates an attachment conversation only after a native pick and deselects th
|
||||
fireEvent.click(remove);
|
||||
expect(screen.queryByRole('button', { name: '移除附件 notes.txt' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '发送', exact: true })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '对话文件' }));
|
||||
expect(await screen.findByRole('checkbox', { name: '选择附件 notes.txt' })).not.toBeChecked();
|
||||
fireEvent.click(screen.getByRole('button', { name: '附件,共 1 个文件' }));
|
||||
const checkbox = await screen.findByRole('checkbox', { name: '选择附件 notes.txt' });
|
||||
expect(checkbox).not.toBeChecked();
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox).toBeChecked();
|
||||
fireEvent.click(screen.getByRole('button', { name: '返回聊天 · 已选 1 个附件' }));
|
||||
expect(await screen.findByRole('button', { name: '移除附件 notes.txt' })).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '移除附件 notes.txt' }));
|
||||
expect(screen.queryByRole('button', { name: '移除附件 notes.txt' })).not.toBeInTheDocument();
|
||||
expect(api.call.mock.calls.some(call => ['deleteAttachment', 'submit', 'preview'].includes(call[0]))).toBe(false);
|
||||
});
|
||||
|
||||
@@ -419,7 +431,7 @@ it('blocks preview switching while creating an attachment thread and ignores a l
|
||||
api.upload.mockResolvedValue({ object_name: 'temporary/old-agent-notes.txt', file_name: 'old-agent-notes.txt', file_type: 'txt', parse_supported: false, parse_methods: [] });
|
||||
const view = render(<CloudChat slug={slug} previewRevision={1} />);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '添加文件或图片' })).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加文件或图片' }));
|
||||
await chooseFileFromMenu();
|
||||
await waitFor(() => expect(api.call.mock.calls.filter(call => call[0] === 'createThread')).toHaveLength(1));
|
||||
expect(screen.getByRole('button', { name: '新对话' })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '新对话' }));
|
||||
@@ -456,7 +468,7 @@ it('retries the same picked attachment and thread after confirmation fails, then
|
||||
api.upload.mockResolvedValue({ object_name: 'temporary/notes.txt', file_name: 'notes.txt', file_type: 'txt', parse_supported: false, parse_methods: [] });
|
||||
render(<CloudChat slug={slug} />);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '添加文件或图片' })).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加文件或图片' }));
|
||||
await chooseFileFromMenu();
|
||||
const retry = await screen.findByRole('button', { name: '重试添加' });
|
||||
fireEvent.change(screen.getByLabelText('消息'), { target: { value: '请阅读这个附件' } });
|
||||
expect(screen.getByRole('button', { name: '发送', exact: true })).toBeDisabled();
|
||||
@@ -490,7 +502,7 @@ it('cancels an unconfirmed attachment without deleting it and permits the typed
|
||||
render(<CloudChat slug={slug} />);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '添加文件或图片' })).toBeEnabled());
|
||||
fireEvent.change(screen.getByLabelText('消息'), { target: { value: '继续讨论我的想法' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加文件或图片' }));
|
||||
await chooseFileFromMenu();
|
||||
await screen.findByRole('button', { name: '重试添加' });
|
||||
expect(screen.getByRole('button', { name: '发送', exact: true })).toBeDisabled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '取消添加' }));
|
||||
@@ -569,3 +581,30 @@ it('does not mark a retained hidden conversation as read until it is shown', asy
|
||||
view.rerender(<CloudChat slug={slug} activeView />);
|
||||
await waitFor(() => expect(api.call).toHaveBeenCalledWith('viewed', { thread_id: 'thread-1', run_id: 'run-1' }));
|
||||
});
|
||||
|
||||
it('discovers generated files after the authoritative completed reply without reopening the conversation', async () => {
|
||||
const base = api.call.getMockImplementation()!;
|
||||
const events = new Stream();
|
||||
api.events.mockResolvedValue(events);
|
||||
let completed = false;
|
||||
api.call.mockImplementation(async (operation, input) => {
|
||||
if (operation === 'threads') return { threads: [{ thread_id: 'thread-1', client_thread_id: 'client', mode: 'published' }], next_offset: null };
|
||||
if (operation === 'history') return { ...history, messages: completed ? [{ id: 'answer-1', role: 'assistant', content: '故事文件已做好', run_id: 'run-1' }] : [], run: { ...run, status: completed ? 'completed' : 'running' } };
|
||||
if (operation === 'files') return { files: completed ? [{ name: 'story.txt', path: '/story.txt', directory_path: '/', is_dir: false, size: 1024 }] : [] };
|
||||
return base(operation, input);
|
||||
});
|
||||
render(<CloudChat slug={slug} />);
|
||||
await waitFor(() => expect(api.events).toHaveBeenCalledWith('run-1'));
|
||||
await waitFor(() => expect(api.call).toHaveBeenCalledWith('files', { thread_id: 'thread-1', path: '/' }));
|
||||
expect(screen.queryByRole('button', { name: /^附件/ })).not.toBeInTheDocument();
|
||||
completed = true;
|
||||
await act(async () => events.dispatchEvent(new MessageEvent('end', {
|
||||
data: JSON.stringify({ run_id: 'run-1', thread_id: 'thread-1', payload: { chunk: { status: 'completed' } } }),
|
||||
})));
|
||||
expect(await screen.findByText('故事文件已做好')).toBeVisible();
|
||||
fireEvent.click(await screen.findByRole('button', { name: '附件,共 1 个文件' }));
|
||||
expect(await screen.findByRole('button', { name: '保存 story.txt' })).toBeEnabled();
|
||||
expect(api.call.mock.calls.filter(([operation]) => operation === 'history')).toHaveLength(2);
|
||||
expect(api.call.mock.calls.some(([operation]) => ['createThread', 'confirmAttachment', 'submit'].includes(operation))).toBe(false);
|
||||
expect(api.upload).not.toHaveBeenCalled();
|
||||
});
|
||||
Reference in new issue
Block a user