8dad52f040
新增画布Agent会话契约、领域规则、SpacetimeDB元数据表和生成绑定。 新增api-server画布Agent会话CRUD、OSS消息文档读写、SSE消息流和生成工具编排。 新增前端右侧Agent对话面板、会话状态hook、SSE客户端、BFF客户端和相关测试。 调整画布侧栏、任务栏、小地图默认状态和面板互斥交互。 补齐画布Agent、OSS消息存储、SSE收口、后端契约和项目记忆文档。
170 lines
5.0 KiB
TypeScript
170 lines
5.0 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
createEditorAgentConversation,
|
|
deleteEditorAgentConversation,
|
|
getEditorAgentConversation,
|
|
listEditorAgentConversations,
|
|
streamEditorAgentMessage,
|
|
} from './editorAgentClient';
|
|
|
|
const requestJsonMock = vi.hoisted(() => vi.fn());
|
|
const fetchWithApiAuthMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock('../apiClient', () => ({
|
|
requestJson: requestJsonMock,
|
|
fetchWithApiAuth: fetchWithApiAuthMock,
|
|
}));
|
|
|
|
function createSseResponse(payload: string) {
|
|
const encoder = new TextEncoder();
|
|
return new Response(
|
|
new ReadableStream<Uint8Array>({
|
|
start(controller) {
|
|
controller.enqueue(encoder.encode(payload));
|
|
controller.close();
|
|
},
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { 'Content-Type': 'text/event-stream; charset=utf-8' },
|
|
},
|
|
);
|
|
}
|
|
|
|
describe('editorAgentClient', () => {
|
|
afterEach(() => {
|
|
requestJsonMock.mockReset();
|
|
fetchWithApiAuthMock.mockReset();
|
|
});
|
|
|
|
it('uses the planned editor agent conversation CRUD routes', async () => {
|
|
requestJsonMock
|
|
.mockResolvedValueOnce({
|
|
conversations: [
|
|
{
|
|
conversationId: 'conversation-1',
|
|
projectId: 'project-1',
|
|
title: '角色参考',
|
|
createdAt: '2026-07-03T00:00:00.000Z',
|
|
updatedAt: '2026-07-03T00:00:00.000Z',
|
|
},
|
|
],
|
|
})
|
|
.mockResolvedValueOnce({
|
|
conversation: {
|
|
conversationId: 'conversation-2',
|
|
projectId: 'project-1',
|
|
title: '新对话',
|
|
messages: [],
|
|
createdAt: '2026-07-03T00:00:00.000Z',
|
|
updatedAt: '2026-07-03T00:01:00.000Z',
|
|
},
|
|
})
|
|
.mockResolvedValueOnce({
|
|
conversation: {
|
|
conversationId: 'conversation-1',
|
|
projectId: 'project-1',
|
|
title: '角色参考',
|
|
messages: [],
|
|
createdAt: '2026-07-03T00:00:00.000Z',
|
|
updatedAt: '2026-07-03T00:02:00.000Z',
|
|
},
|
|
})
|
|
.mockResolvedValueOnce({
|
|
conversation: {
|
|
conversationId: 'conversation-1',
|
|
projectId: 'project-1',
|
|
title: '角色参考',
|
|
messages: [],
|
|
createdAt: '2026-07-03T00:00:00.000Z',
|
|
updatedAt: '2026-07-03T00:02:00.000Z',
|
|
},
|
|
});
|
|
|
|
await listEditorAgentConversations('project-1');
|
|
await createEditorAgentConversation('project-1', { title: '新对话' });
|
|
await getEditorAgentConversation('conversation-1');
|
|
await deleteEditorAgentConversation('conversation-1');
|
|
|
|
expect(requestJsonMock).toHaveBeenNthCalledWith(
|
|
1,
|
|
'/api/editor/projects/project-1/agent-conversations',
|
|
{ method: 'GET' },
|
|
'读取画布 Agent 会话列表失败',
|
|
);
|
|
expect(requestJsonMock).toHaveBeenNthCalledWith(
|
|
2,
|
|
'/api/editor/projects/project-1/agent-conversations',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ title: '新对话' }),
|
|
}),
|
|
'创建画布 Agent 会话失败',
|
|
);
|
|
expect(requestJsonMock).toHaveBeenNthCalledWith(
|
|
3,
|
|
'/api/editor/agent-conversations/conversation-1',
|
|
{ method: 'GET' },
|
|
'读取画布 Agent 会话失败',
|
|
);
|
|
expect(requestJsonMock).toHaveBeenNthCalledWith(
|
|
4,
|
|
'/api/editor/agent-conversations/conversation-1',
|
|
{ method: 'DELETE' },
|
|
'删除画布 Agent 会话失败',
|
|
);
|
|
});
|
|
|
|
it('posts editor agent message streams and emits typed SSE events', async () => {
|
|
fetchWithApiAuthMock.mockResolvedValueOnce(
|
|
createSseResponse(
|
|
[
|
|
'event: stage',
|
|
'data: {"conversationId":"conversation-1","stage":"thinking"}',
|
|
'',
|
|
'event: message_delta',
|
|
'data: {"conversationId":"conversation-1","messageId":"assistant-1","role":"assistant","kind":"chat","textDelta":"我先看一下"}',
|
|
'',
|
|
'event: done',
|
|
'data: {"conversationId":"conversation-1","title":"生成角色"}',
|
|
'',
|
|
'',
|
|
].join('\n'),
|
|
),
|
|
);
|
|
const events: string[] = [];
|
|
|
|
await streamEditorAgentMessage(
|
|
'conversation-1',
|
|
{
|
|
clientMessageId: 'client-message-1',
|
|
text: '帮我把角色改成像素风',
|
|
attachments: [],
|
|
},
|
|
{
|
|
onEvent: (event) => events.push(event.event),
|
|
},
|
|
);
|
|
|
|
expect(events).toEqual(['stage', 'message_delta', 'done']);
|
|
expect(fetchWithApiAuthMock).toHaveBeenCalledWith(
|
|
'/api/editor/agent-conversations/conversation-1/messages/stream',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
clientMessageId: 'client-message-1',
|
|
text: '帮我把角色改成像素风',
|
|
attachments: [],
|
|
}),
|
|
}),
|
|
expect.objectContaining({
|
|
timeoutMs: 1_200_000,
|
|
authImpact: 'local',
|
|
}),
|
|
);
|
|
});
|
|
});
|