@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.
Files changed (500) hide show
  1. package/package.json +4 -14
  2. package/src/__tests__/integration/login-email-acceptance.test.ts +16 -0
  3. package/src/__tests__/story-view.test.ts +33 -1
  4. package/src/components/agents/__tests__/agent-bindings-tab.test.tsx +221 -0
  5. package/src/components/agents/__tests__/agents-page.test.tsx +284 -0
  6. package/src/components/agents/agent-bindings-tab.tsx +59 -45
  7. package/src/components/agents/agent-form-dialog.tsx +3 -3
  8. package/src/components/agents/agent-selector.tsx +2 -3
  9. package/src/components/agents/agents-page.tsx +66 -31
  10. package/src/components/agents/slash-skill-menu.tsx +2 -3
  11. package/src/components/auth/__tests__/session-guard.test.tsx +74 -0
  12. package/src/components/auth/session-guard.tsx +8 -0
  13. package/src/components/billing/login-prompt-dialog.tsx +1 -1
  14. package/src/components/billing/subscription-provider.tsx +2 -16
  15. package/src/components/bug/__tests__/peek-modes.test.tsx +8 -0
  16. package/src/components/bug/__tests__/subgroup-header.test.tsx +202 -40
  17. package/src/components/bug/peek-panel.tsx +5 -3
  18. package/src/components/bug/views/bug-list/group.tsx +12 -2
  19. package/src/components/bug/views/bug-list/index.tsx +94 -67
  20. package/src/components/command/command-palette.tsx +10 -9
  21. package/src/components/design/canvas/document/frame-custom-presets.ts +9 -1
  22. package/src/components/design/canvas/panels/toolbar.tsx +21 -22
  23. package/src/components/design/canvas/react/__tests__/canvas-context-load-echo.test.tsx +199 -0
  24. package/src/components/design/canvas/react/__tests__/hooks.test.tsx +17 -3
  25. package/src/components/design/canvas/react/canvas-context.tsx +47 -26
  26. package/src/components/design/canvas/react/hooks.ts +39 -34
  27. package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +17 -0
  28. package/src/components/design/editor/layout/__tests__/context-menu.test.tsx +17 -1
  29. package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +17 -0
  30. package/src/components/design/editor/layout/__tests__/persistence-restore.test.tsx +17 -1
  31. package/src/components/design/editor/layout/__tests__/ssr-localstorage-smoke.test.tsx +17 -1
  32. package/src/components/design/editor/layout/design-editor-layout.tsx +13 -2
  33. package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +3 -19
  34. package/src/components/devtools/devtools-panel.tsx +56 -0
  35. package/src/components/forms/__tests__/create-app-form.test.tsx +131 -0
  36. package/src/components/forms/app-field-error.tsx +21 -1
  37. package/src/components/forms/app-field.tsx +8 -8
  38. package/src/components/forms/app-submit.tsx +12 -9
  39. package/src/components/forms/create-app-form.tsx +124 -0
  40. package/src/components/forms/index.ts +2 -3
  41. package/src/components/harness/__tests__/gallery-behavior.test.tsx +37 -1
  42. package/src/components/harness/__tests__/harness-workbench-first-send.test.tsx +4 -1
  43. package/src/components/harness/__tests__/message-actions.test.tsx +7 -6
  44. package/src/components/harness/__tests__/use-video-generation.test.tsx +165 -0
  45. package/src/components/harness/ai-chat-history/index.tsx +5 -3
  46. package/src/components/harness/gallery/gallery-card.tsx +57 -35
  47. package/src/components/harness/gallery/gallery-grid.tsx +32 -15
  48. package/src/components/harness/gallery/gallery-types.ts +19 -0
  49. package/src/components/harness/gallery/gallery-video-card.tsx +54 -0
  50. package/src/components/harness/gallery/types.ts +2 -0
  51. package/src/components/harness/gallery/use-video-generation.ts +95 -0
  52. package/src/components/harness/harness-workbench-page.tsx +2 -3
  53. package/src/components/harness/message-actions.tsx +5 -3
  54. package/src/components/hotkeys/shortcuts-panel.tsx +28 -13
  55. package/src/components/iteration/__tests__/burndown-chart.test.tsx +118 -0
  56. package/src/components/iteration/burndown-chart.tsx +54 -23
  57. package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +96 -0
  58. package/src/components/knowledge/editor/knowledge-doc-editor.tsx +56 -1
  59. package/src/components/knowledge/editor/mindmap-doc-editor.tsx +12 -0
  60. package/src/components/knowledge/editor/use-auto-save-channels.ts +3 -1
  61. package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +0 -4
  62. package/src/components/layout/sidebar-03/nav-knowledge.tsx +11 -11
  63. package/src/components/layout/sidebar-03/nav-main.tsx +12 -15
  64. package/src/components/layout/sidebar-03/nav-notifications.tsx +5 -8
  65. package/src/components/layout/sidebar-03/nav-workspace.tsx +11 -9
  66. package/src/components/notifications/__tests__/settings-notifications.test.tsx +158 -0
  67. package/src/components/notifications/settings-notifications.tsx +37 -13
  68. package/src/components/notifications/team-notifications.tsx +10 -14
  69. package/src/components/settings/personal-team/account/__tests__/account-client.test.tsx +170 -0
  70. package/src/components/settings/personal-team/account/account-client.tsx +44 -7
  71. package/src/components/settings/personal-team/team/team-client.tsx +17 -19
  72. package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +210 -41
  73. package/src/components/shared-kit/list-kit/frozen-header.ts +134 -21
  74. package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +20 -27
  75. package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +8 -0
  76. package/src/components/story/__tests__/story-list-table.test.tsx +214 -0
  77. package/src/components/story/columns.tsx +11 -2
  78. package/src/components/story/peek-panel.tsx +4 -2
  79. package/src/components/story/story-list-table.tsx +16 -1
  80. package/src/components/story/views/story-list/__tests__/frozen-headers.test.tsx +186 -0
  81. package/src/components/story/views/story-list/group.tsx +12 -1
  82. package/src/components/story/views/story-list/index.tsx +101 -68
  83. package/src/components/testcase/__tests__/case-row.test.tsx +134 -2
  84. package/src/components/testcase/__tests__/peek-panel.test.tsx +8 -0
  85. package/src/components/testcase/__tests__/step-editor.test.tsx +106 -0
  86. package/src/components/testcase/list/grouped-list.tsx +50 -22
  87. package/src/components/testcase/peek-panel.tsx +7 -4
  88. package/src/components/testcase/step-editor.tsx +196 -150
  89. package/src/components/tiptap/notion-like/editor-extensions.ts +0 -3
  90. package/src/components/tiptap/notion-like/notion-like-editor-header.tsx +1 -31
  91. package/src/components/tiptap/notion-like/notion-like-editor.tsx +32 -1
  92. package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +2 -28
  93. package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +2 -28
  94. package/src/components/workspace/dialogs/create-workspace-dialog.tsx +45 -43
  95. package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +38 -36
  96. package/src/design-engine/engine/engine-decor.ts +2 -0
  97. package/src/design-engine/engine/engine-path.ts +2 -0
  98. package/src/hooks/__tests__/use-unsaved-guard.test.tsx +180 -0
  99. package/src/hooks/use-notifications.ts +9 -16
  100. package/src/hooks/use-unsaved-guard.tsx +87 -0
  101. package/src/lib/__tests__/pacer.test.ts +33 -96
  102. package/src/lib/__tests__/query-defaults.test.ts +169 -0
  103. package/src/lib/__tests__/query-global-error.test.ts +256 -0
  104. package/src/lib/anyhub/anyhub-store.ts +2 -2
  105. package/src/lib/changelog/sdk-versions.ts +2 -10
  106. package/src/lib/collab/presence-store.ts +5 -5
  107. package/src/lib/collab/use-presence-list-refresh.ts +2 -2
  108. package/src/lib/collab/use-share-presence.ts +2 -2
  109. package/src/lib/collections/query-keys.ts +7 -2
  110. package/src/lib/commands/__tests__/command-palette-state.test.ts +12 -14
  111. package/src/lib/commands/__tests__/registry.test.ts +6 -3
  112. package/src/lib/commands/command-palette-state.ts +8 -31
  113. package/src/lib/hotkeys/__tests__/registry.test.ts +71 -18
  114. package/src/lib/hotkeys/registry.ts +188 -85
  115. package/src/lib/hotkeys/types.ts +1 -13
  116. package/src/lib/pacer.ts +1 -79
  117. package/src/lib/queries/README.md +67 -0
  118. package/src/lib/queries/__tests__/agents-queries.test.ts +101 -0
  119. package/src/lib/queries/__tests__/compliance-queries.test.ts +136 -0
  120. package/src/lib/queries/__tests__/notifications-queries.test.ts +175 -0
  121. package/src/lib/queries/__tests__/team-queries.test.ts +87 -0
  122. package/src/lib/queries/agents.ts +40 -0
  123. package/src/lib/queries/compliance.ts +69 -0
  124. package/src/lib/queries/notifications.ts +63 -0
  125. package/src/lib/queries/team.ts +39 -0
  126. package/src/lib/query-defaults.ts +8 -0
  127. package/src/lib/query-global-error.ts +128 -0
  128. package/src/lib/remote-auth-client.ts +16 -0
  129. package/src/paraglide/README.md +1 -1
  130. package/src/paraglide/messages/_index.d.ts +11 -0
  131. package/src/paraglide/messages/_index.js +5659 -88285
  132. package/src/paraglide/messages/common_error_generic.d.ts +16 -0
  133. package/src/paraglide/messages/common_error_generic.js +29 -0
  134. package/src/paraglide/messages/common_unsaved_body.d.ts +16 -0
  135. package/src/paraglide/messages/common_unsaved_body.js +29 -0
  136. package/src/paraglide/messages/common_unsaved_leave.d.ts +16 -0
  137. package/src/paraglide/messages/common_unsaved_leave.js +29 -0
  138. package/src/paraglide/messages/common_unsaved_stay.d.ts +16 -0
  139. package/src/paraglide/messages/common_unsaved_stay.js +29 -0
  140. package/src/paraglide/messages/common_unsaved_title.d.ts +16 -0
  141. package/src/paraglide/messages/common_unsaved_title.js +29 -0
  142. package/src/paraglide/messages/gallery_videofailed1.d.ts +17 -0
  143. package/src/paraglide/messages/gallery_videofailed1.js +30 -0
  144. package/src/paraglide/messages/gallery_videogenerating1.d.ts +17 -0
  145. package/src/paraglide/messages/gallery_videogenerating1.js +30 -0
  146. package/src/paraglide/messages/gallery_videomissingprompt2.d.ts +17 -0
  147. package/src/paraglide/messages/gallery_videomissingprompt2.js +30 -0
  148. package/src/paraglide/messages/gallery_videoprogress1.d.ts +19 -0
  149. package/src/paraglide/messages/gallery_videoprogress1.js +30 -0
  150. package/src/paraglide/messages/gallery_videostop1.d.ts +17 -0
  151. package/src/paraglide/messages/gallery_videostop1.js +30 -0
  152. package/src/paraglide/messages/story_table_resizecolumn1.d.ts +17 -0
  153. package/src/paraglide/messages/story_table_resizecolumn1.js +30 -0
  154. package/src/providers/index.tsx +5 -50
  155. package/src/routeTree.gen.ts +21 -0
  156. package/src/router.tsx +21 -8
  157. package/src/routes/__root.tsx +2 -1
  158. package/src/routes/__tests__/-edition-routing.test.tsx +6 -1
  159. package/src/routes/_login/forgot-password/route.lazy.tsx +7 -6
  160. package/src/routes/_login/login/route.lazy.tsx +10 -9
  161. package/src/routes/_login/register/route.lazy.tsx +19 -18
  162. package/src/routes/_login/reset-password/route.lazy.tsx +10 -9
  163. package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -12
  164. package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +16 -13
  165. package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +1 -1
  166. package/src/routes/_nav/notifications/route.tsx +6 -16
  167. package/src/routes/_nav/settings/-settings-tabs.tsx +9 -12
  168. package/src/routes/api/generate/__tests__/media.test.ts +378 -0
  169. package/src/routes/api/generate/media.ts +179 -0
  170. package/src/server/__tests__/agent-client-graph.test.ts +0 -1
  171. package/src/server/__tests__/agent-crud.test.ts +37 -4
  172. package/src/server/__tests__/bug-groups-two-level.test.ts +34 -1
  173. package/src/server/__tests__/chat-conversations.test.ts +37 -4
  174. package/src/server/__tests__/chat-list-messages.test.ts +40 -7
  175. package/src/server/__tests__/csv-export.test.ts +36 -3
  176. package/src/server/__tests__/desktop-bootstrap.test.ts +189 -0
  177. package/src/server/__tests__/remote-auth.test.ts +72 -0
  178. package/src/server/__tests__/skill-crud.test.ts +37 -4
  179. package/src/server/__tests__/sso-domain.test.ts +1 -1
  180. package/src/server/__tests__/sso-rate-limit-singleton.test.ts +123 -0
  181. package/src/server/__tests__/task-serverfn-crud.test.ts +35 -2
  182. package/src/server/__tests__/task-serverfn-queries.test.ts +35 -2
  183. package/src/server/__tests__/testcase-board.test.ts +38 -5
  184. package/src/server/__tests__/testcase-bulk.test.ts +38 -5
  185. package/src/server/__tests__/testcase-list-scope.test.ts +38 -5
  186. package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +126 -2
  187. package/src/server/ai-gateway/__tests__/chat-stream-builder-code-mode.test.ts +4 -0
  188. package/src/server/ai-gateway/__tests__/chat-stream-builder-memory.test.ts +4 -0
  189. package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +4 -0
  190. package/src/server/ai-gateway/__tests__/chat-stream-builder-skills.test.ts +4 -0
  191. package/src/server/ai-gateway/__tests__/pgvector-memory-adapter.test.ts +30 -0
  192. package/src/server/ai-gateway/__tests__/pgvector-memory-conformance.test.ts +92 -0
  193. package/src/server/ai-gateway/__tests__/source-mode.test.ts +49 -8
  194. package/src/server/ai-gateway/ai-output-guard.ts +50 -2
  195. package/src/server/ai-gateway/chat-stream-builder.ts +3 -0
  196. package/src/server/ai-gateway/media/index.ts +36 -29
  197. package/src/server/ai-gateway/media/trim-input.ts +52 -0
  198. package/src/server/ai-gateway/memory/pgvector-adapter.ts +27 -13
  199. package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +252 -41
  200. package/src/server/ai-gateway/sandbox/harness-sandbox.ts +6 -67
  201. package/src/server/ai-gateway/source-mode.ts +24 -8
  202. package/src/server/desktop-bootstrap.ts +74 -0
  203. package/src/server/lib/__tests__/rate-limit.test.ts +0 -1
  204. package/src/server/lib/rate-limit.ts +0 -2
  205. package/src/server/middlewares/__tests__/auth-context.test.ts +156 -0
  206. package/src/server/middlewares/auth-context.ts +62 -0
  207. package/src/server/remote-auth.ts +58 -31
  208. package/src/server/serverFns/__tests__/ai-hosted-models.test.ts +47 -15
  209. package/src/server/serverFns/__tests__/ai-source-mode.test.ts +45 -15
  210. package/src/server/serverFns/__tests__/billing.test.ts +37 -2
  211. package/src/server/serverFns/__tests__/collab-snapshot.test.ts +41 -14
  212. package/src/server/serverFns/__tests__/collab.test.ts +41 -14
  213. package/src/server/serverFns/__tests__/compliance.test.ts +33 -1
  214. package/src/server/serverFns/__tests__/email-verification.test.ts +35 -1
  215. package/src/server/serverFns/__tests__/knowledge-breadcrumb.test.ts +46 -6
  216. package/src/server/serverFns/__tests__/knowledge-delete.test.ts +42 -14
  217. package/src/server/serverFns/__tests__/knowledge-kb-projects.test.ts +46 -6
  218. package/src/server/serverFns/__tests__/knowledge-public-compliance.test.ts +36 -2
  219. package/src/server/serverFns/__tests__/knowledge-readable-kb.test.ts +42 -10
  220. package/src/server/serverFns/__tests__/knowledge-search.test.ts +45 -9
  221. package/src/server/serverFns/__tests__/legal-code-canonical.test.ts +36 -2
  222. package/src/server/serverFns/__tests__/link-preview.test.ts +38 -4
  223. package/src/server/serverFns/__tests__/notifications-list.test.ts +36 -3
  224. package/src/server/serverFns/__tests__/ownership-merge.test.ts +38 -4
  225. package/src/server/serverFns/__tests__/project-delete-transaction.test.ts +39 -2
  226. package/src/server/serverFns/__tests__/project-kb.test.ts +34 -1
  227. package/src/server/serverFns/__tests__/project-member-idor.test.ts +34 -1
  228. package/src/server/serverFns/__tests__/project-team-assign.test.ts +34 -1
  229. package/src/server/serverFns/__tests__/read-write-merge.test.ts +38 -4
  230. package/src/server/serverFns/__tests__/story-comments.test.ts +38 -4
  231. package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +38 -4
  232. package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +37 -3
  233. package/src/server/serverFns/__tests__/story-crud.test.ts +36 -3
  234. package/src/server/serverFns/__tests__/story-links.test.ts +38 -4
  235. package/src/server/serverFns/__tests__/story-reactions.test.ts +38 -4
  236. package/src/server/serverFns/__tests__/story-tree.test.ts +38 -4
  237. package/src/server/serverFns/__tests__/team-accept.test.ts +34 -1
  238. package/src/server/serverFns/__tests__/team-invitation.test.ts +34 -1
  239. package/src/server/serverFns/__tests__/team-join-request.test.ts +34 -1
  240. package/src/server/serverFns/__tests__/team-members.test.ts +34 -1
  241. package/src/server/serverFns/__tests__/team-ownership-merge.test.ts +34 -1
  242. package/src/server/serverFns/__tests__/update-bug-guard.test.ts +37 -3
  243. package/src/server/serverFns/__tests__/update-document-guard.test.ts +38 -4
  244. package/src/server/serverFns/__tests__/update-page-guard.test.ts +40 -4
  245. package/src/server/serverFns/__tests__/update-seats.test.ts +33 -1
  246. package/src/server/serverFns/__tests__/update-story-guard.test.ts +36 -3
  247. package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +37 -3
  248. package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +36 -2
  249. package/src/server/serverFns/__tests__/workspace-kb.test.ts +36 -2
  250. package/src/server/serverFns/__tests__/workspace.test.ts +34 -1
  251. package/src/server/serverFns/activity.ts +10 -14
  252. package/src/server/serverFns/agents/agent-crud.ts +16 -15
  253. package/src/server/serverFns/agents/bindings.ts +12 -13
  254. package/src/server/serverFns/agents/skill-crud.ts +16 -15
  255. package/src/server/serverFns/ai/code-mode-readiness.ts +3 -4
  256. package/src/server/serverFns/ai/search-capability.ts +3 -4
  257. package/src/server/serverFns/anyhub/app.ts +10 -14
  258. package/src/server/serverFns/anyhub/index.ts +7 -10
  259. package/src/server/serverFns/anyhub/install.ts +4 -4
  260. package/src/server/serverFns/anyhub/permissions.ts +10 -14
  261. package/src/server/serverFns/anyhub/proxy.ts +4 -6
  262. package/src/server/serverFns/auth/email-verification.ts +4 -5
  263. package/src/server/serverFns/auth/session.ts +9 -0
  264. package/src/server/serverFns/billing.ts +18 -28
  265. package/src/server/serverFns/blob-upload.ts +6 -9
  266. package/src/server/serverFns/bug/__tests__/bug-access-tokens.test.ts +34 -1
  267. package/src/server/serverFns/bug/__tests__/bug-activities.test.ts +34 -1
  268. package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +34 -1
  269. package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +34 -1
  270. package/src/server/serverFns/bug/__tests__/bug-fix.test.ts +34 -1
  271. package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +34 -1
  272. package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +34 -1
  273. package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +39 -5
  274. package/src/server/serverFns/bug/__tests__/bug-repro.test.ts +34 -1
  275. package/src/server/serverFns/bug/__tests__/bug-story.test.ts +34 -1
  276. package/src/server/serverFns/bug/__tests__/bug-webhooks.test.ts +34 -1
  277. package/src/server/serverFns/bug/bug-access-tokens.ts +16 -22
  278. package/src/server/serverFns/bug/bug-activities.ts +4 -6
  279. package/src/server/serverFns/bug/bug-collect-tokens.ts +13 -18
  280. package/src/server/serverFns/bug/bug-comments.ts +13 -18
  281. package/src/server/serverFns/bug/bug-crud/detail.ts +10 -14
  282. package/src/server/serverFns/bug/bug-crud.ts +28 -38
  283. package/src/server/serverFns/bug/bug-display-options.ts +8 -11
  284. package/src/server/serverFns/bug/bug-export.ts +5 -7
  285. package/src/server/serverFns/bug/bug-fix.ts +10 -14
  286. package/src/server/serverFns/bug/bug-groups.ts +10 -14
  287. package/src/server/serverFns/bug/bug-reactions.ts +10 -14
  288. package/src/server/serverFns/bug/bug-reorder.ts +4 -6
  289. package/src/server/serverFns/bug/bug-repro.ts +8 -11
  290. package/src/server/serverFns/bug/bug-story-bulk.ts +13 -18
  291. package/src/server/serverFns/bug/bug-story.ts +10 -14
  292. package/src/server/serverFns/bug/bug-views.ts +12 -18
  293. package/src/server/serverFns/bug/bug-webhooks.ts +13 -18
  294. package/src/server/serverFns/bug/test-bug-confirm.ts +7 -10
  295. package/src/server/serverFns/changelog/__tests__/fetch.test.ts +40 -1
  296. package/src/server/serverFns/changelog/fetch.ts +4 -6
  297. package/src/server/serverFns/chat/conversations.ts +17 -28
  298. package/src/server/serverFns/chat/list-messages.ts +4 -7
  299. package/src/server/serverFns/collab-presence.ts +11 -13
  300. package/src/server/serverFns/collab-snapshot.ts +6 -7
  301. package/src/server/serverFns/collab.ts +7 -7
  302. package/src/server/serverFns/compliance.ts +8 -8
  303. package/src/server/serverFns/design/__tests__/comments.test.ts +54 -10
  304. package/src/server/serverFns/design/__tests__/designs.test.ts +39 -16
  305. package/src/server/serverFns/design/__tests__/page-versions.test.ts +52 -14
  306. package/src/server/serverFns/design/__tests__/shares-password.test.ts +44 -14
  307. package/src/server/serverFns/design/__tests__/shares.test.ts +44 -14
  308. package/src/server/serverFns/design/__tests__/versions.test.ts +44 -14
  309. package/src/server/serverFns/design/ai/__tests__/agent-conversations.test.ts +50 -21
  310. package/src/server/serverFns/design/ai/__tests__/providers.test.ts +42 -8
  311. package/src/server/serverFns/design/ai/agent-conversations.ts +19 -26
  312. package/src/server/serverFns/design/ai/fill-annotation.ts +5 -7
  313. package/src/server/serverFns/design/ai/generate-prototype.ts +5 -7
  314. package/src/server/serverFns/design/ai/hosted-models.ts +4 -5
  315. package/src/server/serverFns/design/ai/providers.ts +30 -39
  316. package/src/server/serverFns/design/ai/source-mode.ts +10 -12
  317. package/src/server/serverFns/design/comments.ts +14 -13
  318. package/src/server/serverFns/design/designs.ts +13 -19
  319. package/src/server/serverFns/design/page-versions.ts +17 -18
  320. package/src/server/serverFns/design/pages.ts +17 -18
  321. package/src/server/serverFns/design/reviews.ts +8 -9
  322. package/src/server/serverFns/design/shares-password.ts +13 -19
  323. package/src/server/serverFns/design/shares.ts +22 -31
  324. package/src/server/serverFns/design/versions.ts +8 -9
  325. package/src/server/serverFns/harness/__tests__/attach-prototype.spec.ts +52 -2
  326. package/src/server/serverFns/harness/__tests__/attach-to-kb.spec.ts +52 -2
  327. package/src/server/serverFns/harness/__tests__/attach-to-story-ssrf.spec.ts +52 -2
  328. package/src/server/serverFns/harness/__tests__/attach-to-story.spec.ts +52 -2
  329. package/src/server/serverFns/harness/__tests__/cancel-run.test.ts +40 -15
  330. package/src/server/serverFns/harness/__tests__/extract-attachment.spec.ts +52 -2
  331. package/src/server/serverFns/harness/__tests__/gallery.spec.ts +76 -12
  332. package/src/server/serverFns/harness/__tests__/github-workspace.spec.ts +38 -6
  333. package/src/server/serverFns/harness/__tests__/media-prototype.spec.ts +50 -6
  334. package/src/server/serverFns/harness/__tests__/media.spec.ts +55 -11
  335. package/src/server/serverFns/harness/__tests__/sandbox-policy.test.ts +41 -16
  336. package/src/server/serverFns/harness/__tests__/workspace-files.test.ts +41 -16
  337. package/src/server/serverFns/harness/attach-prototype.ts +4 -4
  338. package/src/server/serverFns/harness/attach-to-kb.ts +4 -4
  339. package/src/server/serverFns/harness/attach-to-story.ts +4 -4
  340. package/src/server/serverFns/harness/cancel-run.ts +4 -6
  341. package/src/server/serverFns/harness/extract-attachment.ts +3 -4
  342. package/src/server/serverFns/harness/gallery.ts +19 -23
  343. package/src/server/serverFns/harness/github-workspace.ts +14 -21
  344. package/src/server/serverFns/harness/media.ts +18 -117
  345. package/src/server/serverFns/harness/sandbox-policy.ts +11 -10
  346. package/src/server/serverFns/harness/search-mention-candidates.ts +4 -6
  347. package/src/server/serverFns/harness/workspace-files.ts +19 -26
  348. package/src/server/serverFns/iteration/__tests__/iteration-auto-schedule.test.ts +35 -3
  349. package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +35 -3
  350. package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +35 -3
  351. package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +35 -3
  352. package/src/server/serverFns/iteration/iteration-auto-schedule.ts +7 -10
  353. package/src/server/serverFns/iteration/iteration-burndown.ts +4 -6
  354. package/src/server/serverFns/iteration/iteration-crud.ts +26 -35
  355. package/src/server/serverFns/iteration/iteration-stories.ts +10 -14
  356. package/src/server/serverFns/iteration/iteration-transfer.ts +4 -6
  357. package/src/server/serverFns/knowledge/public/bookmarks/categories.ts +11 -15
  358. package/src/server/serverFns/knowledge/public/bookmarks/create.ts +4 -6
  359. package/src/server/serverFns/knowledge/public/bookmarks/delete.ts +5 -7
  360. package/src/server/serverFns/knowledge/public/bookmarks/detail.ts +4 -6
  361. package/src/server/serverFns/knowledge/public/bookmarks/favorite.ts +4 -6
  362. package/src/server/serverFns/knowledge/public/bookmarks/list.ts +4 -6
  363. package/src/server/serverFns/knowledge/public/bookmarks/refresh-favicon.ts +4 -6
  364. package/src/server/serverFns/knowledge/public/bookmarks/share.ts +4 -6
  365. package/src/server/serverFns/knowledge/public/bookmarks/sync-github.ts +5 -7
  366. package/src/server/serverFns/knowledge/public/bookmarks/unshare.ts +4 -6
  367. package/src/server/serverFns/knowledge/public/bookmarks/update.ts +6 -8
  368. package/src/server/serverFns/knowledge/public/compliance/create.ts +4 -6
  369. package/src/server/serverFns/knowledge/public/compliance/delete.ts +5 -7
  370. package/src/server/serverFns/knowledge/public/compliance/detail.ts +4 -6
  371. package/src/server/serverFns/knowledge/public/compliance/list.ts +4 -6
  372. package/src/server/serverFns/knowledge/public/compliance/share.ts +4 -6
  373. package/src/server/serverFns/knowledge/public/compliance/unshare.ts +4 -6
  374. package/src/server/serverFns/knowledge/public/compliance/update.ts +5 -7
  375. package/src/server/serverFns/knowledge/public/dictionary/create.ts +4 -6
  376. package/src/server/serverFns/knowledge/public/dictionary/delete.ts +5 -7
  377. package/src/server/serverFns/knowledge/public/dictionary/detail.ts +4 -6
  378. package/src/server/serverFns/knowledge/public/dictionary/list.ts +4 -6
  379. package/src/server/serverFns/knowledge/public/dictionary/share.ts +4 -6
  380. package/src/server/serverFns/knowledge/public/dictionary/unshare.ts +4 -6
  381. package/src/server/serverFns/knowledge/public/dictionary/update.ts +5 -7
  382. package/src/server/serverFns/knowledge/public/entity-tags.ts +4 -6
  383. package/src/server/serverFns/knowledge/public/favorite.ts +7 -10
  384. package/src/server/serverFns/knowledge/public/legal/create.ts +4 -6
  385. package/src/server/serverFns/knowledge/public/legal/delete.ts +5 -7
  386. package/src/server/serverFns/knowledge/public/legal/detail.ts +4 -6
  387. package/src/server/serverFns/knowledge/public/legal/list.ts +4 -6
  388. package/src/server/serverFns/knowledge/public/legal/share.ts +4 -6
  389. package/src/server/serverFns/knowledge/public/legal/unshare.ts +4 -6
  390. package/src/server/serverFns/knowledge/public/legal/update.ts +5 -7
  391. package/src/server/serverFns/knowledge-index.ts +12 -16
  392. package/src/server/serverFns/knowledge.ts +50 -67
  393. package/src/server/serverFns/notifications.ts +25 -34
  394. package/src/server/serverFns/plugins/harness-plugins.ts +16 -23
  395. package/src/server/serverFns/project/module-overview.ts +7 -10
  396. package/src/server/serverFns/project.ts +48 -68
  397. package/src/server/serverFns/references/create.ts +4 -6
  398. package/src/server/serverFns/references/resolve.ts +4 -6
  399. package/src/server/serverFns/references/search.ts +4 -6
  400. package/src/server/serverFns/share/design/[designId]/events.ts +4 -6
  401. package/src/server/serverFns/sso.ts +1 -1
  402. package/src/server/serverFns/story/__tests__/bulk.test.ts +34 -1
  403. package/src/server/serverFns/story/__tests__/story-assoc.test.ts +34 -1
  404. package/src/server/serverFns/story/__tests__/story-attachments.test.ts +34 -1
  405. package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +42 -17
  406. package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +34 -1
  407. package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +34 -1
  408. package/src/server/serverFns/story/__tests__/story-groups.test.ts +34 -1
  409. package/src/server/serverFns/story/__tests__/story-read-path-authz.test.ts +40 -4
  410. package/src/server/serverFns/story/__tests__/story-search.test.ts +34 -1
  411. package/src/server/serverFns/story/__tests__/story-transfer.test.ts +34 -1
  412. package/src/server/serverFns/story/link-preview.ts +5 -7
  413. package/src/server/serverFns/story/story-assoc.ts +28 -38
  414. package/src/server/serverFns/story/story-attachments.ts +7 -10
  415. package/src/server/serverFns/story/story-children.ts +4 -6
  416. package/src/server/serverFns/story/story-comments.ts +13 -18
  417. package/src/server/serverFns/story/story-crud/activities.ts +4 -6
  418. package/src/server/serverFns/story/story-crud/archive.ts +7 -10
  419. package/src/server/serverFns/story/story-crud/bulk.ts +10 -14
  420. package/src/server/serverFns/story/story-crud/detail.ts +7 -10
  421. package/src/server/serverFns/story/story-crud/label-suggest.ts +4 -6
  422. package/src/server/serverFns/story/story-crud/labels.ts +6 -9
  423. package/src/server/serverFns/story/story-crud/user-brief.ts +4 -6
  424. package/src/server/serverFns/story/story-crud.ts +13 -18
  425. package/src/server/serverFns/story/story-dependencies.ts +16 -22
  426. package/src/server/serverFns/story/story-groups.ts +10 -14
  427. package/src/server/serverFns/story/story-links.ts +13 -18
  428. package/src/server/serverFns/story/story-reactions.ts +13 -18
  429. package/src/server/serverFns/story/story-search.ts +7 -10
  430. package/src/server/serverFns/story/story-transfer.ts +10 -14
  431. package/src/server/serverFns/story/story-tree.ts +7 -10
  432. package/src/server/serverFns/story-display-options.ts +7 -8
  433. package/src/server/serverFns/story-view.ts +12 -18
  434. package/src/server/serverFns/tasks/task-crud.ts +13 -17
  435. package/src/server/serverFns/tasks/task-queries.ts +12 -18
  436. package/src/server/serverFns/team.ts +46 -66
  437. package/src/server/serverFns/testcase/__tests__/automation.test.ts +38 -5
  438. package/src/server/serverFns/testcase/__tests__/evidence-upload.test.ts +37 -3
  439. package/src/server/serverFns/testcase/__tests__/generation.test.ts +38 -5
  440. package/src/server/serverFns/testcase/__tests__/plans.test.ts +36 -3
  441. package/src/server/serverFns/testcase/__tests__/regression.test.ts +36 -3
  442. package/src/server/serverFns/testcase/__tests__/run-cases.test.ts +36 -3
  443. package/src/server/serverFns/testcase/__tests__/runs.test.ts +36 -3
  444. package/src/server/serverFns/testcase/__tests__/stats.test.ts +36 -3
  445. package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +39 -5
  446. package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +36 -3
  447. package/src/server/serverFns/testcase/__tests__/testcase-folders.test.ts +36 -3
  448. package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +38 -5
  449. package/src/server/serverFns/testcase/automation.ts +13 -17
  450. package/src/server/serverFns/testcase/bulk.ts +10 -14
  451. package/src/server/serverFns/testcase/evidence-upload.ts +5 -7
  452. package/src/server/serverFns/testcase/generation.ts +10 -13
  453. package/src/server/serverFns/testcase/plans.ts +23 -31
  454. package/src/server/serverFns/testcase/regression.ts +7 -10
  455. package/src/server/serverFns/testcase/run-cases.ts +13 -18
  456. package/src/server/serverFns/testcase/runs.ts +13 -18
  457. package/src/server/serverFns/testcase/stats.ts +13 -18
  458. package/src/server/serverFns/testcase/testcase-activities.ts +4 -6
  459. package/src/server/serverFns/testcase/testcase-board.ts +4 -6
  460. package/src/server/serverFns/testcase/testcase-crud.ts +20 -27
  461. package/src/server/serverFns/testcase/testcase-display-options.ts +8 -11
  462. package/src/server/serverFns/testcase/testcase-export.ts +5 -7
  463. package/src/server/serverFns/testcase/testcase-filter-views.ts +12 -18
  464. package/src/server/serverFns/testcase/testcase-folders.ts +16 -22
  465. package/src/server/serverFns/testcase/testcase-groups.ts +4 -6
  466. package/src/server/serverFns/testcase/testcase-reorder.ts +4 -6
  467. package/src/server/serverFns/translation.ts +4 -6
  468. package/src/server/serverFns/user/avatar.ts +4 -6
  469. package/src/server/serverFns/user/settings.ts +4 -6
  470. package/src/server/serverFns/user/update-locale.ts +4 -6
  471. package/src/server/serverFns/workspace-kb.ts +26 -35
  472. package/src/server/serverFns/workspace.ts +43 -59
  473. package/src/server/serverFns/ws.ts +4 -6
  474. package/src/server/serverfn-public-allowlist.ts +117 -0
  475. package/src/server/story/types.ts +0 -1
  476. package/src/server/sync/__tests__/scopes.test.ts +64 -0
  477. package/src/server/sync/scopes.ts +7 -0
  478. package/src/server/testcase/__tests__/auto-bug.test.ts +44 -12
  479. package/src/__tests__/bench/baselines/format-timestamp.json +0 -44
  480. package/src/__typeprobe__.d.ts +0 -4
  481. package/src/components/forms/app-form-dialog.tsx +0 -126
  482. package/src/components/forms/hooks/use-form-submit.ts +0 -57
  483. package/src/components/forms/types.ts +0 -34
  484. package/src/components/harness/code/workspace-picker.tsx +0 -239
  485. package/src/components/tiptap/extensions/local-file/insert-local-file.ts +0 -35
  486. package/src/components/tiptap/extensions/local-file/local-file-node.ts +0 -71
  487. package/src/devframe-docks/ai-chat-iframe.tsx +0 -29
  488. package/src/devframe-docks/session-iframe.tsx +0 -90
  489. package/src/lib/__tests__/desktop-detect.test.ts +0 -19
  490. package/src/lib/collections/index.ts +0 -22
  491. package/src/lib/collections/knowledge/index.ts +0 -18
  492. package/src/lib/collections/knowledge/knowledge-bases-collection.ts +0 -34
  493. package/src/lib/collections/reference/bookmark-categories-collection.ts +0 -31
  494. package/src/lib/collections/reference/labels-collection.ts +0 -31
  495. package/src/lib/collections/reference/projects-collection.ts +0 -31
  496. package/src/lib/collections/reference/users-brief-collection.ts +0 -35
  497. package/src/lib/collections/reference/workspaces-collection.ts +0 -34
  498. package/src/lib/desktop-detect.ts +0 -29
  499. package/src/paraglide/messages/en.js +0 -28260
  500. 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: "ArrowUp", callback: (e) => focusItem(e) },
123
- { hotkey: "ArrowDown", callback: (e) => focusItem(e) },
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
- <Button variant="ghost" className="relative h-auto w-full overflow-hidden p-0" disabled={!url} onClick={onOpen} aria-label={m.gallery_previewItem({ name: artifacts?.[0]?.name || id })}>
46
- <span className="flex aspect-square w-full items-center justify-center">
47
- {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" />}
48
- </span>
49
- </Button>
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
- {onVariant && <Button size="sm" variant="outline" disabled={variantPending} onClick={() => onVariant(id)}><RefreshCw />{m.gallery_createVariant()}</Button>}
54
- {kind === "video" && onVariantWithRef && (
69
+ {videoGenerating && onStopVideo ? (
70
+ <Button size="sm" variant="outline" onClick={onStopVideo}><Square />{m.gallery_videoStop()}</Button>
71
+ ) : (
55
72
  <>
56
- <input ref={refInputRef} type="file" accept="image/*" className="hidden" aria-hidden="true" tabIndex={-1} onChange={async (e) => {
57
- const file = e.target.files?.[0]
58
- if (!file) return
59
- if (file.size > MAX_REF_FRAME_SIZE) {
60
- toast.error(m.gallery_refFrameTooLarge({ size: "10MB" }))
61
- e.target.value = ""
62
- return
63
- }
64
- const reader = new FileReader()
65
- reader.onload = () => {
66
- const dataURL = typeof reader.result === "string" ? reader.result : ""
67
- if (dataURL) onVariantWithRef(id, dataURL)
68
- }
69
- reader.onerror = () => {
70
- // 读取失败必须可见,否则 input value 已重置,用户无从得知未生效
71
- toast.error(m.gallery_refFrameReadFailed())
72
- }
73
- reader.readAsDataURL(file)
74
- e.target.value = ""
75
- }} />
76
- <Tooltip>
77
- <TooltipTrigger render={<Button size="sm" variant="outline" disabled={variantPending} onClick={() => refInputRef.current?.click()} aria-label={m.gallery_referenceFrame()} />}>
78
- <Film />
79
- </TooltipTrigger>
80
- <TooltipContent>{m.gallery_referenceFrameTooltip()}</TooltipContent>
81
- </Tooltip>
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
- mutationFn: ({ id, threadId, referenceImage }: { id: string; threadId: string; referenceImage?: string }) =>
60
- createVariantFn({ data: { sourceRunId: id, threadId, ...(referenceImage ? { referenceImage } : {}) } }),
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
- <GalleryCard
138
- key={item.id}
139
- item={item}
140
- onOpen={() => setSelectedId(item.id)}
141
- variantPending={variant.isPending}
142
- onVariant={["image", "video", "speech"].includes(item.kind) && item.threadId && item.status === "completed" ? (id) => variant.mutate({ id, threadId: item.threadId }) : undefined}
143
- onVariantWithRef={item.kind === "video" && item.threadId && item.status === "completed" ? (id, referenceImage) => variant.mutate({ id, threadId: item.threadId, referenceImage }) : undefined}
144
- onCopyPrompt={async (prompt) => {
145
- try { await navigator.clipboard.writeText(prompt); setCopyFailed(false) } catch { setCopyFailed(true) }
146
- }}
147
- onAttach={(target) => { void openAttachDialog(item, target) }}
148
- attachDisabled={attach.isPending}
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
+ }