feat: 完善图像工作区与创作工具体验
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
inman
2026-08-16 14:08:02 +08:00
parent bfcb88cfef
commit 26b52d76e3
92 changed files with 16678 additions and 2975 deletions

View File

@@ -41,12 +41,12 @@ afterEach(() => {
});
describe('local AI design workspace', () => {
it('routes the default development command through the local adapter', () => {
it('routes the default development command through the cloud adapter and keeps local mode explicit', () => {
const packageJson = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf8')) as {
scripts: Record<string, string>;
};
expect(packageJson.scripts.dev).toBe('node scripts/run-local-image-workspace-dev.mjs');
expect(packageJson.scripts.dev).toBe('node scripts/run-cloud-image-workspace-dev.mjs');
expect(packageJson.scripts['dev:image-workspace:local'])
.toBe('node scripts/run-local-image-workspace-dev.mjs');
});
@@ -205,6 +205,8 @@ describe('local AI design workspace', () => {
clientTurnId: 'turn-two',
expectedTurnRevision: 1,
quoteId: quote!.quoteId,
finalPrompt: quote!.finalPrompt,
generationParameters: quote!.generationParameters,
});
expect(confirmed.turnRevision).toBe(2);
expect(confirmed.latestMessagePreview).toContain('图片任务已创建');
@@ -226,6 +228,172 @@ describe('local AI design workspace', () => {
expect(await response.text()).toContain('AI Design Workspace');
});
it('re-quotes local generation with the server-shaped options contract', async () => {
const service = createService(createTemporaryDirectory());
const created = await service.createWorkspace({
clientWorkspaceId: 'client-requote',
title: '校园动态海报',
});
const conversationId = created.conversations[0].conversationId;
const discussed = await service.submitMessage({
workspaceId: created.workspaceId,
conversationId,
clientTurnId: 'turn-requote-one',
expectedTurnRevision: 0,
message: '把校园海报转为 6 秒视频视觉',
});
const originalQuote = discussed.messages.at(-1)!.generationQuote!;
const updatedQuote = await service.updateGenerationQuote({
workspaceId: created.workspaceId,
quoteId: originalQuote.quoteId,
finalPrompt: '用户最新编辑后的完整提示词',
generationParameters: {
model: 'local-video-preview',
resolution: '720P',
aspectRatio: '16:9',
durationSeconds: 6,
},
});
expect(updatedQuote).toMatchObject({
finalPrompt: '用户最新编辑后的完整提示词',
generationParameters: {
model: 'local-video-preview',
resolution: '720P',
aspectRatio: '16:9',
durationSeconds: 6,
},
quotedDesignPoints: 2,
});
expect(updatedQuote.generationOptions.resolutions).toEqual(
expect.arrayContaining([expect.objectContaining({ value: '720P', disabled: false })]),
);
expect(updatedQuote.generationOptions.durations).toEqual(
expect.arrayContaining([expect.objectContaining({ value: 8, disabled: true })]),
);
const refreshed = await service.getConversation(created.workspaceId, conversationId);
const refreshedQuote = refreshed.messages.at(-1)!.generationQuote!;
expect(refreshedQuote.finalPrompt).toBe('用户最新编辑后的完整提示词');
await expect(service.confirmGeneration({
workspaceId: created.workspaceId,
conversationId,
clientTurnId: 'turn-requote-two',
expectedTurnRevision: 1,
quoteId: updatedQuote.quoteId,
finalPrompt: updatedQuote.finalPrompt,
generationParameters: updatedQuote.generationParameters,
})).resolves.toMatchObject({ turnRevision: 2 });
});
it('deletes a project while a task is queued and makes its task state inaccessible', async () => {
const userDataDir = createTemporaryDirectory();
const service = createService(userDataDir);
const created = await service.createWorkspace({
clientWorkspaceId: 'client-active',
title: '生成中的项目',
});
const conversationId = created.conversations[0].conversationId;
const discussed = await service.submitMessage({
workspaceId: created.workspaceId,
conversationId,
clientTurnId: 'turn-active-one',
expectedTurnRevision: 0,
message: '生成一张海报',
});
await service.confirmGeneration({
workspaceId: created.workspaceId,
conversationId,
clientTurnId: 'turn-active-two',
expectedTurnRevision: 1,
quoteId: discussed.messages.at(-1)!.generationQuote!.quoteId,
finalPrompt: discussed.messages.at(-1)!.generationQuote!.finalPrompt,
generationParameters: discussed.messages.at(-1)!.generationQuote!.generationParameters,
});
const stateFile = join(
getLocalImageWorkspaceDirectory(userDataDir),
'design-workspace-v3.json',
);
await expect(service.deleteWorkspace(created.workspaceId)).resolves.toEqual({
workspaceId: created.workspaceId,
deleted: true,
});
expect(readFileSync(stateFile, 'utf8')).not.toContain(created.workspaceId);
await expect(service.getWorkspace(created.workspaceId)).rejects.toMatchObject<LocalImageWorkspaceError>({
status: 404,
code: 'workspace_not_found',
});
});
it('atomically cascades conversations, completed tasks, and assets for only the deleted project', async () => {
const userDataDir = createTemporaryDirectory();
const service = createService(userDataDir);
const target = await service.createWorkspace({
clientWorkspaceId: 'client-delete',
title: '待删除项目',
});
const keeper = await service.createWorkspace({
clientWorkspaceId: 'client-keep',
title: '保留项目',
});
const conversationId = target.conversations[0].conversationId;
const discussed = await service.submitMessage({
workspaceId: target.workspaceId,
conversationId,
clientTurnId: 'turn-delete-one',
expectedTurnRevision: 0,
message: '生成一张可删除的海报',
});
await service.confirmGeneration({
workspaceId: target.workspaceId,
conversationId,
clientTurnId: 'turn-delete-two',
expectedTurnRevision: 1,
quoteId: discussed.messages.at(-1)!.generationQuote!.quoteId,
finalPrompt: discussed.messages.at(-1)!.generationQuote!.finalPrompt,
generationParameters: discussed.messages.at(-1)!.generationQuote!.generationParameters,
});
await service.listTasks(target.workspaceId);
await service.listTasks(target.workspaceId);
const [completedTask] = await service.listTasks(target.workspaceId);
const assetId = completedTask.resultAssets[0].assetId;
await expect(service.deleteWorkspace(target.workspaceId)).resolves.toEqual({
workspaceId: target.workspaceId,
deleted: true,
});
await expect(service.deleteWorkspace(target.workspaceId)).rejects.toMatchObject<
LocalImageWorkspaceError
>({
status: 404,
code: 'workspace_not_found',
});
await expect(service.bootstrap()).resolves.toMatchObject({
workspaces: [{ workspaceId: keeper.workspaceId, title: '保留项目' }],
});
const persisted = JSON.parse(readFileSync(join(
getLocalImageWorkspaceDirectory(userDataDir),
'design-workspace-v3.json',
), 'utf8')) as {
conversationsByWorkspaceId: Record<string, unknown>;
tasksByWorkspaceId: Record<string, unknown>;
assetsById: Record<string, { workspaceId: string }>;
};
expect(persisted.conversationsByWorkspaceId).not.toHaveProperty(target.workspaceId);
expect(persisted.tasksByWorkspaceId).not.toHaveProperty(target.workspaceId);
expect(persisted.assetsById).not.toHaveProperty(assetId);
expect(Object.values(persisted.assetsById)).not.toContainEqual(
expect.objectContaining({ workspaceId: target.workspaceId }),
);
expect(persisted.conversationsByWorkspaceId).toHaveProperty(keeper.workspaceId);
const reloaded = new LocalImageWorkspace({ userDataDir });
await expect(reloaded.bootstrap()).resolves.toMatchObject({
workspaces: [{ workspaceId: keeper.workspaceId }],
});
});
it('enforces turn CAS and removes only the isolated development data on reset', async () => {
const userDataDir = createTemporaryDirectory();
const service = createService(userDataDir);