@allbluecn/web-app 0.22.4 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -14
- package/src/__tests__/integration/login-email-acceptance.test.ts +16 -0
- package/src/__tests__/story-view.test.ts +33 -1
- package/src/components/agents/__tests__/agent-bindings-tab.test.tsx +221 -0
- package/src/components/agents/__tests__/agents-page.test.tsx +284 -0
- package/src/components/agents/agent-bindings-tab.tsx +59 -45
- package/src/components/agents/agent-form-dialog.tsx +3 -3
- package/src/components/agents/agent-selector.tsx +2 -3
- package/src/components/agents/agents-page.tsx +66 -31
- package/src/components/agents/slash-skill-menu.tsx +2 -3
- package/src/components/auth/__tests__/session-guard.test.tsx +74 -0
- package/src/components/auth/session-guard.tsx +8 -0
- package/src/components/billing/login-prompt-dialog.tsx +1 -1
- package/src/components/billing/subscription-provider.tsx +2 -16
- package/src/components/bug/__tests__/peek-modes.test.tsx +8 -0
- package/src/components/bug/__tests__/subgroup-header.test.tsx +202 -40
- package/src/components/bug/peek-panel.tsx +5 -3
- package/src/components/bug/views/bug-list/group.tsx +12 -2
- package/src/components/bug/views/bug-list/index.tsx +94 -67
- package/src/components/command/command-palette.tsx +10 -9
- package/src/components/design/canvas/document/frame-custom-presets.ts +9 -1
- package/src/components/design/canvas/panels/toolbar.tsx +21 -22
- package/src/components/design/canvas/react/__tests__/canvas-context-load-echo.test.tsx +199 -0
- package/src/components/design/canvas/react/__tests__/hooks.test.tsx +17 -3
- package/src/components/design/canvas/react/canvas-context.tsx +47 -26
- package/src/components/design/canvas/react/hooks.ts +39 -34
- package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +17 -0
- package/src/components/design/editor/layout/__tests__/context-menu.test.tsx +17 -1
- package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +17 -0
- package/src/components/design/editor/layout/__tests__/persistence-restore.test.tsx +17 -1
- package/src/components/design/editor/layout/__tests__/ssr-localstorage-smoke.test.tsx +17 -1
- package/src/components/design/editor/layout/design-editor-layout.tsx +13 -2
- package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +3 -19
- package/src/components/devtools/devtools-panel.tsx +56 -0
- package/src/components/forms/__tests__/create-app-form.test.tsx +131 -0
- package/src/components/forms/app-field-error.tsx +21 -1
- package/src/components/forms/app-field.tsx +8 -8
- package/src/components/forms/app-submit.tsx +12 -9
- package/src/components/forms/create-app-form.tsx +124 -0
- package/src/components/forms/index.ts +2 -3
- package/src/components/harness/__tests__/gallery-behavior.test.tsx +37 -1
- package/src/components/harness/__tests__/harness-workbench-first-send.test.tsx +4 -1
- package/src/components/harness/__tests__/message-actions.test.tsx +7 -6
- package/src/components/harness/__tests__/use-video-generation.test.tsx +165 -0
- package/src/components/harness/ai-chat-history/index.tsx +5 -3
- package/src/components/harness/gallery/gallery-card.tsx +57 -35
- package/src/components/harness/gallery/gallery-grid.tsx +32 -15
- package/src/components/harness/gallery/gallery-types.ts +19 -0
- package/src/components/harness/gallery/gallery-video-card.tsx +54 -0
- package/src/components/harness/gallery/types.ts +2 -0
- package/src/components/harness/gallery/use-video-generation.ts +95 -0
- package/src/components/harness/harness-workbench-page.tsx +2 -3
- package/src/components/harness/message-actions.tsx +5 -3
- package/src/components/hotkeys/shortcuts-panel.tsx +28 -13
- package/src/components/iteration/__tests__/burndown-chart.test.tsx +118 -0
- package/src/components/iteration/burndown-chart.tsx +54 -23
- package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +96 -0
- package/src/components/knowledge/editor/knowledge-doc-editor.tsx +56 -1
- package/src/components/knowledge/editor/mindmap-doc-editor.tsx +12 -0
- package/src/components/knowledge/editor/use-auto-save-channels.ts +3 -1
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +0 -4
- package/src/components/layout/sidebar-03/nav-knowledge.tsx +11 -11
- package/src/components/layout/sidebar-03/nav-main.tsx +12 -15
- package/src/components/layout/sidebar-03/nav-notifications.tsx +5 -8
- package/src/components/layout/sidebar-03/nav-workspace.tsx +11 -9
- package/src/components/notifications/__tests__/settings-notifications.test.tsx +158 -0
- package/src/components/notifications/settings-notifications.tsx +37 -13
- package/src/components/notifications/team-notifications.tsx +10 -14
- package/src/components/settings/personal-team/account/__tests__/account-client.test.tsx +170 -0
- package/src/components/settings/personal-team/account/account-client.tsx +44 -7
- package/src/components/settings/personal-team/team/team-client.tsx +17 -19
- package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +210 -41
- package/src/components/shared-kit/list-kit/frozen-header.ts +134 -21
- package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +20 -27
- package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +8 -0
- package/src/components/story/__tests__/story-list-table.test.tsx +214 -0
- package/src/components/story/columns.tsx +11 -2
- package/src/components/story/peek-panel.tsx +4 -2
- package/src/components/story/story-list-table.tsx +16 -1
- package/src/components/story/views/story-list/__tests__/frozen-headers.test.tsx +186 -0
- package/src/components/story/views/story-list/group.tsx +12 -1
- package/src/components/story/views/story-list/index.tsx +101 -68
- package/src/components/testcase/__tests__/case-row.test.tsx +134 -2
- package/src/components/testcase/__tests__/peek-panel.test.tsx +8 -0
- package/src/components/testcase/__tests__/step-editor.test.tsx +106 -0
- package/src/components/testcase/list/grouped-list.tsx +50 -22
- package/src/components/testcase/peek-panel.tsx +7 -4
- package/src/components/testcase/step-editor.tsx +196 -150
- package/src/components/tiptap/notion-like/editor-extensions.ts +0 -3
- package/src/components/tiptap/notion-like/notion-like-editor-header.tsx +1 -31
- package/src/components/tiptap/notion-like/notion-like-editor.tsx +32 -1
- package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +2 -28
- package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +2 -28
- package/src/components/workspace/dialogs/create-workspace-dialog.tsx +45 -43
- package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +38 -36
- package/src/design-engine/engine/engine-decor.ts +2 -0
- package/src/design-engine/engine/engine-path.ts +2 -0
- package/src/hooks/__tests__/use-unsaved-guard.test.tsx +180 -0
- package/src/hooks/use-notifications.ts +9 -16
- package/src/hooks/use-unsaved-guard.tsx +87 -0
- package/src/lib/__tests__/pacer.test.ts +33 -96
- package/src/lib/__tests__/query-defaults.test.ts +169 -0
- package/src/lib/__tests__/query-global-error.test.ts +256 -0
- package/src/lib/anyhub/anyhub-store.ts +2 -2
- package/src/lib/changelog/sdk-versions.ts +2 -10
- package/src/lib/collab/presence-store.ts +5 -5
- package/src/lib/collab/use-presence-list-refresh.ts +2 -2
- package/src/lib/collab/use-share-presence.ts +2 -2
- package/src/lib/collections/query-keys.ts +7 -2
- package/src/lib/commands/__tests__/command-palette-state.test.ts +12 -14
- package/src/lib/commands/__tests__/registry.test.ts +6 -3
- package/src/lib/commands/command-palette-state.ts +8 -31
- package/src/lib/hotkeys/__tests__/registry.test.ts +71 -18
- package/src/lib/hotkeys/registry.ts +188 -85
- package/src/lib/hotkeys/types.ts +1 -13
- package/src/lib/pacer.ts +1 -79
- package/src/lib/queries/README.md +67 -0
- package/src/lib/queries/__tests__/agents-queries.test.ts +101 -0
- package/src/lib/queries/__tests__/compliance-queries.test.ts +136 -0
- package/src/lib/queries/__tests__/notifications-queries.test.ts +175 -0
- package/src/lib/queries/__tests__/team-queries.test.ts +87 -0
- package/src/lib/queries/agents.ts +40 -0
- package/src/lib/queries/compliance.ts +69 -0
- package/src/lib/queries/notifications.ts +63 -0
- package/src/lib/queries/team.ts +39 -0
- package/src/lib/query-defaults.ts +8 -0
- package/src/lib/query-global-error.ts +128 -0
- package/src/lib/remote-auth-client.ts +16 -0
- package/src/paraglide/README.md +1 -1
- package/src/paraglide/messages/_index.d.ts +11 -0
- package/src/paraglide/messages/_index.js +5659 -88285
- package/src/paraglide/messages/common_error_generic.d.ts +16 -0
- package/src/paraglide/messages/common_error_generic.js +29 -0
- package/src/paraglide/messages/common_unsaved_body.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_body.js +29 -0
- package/src/paraglide/messages/common_unsaved_leave.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_leave.js +29 -0
- package/src/paraglide/messages/common_unsaved_stay.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_stay.js +29 -0
- package/src/paraglide/messages/common_unsaved_title.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_title.js +29 -0
- package/src/paraglide/messages/gallery_videofailed1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videofailed1.js +30 -0
- package/src/paraglide/messages/gallery_videogenerating1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videogenerating1.js +30 -0
- package/src/paraglide/messages/gallery_videomissingprompt2.d.ts +17 -0
- package/src/paraglide/messages/gallery_videomissingprompt2.js +30 -0
- package/src/paraglide/messages/gallery_videoprogress1.d.ts +19 -0
- package/src/paraglide/messages/gallery_videoprogress1.js +30 -0
- package/src/paraglide/messages/gallery_videostop1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videostop1.js +30 -0
- package/src/paraglide/messages/story_table_resizecolumn1.d.ts +17 -0
- package/src/paraglide/messages/story_table_resizecolumn1.js +30 -0
- package/src/providers/index.tsx +5 -50
- package/src/routeTree.gen.ts +21 -0
- package/src/router.tsx +21 -8
- package/src/routes/__root.tsx +2 -1
- package/src/routes/__tests__/-edition-routing.test.tsx +6 -1
- package/src/routes/_login/forgot-password/route.lazy.tsx +7 -6
- package/src/routes/_login/login/route.lazy.tsx +10 -9
- package/src/routes/_login/register/route.lazy.tsx +19 -18
- package/src/routes/_login/reset-password/route.lazy.tsx +10 -9
- package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -12
- package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +16 -13
- package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +1 -1
- package/src/routes/_nav/notifications/route.tsx +6 -16
- package/src/routes/_nav/settings/-settings-tabs.tsx +9 -12
- package/src/routes/api/generate/__tests__/media.test.ts +378 -0
- package/src/routes/api/generate/media.ts +179 -0
- package/src/server/__tests__/agent-client-graph.test.ts +0 -1
- package/src/server/__tests__/agent-crud.test.ts +37 -4
- package/src/server/__tests__/bug-groups-two-level.test.ts +34 -1
- package/src/server/__tests__/chat-conversations.test.ts +37 -4
- package/src/server/__tests__/chat-list-messages.test.ts +40 -7
- package/src/server/__tests__/csv-export.test.ts +36 -3
- package/src/server/__tests__/desktop-bootstrap.test.ts +189 -0
- package/src/server/__tests__/remote-auth.test.ts +72 -0
- package/src/server/__tests__/skill-crud.test.ts +37 -4
- package/src/server/__tests__/sso-domain.test.ts +1 -1
- package/src/server/__tests__/sso-rate-limit-singleton.test.ts +123 -0
- package/src/server/__tests__/task-serverfn-crud.test.ts +35 -2
- package/src/server/__tests__/task-serverfn-queries.test.ts +35 -2
- package/src/server/__tests__/testcase-board.test.ts +38 -5
- package/src/server/__tests__/testcase-bulk.test.ts +38 -5
- package/src/server/__tests__/testcase-list-scope.test.ts +38 -5
- package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +126 -2
- package/src/server/ai-gateway/__tests__/chat-stream-builder-code-mode.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-memory.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-skills.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/pgvector-memory-adapter.test.ts +30 -0
- package/src/server/ai-gateway/__tests__/pgvector-memory-conformance.test.ts +92 -0
- package/src/server/ai-gateway/__tests__/source-mode.test.ts +49 -8
- package/src/server/ai-gateway/ai-output-guard.ts +50 -2
- package/src/server/ai-gateway/chat-stream-builder.ts +3 -0
- package/src/server/ai-gateway/media/index.ts +36 -29
- package/src/server/ai-gateway/media/trim-input.ts +52 -0
- package/src/server/ai-gateway/memory/pgvector-adapter.ts +27 -13
- package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +252 -41
- package/src/server/ai-gateway/sandbox/harness-sandbox.ts +6 -67
- package/src/server/ai-gateway/source-mode.ts +24 -8
- package/src/server/desktop-bootstrap.ts +74 -0
- package/src/server/lib/__tests__/rate-limit.test.ts +0 -1
- package/src/server/lib/rate-limit.ts +0 -2
- package/src/server/middlewares/__tests__/auth-context.test.ts +156 -0
- package/src/server/middlewares/auth-context.ts +62 -0
- package/src/server/remote-auth.ts +58 -31
- package/src/server/serverFns/__tests__/ai-hosted-models.test.ts +47 -15
- package/src/server/serverFns/__tests__/ai-source-mode.test.ts +45 -15
- package/src/server/serverFns/__tests__/billing.test.ts +37 -2
- package/src/server/serverFns/__tests__/collab-snapshot.test.ts +41 -14
- package/src/server/serverFns/__tests__/collab.test.ts +41 -14
- package/src/server/serverFns/__tests__/compliance.test.ts +33 -1
- package/src/server/serverFns/__tests__/email-verification.test.ts +35 -1
- package/src/server/serverFns/__tests__/knowledge-breadcrumb.test.ts +46 -6
- package/src/server/serverFns/__tests__/knowledge-delete.test.ts +42 -14
- package/src/server/serverFns/__tests__/knowledge-kb-projects.test.ts +46 -6
- package/src/server/serverFns/__tests__/knowledge-public-compliance.test.ts +36 -2
- package/src/server/serverFns/__tests__/knowledge-readable-kb.test.ts +42 -10
- package/src/server/serverFns/__tests__/knowledge-search.test.ts +45 -9
- package/src/server/serverFns/__tests__/legal-code-canonical.test.ts +36 -2
- package/src/server/serverFns/__tests__/link-preview.test.ts +38 -4
- package/src/server/serverFns/__tests__/notifications-list.test.ts +36 -3
- package/src/server/serverFns/__tests__/ownership-merge.test.ts +38 -4
- package/src/server/serverFns/__tests__/project-delete-transaction.test.ts +39 -2
- package/src/server/serverFns/__tests__/project-kb.test.ts +34 -1
- package/src/server/serverFns/__tests__/project-member-idor.test.ts +34 -1
- package/src/server/serverFns/__tests__/project-team-assign.test.ts +34 -1
- package/src/server/serverFns/__tests__/read-write-merge.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-comments.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +37 -3
- package/src/server/serverFns/__tests__/story-crud.test.ts +36 -3
- package/src/server/serverFns/__tests__/story-links.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-reactions.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-tree.test.ts +38 -4
- package/src/server/serverFns/__tests__/team-accept.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-invitation.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-join-request.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-members.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-ownership-merge.test.ts +34 -1
- package/src/server/serverFns/__tests__/update-bug-guard.test.ts +37 -3
- package/src/server/serverFns/__tests__/update-document-guard.test.ts +38 -4
- package/src/server/serverFns/__tests__/update-page-guard.test.ts +40 -4
- package/src/server/serverFns/__tests__/update-seats.test.ts +33 -1
- package/src/server/serverFns/__tests__/update-story-guard.test.ts +36 -3
- package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +37 -3
- package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +36 -2
- package/src/server/serverFns/__tests__/workspace-kb.test.ts +36 -2
- package/src/server/serverFns/__tests__/workspace.test.ts +34 -1
- package/src/server/serverFns/activity.ts +10 -14
- package/src/server/serverFns/agents/agent-crud.ts +16 -15
- package/src/server/serverFns/agents/bindings.ts +12 -13
- package/src/server/serverFns/agents/skill-crud.ts +16 -15
- package/src/server/serverFns/ai/code-mode-readiness.ts +3 -4
- package/src/server/serverFns/ai/search-capability.ts +3 -4
- package/src/server/serverFns/anyhub/app.ts +10 -14
- package/src/server/serverFns/anyhub/index.ts +7 -10
- package/src/server/serverFns/anyhub/install.ts +4 -4
- package/src/server/serverFns/anyhub/permissions.ts +10 -14
- package/src/server/serverFns/anyhub/proxy.ts +4 -6
- package/src/server/serverFns/auth/email-verification.ts +4 -5
- package/src/server/serverFns/auth/session.ts +9 -0
- package/src/server/serverFns/billing.ts +18 -28
- package/src/server/serverFns/blob-upload.ts +6 -9
- package/src/server/serverFns/bug/__tests__/bug-access-tokens.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-activities.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-fix.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +39 -5
- package/src/server/serverFns/bug/__tests__/bug-repro.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-story.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-webhooks.test.ts +34 -1
- package/src/server/serverFns/bug/bug-access-tokens.ts +16 -22
- package/src/server/serverFns/bug/bug-activities.ts +4 -6
- package/src/server/serverFns/bug/bug-collect-tokens.ts +13 -18
- package/src/server/serverFns/bug/bug-comments.ts +13 -18
- package/src/server/serverFns/bug/bug-crud/detail.ts +10 -14
- package/src/server/serverFns/bug/bug-crud.ts +28 -38
- package/src/server/serverFns/bug/bug-display-options.ts +8 -11
- package/src/server/serverFns/bug/bug-export.ts +5 -7
- package/src/server/serverFns/bug/bug-fix.ts +10 -14
- package/src/server/serverFns/bug/bug-groups.ts +10 -14
- package/src/server/serverFns/bug/bug-reactions.ts +10 -14
- package/src/server/serverFns/bug/bug-reorder.ts +4 -6
- package/src/server/serverFns/bug/bug-repro.ts +8 -11
- package/src/server/serverFns/bug/bug-story-bulk.ts +13 -18
- package/src/server/serverFns/bug/bug-story.ts +10 -14
- package/src/server/serverFns/bug/bug-views.ts +12 -18
- package/src/server/serverFns/bug/bug-webhooks.ts +13 -18
- package/src/server/serverFns/bug/test-bug-confirm.ts +7 -10
- package/src/server/serverFns/changelog/__tests__/fetch.test.ts +40 -1
- package/src/server/serverFns/changelog/fetch.ts +4 -6
- package/src/server/serverFns/chat/conversations.ts +17 -28
- package/src/server/serverFns/chat/list-messages.ts +4 -7
- package/src/server/serverFns/collab-presence.ts +11 -13
- package/src/server/serverFns/collab-snapshot.ts +6 -7
- package/src/server/serverFns/collab.ts +7 -7
- package/src/server/serverFns/compliance.ts +8 -8
- package/src/server/serverFns/design/__tests__/comments.test.ts +54 -10
- package/src/server/serverFns/design/__tests__/designs.test.ts +39 -16
- package/src/server/serverFns/design/__tests__/page-versions.test.ts +52 -14
- package/src/server/serverFns/design/__tests__/shares-password.test.ts +44 -14
- package/src/server/serverFns/design/__tests__/shares.test.ts +44 -14
- package/src/server/serverFns/design/__tests__/versions.test.ts +44 -14
- package/src/server/serverFns/design/ai/__tests__/agent-conversations.test.ts +50 -21
- package/src/server/serverFns/design/ai/__tests__/providers.test.ts +42 -8
- package/src/server/serverFns/design/ai/agent-conversations.ts +19 -26
- package/src/server/serverFns/design/ai/fill-annotation.ts +5 -7
- package/src/server/serverFns/design/ai/generate-prototype.ts +5 -7
- package/src/server/serverFns/design/ai/hosted-models.ts +4 -5
- package/src/server/serverFns/design/ai/providers.ts +30 -39
- package/src/server/serverFns/design/ai/source-mode.ts +10 -12
- package/src/server/serverFns/design/comments.ts +14 -13
- package/src/server/serverFns/design/designs.ts +13 -19
- package/src/server/serverFns/design/page-versions.ts +17 -18
- package/src/server/serverFns/design/pages.ts +17 -18
- package/src/server/serverFns/design/reviews.ts +8 -9
- package/src/server/serverFns/design/shares-password.ts +13 -19
- package/src/server/serverFns/design/shares.ts +22 -31
- package/src/server/serverFns/design/versions.ts +8 -9
- package/src/server/serverFns/harness/__tests__/attach-prototype.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-kb.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-story-ssrf.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-story.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/cancel-run.test.ts +40 -15
- package/src/server/serverFns/harness/__tests__/extract-attachment.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/gallery.spec.ts +76 -12
- package/src/server/serverFns/harness/__tests__/github-workspace.spec.ts +38 -6
- package/src/server/serverFns/harness/__tests__/media-prototype.spec.ts +50 -6
- package/src/server/serverFns/harness/__tests__/media.spec.ts +55 -11
- package/src/server/serverFns/harness/__tests__/sandbox-policy.test.ts +41 -16
- package/src/server/serverFns/harness/__tests__/workspace-files.test.ts +41 -16
- package/src/server/serverFns/harness/attach-prototype.ts +4 -4
- package/src/server/serverFns/harness/attach-to-kb.ts +4 -4
- package/src/server/serverFns/harness/attach-to-story.ts +4 -4
- package/src/server/serverFns/harness/cancel-run.ts +4 -6
- package/src/server/serverFns/harness/extract-attachment.ts +3 -4
- package/src/server/serverFns/harness/gallery.ts +19 -23
- package/src/server/serverFns/harness/github-workspace.ts +14 -21
- package/src/server/serverFns/harness/media.ts +18 -117
- package/src/server/serverFns/harness/sandbox-policy.ts +11 -10
- package/src/server/serverFns/harness/search-mention-candidates.ts +4 -6
- package/src/server/serverFns/harness/workspace-files.ts +19 -26
- package/src/server/serverFns/iteration/__tests__/iteration-auto-schedule.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +35 -3
- package/src/server/serverFns/iteration/iteration-auto-schedule.ts +7 -10
- package/src/server/serverFns/iteration/iteration-burndown.ts +4 -6
- package/src/server/serverFns/iteration/iteration-crud.ts +26 -35
- package/src/server/serverFns/iteration/iteration-stories.ts +10 -14
- package/src/server/serverFns/iteration/iteration-transfer.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/categories.ts +11 -15
- package/src/server/serverFns/knowledge/public/bookmarks/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/bookmarks/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/favorite.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/refresh-favicon.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/sync-github.ts +5 -7
- package/src/server/serverFns/knowledge/public/bookmarks/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/update.ts +6 -8
- package/src/server/serverFns/knowledge/public/compliance/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/compliance/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/update.ts +5 -7
- package/src/server/serverFns/knowledge/public/dictionary/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/dictionary/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/update.ts +5 -7
- package/src/server/serverFns/knowledge/public/entity-tags.ts +4 -6
- package/src/server/serverFns/knowledge/public/favorite.ts +7 -10
- package/src/server/serverFns/knowledge/public/legal/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/legal/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/update.ts +5 -7
- package/src/server/serverFns/knowledge-index.ts +12 -16
- package/src/server/serverFns/knowledge.ts +50 -67
- package/src/server/serverFns/notifications.ts +25 -34
- package/src/server/serverFns/plugins/harness-plugins.ts +16 -23
- package/src/server/serverFns/project/module-overview.ts +7 -10
- package/src/server/serverFns/project.ts +48 -68
- package/src/server/serverFns/references/create.ts +4 -6
- package/src/server/serverFns/references/resolve.ts +4 -6
- package/src/server/serverFns/references/search.ts +4 -6
- package/src/server/serverFns/share/design/[designId]/events.ts +4 -6
- package/src/server/serverFns/sso.ts +1 -1
- package/src/server/serverFns/story/__tests__/bulk.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-attachments.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +42 -17
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-read-path-authz.test.ts +40 -4
- package/src/server/serverFns/story/__tests__/story-search.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-transfer.test.ts +34 -1
- package/src/server/serverFns/story/link-preview.ts +5 -7
- package/src/server/serverFns/story/story-assoc.ts +28 -38
- package/src/server/serverFns/story/story-attachments.ts +7 -10
- package/src/server/serverFns/story/story-children.ts +4 -6
- package/src/server/serverFns/story/story-comments.ts +13 -18
- package/src/server/serverFns/story/story-crud/activities.ts +4 -6
- package/src/server/serverFns/story/story-crud/archive.ts +7 -10
- package/src/server/serverFns/story/story-crud/bulk.ts +10 -14
- package/src/server/serverFns/story/story-crud/detail.ts +7 -10
- package/src/server/serverFns/story/story-crud/label-suggest.ts +4 -6
- package/src/server/serverFns/story/story-crud/labels.ts +6 -9
- package/src/server/serverFns/story/story-crud/user-brief.ts +4 -6
- package/src/server/serverFns/story/story-crud.ts +13 -18
- package/src/server/serverFns/story/story-dependencies.ts +16 -22
- package/src/server/serverFns/story/story-groups.ts +10 -14
- package/src/server/serverFns/story/story-links.ts +13 -18
- package/src/server/serverFns/story/story-reactions.ts +13 -18
- package/src/server/serverFns/story/story-search.ts +7 -10
- package/src/server/serverFns/story/story-transfer.ts +10 -14
- package/src/server/serverFns/story/story-tree.ts +7 -10
- package/src/server/serverFns/story-display-options.ts +7 -8
- package/src/server/serverFns/story-view.ts +12 -18
- package/src/server/serverFns/tasks/task-crud.ts +13 -17
- package/src/server/serverFns/tasks/task-queries.ts +12 -18
- package/src/server/serverFns/team.ts +46 -66
- package/src/server/serverFns/testcase/__tests__/automation.test.ts +38 -5
- package/src/server/serverFns/testcase/__tests__/evidence-upload.test.ts +37 -3
- package/src/server/serverFns/testcase/__tests__/generation.test.ts +38 -5
- package/src/server/serverFns/testcase/__tests__/plans.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/regression.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/run-cases.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/runs.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/stats.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +39 -5
- package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-folders.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +38 -5
- package/src/server/serverFns/testcase/automation.ts +13 -17
- package/src/server/serverFns/testcase/bulk.ts +10 -14
- package/src/server/serverFns/testcase/evidence-upload.ts +5 -7
- package/src/server/serverFns/testcase/generation.ts +10 -13
- package/src/server/serverFns/testcase/plans.ts +23 -31
- package/src/server/serverFns/testcase/regression.ts +7 -10
- package/src/server/serverFns/testcase/run-cases.ts +13 -18
- package/src/server/serverFns/testcase/runs.ts +13 -18
- package/src/server/serverFns/testcase/stats.ts +13 -18
- package/src/server/serverFns/testcase/testcase-activities.ts +4 -6
- package/src/server/serverFns/testcase/testcase-board.ts +4 -6
- package/src/server/serverFns/testcase/testcase-crud.ts +20 -27
- package/src/server/serverFns/testcase/testcase-display-options.ts +8 -11
- package/src/server/serverFns/testcase/testcase-export.ts +5 -7
- package/src/server/serverFns/testcase/testcase-filter-views.ts +12 -18
- package/src/server/serverFns/testcase/testcase-folders.ts +16 -22
- package/src/server/serverFns/testcase/testcase-groups.ts +4 -6
- package/src/server/serverFns/testcase/testcase-reorder.ts +4 -6
- package/src/server/serverFns/translation.ts +4 -6
- package/src/server/serverFns/user/avatar.ts +4 -6
- package/src/server/serverFns/user/settings.ts +4 -6
- package/src/server/serverFns/user/update-locale.ts +4 -6
- package/src/server/serverFns/workspace-kb.ts +26 -35
- package/src/server/serverFns/workspace.ts +43 -59
- package/src/server/serverFns/ws.ts +4 -6
- package/src/server/serverfn-public-allowlist.ts +117 -0
- package/src/server/story/types.ts +0 -1
- package/src/server/sync/__tests__/scopes.test.ts +64 -0
- package/src/server/sync/scopes.ts +7 -0
- package/src/server/testcase/__tests__/auto-bug.test.ts +44 -12
- package/src/__tests__/bench/baselines/format-timestamp.json +0 -44
- package/src/__typeprobe__.d.ts +0 -4
- package/src/components/forms/app-form-dialog.tsx +0 -126
- package/src/components/forms/hooks/use-form-submit.ts +0 -57
- package/src/components/forms/types.ts +0 -34
- package/src/components/harness/code/workspace-picker.tsx +0 -239
- package/src/components/tiptap/extensions/local-file/insert-local-file.ts +0 -35
- package/src/components/tiptap/extensions/local-file/local-file-node.ts +0 -71
- package/src/devframe-docks/ai-chat-iframe.tsx +0 -29
- package/src/devframe-docks/session-iframe.tsx +0 -90
- package/src/lib/__tests__/desktop-detect.test.ts +0 -19
- package/src/lib/collections/index.ts +0 -22
- package/src/lib/collections/knowledge/index.ts +0 -18
- package/src/lib/collections/knowledge/knowledge-bases-collection.ts +0 -34
- package/src/lib/collections/reference/bookmark-categories-collection.ts +0 -31
- package/src/lib/collections/reference/labels-collection.ts +0 -31
- package/src/lib/collections/reference/projects-collection.ts +0 -31
- package/src/lib/collections/reference/users-brief-collection.ts +0 -35
- package/src/lib/collections/reference/workspaces-collection.ts +0 -34
- package/src/lib/desktop-detect.ts +0 -29
- package/src/paraglide/messages/en.js +0 -28260
- package/src/paraglide/messages/zh.js +0 -28258
|
@@ -20,6 +20,7 @@ import { render, screen, fireEvent } from '@testing-library/react'
|
|
|
20
20
|
import userEvent from '@testing-library/user-event'
|
|
21
21
|
import type { ComponentType } from 'react'
|
|
22
22
|
import { HotkeysProvider } from '@tanstack/react-hotkeys'
|
|
23
|
+
import { appHotkeys } from '@/lib/hotkeys/registry'
|
|
23
24
|
import type { UIMessage } from '@tanstack/ai-client'
|
|
24
25
|
|
|
25
26
|
const { mockChat } = vi.hoisted(() => ({
|
|
@@ -58,7 +59,7 @@ function renderAssistant(overrides: Partial<typeof mockChat> = {}) {
|
|
|
58
59
|
mockChat.isLoading = false
|
|
59
60
|
Object.assign(mockChat, overrides)
|
|
60
61
|
return render(
|
|
61
|
-
<HotkeysProvider>
|
|
62
|
+
<HotkeysProvider appHotkeys={appHotkeys}>
|
|
62
63
|
<HarnessMessage
|
|
63
64
|
message={makeMessage('a1', 'assistant', '你好,世界')}
|
|
64
65
|
Parts={StubParts}
|
|
@@ -82,7 +83,7 @@ describe('MessageActions', () => {
|
|
|
82
83
|
const onCopy = vi.fn()
|
|
83
84
|
|
|
84
85
|
render(
|
|
85
|
-
<HotkeysProvider>
|
|
86
|
+
<HotkeysProvider appHotkeys={appHotkeys}>
|
|
86
87
|
<MessageActions copied={false} onCopy={onCopy} />
|
|
87
88
|
</HotkeysProvider>,
|
|
88
89
|
)
|
|
@@ -97,7 +98,7 @@ describe('MessageActions', () => {
|
|
|
97
98
|
const onRegenerate = vi.fn()
|
|
98
99
|
|
|
99
100
|
render(
|
|
100
|
-
<HotkeysProvider>
|
|
101
|
+
<HotkeysProvider appHotkeys={appHotkeys}>
|
|
101
102
|
<MessageActions
|
|
102
103
|
copied={false}
|
|
103
104
|
onCopy={vi.fn()}
|
|
@@ -114,7 +115,7 @@ describe('MessageActions', () => {
|
|
|
114
115
|
|
|
115
116
|
it('未传 onRegenerate 时不渲染重新生成项', async () => {
|
|
116
117
|
render(
|
|
117
|
-
<HotkeysProvider>
|
|
118
|
+
<HotkeysProvider appHotkeys={appHotkeys}>
|
|
118
119
|
<MessageActions copied={false} onCopy={vi.fn()} />
|
|
119
120
|
</HotkeysProvider>,
|
|
120
121
|
)
|
|
@@ -126,7 +127,7 @@ describe('MessageActions', () => {
|
|
|
126
127
|
|
|
127
128
|
it('copied 为真时复制项文案切换为「已复制」', async () => {
|
|
128
129
|
render(
|
|
129
|
-
<HotkeysProvider>
|
|
130
|
+
<HotkeysProvider appHotkeys={appHotkeys}>
|
|
130
131
|
<MessageActions copied onCopy={vi.fn()} />
|
|
131
132
|
</HotkeysProvider>,
|
|
132
133
|
)
|
|
@@ -186,7 +187,7 @@ describe('HarnessMessage 操作按钮挂载', () => {
|
|
|
186
187
|
mockChat.messages = [makeMessage('u1', 'user', '提问')]
|
|
187
188
|
mockChat.isLoading = false
|
|
188
189
|
render(
|
|
189
|
-
<HotkeysProvider>
|
|
190
|
+
<HotkeysProvider appHotkeys={appHotkeys}>
|
|
190
191
|
<HarnessMessage
|
|
191
192
|
message={makeMessage('u1', 'user', '提问')}
|
|
192
193
|
Parts={StubParts}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
// useVideoGeneration 封装层单测:官方 useGenerateVideo 以可控 fake 替换,断言
|
|
19
|
+
// 端点接线(fetchServerSentEvents + /api/generate/media)、start 的 MediaPrompt
|
|
20
|
+
// 构造(参考帧 start_frame 分片 / 分片形态源 prompt 拼接 / 缺 prompt 拒绝)、
|
|
21
|
+
// 完成/失败双路径都刷新 generationRuns(失败即降级路径)、stop 同步刷新。
|
|
22
|
+
|
|
23
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
24
|
+
import { cleanup, render, screen, fireEvent } from "@testing-library/react"
|
|
25
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
|
26
|
+
import type { MediaPrompt } from "@tanstack/ai"
|
|
27
|
+
import { MEDIA_GENERATE_ENDPOINT, useVideoGeneration } from "../gallery/use-video-generation"
|
|
28
|
+
import type { GalleryItem } from "../gallery/gallery-types"
|
|
29
|
+
|
|
30
|
+
const { useGenerateVideoMock, fetchServerSentEventsMock } = vi.hoisted(() => ({
|
|
31
|
+
useGenerateVideoMock: vi.fn(),
|
|
32
|
+
fetchServerSentEventsMock: vi.fn(() => ({ connect: vi.fn() })),
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
vi.mock("@tanstack/ai-react", () => ({
|
|
36
|
+
useGenerateVideo: useGenerateVideoMock,
|
|
37
|
+
fetchServerSentEvents: fetchServerSentEventsMock,
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
function videoItem(overrides: Partial<GalleryItem> = {}): GalleryItem {
|
|
41
|
+
return {
|
|
42
|
+
id: "run-1", kind: "video", threadId: "thread-1", status: "completed",
|
|
43
|
+
artifacts: null, result: {}, usage: null, startedAt: new Date("2026-10-31T00:00:00Z"),
|
|
44
|
+
input: { prompt: "源提示词" },
|
|
45
|
+
...overrides,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const onComplete = vi.fn()
|
|
50
|
+
const onFail = vi.fn()
|
|
51
|
+
|
|
52
|
+
interface CapturedHookOptions {
|
|
53
|
+
threadId: string
|
|
54
|
+
onResult: () => void
|
|
55
|
+
onError: (e: Error) => void
|
|
56
|
+
}
|
|
57
|
+
let capturedOptions: CapturedHookOptions | null = null
|
|
58
|
+
let invalidateSpy: ReturnType<typeof vi.fn>
|
|
59
|
+
let generateMock: ReturnType<typeof vi.fn>
|
|
60
|
+
let stopMock: ReturnType<typeof vi.fn>
|
|
61
|
+
let client: QueryClient
|
|
62
|
+
|
|
63
|
+
/** start 引用探针:把 hook 返回值暴露给测试 */
|
|
64
|
+
let lastHook: ReturnType<typeof useVideoGeneration> | null = null
|
|
65
|
+
function Probe({ items }: { items: GalleryItem[] }) {
|
|
66
|
+
lastHook = useVideoGeneration({ threadId: "thread-1", onComplete, onFail })
|
|
67
|
+
return <button onClick={() => { for (const item of items) lastHook?.start(item) }}>start</button>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function setup(snapshot: { isLoading?: boolean; videoStatus?: { progress?: number; status?: string } | null } = {}) {
|
|
71
|
+
client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
|
72
|
+
invalidateSpy = vi.fn()
|
|
73
|
+
client.invalidateQueries = invalidateSpy as unknown as QueryClient["invalidateQueries"]
|
|
74
|
+
generateMock = vi.fn()
|
|
75
|
+
stopMock = vi.fn()
|
|
76
|
+
capturedOptions = null
|
|
77
|
+
lastHook = null
|
|
78
|
+
useGenerateVideoMock.mockImplementation((options: CapturedHookOptions) => {
|
|
79
|
+
capturedOptions = options
|
|
80
|
+
return {
|
|
81
|
+
generate: generateMock,
|
|
82
|
+
stop: stopMock,
|
|
83
|
+
isLoading: snapshot.isLoading ?? false,
|
|
84
|
+
videoStatus: snapshot.videoStatus ?? null,
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
const view = render(<QueryClientProvider client={client}><Probe items={[videoItem()]} /></QueryClientProvider>)
|
|
88
|
+
return view
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
onComplete.mockClear()
|
|
93
|
+
onFail.mockClear()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
afterEach(cleanup)
|
|
97
|
+
|
|
98
|
+
describe("useVideoGeneration", () => {
|
|
99
|
+
it("wires the official SSE transport to the /api/generate/media endpoint", () => {
|
|
100
|
+
setup()
|
|
101
|
+
expect(fetchServerSentEventsMock).toHaveBeenCalledWith(MEDIA_GENERATE_ENDPOINT)
|
|
102
|
+
expect(MEDIA_GENERATE_ENDPOINT).toBe("/api/generate/media")
|
|
103
|
+
expect(capturedOptions?.threadId).toBe("thread-1")
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it("starts generation with the source prompt from the run input", () => {
|
|
107
|
+
setup()
|
|
108
|
+
fireEvent.click(screen.getByRole("button", { name: "start" }))
|
|
109
|
+
expect(generateMock).toHaveBeenCalledWith({ prompt: "源提示词" })
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("wraps the reference image as a start_frame media part", () => {
|
|
113
|
+
setup()
|
|
114
|
+
const referenceImage = "data:image/png;base64,QQ=="
|
|
115
|
+
lastHook!.start(videoItem(), referenceImage)
|
|
116
|
+
const call = generateMock.mock.calls[0]?.[0] as { prompt: MediaPrompt }
|
|
117
|
+
expect(Array.isArray(call.prompt)).toBe(true)
|
|
118
|
+
expect(call.prompt).toEqual([
|
|
119
|
+
{ type: "text", content: "源提示词" },
|
|
120
|
+
{ type: "image", source: { type: "url", value: referenceImage }, metadata: { role: "start_frame" } },
|
|
121
|
+
])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("accepts MediaPrompt part inputs from streaming runs by joining text parts", () => {
|
|
125
|
+
setup()
|
|
126
|
+
const item = videoItem({ input: { prompt: [
|
|
127
|
+
{ type: "text", content: "第一段" },
|
|
128
|
+
{ type: "image", source: { type: "url", value: "data:image/png;base64,QQ==" }, metadata: { role: "start_frame" } },
|
|
129
|
+
{ type: "text", content: "第二段" },
|
|
130
|
+
] } })
|
|
131
|
+
lastHook!.start(item)
|
|
132
|
+
expect(generateMock).toHaveBeenCalledWith({ prompt: "第一段 第二段" })
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it("rejects start without a source prompt and without a thread", () => {
|
|
136
|
+
setup()
|
|
137
|
+
expect(lastHook!.start(videoItem({ input: {}, result: {} }))).toBe(false)
|
|
138
|
+
expect(lastHook!.start(videoItem({ threadId: "" }))).toBe(false)
|
|
139
|
+
expect(generateMock).not.toHaveBeenCalled()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it("invalidates generationRuns on result and on error (fallback refresh)", () => {
|
|
143
|
+
setup()
|
|
144
|
+
capturedOptions!.onResult()
|
|
145
|
+
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ["generationRuns"] })
|
|
146
|
+
expect(onComplete).toHaveBeenCalledTimes(1)
|
|
147
|
+
capturedOptions!.onError(new Error("stream truncated"))
|
|
148
|
+
expect(invalidateSpy).toHaveBeenCalledTimes(2)
|
|
149
|
+
expect(onFail).toHaveBeenCalledTimes(1)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("stop also refreshes generationRuns so the interrupted record shows up", () => {
|
|
153
|
+
setup()
|
|
154
|
+
lastHook!.stop()
|
|
155
|
+
expect(stopMock).toHaveBeenCalledTimes(1)
|
|
156
|
+
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ["generationRuns"] })
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("exposes isLoading and provider progress from the official client snapshot", () => {
|
|
160
|
+
setup({ isLoading: true, videoStatus: { progress: 64, status: "processing" } })
|
|
161
|
+
expect(lastHook!.isActive).toBe(true)
|
|
162
|
+
expect(lastHook!.progress).toBe(64)
|
|
163
|
+
expect(lastHook!.status).toBe("processing")
|
|
164
|
+
})
|
|
165
|
+
})
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
20
|
-
import { useHotkeys } from "@tanstack/react-hotkeys";
|
|
20
|
+
import { useAppHotkeys, useHotkeys } from "@tanstack/react-hotkeys";
|
|
21
21
|
import { cn } from "@/lib/utils";
|
|
22
22
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
|
23
23
|
import { Separator } from "@/components/ui/separator";
|
|
@@ -118,9 +118,11 @@ export default function AIChatHistory({
|
|
|
118
118
|
setEditValue("");
|
|
119
119
|
}, []);
|
|
120
120
|
|
|
121
|
+
// 声明即绑定:chat-prev / chat-next 条目单一来源(lib/hotkeys/registry.ts)
|
|
122
|
+
const hk = useAppHotkeys();
|
|
121
123
|
useHotkeys([
|
|
122
|
-
{ hotkey: "
|
|
123
|
-
{ hotkey: "
|
|
124
|
+
{ hotkey: hk["chat-prev"], callback: (e) => focusItem(e) },
|
|
125
|
+
{ hotkey: hk["chat-next"], callback: (e) => focusItem(e) },
|
|
124
126
|
]);
|
|
125
127
|
|
|
126
128
|
function focusItem(e: KeyboardEvent) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef } from "react"
|
|
2
|
-
import { RefreshCw, Copy, Share2, Film } from "lucide-react"
|
|
2
|
+
import { RefreshCw, Copy, Share2, Film, Loader2, Square } from "lucide-react"
|
|
3
3
|
import { formatTimestamp } from "@/lib/datetime"
|
|
4
4
|
import { KIND_ICONS, getKindLabel, getArtifactUrl, getPrompt } from "./gallery-types"
|
|
5
5
|
export type { GalleryItem } from "./gallery-types"
|
|
@@ -7,6 +7,7 @@ import type { GalleryItem } from "./gallery-types"
|
|
|
7
7
|
import { m } from "@/paraglide/messages.js"
|
|
8
8
|
import { Button } from "@/components/ui/button"
|
|
9
9
|
import { Badge } from "@/components/ui/badge"
|
|
10
|
+
import { Progress } from "@/components/ui/progress"
|
|
10
11
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
|
11
12
|
import { toast } from "@/components/ui/toast"
|
|
12
13
|
import {
|
|
@@ -25,13 +26,18 @@ interface GalleryCardProps {
|
|
|
25
26
|
onVariant?: (id: string) => void
|
|
26
27
|
onVariantWithRef?: (id: string, referenceImage: string) => void
|
|
27
28
|
variantPending?: boolean
|
|
29
|
+
/** 流式视频生成进行中(该卡片自身任务的进度,缩略图上方叠加进度层) */
|
|
30
|
+
videoGenerating?: boolean
|
|
31
|
+
/** 0-100;provider 未上报时为 null,进度条走不确定态 */
|
|
32
|
+
videoProgress?: number | null
|
|
33
|
+
onStopVideo?: () => void
|
|
28
34
|
onCopyPrompt?: (prompt: string) => void
|
|
29
35
|
onAttach?: (target: "story" | "kb" | "design") => void
|
|
30
36
|
attachDisabled?: boolean
|
|
31
37
|
onRetranscribe?: (audioBlobRef: string) => void
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
export function GalleryCard({ item, onOpen, onVariant, onVariantWithRef, variantPending, onCopyPrompt, onAttach, attachDisabled, onRetranscribe }: GalleryCardProps) {
|
|
40
|
+
export function GalleryCard({ item, onOpen, onVariant, onVariantWithRef, variantPending, videoGenerating, videoProgress, onStopVideo, onCopyPrompt, onAttach, attachDisabled, onRetranscribe }: GalleryCardProps) {
|
|
35
41
|
const { kind, status, artifacts, startedAt, id, linkedTarget } = item
|
|
36
42
|
const Icon = KIND_ICONS[kind] ?? KIND_ICONS.image
|
|
37
43
|
const label = getKindLabel(kind)
|
|
@@ -39,46 +45,62 @@ export function GalleryCard({ item, onOpen, onVariant, onVariantWithRef, variant
|
|
|
39
45
|
const prompt = getPrompt(item.result)
|
|
40
46
|
const audioBlobRef = artifacts?.find((entry) => entry.audioBlobRef)?.audioBlobRef
|
|
41
47
|
const refInputRef = useRef<HTMLInputElement>(null)
|
|
48
|
+
const busy = variantPending === true || videoGenerating === true
|
|
42
49
|
|
|
43
50
|
return (
|
|
44
51
|
<div className="flex min-w-0 flex-col gap-2 rounded-lg border bg-muted/50 p-2">
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
<div className="relative">
|
|
53
|
+
<Button variant="ghost" className="relative h-auto w-full overflow-hidden p-0" disabled={!url || busy} onClick={onOpen} aria-label={m.gallery_previewItem({ name: artifacts?.[0]?.name || id })}>
|
|
54
|
+
<span className="flex aspect-square w-full items-center justify-center">
|
|
55
|
+
{url && kind === "video" ? <video src={url} preload="metadata" muted className="size-full object-cover" /> : url && kind === "image" ? <img src={url} alt={label} className="size-full object-cover" /> : <Icon className="size-8 text-muted-foreground" />}
|
|
56
|
+
</span>
|
|
57
|
+
</Button>
|
|
58
|
+
{videoGenerating && (
|
|
59
|
+
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 rounded-lg bg-background/80 p-3" role="status" aria-live="polite">
|
|
60
|
+
<Loader2 className="size-5 animate-spin text-muted-foreground" aria-hidden="true" />
|
|
61
|
+
<Progress value={videoProgress ?? null} className="w-full" />
|
|
62
|
+
<span className="text-xs text-muted-foreground">{videoProgress != null ? m.gallery_videoProgress({ progress: Math.round(videoProgress) }) : m.gallery_videoGenerating()}</span>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
50
66
|
<div className="flex items-center justify-between gap-2 text-xs text-muted-foreground"><span>{label}</span>{status === "failed" && <span>{m.gallery_failed()}</span>}</div>
|
|
51
67
|
<p className="text-xs text-muted-foreground">{formatTimestamp(startedAt, "yyyy-MM-dd HH:mm")}</p>
|
|
52
68
|
<div className="flex flex-wrap gap-1">
|
|
53
|
-
{
|
|
54
|
-
|
|
69
|
+
{videoGenerating && onStopVideo ? (
|
|
70
|
+
<Button size="sm" variant="outline" onClick={onStopVideo}><Square />{m.gallery_videoStop()}</Button>
|
|
71
|
+
) : (
|
|
55
72
|
<>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
{onVariant && <Button size="sm" variant="outline" disabled={busy} onClick={() => onVariant(id)}><RefreshCw />{m.gallery_createVariant()}</Button>}
|
|
74
|
+
{kind === "video" && onVariantWithRef && (
|
|
75
|
+
<>
|
|
76
|
+
<input ref={refInputRef} type="file" accept="image/*" className="hidden" aria-hidden="true" tabIndex={-1} onChange={async (e) => {
|
|
77
|
+
const file = e.target.files?.[0]
|
|
78
|
+
if (!file) return
|
|
79
|
+
if (file.size > MAX_REF_FRAME_SIZE) {
|
|
80
|
+
toast.error(m.gallery_refFrameTooLarge({ size: "10MB" }))
|
|
81
|
+
e.target.value = ""
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const reader = new FileReader()
|
|
85
|
+
reader.onload = () => {
|
|
86
|
+
const dataURL = typeof reader.result === "string" ? reader.result : ""
|
|
87
|
+
if (dataURL) onVariantWithRef(id, dataURL)
|
|
88
|
+
}
|
|
89
|
+
reader.onerror = () => {
|
|
90
|
+
// 读取失败必须可见,否则 input value 已重置,用户无从得知未生效
|
|
91
|
+
toast.error(m.gallery_refFrameReadFailed())
|
|
92
|
+
}
|
|
93
|
+
reader.readAsDataURL(file)
|
|
94
|
+
e.target.value = ""
|
|
95
|
+
}} />
|
|
96
|
+
<Tooltip>
|
|
97
|
+
<TooltipTrigger render={<Button size="sm" variant="outline" disabled={busy} onClick={() => refInputRef.current?.click()} aria-label={m.gallery_referenceFrame()} />}>
|
|
98
|
+
<Film />
|
|
99
|
+
</TooltipTrigger>
|
|
100
|
+
<TooltipContent>{m.gallery_referenceFrameTooltip()}</TooltipContent>
|
|
101
|
+
</Tooltip>
|
|
102
|
+
</>
|
|
103
|
+
)}
|
|
82
104
|
</>
|
|
83
105
|
)}
|
|
84
106
|
{prompt && onCopyPrompt && <Button size="sm" variant="ghost" onClick={() => onCopyPrompt(prompt)}><Copy />{m.gallery_copyPrompt()}</Button>}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from "react"
|
|
2
2
|
import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
|
3
3
|
import { GalleryCard } from "./gallery-card"
|
|
4
|
+
import { GalleryVideoCard } from "./gallery-video-card"
|
|
4
5
|
import { getArtifactUrl, getPrompt, type GalleryItem } from "./gallery-types"
|
|
5
6
|
import { LightboxViewer, type LightboxData } from "./lightbox-viewer"
|
|
6
7
|
import { listGenerationRunsFn, createVariantFn } from "@/server/serverFns/harness/gallery"
|
|
@@ -56,8 +57,10 @@ export function GalleryGrid({ kind, threadId, className, compact = false }: Gall
|
|
|
56
57
|
})
|
|
57
58
|
const items: GalleryItem[] = Array.from(new Map((query.data?.pages.flatMap((page) => page.runs) ?? []).map((run) => [run.id, run])).values())
|
|
58
59
|
const variant = useMutation({
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
// 视频再创作已改走流式端点(每卡 GalleryVideoCard),此处仅剩 image/speech:
|
|
61
|
+
// 服务端 CreateVariantSchema 也已无 referenceImage 字段
|
|
62
|
+
mutationFn: ({ id, threadId }: { id: string; threadId: string }) =>
|
|
63
|
+
createVariantFn({ data: { sourceRunId: id, threadId } }),
|
|
61
64
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["generationRuns"] }),
|
|
62
65
|
})
|
|
63
66
|
const attach = useMutation({
|
|
@@ -74,6 +77,10 @@ export function GalleryGrid({ kind, threadId, className, compact = false }: Gall
|
|
|
74
77
|
onError: () => toast.error(m.gallery_attachFailed()),
|
|
75
78
|
})
|
|
76
79
|
|
|
80
|
+
const copyPrompt = async (prompt: string) => {
|
|
81
|
+
try { await navigator.clipboard.writeText(prompt); setCopyFailed(false) } catch { setCopyFailed(true) }
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
const openAttachDialog = async (item: GalleryItem, attachKind: AttachKind) => {
|
|
78
85
|
setAttachTarget({ id: item.id, kind: attachKind, item })
|
|
79
86
|
setAttachPicked(null)
|
|
@@ -134,19 +141,29 @@ export function GalleryGrid({ kind, threadId, className, compact = false }: Gall
|
|
|
134
141
|
) : (
|
|
135
142
|
<div className={cn("grid gap-4", columns)}>
|
|
136
143
|
{items.map((item) => (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
item.kind === "video" && item.threadId && item.status === "completed" ? (
|
|
145
|
+
<GalleryVideoCard
|
|
146
|
+
key={item.id}
|
|
147
|
+
item={item}
|
|
148
|
+
onOpen={() => setSelectedId(item.id)}
|
|
149
|
+
onCopyPrompt={copyPrompt}
|
|
150
|
+
onAttach={(target) => { void openAttachDialog(item, target) }}
|
|
151
|
+
attachDisabled={attach.isPending}
|
|
152
|
+
onMissingPrompt={() => toast.error(m.gallery_videoMissingPrompt())}
|
|
153
|
+
onFail={() => toast.error(m.gallery_videoFailed())}
|
|
154
|
+
/>
|
|
155
|
+
) : (
|
|
156
|
+
<GalleryCard
|
|
157
|
+
key={item.id}
|
|
158
|
+
item={item}
|
|
159
|
+
onOpen={() => setSelectedId(item.id)}
|
|
160
|
+
variantPending={variant.isPending}
|
|
161
|
+
onVariant={["image", "speech"].includes(item.kind) && item.threadId && item.status === "completed" ? (id) => variant.mutate({ id, threadId: item.threadId }) : undefined}
|
|
162
|
+
onCopyPrompt={copyPrompt}
|
|
163
|
+
onAttach={(target) => { void openAttachDialog(item, target) }}
|
|
164
|
+
attachDisabled={attach.isPending}
|
|
165
|
+
/>
|
|
166
|
+
)
|
|
150
167
|
))}
|
|
151
168
|
</div>
|
|
152
169
|
)}
|
|
@@ -33,6 +33,25 @@ export function getPrompt(result: unknown): string | undefined {
|
|
|
33
33
|
return result && typeof result === "object" && "prompt" in result && typeof result.prompt === "string" ? result.prompt : undefined
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** 流式再创作的源 prompt:优先入参快照 input.prompt(视频运行落库口径),
|
|
37
|
+
* 回落 result.prompt(图像运行)。与 createVariantFn 旧解析顺序一致。
|
|
38
|
+
* MediaPrompt 分片形态(流式生成的运行)取 text 分片拼接。 */
|
|
39
|
+
export function getSourcePrompt(item: GalleryItem): string | undefined {
|
|
40
|
+
const fromInput = item.input && typeof item.input === "object" && "prompt" in item.input ? normalizeMediaPromptText(item.input.prompt) : undefined
|
|
41
|
+
return fromInput ?? getPrompt(item.result)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeMediaPromptText(prompt: unknown): string | undefined {
|
|
45
|
+
if (typeof prompt === "string") return prompt
|
|
46
|
+
if (!Array.isArray(prompt)) return undefined
|
|
47
|
+
const text = prompt
|
|
48
|
+
.filter((part): part is { type: "text"; content: string } => Boolean(part) && typeof part === "object" && (part as { type?: unknown }).type === "text" && typeof (part as { content?: unknown }).content === "string")
|
|
49
|
+
.map((part) => part.content)
|
|
50
|
+
.join(" ")
|
|
51
|
+
.trim()
|
|
52
|
+
return text || undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
export type GalleryItem = GenerationRun
|
|
37
56
|
|
|
38
57
|
export function getThumbnailAspect(kind: string): string {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
// 视频卡片的流式再创作包装:每卡一个 useVideoGeneration 实例——hook 的 threadId
|
|
19
|
+
// 取自该记录自身的 threadId(也是 SSE 端点的归属校验键),随卡稳定不漂移。
|
|
20
|
+
// 进度层/停止按钮由 GalleryCard 的 video* props 渲染;缺源 prompt 与失败提示
|
|
21
|
+
// 由父级注入(i18n toast 在列表层统一出口)。
|
|
22
|
+
|
|
23
|
+
import { GalleryCard, type GalleryItem } from "./gallery-card"
|
|
24
|
+
import { useVideoGeneration } from "./use-video-generation"
|
|
25
|
+
|
|
26
|
+
interface GalleryVideoCardProps {
|
|
27
|
+
item: GalleryItem
|
|
28
|
+
onOpen?: () => void
|
|
29
|
+
onCopyPrompt?: (prompt: string) => void
|
|
30
|
+
onAttach?: (target: "story" | "kb" | "design") => void
|
|
31
|
+
attachDisabled?: boolean
|
|
32
|
+
/** start() 因缺源 prompt 被拒时回调(列表层 toast) */
|
|
33
|
+
onMissingPrompt: () => void
|
|
34
|
+
/** 生成失败/断流回调(hook 已先做一次降级刷新) */
|
|
35
|
+
onFail: () => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function GalleryVideoCard({ item, onOpen, onCopyPrompt, onAttach, attachDisabled, onMissingPrompt, onFail }: GalleryVideoCardProps) {
|
|
39
|
+
const videoGen = useVideoGeneration({ threadId: item.threadId, onFail })
|
|
40
|
+
return (
|
|
41
|
+
<GalleryCard
|
|
42
|
+
item={item}
|
|
43
|
+
onOpen={onOpen}
|
|
44
|
+
onCopyPrompt={onCopyPrompt}
|
|
45
|
+
onAttach={onAttach}
|
|
46
|
+
attachDisabled={attachDisabled}
|
|
47
|
+
videoGenerating={videoGen.isActive}
|
|
48
|
+
videoProgress={videoGen.progress ?? null}
|
|
49
|
+
onStopVideo={videoGen.stop}
|
|
50
|
+
onVariant={() => { if (!videoGen.start(item)) onMissingPrompt() }}
|
|
51
|
+
onVariantWithRef={(_, referenceImage) => { if (!videoGen.start(item, referenceImage)) onMissingPrompt() }}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -46,6 +46,8 @@ export interface GenerationRun {
|
|
|
46
46
|
model?: string
|
|
47
47
|
artifacts: PersistedArtifactRef[] | null
|
|
48
48
|
result: unknown
|
|
49
|
+
/** 提交时的入参快照(视频运行含 { prompt, referenceImage? },流式再创作据此重发源 prompt) */
|
|
50
|
+
input?: unknown
|
|
49
51
|
usage: unknown
|
|
50
52
|
startedAt: Date
|
|
51
53
|
threadId: string
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
// 画廊视频再创作的流式进度消费(任务 15):useGenerateVideo + fetchServerSentEvents
|
|
19
|
+
// 对接 /api/generate/media(server.handlers SSE 路由)。事件面由官方客户端解析:
|
|
20
|
+
// video:job:created → video:status(progress) → generation:result → RUN_FINISHED。
|
|
21
|
+
// 降级路径:失败/断流时回退一次手动刷新 generationRuns(服务端持久化中间件已把
|
|
22
|
+
// 终态落库),用户看到的不是黑屏而是落库的失败/已完成记录。
|
|
23
|
+
// 服务端官方 adapter 全链不动,只换客户端消费面(弃 startVideoFn/pollVideoFn 轮询)。
|
|
24
|
+
|
|
25
|
+
import { useCallback, useMemo } from "react"
|
|
26
|
+
import { useQueryClient } from "@tanstack/react-query"
|
|
27
|
+
import { fetchServerSentEvents, useGenerateVideo } from "@tanstack/ai-react"
|
|
28
|
+
import type { MediaPrompt } from "@tanstack/ai"
|
|
29
|
+
import type { GalleryItem } from "./gallery-types"
|
|
30
|
+
import { getSourcePrompt } from "./gallery-types"
|
|
31
|
+
|
|
32
|
+
/** 流式进度端点(server.handlers SSE 路由,与 /api/chat 同模式) */
|
|
33
|
+
export const MEDIA_GENERATE_ENDPOINT = "/api/generate/media"
|
|
34
|
+
|
|
35
|
+
interface UseVideoGenerationOptions {
|
|
36
|
+
threadId: string
|
|
37
|
+
/** 生成完成后回调(已内部刷新 generationRuns,供 UI 层附加行为) */
|
|
38
|
+
onComplete?: () => void
|
|
39
|
+
/** 失败/断流回调(已内部执行一次降级刷新,供 UI 层报错提示) */
|
|
40
|
+
onFail?: () => void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useVideoGeneration({ threadId, onComplete, onFail }: UseVideoGenerationOptions) {
|
|
44
|
+
const queryClient = useQueryClient()
|
|
45
|
+
const invalidateRuns = useCallback(() => {
|
|
46
|
+
void queryClient.invalidateQueries({ queryKey: ["generationRuns"] })
|
|
47
|
+
}, [queryClient])
|
|
48
|
+
|
|
49
|
+
const video = useGenerateVideo({
|
|
50
|
+
connection: fetchServerSentEvents(MEDIA_GENERATE_ENDPOINT),
|
|
51
|
+
threadId,
|
|
52
|
+
onResult: () => {
|
|
53
|
+
invalidateRuns()
|
|
54
|
+
onComplete?.()
|
|
55
|
+
},
|
|
56
|
+
onError: () => {
|
|
57
|
+
// 降级路径:hook 失败/断流 → 手动刷新一次,落库的终态记录(含失败)可见
|
|
58
|
+
invalidateRuns()
|
|
59
|
+
onFail?.()
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const start = useCallback((item: GalleryItem, referenceImage?: string) => {
|
|
64
|
+
const prompt = getSourcePrompt(item)
|
|
65
|
+
if (!prompt?.trim() || !item.threadId) return false
|
|
66
|
+
const mediaPrompt: MediaPrompt = referenceImage
|
|
67
|
+
? [
|
|
68
|
+
{ type: "text", content: prompt },
|
|
69
|
+
{ type: "image", source: { type: "url", value: referenceImage }, metadata: { role: "start_frame" } },
|
|
70
|
+
]
|
|
71
|
+
: prompt
|
|
72
|
+
void video.generate({ prompt: mediaPrompt })
|
|
73
|
+
return true
|
|
74
|
+
}, [video])
|
|
75
|
+
|
|
76
|
+
// stop() 只断本地流:服务端 run 由 request.signal abort 收敛为 interrupted 落库,
|
|
77
|
+
// 主动刷新一次让中断记录立即可见(与失败降级同一手动刷新口径)
|
|
78
|
+
const stop = useCallback(() => {
|
|
79
|
+
video.stop()
|
|
80
|
+
invalidateRuns()
|
|
81
|
+
}, [video, invalidateRuns])
|
|
82
|
+
|
|
83
|
+
const progress = video.videoStatus?.progress
|
|
84
|
+
const status = video.videoStatus?.status
|
|
85
|
+
return useMemo(() => ({
|
|
86
|
+
/** 是否有生成任务进行中(提交后到终态前) */
|
|
87
|
+
isActive: video.isLoading,
|
|
88
|
+
/** 0-100 进度;provider 未上报时为 undefined(UI 走不确定态) */
|
|
89
|
+
progress,
|
|
90
|
+
/** provider 侧任务状态:pending | processing | completed | failed */
|
|
91
|
+
status,
|
|
92
|
+
start,
|
|
93
|
+
stop,
|
|
94
|
+
}), [video.isLoading, progress, status, start, stop])
|
|
95
|
+
}
|