@vibe-forge/client 0.11.2 → 1.0.0

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 (232) hide show
  1. package/cli.cjs +6 -11
  2. package/dist/assets/{arc-De_WjPJ3.js → arc-CbOXL0l9.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-C4aR2zTE.js → blockDiagram-c4efeb88-CqxINvsS.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BZH3rq_m.js → c4Diagram-c83219d4-BKazU0hb.js} +1 -1
  5. package/dist/assets/channel-Dnopc5A6.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-BzJgBrIK.js → classDiagram-beda092f-fAFX5BpB.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-5ZtXcnT3.js → classDiagram-v2-2358418a-w1VkNGJj.js} +1 -1
  8. package/dist/assets/clone-sQthahUA.js +1 -0
  9. package/dist/assets/{createText-1719965b-DUVvEtmR.js → createText-1719965b-CEinakVP.js} +1 -1
  10. package/dist/assets/{cssMode-GoTNjuXX.js → cssMode-DPqRki4y.js} +1 -1
  11. package/dist/assets/{edges-96097737-Dd7m4Cvs.js → edges-96097737-Cb0F1_3K.js} +1 -1
  12. package/dist/assets/{erDiagram-0228fc6a-DxqFlG_f.js → erDiagram-0228fc6a-C-N2fx-J.js} +1 -1
  13. package/dist/assets/{flowDb-c6c81e3f-DU0C5kCI.js → flowDb-c6c81e3f-D1Xz_8Gf.js} +1 -1
  14. package/dist/assets/{flowDiagram-50d868cf-Di1uDa_X.js → flowDiagram-50d868cf-DyPSZyAj.js} +1 -1
  15. package/dist/assets/flowDiagram-v2-4f6560a1-OazrdWQO.js +1 -0
  16. package/dist/assets/{flowchart-elk-definition-6af322e1-CwG8aty5.js → flowchart-elk-definition-6af322e1-Dr1DDXwE.js} +1 -1
  17. package/dist/assets/{freemarker2-j39cqTlI.js → freemarker2-C3DvPFaK.js} +1 -1
  18. package/dist/assets/{ganttDiagram-a2739b55-baO_lzL-.js → ganttDiagram-a2739b55-DmvY1GRj.js} +1 -1
  19. package/dist/assets/{gitGraphDiagram-82fe8481-COoHjYMf.js → gitGraphDiagram-82fe8481-CoXfPYYi.js} +1 -1
  20. package/dist/assets/{graph-KxESr4M5.js → graph-BkDQy7Qt.js} +1 -1
  21. package/dist/assets/{handlebars-BgjdZO8G.js → handlebars-BcTFdqjl.js} +1 -1
  22. package/dist/assets/{html-Ba7tYObe.js → html-Dg-O6XFr.js} +1 -1
  23. package/dist/assets/{htmlMode-Bztvbig1.js → htmlMode-B_wqYWvn.js} +1 -1
  24. package/dist/assets/{index-5325376f-BMTAx2mL.js → index-5325376f-kxPTR3_e.js} +1 -1
  25. package/dist/assets/index-o93dlo92.css +32 -0
  26. package/dist/assets/{index-Pm_kLJvG.js → index-wkhI4dr6.js} +350 -329
  27. package/dist/assets/{infoDiagram-8eee0895-CC74qbHY.js → infoDiagram-8eee0895-BEvqkwPI.js} +1 -1
  28. package/dist/assets/{javascript-C1e1cllX.js → javascript-DhlOH8_z.js} +1 -1
  29. package/dist/assets/{journeyDiagram-c64418c1-C4MyOdE6.js → journeyDiagram-c64418c1-gKtLYmmp.js} +1 -1
  30. package/dist/assets/{jsonMode-BC98AlvF.js → jsonMode-DxTbF9OD.js} +1 -1
  31. package/dist/assets/{layout-CxAyTlr7.js → layout-CDaZEk6E.js} +1 -1
  32. package/dist/assets/{line-DhaUfI71.js → line-DNRQu8iq.js} +1 -1
  33. package/dist/assets/{linear-MYukzldK.js → linear-Cph9Z6_j.js} +1 -1
  34. package/dist/assets/{liquid-DahfJEYl.js → liquid-ByZ6JgRG.js} +1 -1
  35. package/dist/assets/{lspLanguageFeatures-BWDJcswW.js → lspLanguageFeatures-DzvhkgnM.js} +1 -1
  36. package/dist/assets/{mdx-BELlF_FD.js → mdx-D8RGHTl6.js} +1 -1
  37. package/dist/assets/{mermaid.core-BrQnSGSY.js → mermaid.core-BgcryF__.js} +4 -4
  38. package/dist/assets/{mindmap-definition-8da855dc-B0FoxTiy.js → mindmap-definition-8da855dc-WrxK0FcB.js} +1 -1
  39. package/dist/assets/{pieDiagram-a8764435-Ddr2cjSL.js → pieDiagram-a8764435-VsZBsiQy.js} +1 -1
  40. package/dist/assets/{python--C9if_AD.js → python-CXVtk_cg.js} +1 -1
  41. package/dist/assets/{quadrantDiagram-1e28029f-BlEs7Mrl.js → quadrantDiagram-1e28029f-BVlgwOvU.js} +1 -1
  42. package/dist/assets/{razor-B9U9JxKn.js → razor-0tind7h2.js} +1 -1
  43. package/dist/assets/{requirementDiagram-08caed73-kEFOAu2v.js → requirementDiagram-08caed73-CpPMPoYp.js} +1 -1
  44. package/dist/assets/{sankeyDiagram-a04cb91d-BBghez8I.js → sankeyDiagram-a04cb91d-Cm5nnRmc.js} +1 -1
  45. package/dist/assets/{sequenceDiagram-c5b8d532-CJqgzdUE.js → sequenceDiagram-c5b8d532-DpMlJvJB.js} +1 -1
  46. package/dist/assets/{stateDiagram-1ecb1508-BER4XEI6.js → stateDiagram-1ecb1508-DU1zc7vq.js} +1 -1
  47. package/dist/assets/{stateDiagram-v2-c2b004d7-EBV2vSks.js → stateDiagram-v2-c2b004d7-D-0RgmAp.js} +1 -1
  48. package/dist/assets/{styles-b4e223ce-k0eswZsE.js → styles-b4e223ce-BSO-yNWV.js} +1 -1
  49. package/dist/assets/{styles-ca3715f6-Ckr7GA-0.js → styles-ca3715f6-CHnsn2Ro.js} +1 -1
  50. package/dist/assets/{styles-d45a18b0-C1bpSwV3.js → styles-d45a18b0-B-rVGjEq.js} +1 -1
  51. package/dist/assets/{svgDrawCommon-b86b1483-CDtKpGvy.js → svgDrawCommon-b86b1483-CA3Pl89f.js} +1 -1
  52. package/dist/assets/{timeline-definition-faaaa080-BeGR-vua.js → timeline-definition-faaaa080-BcihLR6s.js} +1 -1
  53. package/dist/assets/{tsMode-D_gJXIy3.js → tsMode-D9GGa5Ur.js} +1 -1
  54. package/dist/assets/{typescript-BoKcNXkN.js → typescript-BT9CK_EL.js} +1 -1
  55. package/dist/assets/{xml-DZvURlJ-.js → xml-DNO75J-T.js} +1 -1
  56. package/dist/assets/{xychartDiagram-f5964ef8-DxfeLuYV.js → xychartDiagram-f5964ef8-DJTwe32X.js} +1 -1
  57. package/dist/assets/{yaml-CTC8PAGY.js → yaml-7CVzhiP2.js} +1 -1
  58. package/dist/index.html +2 -2
  59. package/package.json +13 -10
  60. package/src/api/git.ts +12 -0
  61. package/src/api/sessions.ts +131 -4
  62. package/src/api/types.ts +2 -1
  63. package/src/api.ts +13 -0
  64. package/src/components/ArchiveView.scss +143 -54
  65. package/src/components/ArchiveView.tsx +181 -167
  66. package/src/components/CodeBlock.scss +5 -0
  67. package/src/components/ConfigView.scss +142 -31
  68. package/src/components/ConfigView.tsx +161 -86
  69. package/src/components/MarkdownContent.tsx +7 -0
  70. package/src/components/NavRail.scss +248 -0
  71. package/src/components/NavRail.tsx +80 -107
  72. package/src/components/NavRailCompact.tsx +107 -0
  73. package/src/components/NavRailCompactMoreSheet.tsx +141 -0
  74. package/src/components/ShortcutTooltip.tsx +4 -2
  75. package/src/components/Sidebar.scss +51 -0
  76. package/src/components/Sidebar.tsx +43 -16
  77. package/src/components/automation-view/RuleFormPanel.scss +40 -13
  78. package/src/components/automation-view/RuleSidebar.scss +73 -47
  79. package/src/components/automation-view/RuleSidebar.tsx +9 -13
  80. package/src/components/automation-view/RunHistoryPanel.scss +141 -13
  81. package/src/components/automation-view/RunHistoryPanel.tsx +203 -161
  82. package/src/components/automation-view/TaskList.scss +44 -13
  83. package/src/components/automation-view/TriggerList.scss +46 -14
  84. package/src/components/automation-view/index.scss +82 -10
  85. package/src/components/automation-view/index.tsx +108 -55
  86. package/src/components/benchmark-view/BenchmarkCasePanel.scss +36 -16
  87. package/src/components/benchmark-view/BenchmarkSidebar.scss +44 -22
  88. package/src/components/benchmark-view/BenchmarkSidebar.tsx +0 -6
  89. package/src/components/benchmark-view/BenchmarkView.scss +63 -20
  90. package/src/components/benchmark-view/index.tsx +71 -34
  91. package/src/components/chat/AGENTS.md +14 -2
  92. package/src/components/chat/ChatComposerCard.scss +77 -0
  93. package/src/components/chat/ChatComposerCard.tsx +59 -0
  94. package/src/components/chat/ChatHeader.scss +187 -0
  95. package/src/components/chat/ChatHeader.tsx +209 -57
  96. package/src/components/chat/ChatHistoryView.tsx +279 -52
  97. package/src/components/chat/ChatTimelineView.scss +94 -1
  98. package/src/components/chat/ChatTimelineView.tsx +42 -0
  99. package/src/components/chat/CurrentTodoList.scss +210 -200
  100. package/src/components/chat/CurrentTodoList.tsx +116 -48
  101. package/src/components/chat/NewSessionGuide.scss +139 -1
  102. package/src/components/chat/NewSessionGuide.tsx +57 -100
  103. package/src/components/chat/NewSessionGuideCompactPanel.tsx +130 -0
  104. package/src/components/chat/NewSessionGuideGrid.tsx +141 -0
  105. package/src/components/chat/QueuedMessagesCard.scss +195 -0
  106. package/src/components/chat/QueuedMessagesCard.tsx +170 -0
  107. package/src/components/chat/git-controls/BranchSwitcherDropdown.tsx +61 -56
  108. package/src/components/chat/git-controls/BranchSwitcherResults.tsx +167 -0
  109. package/src/components/chat/git-controls/BranchTreeEntries.tsx +99 -0
  110. package/src/components/chat/git-controls/ChatGitControls.scss +437 -5
  111. package/src/components/chat/git-controls/ChatGitControls.tsx +136 -109
  112. package/src/components/chat/git-controls/DraftGitControls.tsx +91 -0
  113. package/src/components/chat/git-controls/GitOperationsDropdown.tsx +10 -2
  114. package/src/components/chat/git-controls/GitWorktreeDropdown.tsx +301 -28
  115. package/src/components/chat/git-controls/git-branch-tree.ts +148 -0
  116. package/src/components/chat/git-controls/use-chat-draft-git-controls.ts +168 -0
  117. package/src/components/chat/git-controls/use-chat-git-controls.ts +76 -3
  118. package/src/components/chat/messages/MessageContextMenu.tsx +3 -1
  119. package/src/components/chat/messages/MessageItem.scss +78 -4
  120. package/src/components/chat/messages/MessageItem.tsx +47 -3
  121. package/src/components/chat/sender/@components/adapter-select/AdapterSelectControl.scss +23 -0
  122. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsControl.scss +17 -0
  123. package/src/components/chat/sender/@components/reference-actions/ReferencePermissionActionsPopover.tsx +4 -1
  124. package/src/components/chat/sender/@components/sender-attachments/SenderAttachments.scss +167 -30
  125. package/src/components/chat/sender/@components/sender-attachments/SenderAttachments.tsx +95 -23
  126. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +10 -0
  127. package/src/components/chat/sender/@components/sender-interaction-panel/SenderInteractionPanel.scss +161 -45
  128. package/src/components/chat/sender/@components/sender-interaction-panel/SenderInteractionPanel.tsx +310 -71
  129. package/src/components/chat/sender/@components/sender-monaco-editor/SenderMonacoEditor.tsx +18 -0
  130. package/src/components/chat/sender/@components/sender-monaco-editor/use-sender-monaco-editor.ts +86 -9
  131. package/src/components/chat/sender/@components/sender-monaco-editor/use-sender-monaco-theme.ts +52 -3
  132. package/src/components/chat/sender/@components/sender-submit-action/SenderSubmitAction.scss +110 -1
  133. package/src/components/chat/sender/@components/sender-submit-action/SenderSubmitAction.tsx +137 -17
  134. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectBase.scss +21 -0
  135. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectShared.scss +21 -0
  136. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.scss +63 -0
  137. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.tsx +12 -6
  138. package/src/components/chat/sender/@core/build-sender-controller-result.ts +6 -0
  139. package/src/components/chat/sender/@core/build-sender-toolbar.ts +25 -2
  140. package/src/components/chat/sender/@core/create-sender-toolbar-handlers.ts +9 -2
  141. package/src/components/chat/sender/@core/get-sender-runtime-state.ts +1 -1
  142. package/src/components/chat/sender/@core/interaction-request.ts +2 -2
  143. package/src/components/chat/sender/@core/sender-toolbar-bindings.ts +28 -4
  144. package/src/components/chat/sender/@hooks/use-model-select-browser.tsx +4 -2
  145. package/src/components/chat/sender/@hooks/use-sender-controller.ts +56 -11
  146. package/src/components/chat/sender/@hooks/use-sender-keydown.ts +64 -0
  147. package/src/components/chat/sender/@hooks/use-sender-shortcuts.ts +16 -1
  148. package/src/components/chat/sender/@hooks/use-sender-submit.ts +16 -8
  149. package/src/components/chat/sender/@types/sender-props.ts +20 -3
  150. package/src/components/chat/sender/@types/sender-toolbar-types.ts +12 -1
  151. package/src/components/chat/sender/Sender.scss +4 -1
  152. package/src/components/chat/sender/Sender.tsx +3 -12
  153. package/src/components/chat/session-timeline-panel/EventList.scss +88 -0
  154. package/src/components/chat/session-timeline-panel/EventList.tsx +99 -47
  155. package/src/components/chat/session-timeline-panel/gantt.ts +23 -7
  156. package/src/components/chat/session-timeline-panel/git-graph.ts +6 -1
  157. package/src/components/chat/session-timeline-panel/index.scss +14 -1
  158. package/src/components/chat/session-timeline-panel/index.tsx +86 -10
  159. package/src/components/chat/session-timeline-panel/types.ts +4 -0
  160. package/src/components/chat/status-bar/ChatStatusBar.scss +27 -0
  161. package/src/components/chat/status-bar/ChatStatusBar.tsx +39 -0
  162. package/src/components/chat/terminal/ChatTerminalView.tsx +6 -0
  163. package/src/components/chat/tools/core/ToolCallBox.scss +19 -0
  164. package/src/components/chat/tools/core/ToolGroup.scss +32 -0
  165. package/src/components/chat/tools/task/components/TaskToolCard.scss +59 -1
  166. package/src/components/config/ConfigEditors.scss +20 -6
  167. package/src/components/config/ConfigFieldRow.scss +57 -17
  168. package/src/components/config/ConfigSectionForm.scss +10 -4
  169. package/src/components/config/ConfigSectionPanel.tsx +18 -11
  170. package/src/components/config/configSchema.ts +1 -0
  171. package/src/components/config/record-editors/RecordEditors.scss +42 -9
  172. package/src/components/dock-panel/DockPanel.scss +6 -2
  173. package/src/components/dock-panel/DockPanel.tsx +12 -16
  174. package/src/components/knowledge-base/KnowledgeBaseView.scss +180 -6
  175. package/src/components/knowledge-base/KnowledgeBaseView.tsx +98 -26
  176. package/src/components/knowledge-base/components/ActionButton.scss +4 -0
  177. package/src/components/knowledge-base/components/EmptyState.scss +5 -8
  178. package/src/components/knowledge-base/components/EntitiesTab.tsx +8 -2
  179. package/src/components/knowledge-base/components/EntityItem.scss +10 -3
  180. package/src/components/knowledge-base/components/FilterBar.scss +13 -2
  181. package/src/components/knowledge-base/components/FlowsTab.tsx +8 -2
  182. package/src/components/knowledge-base/components/KnowledgeBaseHeader.scss +2 -23
  183. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +0 -5
  184. package/src/components/knowledge-base/components/KnowledgeList.scss +15 -6
  185. package/src/components/knowledge-base/components/LoadingState.scss +4 -0
  186. package/src/components/knowledge-base/components/RuleItem.scss +86 -0
  187. package/src/components/knowledge-base/components/RuleItem.tsx +2 -0
  188. package/src/components/knowledge-base/components/RulesTab.tsx +8 -2
  189. package/src/components/knowledge-base/components/SectionHeader.scss +3 -18
  190. package/src/components/knowledge-base/components/SectionHeader.tsx +3 -7
  191. package/src/components/knowledge-base/components/SkillsTab.tsx +8 -3
  192. package/src/components/knowledge-base/components/SpecItem.scss +16 -7
  193. package/src/components/layout/@hooks/use-mobile-sidebar-modal.ts +190 -0
  194. package/src/components/layout/AppShell.scss +106 -6
  195. package/src/components/layout/AppShell.tsx +118 -10
  196. package/src/components/layout/PageShell.scss +41 -0
  197. package/src/components/layout/PageShell.tsx +32 -0
  198. package/src/components/layout/mobile-sidebar-constants.ts +1 -0
  199. package/src/components/nav-rail-compact-config.ts +114 -0
  200. package/src/components/nav-rail-items.tsx +181 -0
  201. package/src/components/sidebar/SessionContextMenu.tsx +3 -1
  202. package/src/components/sidebar/SessionItem.scss +62 -0
  203. package/src/components/sidebar/SessionItem.tsx +97 -52
  204. package/src/components/sidebar/SessionList.tsx +6 -0
  205. package/src/components/sidebar/SidebarHeader.scss +49 -0
  206. package/src/components/sidebar/SidebarHeader.tsx +27 -5
  207. package/src/components/sidebar/SidebarHeaderBatchActions.tsx +8 -4
  208. package/src/components/sidebar/SidebarHeaderSearchActions.tsx +6 -3
  209. package/src/components/sidebar/SidebarUtilityFooter.tsx +69 -0
  210. package/src/components/workspace/ContextFilePicker.tsx +12 -4
  211. package/src/hooks/chat/chat-session-workspace-draft.ts +25 -0
  212. package/src/hooks/chat/session-view-cache.ts +4 -1
  213. package/src/hooks/chat/use-chat-adapter.ts +5 -1
  214. package/src/hooks/chat/use-chat-model-adapter-selection.tsx +5 -1
  215. package/src/hooks/chat/use-chat-scroll.ts +24 -7
  216. package/src/hooks/chat/use-chat-session-actions.ts +118 -6
  217. package/src/hooks/chat/use-chat-session-messages.ts +20 -1
  218. package/src/hooks/chat/use-chat-session.ts +2 -0
  219. package/src/hooks/use-responsive-layout.ts +115 -0
  220. package/src/main.tsx +8 -0
  221. package/src/resources/adapters.ts +15 -0
  222. package/src/resources/locales/en.json +84 -1
  223. package/src/resources/locales/zh.json +84 -1
  224. package/src/routes/ChatRoute.scss +152 -9
  225. package/src/routes/ChatRoute.tsx +31 -34
  226. package/src/store/index.ts +2 -0
  227. package/dist/assets/channel-BvERb8WU.js +0 -1
  228. package/dist/assets/clone-B9_0v-6Y.js +0 -1
  229. package/dist/assets/flowDiagram-v2-4f6560a1-LpS8Kb00.js +0 -1
  230. package/dist/assets/index-C1oh0w9H.css +0 -32
  231. package/src/components/chat/ThinkingStatus.scss +0 -70
  232. package/src/components/chat/ThinkingStatus.tsx +0 -13
@@ -1,4 +1,4 @@
1
- import type { ChatMessage } from '@vibe-forge/core'
1
+ import type { ChatMessage, SessionMessageQueueState } from '@vibe-forge/core'
2
2
  import type { SessionInfo } from '@vibe-forge/types'
3
3
 
4
4
  import type { ChatErrorState, InteractionRequestState } from './interaction-state'
@@ -6,6 +6,7 @@ import type { ChatErrorState, InteractionRequestState } from './interaction-stat
6
6
  export interface ChatSessionViewSnapshot {
7
7
  messages: ChatMessage[]
8
8
  sessionInfo: SessionInfo | null
9
+ queuedMessages: SessionMessageQueueState
9
10
  errorState: ChatErrorState | null
10
11
  interactionRequest: InteractionRequestState | null
11
12
  isHydrated: boolean
@@ -18,6 +19,7 @@ export const createChatSessionViewSnapshot = (
18
19
  ): ChatSessionViewSnapshot => ({
19
20
  messages: value?.messages ?? [],
20
21
  sessionInfo: value?.sessionInfo ?? null,
22
+ queuedMessages: value?.queuedMessages ?? { steer: [], next: [] },
21
23
  errorState: value?.errorState ?? null,
22
24
  interactionRequest: value?.interactionRequest ?? null,
23
25
  isHydrated: value?.isHydrated ?? false
@@ -42,6 +44,7 @@ export const restoreChatSessionViewSnapshot = (snapshot?: ChatSessionViewSnapsho
42
44
  return {
43
45
  messages: restorable.messages,
44
46
  sessionInfo: restorable.sessionInfo,
47
+ queuedMessages: restorable.queuedMessages,
45
48
  errorState: restorable.errorState,
46
49
  interactionRequest: restorable.interactionRequest,
47
50
  isReady: restorable.isHydrated
@@ -57,7 +57,11 @@ export function useChatAdapter() {
57
57
  alt: '',
58
58
  'aria-hidden': true
59
59
  })
60
- : null,
60
+ : createElement('span', {
61
+ key: 'fallback-icon',
62
+ className: 'adapter-option__icon adapter-option__icon--fallback material-symbols-rounded',
63
+ 'aria-hidden': true
64
+ }, 'deployed_code'),
61
65
  createElement('span', { key: 'text', className: 'adapter-option__text' }, display.title)
62
66
  ])
63
67
  }
@@ -410,7 +410,11 @@ export function useChatModelAdapterSelection({
410
410
  alt: '',
411
411
  'aria-hidden': true
412
412
  })
413
- : null,
413
+ : createElement('span', {
414
+ key: 'fallback-icon',
415
+ className: 'adapter-option__icon adapter-option__icon--fallback material-symbols-rounded',
416
+ 'aria-hidden': true
417
+ }, 'deployed_code'),
414
418
  createElement('span', { key: 'text', className: 'adapter-option__text' }, display.title)
415
419
  ])
416
420
  }
@@ -6,6 +6,7 @@ export function useChatScroll({ contentVersion }: { contentVersion: number }) {
6
6
  const messagesEndRef = useRef<HTMLDivElement>(null)
7
7
  const messagesContainerRef = useRef<HTMLDivElement>(null)
8
8
  const messagesContentRef = useRef<HTMLDivElement>(null)
9
+ const scrollTimeoutRef = useRef<number | null>(null)
9
10
  const [showScrollBottom, setShowScrollBottom] = useState(false)
10
11
 
11
12
  const updateScrollState = useCallback(() => {
@@ -15,16 +16,30 @@ export function useChatScroll({ contentVersion }: { contentVersion: number }) {
15
16
  setShowScrollBottom(distanceToBottom > SCROLL_THRESHOLD)
16
17
  }, [])
17
18
 
19
+ const clearScrollTimeout = useCallback(() => {
20
+ if (scrollTimeoutRef.current == null) {
21
+ return
22
+ }
23
+
24
+ window.clearTimeout(scrollTimeoutRef.current)
25
+ scrollTimeoutRef.current = null
26
+ }, [])
27
+
18
28
  const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {
19
- setTimeout(() => {
20
- if (messagesContainerRef.current) {
21
- messagesContainerRef.current.scrollTo({
22
- top: messagesContainerRef.current.scrollHeight,
23
- behavior
24
- })
29
+ clearScrollTimeout()
30
+ scrollTimeoutRef.current = window.setTimeout(() => {
31
+ const container = messagesContainerRef.current
32
+ scrollTimeoutRef.current = null
33
+ if (!container) {
34
+ return
25
35
  }
36
+
37
+ container.scrollTo({
38
+ top: container.scrollHeight,
39
+ behavior
40
+ })
26
41
  }, 50)
27
- }, [])
42
+ }, [clearScrollTimeout])
28
43
 
29
44
  useEffect(() => {
30
45
  const container = messagesContainerRef.current
@@ -41,6 +56,8 @@ export function useChatScroll({ contentVersion }: { contentVersion: number }) {
41
56
  updateScrollState()
42
57
  }, [contentVersion, updateScrollState])
43
58
 
59
+ useEffect(() => clearScrollTimeout, [clearScrollTimeout])
60
+
44
61
  return {
45
62
  messagesEndRef,
46
63
  messagesContainerRef,
@@ -4,9 +4,19 @@ import { useTranslation } from 'react-i18next'
4
4
  import { useLocation, useNavigate } from 'react-router-dom'
5
5
  import { useSWRConfig } from 'swr'
6
6
 
7
- import { branchSessionFromMessage, createSession, getApiErrorMessage } from '#~/api.js'
7
+ import {
8
+ branchSessionFromMessage,
9
+ createQueuedMessage,
10
+ createSession,
11
+ deleteQueuedMessage,
12
+ getApiErrorMessage,
13
+ moveQueuedMessage,
14
+ reorderQueuedMessages,
15
+ updateQueuedMessage
16
+ } from '#~/api.js'
8
17
  import { connectionManager } from '#~/connectionManager.js'
9
- import type { ChatMessageContent, Session } from '@vibe-forge/core'
18
+ import type { ChatMessageContent, Session, SessionQueuedMessageMode } from '@vibe-forge/core'
19
+ import type { ChatSessionWorkspaceDraft } from './chat-session-workspace-draft'
10
20
  import type { ChatEffort } from './use-chat-effort'
11
21
  import type { PermissionMode } from './use-chat-permission-mode'
12
22
 
@@ -17,6 +27,7 @@ export function useChatSessionActions({
17
27
  effort,
18
28
  permissionMode,
19
29
  adapter,
30
+ workspaceDraft,
20
31
  onClearMessages
21
32
  }: {
22
33
  session?: Session
@@ -25,6 +36,7 @@ export function useChatSessionActions({
25
36
  effort: ChatEffort
26
37
  permissionMode: PermissionMode
27
38
  adapter?: string
39
+ workspaceDraft?: ChatSessionWorkspaceDraft
28
40
  onClearMessages: () => void
29
41
  }) {
30
42
  const { message } = App.useApp()
@@ -61,7 +73,7 @@ export function useChatSessionActions({
61
73
  setIsCreating(false)
62
74
  }, [isCreating, session?.id])
63
75
 
64
- const send = useCallback(async (text: string) => {
76
+ const send = useCallback(async (text: string, _mode?: SessionQueuedMessageMode) => {
65
77
  if (text.trim() === '' || isThinking) return false
66
78
  if (!hasAvailableModels) {
67
79
  void message.warning(t('chat.modelConfigRequired'))
@@ -74,7 +86,13 @@ export function useChatSessionActions({
74
86
  const { session: newSession } = await createSession(undefined, text.trim(), undefined, modelForQuery, {
75
87
  effort: effort === 'default' ? undefined : effort,
76
88
  permissionMode,
77
- adapter
89
+ adapter,
90
+ workspace: workspaceDraft == null
91
+ ? undefined
92
+ : {
93
+ createWorktree: workspaceDraft.createWorktree,
94
+ branch: workspaceDraft.branch
95
+ }
78
96
  })
79
97
 
80
98
  await insertSessionIntoCache(newSession)
@@ -103,12 +121,13 @@ export function useChatSessionActions({
103
121
  navigateWithCurrentSearch,
104
122
  effort,
105
123
  permissionMode,
124
+ workspaceDraft,
106
125
  modelForQuery,
107
126
  session?.id,
108
127
  t
109
128
  ])
110
129
 
111
- const sendContent = useCallback(async (content: ChatMessageContent[]) => {
130
+ const sendContent = useCallback(async (content: ChatMessageContent[], _mode?: SessionQueuedMessageMode) => {
112
131
  if (content.length === 0 || isThinking) return false
113
132
  if (!hasAvailableModels) {
114
133
  void message.warning(t('chat.modelConfigRequired'))
@@ -121,7 +140,13 @@ export function useChatSessionActions({
121
140
  const { session: newSession } = await createSession(undefined, undefined, content, modelForQuery, {
122
141
  effort: effort === 'default' ? undefined : effort,
123
142
  permissionMode,
124
- adapter
143
+ adapter,
144
+ workspace: workspaceDraft == null
145
+ ? undefined
146
+ : {
147
+ createWorktree: workspaceDraft.createWorktree,
148
+ branch: workspaceDraft.branch
149
+ }
125
150
  })
126
151
 
127
152
  await insertSessionIntoCache(newSession)
@@ -150,6 +175,7 @@ export function useChatSessionActions({
150
175
  message,
151
176
  effort,
152
177
  permissionMode,
178
+ workspaceDraft,
153
179
  modelForQuery,
154
180
  session?.id,
155
181
  t
@@ -200,11 +226,97 @@ export function useChatSessionActions({
200
226
  return runMessageAction(messageId, 'edit', { content })
201
227
  }, [runMessageAction])
202
228
 
229
+ const enqueueContent = useCallback(async (mode: SessionQueuedMessageMode, content: ChatMessageContent[]) => {
230
+ if (session?.id == null || session.id === '') {
231
+ return false
232
+ }
233
+ if (content.length === 0) {
234
+ return false
235
+ }
236
+
237
+ try {
238
+ await createQueuedMessage(session.id, mode, content)
239
+ return true
240
+ } catch (err) {
241
+ console.error(err)
242
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
243
+ return false
244
+ }
245
+ }, [message, session?.id, t])
246
+
247
+ const updateQueuedContent = useCallback(async (queueId: string, content: ChatMessageContent[]) => {
248
+ if (session?.id == null || session.id === '') {
249
+ return false
250
+ }
251
+ if (content.length === 0) {
252
+ return false
253
+ }
254
+
255
+ try {
256
+ await updateQueuedMessage(session.id, queueId, content)
257
+ return true
258
+ } catch (err) {
259
+ console.error(err)
260
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
261
+ return false
262
+ }
263
+ }, [message, session?.id, t])
264
+
265
+ const removeQueuedContent = useCallback(async (queueId: string) => {
266
+ if (session?.id == null || session.id === '') {
267
+ return false
268
+ }
269
+
270
+ try {
271
+ await deleteQueuedMessage(session.id, queueId)
272
+ return true
273
+ } catch (err) {
274
+ console.error(err)
275
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
276
+ return false
277
+ }
278
+ }, [message, session?.id, t])
279
+
280
+ const moveQueuedContent = useCallback(async (queueId: string, mode: SessionQueuedMessageMode) => {
281
+ if (session?.id == null || session.id === '') {
282
+ return false
283
+ }
284
+
285
+ try {
286
+ await moveQueuedMessage(session.id, queueId, mode)
287
+ return true
288
+ } catch (err) {
289
+ console.error(err)
290
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
291
+ return false
292
+ }
293
+ }, [message, session?.id, t])
294
+
295
+ const reorderQueuedContent = useCallback(async (mode: SessionQueuedMessageMode, ids: string[]) => {
296
+ if (session?.id == null || session.id === '') {
297
+ return false
298
+ }
299
+
300
+ try {
301
+ await reorderQueuedMessages(session.id, mode, ids)
302
+ return true
303
+ } catch (err) {
304
+ console.error(err)
305
+ void message.error(getApiErrorMessage(err, t('common.operationFailed')))
306
+ return false
307
+ }
308
+ }, [message, session?.id, t])
309
+
203
310
  return {
204
311
  isCreating,
205
312
  isThinking,
206
313
  send,
207
314
  sendContent,
315
+ enqueueContent,
316
+ updateQueuedContent,
317
+ removeQueuedContent,
318
+ moveQueuedContent,
319
+ reorderQueuedContent,
208
320
  editMessage,
209
321
  forkMessage,
210
322
  interrupt,
@@ -3,7 +3,7 @@ import type { SetStateAction } from 'react'
3
3
  import { useTranslation } from 'react-i18next'
4
4
  import { useSWRConfig } from 'swr'
5
5
 
6
- import type { AskUserQuestionParams, ChatMessage, Session, WSEvent } from '@vibe-forge/core'
6
+ import type { AskUserQuestionParams, ChatMessage, Session, SessionMessageQueueState, WSEvent } from '@vibe-forge/core'
7
7
  import type { SessionInfo } from '@vibe-forge/types'
8
8
 
9
9
  import { getSessionMessages } from '#~/api.js'
@@ -25,6 +25,8 @@ import type { ChatSessionViewSnapshot } from './session-view-cache'
25
25
  import type { ChatEffort } from './use-chat-effort'
26
26
  import type { PermissionMode } from './use-chat-permission-mode'
27
27
 
28
+ const EMPTY_QUEUED_MESSAGES: SessionMessageQueueState = { steer: [], next: [] }
29
+
28
30
  const applyMessageEvent = (currentMessages: ChatMessage[], data: WSEvent) => {
29
31
  if (data.type !== 'message') return currentMessages
30
32
  const exists = currentMessages.find((msg) => msg.id === data.message.id)
@@ -71,6 +73,7 @@ export function useChatSessionMessages({
71
73
  const { mutate } = useSWRConfig()
72
74
  const [messagesState, setMessagesState] = useState<ChatMessage[]>([])
73
75
  const [sessionInfo, setSessionInfo] = useState<SessionInfo | null>(null)
76
+ const [queuedMessages, setQueuedMessages] = useState<SessionMessageQueueState>({ steer: [], next: [] })
74
77
  const [isReady, setIsReady] = useState(false)
75
78
  const [errorState, setErrorState] = useState<ChatErrorState | null>(null)
76
79
  const [retryCount, setRetryCount] = useState(0)
@@ -94,6 +97,7 @@ export function useChatSessionMessages({
94
97
  patch: Partial<{
95
98
  messages: ChatMessage[]
96
99
  sessionInfo: SessionInfo | null
100
+ queuedMessages: SessionMessageQueueState
97
101
  errorState: ChatErrorState | null
98
102
  interactionRequest: InteractionRequestState | null
99
103
  isHydrated: boolean
@@ -174,9 +178,11 @@ export function useChatSessionMessages({
174
178
  code: latestFatalError.code
175
179
  }
176
180
  : null
181
+ const nextQueuedMessages = res.queuedMessages ?? EMPTY_QUEUED_MESSAGES
177
182
 
178
183
  interactionRequestRef.current = restoredInteraction
179
184
  setInteractionRequest(restoredInteraction)
185
+ setQueuedMessages(nextQueuedMessages)
180
186
  setErrorState(nextErrorState)
181
187
 
182
188
  for (const data of events) {
@@ -192,6 +198,7 @@ export function useChatSessionMessages({
192
198
  updateSessionViewCache(sessionId, {
193
199
  messages: currentMessages,
194
200
  sessionInfo: currentSessionInfo,
201
+ queuedMessages: nextQueuedMessages,
195
202
  errorState: nextErrorState,
196
203
  interactionRequest: restoredInteraction,
197
204
  isHydrated: true
@@ -238,6 +245,7 @@ export function useChatSessionMessages({
238
245
  if (session?.id == null || session.id === '') {
239
246
  setMessagesState([])
240
247
  setSessionInfo(null)
248
+ setQueuedMessages(EMPTY_QUEUED_MESSAGES)
241
249
  setIsReady(true)
242
250
  setErrorState(null)
243
251
  setInteractionRequest(null)
@@ -255,12 +263,14 @@ export function useChatSessionMessages({
255
263
 
256
264
  setMessagesState(restoredState.messages)
257
265
  setSessionInfo(restoredState.sessionInfo)
266
+ setQueuedMessages(restoredState.queuedMessages)
258
267
  setErrorState(restoredState.errorState)
259
268
  setInteractionRequest(restoredState.interactionRequest)
260
269
  interactionRequestRef.current = restoredState.interactionRequest
261
270
  setIsReady(restoredState.isReady)
262
271
  isInitialLoadRef.current = !restoredState.isReady
263
272
 
273
+ void refreshHistory()
264
274
  void refreshHistory()
265
275
 
266
276
  return () => {
@@ -414,6 +424,14 @@ export function useChatSessionMessages({
414
424
  return
415
425
  }
416
426
 
427
+ if (data.type === 'session_queue_updated') {
428
+ setQueuedMessages(data.queue)
429
+ updateSessionViewCache(session.id, {
430
+ queuedMessages: data.queue
431
+ })
432
+ return
433
+ }
434
+
417
435
  if (data.type === 'message') {
418
436
  setMessages((current) => applyMessageEvent(current, data))
419
437
  return
@@ -513,6 +531,7 @@ export function useChatSessionMessages({
513
531
  messages: messagesState,
514
532
  setMessages,
515
533
  sessionInfo,
534
+ queuedMessages,
516
535
  isReady,
517
536
  errorState,
518
537
  retryConnection,
@@ -44,6 +44,7 @@ export function useChatSession({
44
44
  messages,
45
45
  setMessages,
46
46
  sessionInfo,
47
+ queuedMessages,
47
48
  isReady,
48
49
  errorState,
49
50
  retryConnection,
@@ -110,6 +111,7 @@ export function useChatSession({
110
111
  return {
111
112
  messages,
112
113
  sessionInfo,
114
+ queuedMessages,
113
115
  interactionRequest,
114
116
  isReady,
115
117
  errorState,
@@ -0,0 +1,115 @@
1
+ import { useSyncExternalStore } from 'react'
2
+
3
+ const COMPACT_LAYOUT_QUERY = '(max-width: 960px)'
4
+ const TOUCH_INTERACTION_QUERY = '(hover: none), (pointer: coarse)'
5
+
6
+ export interface ResponsiveLayoutState {
7
+ isCompactLayout: boolean
8
+ isTouchInteraction: boolean
9
+ }
10
+
11
+ const DEFAULT_RESPONSIVE_LAYOUT: ResponsiveLayoutState = {
12
+ isCompactLayout: false,
13
+ isTouchInteraction: false
14
+ }
15
+
16
+ const listeners = new Set<() => void>()
17
+
18
+ let compactMediaQuery: MediaQueryList | null = null
19
+ let touchMediaQuery: MediaQueryList | null = null
20
+ let currentSnapshot = DEFAULT_RESPONSIVE_LAYOUT
21
+ let mediaListenersInstalled = false
22
+ let removeMediaQueryListeners: (() => void) | null = null
23
+
24
+ const getMediaQueryMatch = (query: string) => {
25
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
26
+ return false
27
+ }
28
+
29
+ return window.matchMedia(query).matches
30
+ }
31
+
32
+ const readResponsiveLayoutSnapshot = (): ResponsiveLayoutState => ({
33
+ isCompactLayout: compactMediaQuery?.matches ?? getMediaQueryMatch(COMPACT_LAYOUT_QUERY),
34
+ isTouchInteraction: touchMediaQuery?.matches ?? getMediaQueryMatch(TOUCH_INTERACTION_QUERY)
35
+ })
36
+
37
+ const isSameResponsiveLayoutSnapshot = (
38
+ left: ResponsiveLayoutState,
39
+ right: ResponsiveLayoutState
40
+ ) => left.isCompactLayout === right.isCompactLayout && left.isTouchInteraction === right.isTouchInteraction
41
+
42
+ const refreshResponsiveLayoutSnapshot = () => {
43
+ const nextSnapshot = readResponsiveLayoutSnapshot()
44
+ if (!isSameResponsiveLayoutSnapshot(currentSnapshot, nextSnapshot)) {
45
+ currentSnapshot = nextSnapshot
46
+ }
47
+
48
+ return currentSnapshot
49
+ }
50
+
51
+ const emitIfChanged = () => {
52
+ const nextSnapshot = readResponsiveLayoutSnapshot()
53
+ if (isSameResponsiveLayoutSnapshot(currentSnapshot, nextSnapshot)) {
54
+ return
55
+ }
56
+
57
+ currentSnapshot = nextSnapshot
58
+ listeners.forEach(listener => listener())
59
+ }
60
+
61
+ const addMediaQueryListener = (mediaQuery: MediaQueryList, listener: () => void) => {
62
+ if (typeof mediaQuery.addEventListener === 'function') {
63
+ mediaQuery.addEventListener('change', listener)
64
+ return () => mediaQuery.removeEventListener('change', listener)
65
+ }
66
+
67
+ mediaQuery.addListener(listener)
68
+ return () => mediaQuery.removeListener(listener)
69
+ }
70
+
71
+ const ensureResponsiveLayoutListeners = () => {
72
+ if (
73
+ mediaListenersInstalled ||
74
+ typeof window === 'undefined' ||
75
+ typeof window.matchMedia !== 'function'
76
+ ) {
77
+ return
78
+ }
79
+
80
+ compactMediaQuery = window.matchMedia(COMPACT_LAYOUT_QUERY)
81
+ touchMediaQuery = window.matchMedia(TOUCH_INTERACTION_QUERY)
82
+ currentSnapshot = refreshResponsiveLayoutSnapshot()
83
+ const removeCompactMediaQueryListener = addMediaQueryListener(compactMediaQuery, emitIfChanged)
84
+ const removeTouchMediaQueryListener = addMediaQueryListener(touchMediaQuery, emitIfChanged)
85
+ removeMediaQueryListeners = () => {
86
+ removeCompactMediaQueryListener()
87
+ removeTouchMediaQueryListener()
88
+ compactMediaQuery = null
89
+ touchMediaQuery = null
90
+ mediaListenersInstalled = false
91
+ }
92
+ mediaListenersInstalled = true
93
+ }
94
+
95
+ const subscribeResponsiveLayout = (listener: () => void) => {
96
+ ensureResponsiveLayoutListeners()
97
+ listeners.add(listener)
98
+ return () => {
99
+ listeners.delete(listener)
100
+ if (listeners.size === 0) {
101
+ removeMediaQueryListeners?.()
102
+ removeMediaQueryListeners = null
103
+ }
104
+ }
105
+ }
106
+
107
+ const getResponsiveLayoutSnapshot = () => refreshResponsiveLayoutSnapshot()
108
+
109
+ export function useResponsiveLayout() {
110
+ return useSyncExternalStore(
111
+ subscribeResponsiveLayout,
112
+ getResponsiveLayoutSnapshot,
113
+ () => DEFAULT_RESPONSIVE_LAYOUT
114
+ )
115
+ }
package/src/main.tsx CHANGED
@@ -16,6 +16,14 @@ import { getClientBase, resolveDevDocumentTitle } from '#~/runtime-config.js'
16
16
 
17
17
  import App from './App'
18
18
 
19
+ const gitRefLabel = import.meta.env.__VF_PROJECT_AI_GIT_REF_LABEL__ ?? ''
20
+
21
+ const appTitle = import.meta.env.DEV && gitRefLabel !== ''
22
+ ? `Vibe Forge Web [${gitRefLabel}]`
23
+ : 'Vibe Forge Web'
24
+
25
+ document.title = appTitle
26
+
19
27
  const root = createRoot(document.getElementById('root')!)
20
28
 
21
29
  const clientBase = getClientBase()
@@ -3,6 +3,9 @@ import {
3
3
  adapterIcon as claudeCodeIcon
4
4
  } from '@vibe-forge/adapter-claude-code/icon'
5
5
  import { adapterDisplayName as codexDisplayName, adapterIcon as codexIcon } from '@vibe-forge/adapter-codex/icon'
6
+ import { adapterDisplayName as copilotDisplayName, adapterIcon as copilotIcon } from '@vibe-forge/adapter-copilot/icon'
7
+ import { adapterDisplayName as geminiDisplayName, adapterIcon as geminiIcon } from '@vibe-forge/adapter-gemini/icon'
8
+ import { adapterDisplayName as kimiDisplayName, adapterIcon as kimiIcon } from '@vibe-forge/adapter-kimi/icon'
6
9
  import {
7
10
  adapterDisplayName as opencodeDisplayName,
8
11
  adapterIcon as opencodeIcon
@@ -17,6 +20,18 @@ export const adapterDisplayMap = {
17
20
  title: codexDisplayName,
18
21
  icon: codexIcon
19
22
  },
23
+ copilot: {
24
+ title: copilotDisplayName,
25
+ icon: copilotIcon
26
+ },
27
+ gemini: {
28
+ title: geminiDisplayName,
29
+ icon: geminiIcon
30
+ },
31
+ kimi: {
32
+ title: kimiDisplayName,
33
+ icon: kimiIcon
34
+ },
20
35
  opencode: {
21
36
  title: opencodeDisplayName,
22
37
  icon: opencodeIcon