@vibe-forge/client 0.8.4 → 0.10.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 (301) hide show
  1. package/AGENTS.md +75 -0
  2. package/dist/assets/abap-DLDM7-KI.js +1 -0
  3. package/dist/assets/apex-DNDY2TF8.js +1 -0
  4. package/dist/assets/{arc-DCuZPvAs.js → arc-CCXV7u3V.js} +1 -1
  5. package/dist/assets/azcli-Y6nb8tq_.js +1 -0
  6. package/dist/assets/bat-BwHxbl9M.js +1 -0
  7. package/dist/assets/bicep-CFznDFnq.js +2 -0
  8. package/dist/assets/{blockDiagram-c4efeb88-C39d7-Bu.js → blockDiagram-c4efeb88-Bm52FmvT.js} +1 -1
  9. package/dist/assets/{c4Diagram-c83219d4-jWPBJdeq.js → c4Diagram-c83219d4-C8tTEpcK.js} +1 -1
  10. package/dist/assets/cameligo-Bf6VGUru.js +1 -0
  11. package/dist/assets/channel-gq_WMRvv.js +1 -0
  12. package/dist/assets/{classDiagram-beda092f-Bo7Yvv2T.js → classDiagram-beda092f-CNAIBAH1.js} +1 -1
  13. package/dist/assets/{classDiagram-v2-2358418a-DfoCP9XM.js → classDiagram-v2-2358418a-BHeZAVdc.js} +1 -1
  14. package/dist/assets/clojure-Dnu-v4kV.js +1 -0
  15. package/dist/assets/clone-XxGY7A5N.js +1 -0
  16. package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  17. package/dist/assets/coffee-Bd8akH9Z.js +1 -0
  18. package/dist/assets/cpp-BbWJElDN.js +1 -0
  19. package/dist/assets/{createText-1719965b-8_Ez5rxh.js → createText-1719965b-BS2hLG8t.js} +1 -1
  20. package/dist/assets/csharp-Co3qMtFm.js +1 -0
  21. package/dist/assets/csp-D-4FJmMZ.js +1 -0
  22. package/dist/assets/css-DdJfP1eB.js +3 -0
  23. package/dist/assets/css.worker-BvV5MPou.js +93 -0
  24. package/dist/assets/cssMode-WHcTFAOU.js +1 -0
  25. package/dist/assets/cypher-cTPe9QuQ.js +1 -0
  26. package/dist/assets/dart-BOtBlQCF.js +1 -0
  27. package/dist/assets/dockerfile-BG73LgW2.js +1 -0
  28. package/dist/assets/ecl-BEgZUVRK.js +1 -0
  29. package/dist/assets/{edges-96097737-BEegO-R-.js → edges-96097737-C07f4iWA.js} +1 -1
  30. package/dist/assets/editor.worker-CKy7Pnvo.js +26 -0
  31. package/dist/assets/elixir-BkW5O-1t.js +1 -0
  32. package/dist/assets/{erDiagram-0228fc6a-derRgkLz.js → erDiagram-0228fc6a-BytsAWUs.js} +1 -1
  33. package/dist/assets/flow9-BeJ5waoc.js +1 -0
  34. package/dist/assets/{flowDb-c6c81e3f-ChtOxgsJ.js → flowDb-c6c81e3f-CQJkOpAs.js} +1 -1
  35. package/dist/assets/{flowDiagram-50d868cf-Bm4ufmmA.js → flowDiagram-50d868cf-CD5Tng2S.js} +1 -1
  36. package/dist/assets/flowDiagram-v2-4f6560a1-DIBOANLV.js +1 -0
  37. package/dist/assets/{flowchart-elk-definition-6af322e1-BRYLO-BL.js → flowchart-elk-definition-6af322e1-ylso-GWH.js} +1 -1
  38. package/dist/assets/freemarker2-U_9Jyyr3.js +3 -0
  39. package/dist/assets/fsharp-PahG7c26.js +1 -0
  40. package/dist/assets/{ganttDiagram-a2739b55-CbP6dzRO.js → ganttDiagram-a2739b55-Cg98bJx5.js} +1 -1
  41. package/dist/assets/{gitGraphDiagram-82fe8481-Du44v02s.js → gitGraphDiagram-82fe8481-7Yp4hz0N.js} +1 -1
  42. package/dist/assets/go-acbASCJo.js +1 -0
  43. package/dist/assets/{graph-0_VzJX6O.js → graph-Ig3nvzvL.js} +1 -1
  44. package/dist/assets/graphql-BxJiqAUM.js +1 -0
  45. package/dist/assets/handlebars-DQyCBwHe.js +1 -0
  46. package/dist/assets/hcl-DtV1sZF8.js +1 -0
  47. package/dist/assets/html-CNC2AT5k.js +1 -0
  48. package/dist/assets/html.worker-BLJhxQJQ.js +470 -0
  49. package/dist/assets/htmlMode-DlATk4xW.js +1 -0
  50. package/dist/assets/{index-5325376f-DLP7F7of.js → index-5325376f-C4zed9sb.js} +1 -1
  51. package/dist/assets/index-DRLsOoqb.css +32 -0
  52. package/dist/assets/index-Dbx0JG0p.js +1511 -0
  53. package/dist/assets/{infoDiagram-8eee0895-DY19rRl6.js → infoDiagram-8eee0895-C8oSBaFs.js} +1 -1
  54. package/dist/assets/ini-Kd9XrMLS.js +1 -0
  55. package/dist/assets/java-CXBNlu9o.js +1 -0
  56. package/dist/assets/javascript-9wv9uKW4.js +1 -0
  57. package/dist/assets/{journeyDiagram-c64418c1-4Asnwc86.js → journeyDiagram-c64418c1-D5kJldvy.js} +1 -1
  58. package/dist/assets/json.worker-usMZ-FED.js +58 -0
  59. package/dist/assets/jsonMode-45dv39mU.js +7 -0
  60. package/dist/assets/julia-cl7-CwDS.js +1 -0
  61. package/dist/assets/kotlin-s7OhZKlX.js +1 -0
  62. package/dist/assets/{layout-BILp7GjD.js → layout-DYNFLnIl.js} +1 -1
  63. package/dist/assets/less-9HpZscsL.js +2 -0
  64. package/dist/assets/lexon-OrD6JF1K.js +1 -0
  65. package/dist/assets/{line-D0Xqr8mi.js → line-1gvOYQYZ.js} +1 -1
  66. package/dist/assets/{linear-LpL8RZsq.js → linear-DHGm6Zdw.js} +1 -1
  67. package/dist/assets/liquid-BGoxrdXO.js +1 -0
  68. package/dist/assets/lspLanguageFeatures-CpCCXhrd.js +4 -0
  69. package/dist/assets/lua-Cyyb5UIc.js +1 -0
  70. package/dist/assets/m3-B8OfTtLu.js +1 -0
  71. package/dist/assets/markdown-BFxVWTOG.js +1 -0
  72. package/dist/assets/mdx-Dc2iMbEw.js +1 -0
  73. package/dist/assets/{mermaid.core-Bk0Y_0sz.js → mermaid.core-Cq2bBFF1.js} +6 -6
  74. package/dist/assets/{mindmap-definition-8da855dc-eCAgn5kY.js → mindmap-definition-8da855dc-y5l6GRVh.js} +1 -1
  75. package/dist/assets/mips-CiqrrVzr.js +1 -0
  76. package/dist/assets/msdax-DmeGPVcC.js +1 -0
  77. package/dist/assets/mysql-C_tMU-Nz.js +1 -0
  78. package/dist/assets/objective-c-BDtDVThU.js +1 -0
  79. package/dist/assets/pascal-vHIfCaH5.js +1 -0
  80. package/dist/assets/pascaligo-DtZ0uQbO.js +1 -0
  81. package/dist/assets/perl-Ub6l9XKa.js +1 -0
  82. package/dist/assets/pgsql-BlNEE0v7.js +1 -0
  83. package/dist/assets/php-BBUBE1dy.js +1 -0
  84. package/dist/assets/{pieDiagram-a8764435-BeGFFS1p.js → pieDiagram-a8764435-PNROcv9y.js} +1 -1
  85. package/dist/assets/pla-DSh2-awV.js +1 -0
  86. package/dist/assets/postiats-CocnycG-.js +1 -0
  87. package/dist/assets/powerquery-tScXyioY.js +1 -0
  88. package/dist/assets/powershell-COWaemsV.js +1 -0
  89. package/dist/assets/protobuf-Brw8urJB.js +2 -0
  90. package/dist/assets/pug-8SOpv6rk.js +1 -0
  91. package/dist/assets/python-DjYAge7h.js +1 -0
  92. package/dist/assets/qsharp-Bw9ernYp.js +1 -0
  93. package/dist/assets/{quadrantDiagram-1e28029f-tLqbYOHC.js → quadrantDiagram-1e28029f-CFJ3VPpp.js} +1 -1
  94. package/dist/assets/r-j7ic8hl3.js +1 -0
  95. package/dist/assets/razor-OIY8fx_i.js +1 -0
  96. package/dist/assets/redis-Bu5POkcn.js +1 -0
  97. package/dist/assets/redshift-Bs9aos_-.js +1 -0
  98. package/dist/assets/{requirementDiagram-08caed73-DCverr_g.js → requirementDiagram-08caed73-BpzDIINS.js} +1 -1
  99. package/dist/assets/restructuredtext-CqXO7rUv.js +1 -0
  100. package/dist/assets/ruby-zBfavPgS.js +1 -0
  101. package/dist/assets/rust-BzKRNQWT.js +1 -0
  102. package/dist/assets/{sankeyDiagram-a04cb91d-DidhF5PL.js → sankeyDiagram-a04cb91d-CH69-iIn.js} +1 -1
  103. package/dist/assets/sb-BBc9UKZt.js +1 -0
  104. package/dist/assets/scala-D9hQfWCl.js +1 -0
  105. package/dist/assets/scheme-BPhDTwHR.js +1 -0
  106. package/dist/assets/scss-CBJaRo0y.js +3 -0
  107. package/dist/assets/{sequenceDiagram-c5b8d532-CEKaiwTo.js → sequenceDiagram-c5b8d532-yBBEeVFU.js} +1 -1
  108. package/dist/assets/shell-DiJ1NA_G.js +1 -0
  109. package/dist/assets/solidity-Db0IVjzk.js +1 -0
  110. package/dist/assets/sophia-CnS9iZB_.js +1 -0
  111. package/dist/assets/sparql-CJmd_6j2.js +1 -0
  112. package/dist/assets/sql-ClhHkBeG.js +1 -0
  113. package/dist/assets/st-CHwy0fLd.js +1 -0
  114. package/dist/assets/{stateDiagram-1ecb1508-Cj9ZY7RH.js → stateDiagram-1ecb1508-BvF4sign.js} +1 -1
  115. package/dist/assets/{stateDiagram-v2-c2b004d7-Gsmps-dk.js → stateDiagram-v2-c2b004d7-BeyT7Ghx.js} +1 -1
  116. package/dist/assets/{styles-b4e223ce-D7E8Th0j.js → styles-b4e223ce-C58zxmK6.js} +1 -1
  117. package/dist/assets/{styles-ca3715f6-tTC26Jsm.js → styles-ca3715f6-DCE5sFi5.js} +1 -1
  118. package/dist/assets/{styles-d45a18b0-Bf6oqNdR.js → styles-d45a18b0-CG-C1aM8.js} +1 -1
  119. package/dist/assets/{svgDrawCommon-b86b1483-BxKi01m2.js → svgDrawCommon-b86b1483-F-K8GeDd.js} +1 -1
  120. package/dist/assets/swift-Bqt4WxQ4.js +3 -0
  121. package/dist/assets/systemverilog-Bs9z6M-B.js +1 -0
  122. package/dist/assets/tcl-Dm6ycUr_.js +1 -0
  123. package/dist/assets/{timeline-definition-faaaa080-D6PePEip.js → timeline-definition-faaaa080-DPv4uqVX.js} +1 -1
  124. package/dist/assets/ts.worker-DGHjMaqB.js +67731 -0
  125. package/dist/assets/tsMode-BtU8ZELV.js +11 -0
  126. package/dist/assets/twig-Csy3S7wG.js +1 -0
  127. package/dist/assets/typescript-CJHgISWo.js +1 -0
  128. package/dist/assets/typespec-Btyra-wh.js +1 -0
  129. package/dist/assets/vb-Db0cS2oM.js +1 -0
  130. package/dist/assets/wgsl-BTesnYfV.js +298 -0
  131. package/dist/assets/xml-C_TJw4Bi.js +1 -0
  132. package/dist/assets/{xychartDiagram-f5964ef8-BuP4qfXm.js → xychartDiagram-f5964ef8-BmXlhBzX.js} +1 -1
  133. package/dist/assets/yaml-BujeJOJ6.js +1 -0
  134. package/dist/favicon.svg +3 -3
  135. package/dist/index.html +2 -2
  136. package/package.json +14 -10
  137. package/public/favicon.svg +3 -3
  138. package/src/api/sessions.ts +36 -0
  139. package/src/api/workspace.ts +19 -0
  140. package/src/api.ts +4 -0
  141. package/src/components/NavRail.scss +9 -10
  142. package/src/components/ShortcutDisplay.scss +38 -0
  143. package/src/components/ShortcutDisplay.tsx +37 -0
  144. package/src/components/ShortcutTooltip.scss +36 -0
  145. package/src/components/ShortcutTooltip.tsx +84 -0
  146. package/src/components/Sidebar.scss +55 -13
  147. package/src/components/Sidebar.tsx +141 -52
  148. package/src/components/chat/AGENTS.md +163 -0
  149. package/src/components/chat/ChatHeader.scss +308 -49
  150. package/src/components/chat/ChatHeader.tsx +394 -80
  151. package/src/components/chat/ChatHistoryView.tsx +284 -69
  152. package/src/components/chat/ChatSettingsView.tsx +5 -3
  153. package/src/components/chat/ChatTimelineView.scss +3 -2
  154. package/src/components/chat/{sender/ThinkingStatus.tsx → ThinkingStatus.tsx} +1 -1
  155. package/src/components/chat/messages/MessageContextMenu.scss +145 -0
  156. package/src/components/chat/messages/MessageContextMenu.tsx +108 -0
  157. package/src/components/chat/messages/MessageContextMenuContent.tsx +87 -0
  158. package/src/components/chat/messages/MessageFooter.tsx +44 -24
  159. package/src/components/chat/messages/MessageItem.scss +147 -10
  160. package/src/components/chat/messages/MessageItem.tsx +378 -13
  161. package/src/components/chat/messages/build-message-context-menu-entries.ts +166 -0
  162. package/src/components/chat/messages/message-content-utils.ts +121 -0
  163. package/src/components/chat/messages/message-turns.ts +88 -0
  164. package/src/components/chat/messages/message-utils.ts +19 -1
  165. package/src/components/chat/sender/@components/adapter-select/AdapterSelectControl.scss +86 -0
  166. package/src/components/chat/sender/@components/adapter-select/AdapterSelectControl.tsx +54 -0
  167. package/src/components/chat/sender/@components/adapter-select/AdapterSelectDropdown.scss +42 -0
  168. package/src/components/chat/sender/@components/effort-select/EffortSelectControl.scss +68 -0
  169. package/src/components/chat/sender/@components/effort-select/EffortSelectControl.tsx +137 -0
  170. package/src/components/chat/sender/@components/effort-select/EffortSelectDropdown.scss +96 -0
  171. package/src/components/chat/sender/@components/model-select/ModelSelectControl.scss +82 -0
  172. package/src/components/chat/sender/@components/model-select/ModelSelectControl.tsx +171 -0
  173. package/src/components/chat/sender/@components/model-select/ModelSelectMenu.scss +95 -0
  174. package/src/components/chat/sender/@components/model-select/ModelSelectMenuLabels.scss +144 -0
  175. package/src/components/chat/sender/@components/model-select/ModelSelectOptionLabel.tsx +109 -0
  176. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsControl.scss +106 -0
  177. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsControl.tsx +156 -0
  178. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsOption.scss +34 -0
  179. package/src/components/chat/sender/@components/reference-actions/ReferencePermissionActionsPopover.tsx +111 -0
  180. package/src/components/chat/sender/@components/sender-attachments/SenderAttachments.scss +103 -0
  181. package/src/components/chat/sender/@components/sender-attachments/SenderAttachments.tsx +47 -0
  182. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +137 -0
  183. package/src/components/chat/sender/@components/sender-interaction-panel/SenderInteractionPanel.scss +178 -0
  184. package/src/components/chat/sender/@components/sender-interaction-panel/SenderInteractionPanel.tsx +145 -0
  185. package/src/components/chat/sender/@components/sender-monaco-editor/SenderMonacoEditor.scss +47 -0
  186. package/src/components/chat/sender/@components/sender-monaco-editor/SenderMonacoEditor.tsx +121 -0
  187. package/src/components/chat/sender/@components/sender-monaco-editor/monaco-runtime.ts +99 -0
  188. package/src/components/chat/sender/@components/sender-monaco-editor/use-sender-editor-handle.ts +48 -0
  189. package/src/components/chat/sender/@components/sender-monaco-editor/use-sender-monaco-editor.ts +209 -0
  190. package/src/components/chat/sender/@components/sender-monaco-editor/use-sender-monaco-theme.ts +24 -0
  191. package/src/components/chat/sender/@components/sender-submit-action/SenderSubmitAction.scss +54 -0
  192. package/src/components/chat/sender/@components/sender-submit-action/SenderSubmitAction.tsx +80 -0
  193. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectBase.scss +71 -0
  194. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectShared.scss +118 -0
  195. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.scss +99 -0
  196. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.tsx +87 -0
  197. package/src/components/chat/sender/@core/build-sender-controller-result.ts +119 -0
  198. package/src/components/chat/sender/@core/build-sender-toolbar.ts +122 -0
  199. package/src/components/chat/sender/@core/content-attachments.ts +76 -0
  200. package/src/components/chat/sender/@core/create-sender-toolbar-handlers.ts +115 -0
  201. package/src/components/chat/sender/@core/get-sender-interaction-state.ts +18 -0
  202. package/src/components/chat/sender/@core/get-sender-runtime-state.ts +14 -0
  203. package/src/components/chat/sender/@core/interaction-request.ts +5 -0
  204. package/src/components/chat/sender/@core/sender-toolbar-bindings.ts +155 -0
  205. package/src/components/chat/sender/@hooks/use-model-select-browser.tsx +189 -0
  206. package/src/components/chat/sender/@hooks/use-sender-attachments.ts +143 -0
  207. package/src/components/chat/sender/@hooks/use-sender-autofocus.ts +34 -0
  208. package/src/components/chat/sender/@hooks/use-sender-completion.ts +62 -0
  209. package/src/components/chat/sender/@hooks/use-sender-composer-state.ts +34 -0
  210. package/src/components/chat/sender/@hooks/use-sender-controller.ts +193 -0
  211. package/src/components/chat/sender/@hooks/use-sender-focus-restore.ts +72 -0
  212. package/src/components/chat/sender/@hooks/use-sender-history.ts +79 -0
  213. package/src/components/chat/sender/@hooks/use-sender-keydown.ts +113 -0
  214. package/src/components/chat/sender/@hooks/use-sender-reference-actions.ts +191 -0
  215. package/src/components/chat/sender/@hooks/use-sender-reference-focus-restore.ts +21 -0
  216. package/src/components/chat/sender/@hooks/use-sender-refs.ts +19 -0
  217. package/src/components/chat/sender/@hooks/use-sender-select-overlays.ts +83 -0
  218. package/src/components/chat/sender/@hooks/use-sender-shortcuts.ts +78 -0
  219. package/src/components/chat/sender/@hooks/use-sender-submit.ts +81 -0
  220. package/src/components/chat/sender/@types/sender-composer.ts +19 -0
  221. package/src/components/chat/sender/@types/sender-editor.ts +12 -0
  222. package/src/components/chat/sender/@types/sender-props.ts +50 -0
  223. package/src/components/chat/sender/@types/sender-toolbar-types.ts +83 -0
  224. package/src/components/chat/sender/@types/sender-types.ts +21 -0
  225. package/src/components/chat/sender/@utils/sender-completion.ts +164 -0
  226. package/src/components/chat/sender/@utils/sender-constants.ts +18 -0
  227. package/src/components/chat/sender/@utils/sender-utils.ts +45 -0
  228. package/src/components/chat/sender/Sender.scss +8 -605
  229. package/src/components/chat/sender/Sender.tsx +54 -880
  230. package/src/components/chat/session-metadata.ts +55 -0
  231. package/src/components/chat/terminal/@hooks/use-terminal-instance.ts +152 -0
  232. package/src/components/chat/terminal/@hooks/use-terminal-session.ts +196 -0
  233. package/src/components/chat/terminal/ChatTerminalView.scss +62 -0
  234. package/src/components/chat/terminal/ChatTerminalView.tsx +114 -0
  235. package/src/components/chat/tools/core/ToolGroup.scss +7 -0
  236. package/src/components/chat/tools/core/ToolGroup.tsx +97 -56
  237. package/src/components/config/ConfigSectionForm.tsx +8 -1
  238. package/src/components/config/ConfigShortcutInput.tsx +9 -2
  239. package/src/components/config/configSchema.ts +12 -2
  240. package/src/components/config/record-editors/ModelServicesRecordEditor.tsx +0 -14
  241. package/src/components/dock-panel/DockPanel.scss +152 -0
  242. package/src/components/dock-panel/DockPanel.tsx +195 -0
  243. package/src/components/layout/AppShell.scss +40 -2
  244. package/src/components/layout/AppShell.tsx +25 -10
  245. package/src/components/sidebar/SessionContextMenu.scss +143 -0
  246. package/src/components/sidebar/SessionContextMenu.tsx +196 -0
  247. package/src/components/sidebar/SessionContextMenuContent.tsx +89 -0
  248. package/src/components/sidebar/SessionItem.scss +150 -67
  249. package/src/components/sidebar/SessionItem.tsx +183 -134
  250. package/src/components/sidebar/SessionList.scss +47 -17
  251. package/src/components/sidebar/SessionList.tsx +31 -16
  252. package/src/components/sidebar/SidebarHeader.scss +329 -49
  253. package/src/components/sidebar/SidebarHeader.tsx +108 -86
  254. package/src/components/sidebar/SidebarHeaderBatchActions.tsx +81 -0
  255. package/src/components/sidebar/SidebarHeaderSearchActions.tsx +176 -0
  256. package/src/components/sidebar/SidebarHeaderSelectField.tsx +24 -0
  257. package/src/components/sidebar/filter-utils.ts +23 -0
  258. package/src/components/workspace/ContextFilePicker.scss +64 -0
  259. package/src/components/workspace/ContextFilePicker.tsx +171 -0
  260. package/src/connectionManager.ts +4 -2
  261. package/src/hooks/chat/interaction-state.ts +104 -0
  262. package/src/hooks/chat/model-selector-data-builders.ts +146 -0
  263. package/src/hooks/chat/model-selector-data-option-utils.ts +62 -0
  264. package/src/hooks/chat/model-selector-data-types.ts +27 -0
  265. package/src/hooks/chat/model-selector-data.ts +109 -0
  266. package/src/hooks/chat/model-selector-recommendations.ts +69 -0
  267. package/src/hooks/chat/model-selector.ts +9 -0
  268. package/src/hooks/chat/use-chat-interaction.ts +13 -8
  269. package/src/hooks/chat/use-chat-model-adapter-selection.tsx +167 -164
  270. package/src/hooks/chat/use-chat-models.tsx +46 -23
  271. package/src/hooks/chat/use-chat-session-actions.ts +69 -23
  272. package/src/hooks/chat/use-chat-session-messages.ts +165 -60
  273. package/src/hooks/chat/use-chat-session.ts +34 -9
  274. package/src/hooks/chat/use-chat-view.ts +26 -6
  275. package/src/hooks/chat/use-composer-control-shortcuts.ts +69 -0
  276. package/src/hooks/chat/use-terminal-dock-visibility.ts +39 -0
  277. package/src/hooks/use-roving-focus-list.ts +104 -0
  278. package/src/hooks/use-sidebar-navigation.ts +9 -4
  279. package/src/hooks/use-sidebar-query-state.ts +79 -0
  280. package/src/main.tsx +6 -1
  281. package/src/resources/locales/en.json +151 -6
  282. package/src/resources/locales/zh.json +151 -6
  283. package/src/routes/ChatRoute.scss +159 -4
  284. package/src/routes/ChatRoute.tsx +43 -9
  285. package/src/runtime-config.ts +21 -0
  286. package/src/store/index.ts +1 -3
  287. package/src/styles/global.scss +12 -2
  288. package/src/utils/chat-links.ts +21 -0
  289. package/src/utils/copy.ts +18 -0
  290. package/src/utils/shortcutUtils.ts +111 -1
  291. package/src/vite-env.d.ts +1 -0
  292. package/src/ws.ts +6 -5
  293. package/vite.config.ts +71 -7
  294. package/dist/assets/channel-bLjHfx-Q.js +0 -1
  295. package/dist/assets/clone-upfY39Je.js +0 -1
  296. package/dist/assets/flowDiagram-v2-4f6560a1-F66FzZTY.js +0 -1
  297. package/dist/assets/index-C1O04Df8.js +0 -557
  298. package/dist/assets/index-sE8VA1N7.css +0 -1
  299. package/src/components/chat/sender/CompletionMenu.scss +0 -70
  300. package/src/components/chat/sender/CompletionMenu.tsx +0 -58
  301. /package/src/components/chat/{sender/ThinkingStatus.scss → ThinkingStatus.scss} +0 -0
@@ -1,891 +1,65 @@
1
1
  import './Sender.scss'
2
2
 
3
- import { App, Button, Cascader, Input, Select, Tooltip } from 'antd'
4
- import type { TextAreaRef } from 'antd/es/input/TextArea'
5
- import React, { useRef, useState } from 'react'
6
- import { useTranslation } from 'react-i18next'
7
- import useSWR from 'swr'
3
+ import { ThinkingStatus } from '#~/components/chat/ThinkingStatus'
4
+ import { useSenderController } from '#~/components/chat/sender/@hooks/use-sender-controller'
8
5
 
9
- import type { ChatEffort } from '#~/hooks/chat/use-chat-effort'
10
- import type { PermissionMode } from '#~/hooks/chat/use-chat-permission-mode'
11
- import type { AskUserQuestionParams, ChatMessageContent, SessionStatus } from '@vibe-forge/core'
12
- import type { SessionInfo } from '@vibe-forge/types'
13
- import { isShortcutMatch } from '../../../utils/shortcutUtils'
14
- import type { CompletionItem } from './CompletionMenu'
15
- import { CompletionMenu } from './CompletionMenu'
16
- import { ThinkingStatus } from './ThinkingStatus'
6
+ import { SenderBody } from './@components/sender-body/SenderBody'
7
+ import { SenderInteractionPanel } from './@components/sender-interaction-panel/SenderInteractionPanel'
8
+ import type { SenderProps } from './@types/sender-props'
17
9
 
18
- const { TextArea } = Input
19
-
20
- interface ModelSelectOption {
21
- value: string
22
- label: React.ReactNode
23
- searchText: string
24
- displayLabel: string
25
- }
26
-
27
- interface ModelSelectGroup {
28
- label: React.ReactNode
29
- options: ModelSelectOption[]
30
- }
31
-
32
- interface PendingImage {
33
- id: string
34
- url: string
35
- name?: string
36
- size?: number
37
- mimeType?: string
38
- }
39
-
40
- type SessionAssetDiagnostic = NonNullable<Extract<SessionInfo, { type: 'init' }>['assetDiagnostics']>[number]
41
- type SessionSelectionWarning = NonNullable<Extract<SessionInfo, { type: 'init' }>['selectionWarnings']>[number]
42
-
43
- interface SenderToolGroup {
44
- key: 'chrome-devtools' | 'system'
45
- label: string
46
- tools: string[]
47
- }
48
-
49
- interface SenderToolOption {
50
- value: string
51
- label: React.ReactNode
52
- children?: SenderToolOption[]
53
- }
54
-
55
- const formatToolLabel = (tool: string) => {
56
- const parts = tool.split('__')
57
- return parts[parts.length - 1] || tool
58
- }
59
-
60
- const getToolGroupIcon = (groupKey: SenderToolGroup['key']) => {
61
- return groupKey === 'chrome-devtools' ? 'web_traffic' : 'memory'
62
- }
63
-
64
- export function Sender({
65
- onSend,
66
- onSendContent,
67
- adapterLocked = false,
68
- sessionStatus,
69
- onInterrupt,
70
- onClear,
71
- sessionInfo,
72
- connectionError,
73
- onRetryConnection,
74
- interactionRequest,
75
- onInteractionResponse,
76
- placeholder,
77
- modelOptions,
78
- selectedModel,
79
- onModelChange,
80
- effort,
81
- effortOptions,
82
- onEffortChange,
83
- permissionMode,
84
- permissionModeOptions,
85
- onPermissionModeChange,
86
- selectedAdapter,
87
- adapterOptions,
88
- onAdapterChange,
89
- modelUnavailable
90
- }: {
91
- onSend: (text: string) => void
92
- onSendContent: (content: ChatMessageContent[]) => void
93
- adapterLocked?: boolean
94
- sessionStatus?: SessionStatus
95
- onInterrupt: () => void
96
- onClear?: () => void
97
- sessionInfo?: SessionInfo | null
98
- connectionError?: string | null
99
- onRetryConnection?: () => void
100
- interactionRequest?: { id: string; payload: AskUserQuestionParams } | null
101
- onInteractionResponse?: (id: string, data: string | string[]) => void
102
- placeholder?: string
103
- modelOptions?: ModelSelectGroup[]
104
- selectedModel?: string
105
- onModelChange?: (model: string) => void
106
- effort: ChatEffort
107
- effortOptions: Array<{ value: ChatEffort; label: React.ReactNode }>
108
- onEffortChange: (effort: ChatEffort) => void
109
- permissionMode: PermissionMode
110
- permissionModeOptions: Array<{ value: PermissionMode; label: React.ReactNode }>
111
- onPermissionModeChange: (mode: PermissionMode) => void
112
- selectedAdapter?: string
113
- adapterOptions?: Array<{ value: string; label: React.ReactNode }>
114
- onAdapterChange?: (adapter: string) => void
115
- modelUnavailable?: boolean
116
- }) {
117
- const { t } = useTranslation()
118
- const { message } = App.useApp()
119
- const [input, setInput] = useState('')
120
- const [showCompletion, setShowCompletion] = useState(false)
121
- const [completionItems, setCompletionItems] = useState<CompletionItem[]>([])
122
- const [selectedIndex, setSelectedIndex] = useState(0)
123
- const [triggerChar, setTriggerChar] = useState<string | null>(null)
124
-
125
- const [showToolsList, setShowToolsList] = useState(false)
126
- const textareaRef = useRef<TextAreaRef>(null)
127
- const fileInputRef = useRef<HTMLInputElement>(null)
128
- const isMac = navigator.platform.includes('Mac')
129
- const [pendingImages, setPendingImages] = useState<PendingImage[]>([])
130
-
131
- const { data: configRes } = useSWR<{
132
- sources?: {
133
- merged?: {
134
- shortcuts?: {
135
- sendMessage?: string
136
- clearInput?: string
137
- }
138
- }
139
- }
140
- }>('/api/config')
141
- const sendShortcut = configRes?.sources?.merged?.shortcuts?.sendMessage
142
- const clearInputShortcut = configRes?.sources?.merged?.shortcuts?.clearInput
143
- const resolvedSendShortcut = sendShortcut != null && sendShortcut.trim() !== ''
144
- ? sendShortcut
145
- : 'mod+enter'
146
-
147
- const isThinking = sessionStatus === 'running'
148
- const supportsEffort = selectedAdapter === 'codex' || selectedAdapter === 'claude-code' ||
149
- selectedAdapter === 'opencode'
150
- const groupedTools: SenderToolGroup[] = sessionInfo != null && sessionInfo.type === 'init'
151
- ? ([
152
- {
153
- key: 'chrome-devtools',
154
- label: t('chat.toolGroupChromeDevtools'),
155
- tools: sessionInfo.tools.filter((tool: string) => tool.startsWith('mcp__ChromeDevtools__'))
156
- },
157
- {
158
- key: 'system',
159
- label: t('chat.toolGroupSystem'),
160
- tools: sessionInfo.tools.filter((tool: string) => !tool.startsWith('mcp__ChromeDevtools__'))
161
- }
162
- ] satisfies SenderToolGroup[]).filter((group): group is SenderToolGroup => group.tools.length > 0)
163
- : []
164
- const assetWarnings = sessionInfo != null && sessionInfo.type === 'init'
165
- ? (sessionInfo.assetDiagnostics ?? []).filter((diagnostic: SessionAssetDiagnostic) =>
166
- diagnostic.status === 'skipped'
167
- )
168
- : []
169
- const selectionWarnings = sessionInfo != null && sessionInfo.type === 'init'
170
- ? (sessionInfo.selectionWarnings ?? [])
171
- : []
172
- const toolCascaderOptions: SenderToolOption[] = groupedTools.map(group => ({
173
- value: group.key,
174
- label: (
175
- <span className='sender-tool-group-option'>
176
- <span className='sender-tool-group-option__icon material-symbols-rounded'>{getToolGroupIcon(group.key)}</span>
177
- <span className='sender-tool-group-option__text'>{group.label}</span>
178
- <span className='sender-tool-group-option__count'>{group.tools.length}</span>
179
- </span>
180
- ),
181
- children: group.tools.map(tool => ({
182
- value: tool,
183
- label: (
184
- <span className='sender-tool-option'>
185
- <span className='sender-tool-option__dot' />
186
- <span className='sender-tool-option__text'>{formatToolLabel(tool)}</span>
187
- </span>
188
- )
189
- }))
190
- }))
191
-
192
- const [historyIndex, setHistoryIndex] = useState(-1)
193
- const [draft, setDraft] = useState('')
194
-
195
- const formatSelectionWarning = (warning: SessionSelectionWarning) => {
196
- const reason = warning.reason === 'excluded'
197
- ? t('chat.selectionWarningReasonExcluded')
198
- : t('chat.selectionWarningReasonNotIncluded')
199
-
200
- return t('chat.selectionWarningFallback', {
201
- adapter: warning.adapter,
202
- requestedModel: warning.requestedModel,
203
- resolvedModel: warning.resolvedModel,
204
- reason
205
- })
206
- }
207
-
208
- const readFileAsDataUrl = (file: File) => {
209
- return new Promise<string>((resolve, reject) => {
210
- const reader = new FileReader()
211
- reader.onload = () => {
212
- resolve(typeof reader.result === 'string' ? reader.result : '')
213
- }
214
- reader.onerror = () => {
215
- reject(new Error('read_failed'))
216
- }
217
- reader.readAsDataURL(file)
218
- })
219
- }
220
-
221
- const addImageFiles = async (files: File[]) => {
222
- const maxSize = 5 * 1024 * 1024
223
- for (const file of files) {
224
- if (!file.type.startsWith('image/')) continue
225
- if (file.size > maxSize) {
226
- void message.error(t('chat.imageTooLarge'))
227
- continue
228
- }
229
- try {
230
- const url = await readFileAsDataUrl(file)
231
- if (url === '') {
232
- void message.error(t('chat.imageReadFailed'))
233
- continue
234
- }
235
- setPendingImages(prev => [
236
- ...prev,
237
- {
238
- id: globalThis.crypto?.randomUUID
239
- ? globalThis.crypto.randomUUID()
240
- : `img-${Date.now()}-${Math.random().toString(16).slice(2)}`,
241
- url,
242
- name: file.name,
243
- size: file.size,
244
- mimeType: file.type
245
- }
246
- ])
247
- } catch (err) {
248
- void message.error(t('chat.imageReadFailed'))
249
- }
250
- }
251
- }
252
-
253
- const handleSend = () => {
254
- if (isThinking) return
255
- if (input.trim() === '' && pendingImages.length === 0) return
256
-
257
- if (modelUnavailable) {
258
- void message.warning(t('chat.modelConfigRequired'))
259
- return
260
- }
261
-
262
- if (interactionRequest != null && onInteractionResponse != null) {
263
- if (pendingImages.length > 0) {
264
- void message.warning(t('chat.imageNotSupportedInInteraction'))
265
- return
266
- }
267
- onInteractionResponse(interactionRequest.id, input.trim())
268
- setInput('')
269
- return
270
- }
271
-
272
- if (pendingImages.length > 0) {
273
- const content: ChatMessageContent[] = []
274
- if (input.trim() !== '') {
275
- content.push({ type: 'text', text: input.trim() })
276
- }
277
- content.push(...pendingImages.map((img): ChatMessageContent => ({
278
- type: 'image',
279
- url: img.url,
280
- name: img.name,
281
- size: img.size,
282
- mimeType: img.mimeType
283
- })))
284
- onSendContent(content)
285
- } else {
286
- onSend(input)
287
- }
288
-
289
- // Save to local storage history
290
- try {
291
- const history = JSON.parse(localStorage.getItem('vf_chat_history') ?? '[]') as string[]
292
- const newHistory = [input, ...history.filter((h: string) => h !== input)].slice(0, 50)
293
- localStorage.setItem('vf_chat_history', JSON.stringify(newHistory))
294
- } catch (e) {
295
- console.error('Failed to save chat history', e)
296
- }
297
-
298
- setInput('')
299
- setPendingImages([])
300
- setDraft('')
301
- setShowCompletion(false)
302
- setHistoryIndex(-1)
303
- }
304
-
305
- const handleImageUpload = () => {
306
- if (isThinking) return
307
- if (modelUnavailable) {
308
- void message.warning(t('chat.modelConfigRequired'))
309
- return
310
- }
311
- if (interactionRequest != null) {
312
- void message.warning(t('chat.imageNotSupportedInInteraction'))
313
- return
314
- }
315
- fileInputRef.current?.click()
316
- }
317
-
318
- const handleImageFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
319
- const fileList = Array.from(event.target.files ?? [])
320
- if (fileList.length === 0) return
321
- await addImageFiles(fileList)
322
- event.target.value = ''
323
- }
324
-
325
- const handleRemovePendingImage = (id: string) => {
326
- setPendingImages(prev => prev.filter(img => img.id !== id))
327
- }
328
-
329
- const handlePaste = async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
330
- const items = Array.from(event.clipboardData?.items ?? [])
331
- const files = items
332
- .filter(item => item.kind === 'file' && item.type.startsWith('image/'))
333
- .map(item => item.getAsFile())
334
- .filter((file): file is File => file != null)
335
- if (files.length > 0) {
336
- event.preventDefault()
337
- await addImageFiles(files)
338
- }
339
- }
340
-
341
- const clearInputValue = () => {
342
- if (input === '') return
343
- setInput('')
344
- setHistoryIndex(-1)
345
- }
346
-
347
- const handleHistoryNavigation = (direction: 'up' | 'down') => {
348
- try {
349
- const history = JSON.parse(localStorage.getItem('vf_chat_history') ?? '[]') as string[]
350
- if (history.length === 0) return
351
-
352
- let nextIndex = historyIndex
353
- if (direction === 'up') {
354
- nextIndex = Math.min(historyIndex + 1, history.length - 1)
355
- } else {
356
- nextIndex = Math.max(historyIndex - 1, -1)
357
- }
358
-
359
- if (nextIndex !== historyIndex) {
360
- // Save draft when leaving -1
361
- if (historyIndex === -1) {
362
- setDraft(input)
363
- }
364
-
365
- setHistoryIndex(nextIndex)
366
- const nextValue = nextIndex === -1 ? draft : history[nextIndex]
367
- setInput(nextValue)
368
-
369
- // Set cursor to the end of the text
370
- setTimeout(() => {
371
- if (textareaRef.current?.resizableTextArea?.textArea != null) {
372
- const textArea = textareaRef.current.resizableTextArea.textArea
373
- const length = nextValue.length
374
- textArea.setSelectionRange(length, length)
375
- textArea.focus()
376
- }
377
- }, 0)
378
- }
379
- } catch (e) {
380
- console.error('Failed to navigate chat history', e)
381
- }
382
- }
383
-
384
- const handleSelectCompletion = (item: CompletionItem) => {
385
- if (triggerChar == null || textareaRef.current?.resizableTextArea?.textArea == null) return
386
-
387
- const textArea = textareaRef.current.resizableTextArea.textArea
388
- const cursorFallback = textArea.selectionStart
389
- const textBeforeTrigger = input.slice(0, input.lastIndexOf(triggerChar, cursorFallback - 1))
390
- const textAfterCursor = input.slice(cursorFallback)
391
-
392
- const newValue = `${textBeforeTrigger}${triggerChar}${item.value} ${textAfterCursor}`
393
- setInput(newValue)
394
- setShowCompletion(false)
395
-
396
- // Focus back and set cursor
397
- setTimeout(() => {
398
- if (textareaRef.current?.resizableTextArea?.textArea != null) {
399
- const textArea = textareaRef.current.resizableTextArea.textArea
400
- const newCursorPos = textBeforeTrigger.length + triggerChar.length + item.value.length + 1
401
- textArea.focus()
402
- textArea.setSelectionRange(newCursorPos, newCursorPos)
403
- }
404
- }, 0)
405
- }
406
-
407
- const handleTriggerClick = (char: string) => {
408
- if (textareaRef.current?.resizableTextArea?.textArea == null) return
409
- const textArea = textareaRef.current.resizableTextArea.textArea
410
- const cursor = textArea.selectionStart
411
- const textBefore = input.slice(0, cursor)
412
- const textAfter = input.slice(cursor)
413
-
414
- // Check if we need to add a space before the trigger char
415
- const needsSpaceBefore = textBefore.length > 0 && !textBefore.endsWith(' ')
416
- const trigger = needsSpaceBefore ? ` ${char}` : char
417
-
418
- const newValue = textBefore + trigger + textAfter
419
- setInput(newValue)
420
-
421
- setTimeout(() => {
422
- if (textareaRef.current?.resizableTextArea?.textArea != null) {
423
- const textArea = textareaRef.current.resizableTextArea.textArea
424
- const newPos = cursor + trigger.length
425
- textArea.focus()
426
- textArea.setSelectionRange(newPos, newPos)
427
-
428
- // Trigger handleInputChange logic manually
429
- const event = { target: textArea } as unknown as React.ChangeEvent<HTMLTextAreaElement>
430
- handleInputChange(event)
431
- }
432
- }, 0)
433
- }
434
-
435
- const handleKeyDown = (e: React.KeyboardEvent) => {
436
- if (isShortcutMatch(e, resolvedSendShortcut, isMac)) {
437
- e.preventDefault()
438
- handleSend()
439
- return
440
- }
441
- if (
442
- clearInputShortcut != null && clearInputShortcut.trim() !== '' && isShortcutMatch(e, clearInputShortcut, isMac)
443
- ) {
444
- e.preventDefault()
445
- clearInputValue()
446
- return
447
- }
448
- if (showCompletion) {
449
- if (e.key === 'ArrowDown') {
450
- e.preventDefault()
451
- setSelectedIndex(prev => (prev + 1) % completionItems.length)
452
- return
453
- }
454
- if (e.key === 'ArrowUp') {
455
- e.preventDefault()
456
- setSelectedIndex(prev => (prev - 1 + completionItems.length) % completionItems.length)
457
- return
458
- }
459
- if (e.key === 'Enter' || e.key === 'Tab') {
460
- e.preventDefault()
461
- const selectedItem = completionItems[selectedIndex]
462
- if (selectedItem != null) {
463
- handleSelectCompletion(selectedItem)
464
- }
465
- return
466
- }
467
- if (e.key === 'Escape') {
468
- e.preventDefault()
469
- setShowCompletion(false)
470
- return
471
- }
472
- }
473
-
474
- // History navigation logic
475
- if (e.key === 'ArrowUp') {
476
- const textarea = e.target as HTMLTextAreaElement
477
- const cursorPosition = textarea.selectionStart
478
- const textBeforeCursor = textarea.value.substring(0, cursorPosition)
479
-
480
- // Only navigate if cursor is at the first line
481
- if (!textBeforeCursor.includes('\n')) {
482
- const historyJson = localStorage.getItem('vf_chat_history')
483
- const history = (historyJson != null ? JSON.parse(historyJson) : []) as string[]
484
- const currentHistoryValue = historyIndex === -1 ? null : history[historyIndex]
485
-
486
- // If content is empty OR content matches the current history entry, allow navigation
487
- if (input.trim() === '' || input === currentHistoryValue) {
488
- e.preventDefault()
489
- handleHistoryNavigation('up')
490
- return
491
- }
492
- }
493
- }
494
-
495
- if (e.key === 'ArrowDown') {
496
- const textarea = e.target as HTMLTextAreaElement
497
- const cursorPosition = textarea.selectionEnd
498
- const textAfterCursor = textarea.value.substring(cursorPosition)
499
-
500
- // Only navigate if cursor is at the last line
501
- if (!textAfterCursor.includes('\n')) {
502
- const historyJson = localStorage.getItem('vf_chat_history')
503
- const history = (historyJson != null ? JSON.parse(historyJson) : []) as string[]
504
- const currentHistoryValue = historyIndex === -1 ? null : history[historyIndex]
505
-
506
- // If history navigation has started (index >= 0) OR content matches current history entry
507
- if (historyIndex !== -1 || input === currentHistoryValue) {
508
- e.preventDefault()
509
- handleHistoryNavigation('down')
510
- return
511
- }
512
- }
513
- }
514
-
515
- // More shortcuts
516
- if (e.key === 'Escape') {
517
- if (input !== '') {
518
- e.preventDefault()
519
- clearInputValue()
520
- }
521
- return
522
- }
523
-
524
- // Cmd/Ctrl + L to clear screen
525
- if (e.key === 'l' && (e.metaKey || e.ctrlKey)) {
526
- e.preventDefault()
527
- setInput('')
528
- setHistoryIndex(-1)
529
- if (onClear != null) {
530
- onClear()
531
- } else {
532
- void message.info('Clear screen is not supported in this context')
533
- }
534
- return
535
- }
536
-
537
- // Cmd/Ctrl + Enter to send
538
- if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
539
- e.preventDefault()
540
- handleSend()
541
- }
542
- }
543
-
544
- const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
545
- const value = e.target.value
546
- setInput(value)
547
-
548
- const cursor = e.target.selectionStart
549
- const charBeforeCursor = value[cursor - 1]
550
-
551
- if (['/', '@', '#'].includes(charBeforeCursor)) {
552
- setTriggerChar(charBeforeCursor)
553
- let items: CompletionItem[] = []
554
-
555
- if (sessionInfo?.type === 'init') {
556
- const info = sessionInfo
557
- if (charBeforeCursor === '/') {
558
- items = (info.slashCommands != null ? info.slashCommands : []).map((cmd: string) => ({
559
- label: `/${cmd}`,
560
- value: cmd,
561
- icon: 'terminal'
562
- }))
563
- } else if (charBeforeCursor === '@') {
564
- items = (info.agents != null ? info.agents : []).map((agent: string) => ({
565
- label: `@${agent}`,
566
- value: agent,
567
- icon: 'smart_toy'
568
- }))
569
- } else if (charBeforeCursor === '#') {
570
- items = (info.tools != null ? info.tools : []).map((tool: string) => ({
571
- label: `#${tool}`,
572
- value: tool,
573
- icon: 'check_box'
574
- }))
575
- }
576
- }
577
-
578
- if (items.length > 0) {
579
- setCompletionItems(items)
580
- setSelectedIndex(0)
581
- setShowCompletion(true)
582
- } else {
583
- setShowCompletion(false)
584
- }
585
- } else if (showCompletion) {
586
- // Filter logic could go here if needed
587
- if (!value.includes(triggerChar ?? '')) {
588
- setShowCompletion(false)
589
- }
590
- }
591
- }
10
+ export function Sender(props: SenderProps) {
11
+ const controller = useSenderController(props)
592
12
 
593
13
  return (
594
- <div className='chat-input-wrapper'>
595
- {isThinking && <ThinkingStatus />}
596
- {interactionRequest != null && (
597
- <div
598
- className='interaction-panel'
599
- style={{
600
- display: 'flex',
601
- flexDirection: 'column',
602
- maxHeight: '200px',
603
- overflowY: 'auto',
604
- marginBottom: '10px',
605
- gap: '8px',
606
- padding: '8px',
607
- border: '1px solid var(--border-color)',
608
- borderRadius: '8px',
609
- backgroundColor: 'var(--bg-color)'
610
- }}
611
- >
612
- <div className='interaction-question' style={{ fontWeight: 'bold' }}>
613
- {interactionRequest.payload.question}
614
- </div>
615
- {interactionRequest.payload.options?.map((option: NonNullable<AskUserQuestionParams['options']>[number]) => (
616
- <Button
617
- key={option.label}
618
- block
619
- style={{ height: 'auto', textAlign: 'left', display: 'block', padding: '8px 12px' }}
620
- onClick={() => onInteractionResponse?.(interactionRequest.id, option.label)}
621
- >
622
- <div style={{ fontWeight: 500 }}>{option.label}</div>
623
- {option.description && (
624
- <div style={{ fontSize: '12px', color: 'var(--sub-text-color)', marginTop: '4px' }}>
625
- {option.description}
626
- </div>
627
- )}
628
- </Button>
629
- ))}
630
- </div>
14
+ <div
15
+ className={[
16
+ 'chat-input-wrapper',
17
+ controller.hideSender ? 'chat-input-wrapper--permission' : '',
18
+ controller.isInlineEdit ? 'chat-input-wrapper--inline-edit' : ''
19
+ ].filter(Boolean).join(' ')}
20
+ >
21
+ {controller.isThinking && <ThinkingStatus />}
22
+ {!controller.isInlineEdit && controller.interactionRequest != null && (
23
+ <SenderInteractionPanel
24
+ interactionRequest={controller.interactionRequest}
25
+ permissionContext={controller.permissionContext}
26
+ deniedTools={controller.deniedTools}
27
+ reasons={controller.reasons}
28
+ onInteractionResponse={controller.interactionResponse}
29
+ />
631
30
  )}
632
- <div className='chat-input-container'>
633
- {connectionError && connectionError.trim() !== '' && (
634
- <div className='connection-error-banner'>
635
- <div className='connection-error-content'>
636
- <span className='material-symbols-rounded'>error</span>
637
- <div className='connection-error-copy'>
638
- <div className='connection-error-title'>{t('chat.connectionErrorTitle')}</div>
639
- <div className='connection-error-message'>{connectionError}</div>
640
- </div>
641
- </div>
642
- <Button size='small' onClick={onRetryConnection}>
643
- {t('chat.retryConnection')}
644
- </Button>
645
- </div>
646
- )}
647
- {modelUnavailable && (
648
- <div className='model-unavailable'>
649
- {t('chat.modelConfigRequired')}
650
- </div>
651
- )}
652
- {pendingImages.length > 0 && (
653
- <div className='pending-images'>
654
- {pendingImages.map(img => (
655
- <div key={img.id} className='pending-image'>
656
- <img src={img.url} alt={img.name ?? 'image'} />
657
- <div className='pending-image-remove' onClick={() => handleRemovePendingImage(img.id)}>
658
- <span className='material-symbols-rounded'>close</span>
659
- </div>
660
- </div>
661
- ))}
662
- </div>
663
- )}
664
- {showCompletion && (
665
- <CompletionMenu
666
- items={completionItems}
667
- selectedIndex={selectedIndex}
668
- onSelect={handleSelectCompletion}
669
- onClose={() => setShowCompletion(false)}
670
- />
671
- )}
672
- <TextArea
673
- ref={textareaRef}
674
- className='chat-input-textarea'
675
- placeholder={placeholder ?? interactionRequest?.payload.question ?? t('chat.inputPlaceholder')}
676
- value={input}
677
- onChange={handleInputChange}
678
- onKeyDown={handleKeyDown}
679
- onPaste={handlePaste}
680
- autoSize={{ minRows: 1, maxRows: 10 }}
681
- variant='borderless'
682
- disabled={modelUnavailable}
31
+ {!controller.hideSender && (
32
+ <SenderBody
33
+ isInlineEdit={controller.isInlineEdit}
34
+ isBusy={controller.isBusy}
35
+ modelUnavailable={controller.modelUnavailable}
36
+ errorBanner={controller.errorBanner}
37
+ onRetryConnection={controller.onRetryConnection}
38
+ pendingImages={controller.composer.pendingImages}
39
+ pendingFiles={controller.composer.pendingFiles}
40
+ onRemovePendingImage={(id) =>
41
+ controller.composer.setPendingImages(prev => prev.filter(image => image.id !== id))}
42
+ onRemovePendingFile={(path) =>
43
+ controller.composer.setPendingFiles(prev => prev.filter(file => file.path !== path))}
44
+ editorRef={controller.editorRef}
45
+ sessionInfo={props.sessionInfo}
46
+ placeholder={controller.placeholder}
47
+ input={controller.composer.input}
48
+ onInputChange={controller.onInputChange}
49
+ onCursorChange={controller.onCursorChange}
50
+ onKeyDown={controller.handleKeyDown}
51
+ onPaste={controller.attachments.handlePaste}
52
+ resolveCompletionMatch={controller.completion.resolveCompletionMatch}
53
+ resolveTokenDecorations={controller.completion.resolveTokenDecorations}
54
+ toolbarState={controller.toolbar.toolbarState}
55
+ toolbarData={controller.toolbar.toolbarData}
56
+ toolbarRefs={controller.toolbar.toolbarRefs}
57
+ toolbarHandlers={controller.toolbar.toolbarHandlers}
58
+ showContextPicker={controller.attachments.showContextPicker}
59
+ onCancelContextPicker={controller.onCancelContextPicker}
60
+ onConfirmContextPicker={controller.onConfirmContextPicker}
683
61
  />
684
-
685
- <div className='chat-input-toolbar'>
686
- <input
687
- ref={fileInputRef}
688
- type='file'
689
- accept='image/*'
690
- multiple
691
- onChange={handleImageFileChange}
692
- className='file-input-hidden'
693
- />
694
- <div className='toolbar-left'>
695
- <Tooltip title='快捷指令'>
696
- <span>
697
- <div className='toolbar-btn' onClick={() => handleTriggerClick('/')}>
698
- <span className='material-symbols-rounded'>terminal</span>
699
- </div>
700
- </span>
701
- </Tooltip>
702
- <Tooltip title='提及代理'>
703
- <span>
704
- <div className='toolbar-btn' onClick={() => handleTriggerClick('@')}>
705
- <span className='material-symbols-rounded'>smart_toy</span>
706
- </div>
707
- </span>
708
- </Tooltip>
709
- <Tooltip title='注入上下文'>
710
- <span>
711
- <div className='toolbar-btn' onClick={() => handleTriggerClick('#')}>
712
- <span className='material-symbols-rounded'>description</span>
713
- </div>
714
- </span>
715
- </Tooltip>
716
- <Tooltip title='上传图片'>
717
- <span>
718
- <div className='toolbar-btn' onClick={handleImageUpload}>
719
- <span className='material-symbols-rounded'>image</span>
720
- </div>
721
- </span>
722
- </Tooltip>
723
-
724
- {sessionInfo != null && sessionInfo.type === 'init' && (
725
- <div className='session-info-toolbar'>
726
- {selectionWarnings.length > 0 && (
727
- <Tooltip
728
- placement='topLeft'
729
- title={
730
- <div className='asset-warning-tooltip'>
731
- <div className='asset-warning-tooltip__title'>{t('chat.selectionWarningsTitle')}</div>
732
- {selectionWarnings.slice(0, 5).map((warning: SessionSelectionWarning, index: number) => (
733
- <div
734
- key={`${warning.adapter}:${warning.requestedModel}:${index}`}
735
- className='asset-warning-tooltip__item'
736
- >
737
- <span>{formatSelectionWarning(warning)}</span>
738
- </div>
739
- ))}
740
- {selectionWarnings.length > 5 && (
741
- <div className='asset-warning-tooltip__more'>
742
- {t('chat.assetWarningsMore', { count: selectionWarnings.length - 5 })}
743
- </div>
744
- )}
745
- </div>
746
- }
747
- >
748
- <div className='info-item asset-warning-item'>
749
- <span className='info-item-leading'>
750
- <span className='material-symbols-rounded'>warning</span>
751
- </span>
752
- <span className='info-text'>
753
- {t('chat.selectionWarningsCount', { count: selectionWarnings.length })}
754
- </span>
755
- </div>
756
- </Tooltip>
757
- )}
758
- {assetWarnings.length > 0 && (
759
- <Tooltip
760
- placement='topLeft'
761
- title={
762
- <div className='asset-warning-tooltip'>
763
- <div className='asset-warning-tooltip__title'>{t('chat.assetWarningsTitle')}</div>
764
- {assetWarnings.slice(0, 5).map((warning: SessionAssetDiagnostic) => (
765
- <div key={warning.assetId} className='asset-warning-tooltip__item'>
766
- <code>{warning.assetId}</code>
767
- <span>{warning.reason}</span>
768
- </div>
769
- ))}
770
- {assetWarnings.length > 5 && (
771
- <div className='asset-warning-tooltip__more'>
772
- {t('chat.assetWarningsMore', { count: assetWarnings.length - 5 })}
773
- </div>
774
- )}
775
- </div>
776
- }
777
- >
778
- <div className='info-item asset-warning-item'>
779
- <span className='info-item-leading'>
780
- <span className='material-symbols-rounded'>warning</span>
781
- </span>
782
- <span className='info-text'>{t('chat.assetWarningsCount', { count: assetWarnings.length })}</span>
783
- </div>
784
- </Tooltip>
785
- )}
786
- <Cascader
787
- open={showToolsList}
788
- options={toolCascaderOptions}
789
- expandTrigger='hover'
790
- placement='topLeft'
791
- allowClear={false}
792
- popupClassName='sender-tools-cascader-popup'
793
- onOpenChange={setShowToolsList}
794
- onChange={() => setShowToolsList(false)}
795
- >
796
- <div className={`info-item ${showToolsList ? 'active' : ''}`}>
797
- <span className='info-item-leading'>
798
- <span className='material-symbols-rounded'>build</span>
799
- </span>
800
- <span className='info-text'>{t('chat.toolsCount', { count: sessionInfo.tools.length })}</span>
801
- <span className='material-symbols-rounded arrow-icon'>keyboard_arrow_up</span>
802
- </div>
803
- </Cascader>
804
- </div>
805
- )}
806
- </div>
807
-
808
- <div className='toolbar-right'>
809
- {adapterOptions && adapterOptions.length > 1 && (
810
- <Select
811
- className='adapter-select'
812
- classNames={{ popup: { root: 'adapter-select-popup' } }}
813
- value={selectedAdapter}
814
- options={adapterOptions}
815
- showSearch={false}
816
- allowClear={false}
817
- disabled={adapterLocked || modelUnavailable || isThinking}
818
- onChange={(value) => onAdapterChange?.(value)}
819
- placeholder={t('chat.adapterSelectPlaceholder', { defaultValue: 'Adapter' })}
820
- optionLabelProp='label'
821
- popupMatchSelectWidth={false}
822
- />
823
- )}
824
-
825
- <Select
826
- className='model-select'
827
- classNames={{ popup: { root: 'model-select-popup' } }}
828
- value={selectedModel}
829
- options={modelOptions ?? []}
830
- showSearch
831
- allowClear={false}
832
- disabled={modelUnavailable || isThinking}
833
- onChange={(value) => onModelChange?.(value)}
834
- placeholder={modelUnavailable ? t('chat.modelUnavailable') : t('chat.modelSelectPlaceholder')}
835
- optionLabelProp='displayLabel'
836
- filterOption={(input, option) => {
837
- const searchText = String((option as ModelSelectOption | undefined)?.searchText ?? '')
838
- return searchText.toLowerCase().includes(input.toLowerCase())
839
- }}
840
- popupMatchSelectWidth={false}
841
- />
842
-
843
- {supportsEffort && (
844
- <Select
845
- className='effort-select'
846
- classNames={{ popup: { root: 'effort-select-popup' } }}
847
- value={effort}
848
- options={effortOptions}
849
- showSearch={false}
850
- allowClear={false}
851
- disabled={modelUnavailable || isThinking}
852
- onChange={(value) => onEffortChange(value)}
853
- placeholder='Effort'
854
- optionLabelProp='label'
855
- popupMatchSelectWidth={false}
856
- />
857
- )}
858
-
859
- <Select
860
- className='permission-mode-select'
861
- classNames={{ popup: { root: 'permission-mode-select-popup' } }}
862
- value={permissionMode}
863
- options={permissionModeOptions}
864
- showSearch={false}
865
- allowClear={false}
866
- disabled={modelUnavailable || isThinking}
867
- onChange={(value) => onPermissionModeChange(value)}
868
- placeholder='权限模式'
869
- optionLabelProp='label'
870
- popupMatchSelectWidth={false}
871
- />
872
-
873
- <div
874
- className={`chat-send-btn ${input.trim() !== '' && !modelUnavailable ? 'active' : ''} ${
875
- isThinking ? 'thinking' : ''
876
- } ${modelUnavailable ? 'disabled' : ''}`}
877
- onClick={modelUnavailable ? undefined : (isThinking ? onInterrupt : handleSend)}
878
- >
879
- <span className='material-symbols-rounded'>
880
- {isThinking ? 'stop_circle' : 'send'}
881
- </span>
882
- </div>
883
- </div>
884
- </div>
885
- </div>
886
- <div className='chat-input-hint'>
887
- {t('chat.hint')}
888
- </div>
62
+ )}
889
63
  </div>
890
64
  )
891
65
  }