完善 AI 设计资产预览与视频首帧选择

需求:生成图片需要支持大图查看和本地下载;制作视频时需要从当前项目作品选择首帧,或上传本地图片。

实现:新增首帧选择弹窗、JPEG/PNG/WebP 有界上传、附件 Asset ID 透传、Main 到服务端 multipart 转发,并保留上传失败重试与私有图片下载链路。

验证:相关 57 项单测、TypeScript 类型检查、目标 ESLint 和 Vite 生产构建全部通过。
This commit is contained in:
2026-08-06 11:31:47 +08:00
parent 2a3c9850d1
commit 15b17775cb
11 changed files with 1149 additions and 31 deletions

View File

@@ -223,6 +223,46 @@ describe('Works Square AI design adapter', () => {
);
});
it('uploads a local reference image as multipart data and maps the returned Asset', async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValueOnce(jsonResponse({
asset_id: 'asset-uploaded',
media_type: 'image',
mime_type: 'image/png',
width: 1200,
height: 800,
duration_milliseconds: null,
created_at: '2026-08-06T10:00:00Z',
}));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
});
await expect(adapter.uploadAsset({
workspaceId: 'workspace-one',
fileName: 'poster.webp',
mimeType: 'image/webp',
bytes: new Uint8Array([1, 2, 3]),
})).resolves.toMatchObject({
assetId: 'asset-uploaded',
workspaceId: 'workspace-one',
mimeType: 'image/png',
contentPath: '/api/works/image-workspace/workspaces/workspace-one/assets/asset-uploaded/content',
});
const [url, init] = fetchMock.mock.calls[0];
expect(url).toBe('https://square.example/api/design/workspaces/workspace-one/assets');
expect(init?.method).toBe('POST');
expect((init?.headers as Record<string, string>).Authorization).toBe('Bearer access-one');
expect((init?.headers as Record<string, string>)['Content-Type']).toBeUndefined();
const form = init?.body as FormData;
expect(form.get('file')).toBeInstanceOf(File);
expect((form.get('file') as File).name).toBe('poster.webp');
expect(await (form.get('file') as File).arrayBuffer()).toEqual(
Uint8Array.from([1, 2, 3]).buffer,
);
});
it('completes a design turn from the connected WebSocket without polling the run endpoint', async () => {
const { sockets, webSocketFactory } = scriptedSockets([{ open: true }]);
const fetchMock = vi.fn<typeof fetch>(async (input) => {