feat: 支持 AI 设计项目多会话交互

需求:用户可在同一设计项目中新建和切换会话,任务列表保持项目级共享。

实现:
- 增加会话选择、新建入口及本地数据迁移
- Conversation 独立消息、Brief、Quote 与流式状态
- 保留项目任务并修复 Task revision 与 ABA 异步竞态
- 保持 Enter 发送、Shift+Enter 换行及 IME 保护
This commit is contained in:
2026-08-06 18:22:15 +08:00
parent 4980894017
commit 03dae62cf3
18 changed files with 1810 additions and 702 deletions

View File

@@ -59,11 +59,11 @@ describe('AI design renderer API boundary', () => {
});
it('opens task events only after the local Host API token is ready', async () => {
const source = await openImageWorkspaceTaskEvents('workspace/one');
const source = await openImageWorkspaceTaskEvents('workspace/one', 'conversation/one');
expect(source).toBe(createHostEventSourceMock.mock.results[0].value);
expect(createHostEventSourceMock).toHaveBeenCalledWith(
'/api/works/image-workspace/workspaces/workspace%2Fone/events',
'/api/works/image-workspace/workspaces/workspace%2Fone/conversations/conversation%2Fone/events',
);
expect(ensureHostApiTokenMock.mock.invocationCallOrder[0])
.toBeLessThan(createHostEventSourceMock.mock.invocationCallOrder[0]);
@@ -84,13 +84,14 @@ describe('AI design renderer API boundary', () => {
it('sends conversation turns with the current turn revision and no provider settings', async () => {
await sendImageWorkspaceMessage(
'workspace/one',
'conversation/one',
4,
' 继续调整构图 ',
'turn-client-1',
);
const [path, init] = hostApiFetchMock.mock.calls[0];
expect(path).toBe('/api/works/image-workspace/workspaces/workspace%2Fone/messages');
expect(path).toBe('/api/works/image-workspace/workspaces/workspace%2Fone/conversations/conversation%2Fone/messages');
expect(init).toMatchObject({ method: 'POST' });
expect(JSON.parse(String(init?.body))).toMatchObject({
expectedTurnRevision: 4,
@@ -104,6 +105,7 @@ describe('AI design renderer API boundary', () => {
it('sends the selected first-frame Asset id with the conversation turn', async () => {
await sendImageWorkspaceMessage(
'workspace-one',
'conversation-one',
5,
'使用作品图片作为视频首帧',
'turn-first-frame',
@@ -148,11 +150,11 @@ describe('AI design renderer API boundary', () => {
});
it('confirms a Quote through a separate structured action route', async () => {
await confirmImageWorkspaceGeneration('workspace-one', 5, 'quote/one');
await confirmImageWorkspaceGeneration('workspace-one', 'conversation-one', 5, 'quote/one');
const [path, init] = hostApiFetchMock.mock.calls[0];
expect(path).toBe(
'/api/works/image-workspace/workspaces/workspace-one/quotes/quote%2Fone/confirm',
'/api/works/image-workspace/workspaces/workspace-one/conversations/conversation-one/quotes/quote%2Fone/confirm',
);
expect(JSON.parse(String(init?.body))).toMatchObject({
expectedTurnRevision: 5,