feat: 完善图像工作区与创作工具体验
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user