合并 AI 设计多会话客户端
整合 Enter 发送与多会话交互,保留服务端持久 Conversation Session,并补齐迁移、回归、Electron E2E 与 canonical 文档。
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
@@ -87,18 +94,96 @@ describe('local AI design workspace', () => {
|
||||
|
||||
const reloaded = new LocalImageWorkspace({ userDataDir });
|
||||
await expect(reloaded.bootstrap()).resolves.toMatchObject({
|
||||
workspaces: [{ title: '角色设计第二版', turnRevision: 0, viewRevision: 1 }],
|
||||
workspaces: [{ title: '角色设计第二版', conversationCount: 1, viewRevision: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
it('migrates a legacy single conversation into schema v3 without losing project tasks', async () => {
|
||||
const userDataDir = createTemporaryDirectory();
|
||||
const rootDirectory = getLocalImageWorkspaceDirectory(userDataDir);
|
||||
const timestamp = '2026-07-31T10:00:00.000Z';
|
||||
mkdirSync(rootDirectory, { recursive: true });
|
||||
writeFileSync(join(rootDirectory, 'design-workspace-v2.json'), JSON.stringify({
|
||||
schemaVersion: 2,
|
||||
workspaces: [{
|
||||
workspaceId: 'workspace-legacy',
|
||||
clientWorkspaceId: 'client-legacy',
|
||||
title: '历史设计项目',
|
||||
turnRevision: 2,
|
||||
viewRevision: 4,
|
||||
phase: 'awaiting_confirmation',
|
||||
brief: {
|
||||
version: 2,
|
||||
status: 'ready',
|
||||
medium: 'image',
|
||||
summary: '历史海报',
|
||||
ready: true,
|
||||
missingDecision: null,
|
||||
},
|
||||
messages: [{
|
||||
id: 'message-legacy',
|
||||
role: 'user',
|
||||
kind: 'user',
|
||||
text: '保留这条历史消息',
|
||||
quickReplies: [],
|
||||
generationQuote: null,
|
||||
turnRevision: 1,
|
||||
createdAt: timestamp,
|
||||
}],
|
||||
requestHashes: { 'turn-legacy': 'hash-legacy' },
|
||||
updatedAt: timestamp,
|
||||
}],
|
||||
tasksByWorkspaceId: {
|
||||
'workspace-legacy': [{
|
||||
taskId: 'task-legacy',
|
||||
workspaceId: 'workspace-legacy',
|
||||
medium: 'image',
|
||||
status: 'succeeded',
|
||||
briefVersion: 2,
|
||||
briefSummary: '历史海报',
|
||||
quoteId: null,
|
||||
quotedDesignPoints: null,
|
||||
failureCode: null,
|
||||
resultAssets: [],
|
||||
createdAt: timestamp,
|
||||
updatedAt: timestamp,
|
||||
}],
|
||||
},
|
||||
assetsById: {},
|
||||
}), 'utf8');
|
||||
const service = createService(userDataDir);
|
||||
|
||||
const workspace = await service.getWorkspace('workspace-legacy');
|
||||
const migratedConversation = workspace.conversations[0];
|
||||
|
||||
expect(workspace).toMatchObject({
|
||||
workspaceId: 'workspace-legacy',
|
||||
conversationCount: 1,
|
||||
conversations: [{ title: '历史会话', turnRevision: 2 }],
|
||||
});
|
||||
await expect(service.getConversation(
|
||||
'workspace-legacy',
|
||||
migratedConversation.conversationId,
|
||||
)).resolves.toMatchObject({
|
||||
messages: [{ text: '保留这条历史消息' }],
|
||||
});
|
||||
await expect(service.listTasks('workspace-legacy')).resolves.toMatchObject([{
|
||||
taskId: 'task-legacy',
|
||||
conversationId: migratedConversation.conversationId,
|
||||
}]);
|
||||
expect(existsSync(join(rootDirectory, 'design-workspace-v3.json'))).toBe(true);
|
||||
});
|
||||
|
||||
it('creates a Quote first and a visible generation task only after confirmation', async () => {
|
||||
const service = createService(createTemporaryDirectory());
|
||||
const created = await service.createWorkspace({
|
||||
clientWorkspaceId: 'client-flow',
|
||||
title: '海洋公益海报',
|
||||
});
|
||||
const conversationId = created.conversations[0].conversationId;
|
||||
const discussed = await service.submitMessage({
|
||||
workspaceId: created.workspaceId,
|
||||
conversationId,
|
||||
clientTurnId: 'turn-one',
|
||||
expectedTurnRevision: 0,
|
||||
message: '做一张保护海洋的竖版公益海报',
|
||||
@@ -111,6 +196,7 @@ describe('local AI design workspace', () => {
|
||||
|
||||
const confirmed = await service.confirmGeneration({
|
||||
workspaceId: created.workspaceId,
|
||||
conversationId,
|
||||
clientTurnId: 'turn-two',
|
||||
expectedTurnRevision: 1,
|
||||
quoteId: quote!.quoteId,
|
||||
@@ -141,15 +227,17 @@ describe('local AI design workspace', () => {
|
||||
clientWorkspaceId: 'client-reset',
|
||||
title: '可重置项目',
|
||||
});
|
||||
const conversationId = created.conversations[0].conversationId;
|
||||
|
||||
await expect(service.submitMessage({
|
||||
workspaceId: created.workspaceId,
|
||||
conversationId,
|
||||
clientTurnId: 'stale-turn',
|
||||
expectedTurnRevision: 3,
|
||||
message: '测试',
|
||||
})).rejects.toMatchObject<LocalImageWorkspaceError>({
|
||||
status: 409,
|
||||
code: 'workspace_revision_conflict',
|
||||
code: 'conversation_revision_conflict',
|
||||
});
|
||||
|
||||
expect(existsSync(getLocalImageWorkspaceDirectory(userDataDir))).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user