diff --git a/.project-docs/30-worklog/tasks/20260806-video-source-picker-4d8a.md b/.project-docs/30-worklog/tasks/20260806-video-source-picker-4d8a.md new file mode 100644 index 0000000..4853e70 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260806-video-source-picker-4d8a.md @@ -0,0 +1,43 @@ +# Task: 修复作品图片选择与视频首帧附件提交 + +## Identity + +- Task ID: 20260806-video-source-picker-4d8a +- Mode: Feature +- Branch: codex/fix-video-source-selection +- Worktree: D:\Datas\OthersProjects\makelore-video-source-selection +- Base commit: 929b12485257e03faa45495a8eb429b63fdec038 +- Owner: codex +- Status: Completed + +## Scope + +- 修复从作品列表选择视频首帧后弹窗持续停留的问题。 +- 保持 `resultAssets[].assetId` 到 Gateway `attachment_asset_ids` 的现有传递链路。 +- 增加慢 Agent 响应场景的回归测试。 + +## Intent And Constraints + +- 用户确认选择后立即关闭弹窗,不等待整轮 Agent 响应。 +- Agent 请求失败时在主对话区展示错误,不把错误留在已关闭弹窗中。 +- 不触碰原客户端工作区中的其他未提交改动。 + +## Outcome + +- 作品图片选择后弹窗立即关闭,同时继续提交真实 Asset ID。 +- 请求失败会回到对话区错误提示,用户仍可重新选择。 +- 回归测试覆盖 Agent Promise 未完成时的弹窗状态。 + +## Verification + +- `vitest image-canvas-page + works-square-design-workspace`:38 passed。 +- `pnpm typecheck`:通过。 +- `pnpm build:vite`:通过(仅保留既有 chunk 体积提示)。 + +## Follow-ups + +- None recorded. + +## Promotion Candidates + +- None recorded. diff --git a/src/pages/ImageCanvas/index.tsx b/src/pages/ImageCanvas/index.tsx index 64d5cef..52c4fd6 100644 --- a/src/pages/ImageCanvas/index.tsx +++ b/src/pages/ImageCanvas/index.tsx @@ -547,15 +547,14 @@ export function ImageCanvas() { const requestedWorkspaceId = workspace.workspaceId; setFirstFrameBusyKey(asset.assetId); setFirstFrameError(null); + setActionError(null); setSubmitting(true); + setFirstFramePickerOpen(false); try { await sendMessage('使用作品图片作为视频首帧', [asset.assetId]); - if (useImageWorkspaceStore.getState().activeWorkspaceId === requestedWorkspaceId) { - setFirstFramePickerOpen(false); - } } catch (error) { if (useImageWorkspaceStore.getState().activeWorkspaceId === requestedWorkspaceId) { - setFirstFrameError(error instanceof Error ? error.message : String(error)); + setActionError(error instanceof Error ? error.message : String(error)); } } finally { setFirstFrameBusyKey(null); diff --git a/tests/unit/image-canvas-page.test.tsx b/tests/unit/image-canvas-page.test.tsx index 688f1de..2b75a91 100644 --- a/tests/unit/image-canvas-page.test.tsx +++ b/tests/unit/image-canvas-page.test.tsx @@ -570,6 +570,8 @@ describe('ImageCanvas Workspace-first design experience', () => { }); it('submits a successful work Asset id when it is selected as the video first frame', async () => { + const pendingSelection = deferred(); + sendImageWorkspaceMessageMock.mockReturnValueOnce(pendingSelection.promise); fetchImageWorkspaceProjectMock.mockResolvedValueOnce({ ...workspaceFixture(), turnRevision: 2, @@ -603,6 +605,12 @@ describe('ImageCanvas Workspace-first design experience', () => { expect.stringMatching(/^turn-/), ['asset-one'], )); + expect(screen.queryByRole('dialog', { name: '选择视频首帧' })).not.toBeInTheDocument(); + + await act(async () => { + pendingSelection.resolve(workspaceFixture()); + await pendingSelection.promise; + }); }); it('uploads a local first-frame image and submits its Asset id to the Agent', async () => {