@vibe-forge/client 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-D9HxM0yv.js → arc-C1rWFTer.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-C_pTeiD4.js → blockDiagram-c4efeb88-DlZ9x70F.js} +1 -1
  9. package/dist/assets/{c4Diagram-c83219d4-_oYrCrLr.js → c4Diagram-c83219d4-BKKxi__y.js} +1 -1
  10. package/dist/assets/cameligo-Bf6VGUru.js +1 -0
  11. package/dist/assets/channel-F1aqMANO.js +1 -0
  12. package/dist/assets/{classDiagram-beda092f-9dnkgPhR.js → classDiagram-beda092f-CVGPySZq.js} +1 -1
  13. package/dist/assets/{classDiagram-v2-2358418a-BUIeZpdO.js → classDiagram-v2-2358418a-7kp8GVVj.js} +1 -1
  14. package/dist/assets/clojure-Dnu-v4kV.js +1 -0
  15. package/dist/assets/clone-B-GCuXNo.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-DuRFplcD.js → createText-1719965b-Dykv8kT9.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-B59COYVW.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-CTpq6Ih_.js → edges-96097737-CkZ1ZBro.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-CWFgdO1E.js → erDiagram-0228fc6a-281ADcRp.js} +1 -1
  33. package/dist/assets/flow9-BeJ5waoc.js +1 -0
  34. package/dist/assets/{flowDb-c6c81e3f-8Vdx29oZ.js → flowDb-c6c81e3f-BQjX_flP.js} +1 -1
  35. package/dist/assets/{flowDiagram-50d868cf-6S9wOVNl.js → flowDiagram-50d868cf-DMHZTjES.js} +1 -1
  36. package/dist/assets/flowDiagram-v2-4f6560a1-C5FzdVl1.js +1 -0
  37. package/dist/assets/{flowchart-elk-definition-6af322e1-DVcZ-ZBQ.js → flowchart-elk-definition-6af322e1-CI3yz4z8.js} +1 -1
  38. package/dist/assets/freemarker2-DWnWjibn.js +3 -0
  39. package/dist/assets/fsharp-PahG7c26.js +1 -0
  40. package/dist/assets/{ganttDiagram-a2739b55-DY1QSl-x.js → ganttDiagram-a2739b55-B3IING9L.js} +1 -1
  41. package/dist/assets/{gitGraphDiagram-82fe8481-BP08OBKL.js → gitGraphDiagram-82fe8481-CnArIr_T.js} +1 -1
  42. package/dist/assets/go-acbASCJo.js +1 -0
  43. package/dist/assets/{graph-D8GCszcN.js → graph-BZ1F0Yve.js} +1 -1
  44. package/dist/assets/graphql-BxJiqAUM.js +1 -0
  45. package/dist/assets/handlebars-C1QH9qTz.js +1 -0
  46. package/dist/assets/hcl-DtV1sZF8.js +1 -0
  47. package/dist/assets/html-D1NkqHjC.js +1 -0
  48. package/dist/assets/html.worker-BLJhxQJQ.js +470 -0
  49. package/dist/assets/htmlMode-DAZCE_rA.js +1 -0
  50. package/dist/assets/{index-5325376f-OOoAXU1u.js → index-5325376f-Da9zSHjA.js} +1 -1
  51. package/dist/assets/index-C0vjF3D0.js +1511 -0
  52. package/dist/assets/index-vzEbM21t.css +32 -0
  53. package/dist/assets/{infoDiagram-8eee0895-BHgkXjhJ.js → infoDiagram-8eee0895-DYbFvRM7.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-CoMjGRHa.js +1 -0
  57. package/dist/assets/{journeyDiagram-c64418c1-BljInElp.js → journeyDiagram-c64418c1-Boebox0b.js} +1 -1
  58. package/dist/assets/json.worker-usMZ-FED.js +58 -0
  59. package/dist/assets/jsonMode-D__gAvuz.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-Cw-SwAFD.js → layout-CTcHNbHp.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-Q-uZLuUr.js → line-4AwinCz2.js} +1 -1
  66. package/dist/assets/{linear-BahOnNn3.js → linear-CeSMLzJW.js} +1 -1
  67. package/dist/assets/liquid-DZF6egdE.js +1 -0
  68. package/dist/assets/lspLanguageFeatures-6K4lv5S2.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-Cnt4ka6w.js +1 -0
  73. package/dist/assets/{mermaid.core-BR0Qh3Rd.js → mermaid.core-B0yG5s4D.js} +6 -6
  74. package/dist/assets/{mindmap-definition-8da855dc-Brq2OJUZ.js → mindmap-definition-8da855dc-KJEvXMKj.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-uhYdiXro.js → pieDiagram-a8764435-17nFAXPJ.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-DA3TtjDv.js +1 -0
  92. package/dist/assets/qsharp-Bw9ernYp.js +1 -0
  93. package/dist/assets/{quadrantDiagram-1e28029f-TwqtEe7e.js → quadrantDiagram-1e28029f-Dt4vubi-.js} +1 -1
  94. package/dist/assets/r-j7ic8hl3.js +1 -0
  95. package/dist/assets/razor-CWDJgvX_.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-DSXnPRDS.js → requirementDiagram-08caed73-H6aDyDK-.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-EADpJeAn.js → sankeyDiagram-a04cb91d-DxsVtbjI.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-C8qXvt9z.js → sequenceDiagram-c5b8d532-BHa148XJ.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-5W0ghy_S.js → stateDiagram-1ecb1508-DgwBm8LO.js} +1 -1
  115. package/dist/assets/{stateDiagram-v2-c2b004d7-BfwOQJw4.js → stateDiagram-v2-c2b004d7-BK7IQLVc.js} +1 -1
  116. package/dist/assets/{styles-b4e223ce-EkKH_ULb.js → styles-b4e223ce-DzW27Bc-.js} +1 -1
  117. package/dist/assets/{styles-ca3715f6-DwhrtqlU.js → styles-ca3715f6-Dex2GiLT.js} +1 -1
  118. package/dist/assets/{styles-d45a18b0-yqL8KH6x.js → styles-d45a18b0-B6fGtDKS.js} +1 -1
  119. package/dist/assets/{svgDrawCommon-b86b1483--5VjnjhC.js → svgDrawCommon-b86b1483-B4HYgfV5.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-CWOTwik9.js → timeline-definition-faaaa080--QSbWb25.js} +1 -1
  124. package/dist/assets/ts.worker-DGHjMaqB.js +67731 -0
  125. package/dist/assets/tsMode-ZM7ocZCH.js +11 -0
  126. package/dist/assets/twig-Csy3S7wG.js +1 -0
  127. package/dist/assets/typescript-CKWDmBCc.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-DuEUAzPi.js +1 -0
  132. package/dist/assets/{xychartDiagram-f5964ef8-CZg47H1t.js → xychartDiagram-f5964ef8-D09Zkv2K.js} +1 -1
  133. package/dist/assets/yaml-DL7QPRYk.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 +353 -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 +157 -10
  160. package/src/components/chat/messages/MessageItem.tsx +382 -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/sender-toolbar-bindings.ts +155 -0
  204. package/src/components/chat/sender/@hooks/use-model-select-browser.tsx +189 -0
  205. package/src/components/chat/sender/@hooks/use-sender-attachments.ts +143 -0
  206. package/src/components/chat/sender/@hooks/use-sender-autofocus.ts +34 -0
  207. package/src/components/chat/sender/@hooks/use-sender-completion.ts +62 -0
  208. package/src/components/chat/sender/@hooks/use-sender-composer-state.ts +34 -0
  209. package/src/components/chat/sender/@hooks/use-sender-controller.ts +193 -0
  210. package/src/components/chat/sender/@hooks/use-sender-focus-restore.ts +72 -0
  211. package/src/components/chat/sender/@hooks/use-sender-history.ts +79 -0
  212. package/src/components/chat/sender/@hooks/use-sender-keydown.ts +113 -0
  213. package/src/components/chat/sender/@hooks/use-sender-reference-actions.ts +191 -0
  214. package/src/components/chat/sender/@hooks/use-sender-reference-focus-restore.ts +21 -0
  215. package/src/components/chat/sender/@hooks/use-sender-refs.ts +19 -0
  216. package/src/components/chat/sender/@hooks/use-sender-select-overlays.ts +83 -0
  217. package/src/components/chat/sender/@hooks/use-sender-shortcuts.ts +78 -0
  218. package/src/components/chat/sender/@hooks/use-sender-submit.ts +81 -0
  219. package/src/components/chat/sender/@types/sender-composer.ts +19 -0
  220. package/src/components/chat/sender/@types/sender-editor.ts +12 -0
  221. package/src/components/chat/sender/@types/sender-props.ts +50 -0
  222. package/src/components/chat/sender/@types/sender-toolbar-types.ts +83 -0
  223. package/src/components/chat/sender/@types/sender-types.ts +21 -0
  224. package/src/components/chat/sender/@utils/sender-completion.ts +164 -0
  225. package/src/components/chat/sender/@utils/sender-constants.ts +18 -0
  226. package/src/components/chat/sender/@utils/sender-utils.ts +45 -0
  227. package/src/components/chat/sender/Sender.scss +6 -737
  228. package/src/components/chat/sender/Sender.tsx +53 -921
  229. package/src/components/chat/session-metadata.ts +55 -0
  230. package/src/components/chat/terminal/@hooks/use-terminal-instance.ts +152 -0
  231. package/src/components/chat/terminal/@hooks/use-terminal-session.ts +196 -0
  232. package/src/components/chat/terminal/ChatTerminalView.scss +62 -0
  233. package/src/components/chat/terminal/ChatTerminalView.tsx +114 -0
  234. package/src/components/chat/tools/core/ToolGroup.scss +7 -0
  235. package/src/components/chat/tools/core/ToolGroup.tsx +94 -44
  236. package/src/components/config/ConfigSectionForm.tsx +8 -1
  237. package/src/components/config/ConfigShortcutInput.tsx +9 -2
  238. package/src/components/config/configSchema.ts +12 -2
  239. package/src/components/config/record-editors/ModelServicesRecordEditor.tsx +0 -14
  240. package/src/components/dock-panel/DockPanel.scss +152 -0
  241. package/src/components/dock-panel/DockPanel.tsx +195 -0
  242. package/src/components/layout/AppShell.scss +40 -2
  243. package/src/components/layout/AppShell.tsx +25 -10
  244. package/src/components/sidebar/SessionContextMenu.scss +143 -0
  245. package/src/components/sidebar/SessionContextMenu.tsx +196 -0
  246. package/src/components/sidebar/SessionContextMenuContent.tsx +89 -0
  247. package/src/components/sidebar/SessionItem.scss +150 -67
  248. package/src/components/sidebar/SessionItem.tsx +183 -134
  249. package/src/components/sidebar/SessionList.scss +47 -17
  250. package/src/components/sidebar/SessionList.tsx +31 -16
  251. package/src/components/sidebar/SidebarHeader.scss +329 -49
  252. package/src/components/sidebar/SidebarHeader.tsx +108 -86
  253. package/src/components/sidebar/SidebarHeaderBatchActions.tsx +81 -0
  254. package/src/components/sidebar/SidebarHeaderSearchActions.tsx +176 -0
  255. package/src/components/sidebar/SidebarHeaderSelectField.tsx +24 -0
  256. package/src/components/sidebar/filter-utils.ts +23 -0
  257. package/src/components/workspace/ContextFilePicker.scss +64 -0
  258. package/src/components/workspace/ContextFilePicker.tsx +171 -0
  259. package/src/connectionManager.ts +4 -2
  260. package/src/hooks/chat/interaction-state.ts +67 -0
  261. package/src/hooks/chat/model-selector-data-builders.ts +146 -0
  262. package/src/hooks/chat/model-selector-data-option-utils.ts +62 -0
  263. package/src/hooks/chat/model-selector-data-types.ts +27 -0
  264. package/src/hooks/chat/model-selector-data.ts +109 -0
  265. package/src/hooks/chat/model-selector-recommendations.ts +69 -0
  266. package/src/hooks/chat/model-selector.ts +9 -0
  267. package/src/hooks/chat/use-chat-interaction.ts +13 -8
  268. package/src/hooks/chat/use-chat-model-adapter-selection.tsx +167 -164
  269. package/src/hooks/chat/use-chat-models.tsx +46 -23
  270. package/src/hooks/chat/use-chat-session-actions.ts +69 -23
  271. package/src/hooks/chat/use-chat-session-messages.ts +158 -60
  272. package/src/hooks/chat/use-chat-session.ts +34 -9
  273. package/src/hooks/chat/use-chat-view.ts +26 -6
  274. package/src/hooks/chat/use-composer-control-shortcuts.ts +69 -0
  275. package/src/hooks/chat/use-terminal-dock-visibility.ts +39 -0
  276. package/src/hooks/use-roving-focus-list.ts +104 -0
  277. package/src/hooks/use-sidebar-navigation.ts +9 -4
  278. package/src/hooks/use-sidebar-query-state.ts +79 -0
  279. package/src/main.tsx +6 -1
  280. package/src/resources/locales/en.json +140 -6
  281. package/src/resources/locales/zh.json +140 -6
  282. package/src/routes/ChatRoute.scss +159 -4
  283. package/src/routes/ChatRoute.tsx +72 -10
  284. package/src/runtime-config.ts +21 -0
  285. package/src/store/index.ts +1 -3
  286. package/src/styles/global.scss +12 -2
  287. package/src/utils/chat-links.ts +21 -0
  288. package/src/utils/copy.ts +18 -0
  289. package/src/utils/shortcutUtils.ts +111 -1
  290. package/src/vite-env.d.ts +1 -0
  291. package/src/ws.ts +6 -5
  292. package/vite.config.ts +71 -7
  293. package/dist/assets/channel-Di8KkPU4.js +0 -1
  294. package/dist/assets/clone-B065n6L-.js +0 -1
  295. package/dist/assets/flowDiagram-v2-4f6560a1-nj-oVLPK.js +0 -1
  296. package/dist/assets/index-BZe1Qtye.css +0 -1
  297. package/dist/assets/index-DV3eI2aD.js +0 -557
  298. package/src/components/chat/sender/CompletionMenu.scss +0 -70
  299. package/src/components/chat/sender/CompletionMenu.tsx +0 -58
  300. /package/src/components/chat/{sender/ThinkingStatus.scss → ThinkingStatus.scss} +0 -0
  301. /package/src/components/chat/sender/{interaction-request.ts → @core/interaction-request.ts} +0 -0
@@ -1,932 +1,64 @@
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 { shouldHideSenderForInteraction } from './interaction-request'
17
- 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'
18
9
 
19
- const { TextArea } = Input
20
-
21
- interface ModelSelectOption {
22
- value: string
23
- label: React.ReactNode
24
- searchText: string
25
- displayLabel: string
26
- }
27
-
28
- interface ModelSelectGroup {
29
- label: React.ReactNode
30
- options: ModelSelectOption[]
31
- }
32
-
33
- interface PendingImage {
34
- id: string
35
- url: string
36
- name?: string
37
- size?: number
38
- mimeType?: string
39
- }
40
-
41
- type SessionAssetDiagnostic = NonNullable<Extract<SessionInfo, { type: 'init' }>['assetDiagnostics']>[number]
42
- type SessionSelectionWarning = NonNullable<Extract<SessionInfo, { type: 'init' }>['selectionWarnings']>[number]
43
-
44
- interface SenderToolGroup {
45
- key: 'chrome-devtools' | 'system'
46
- label: string
47
- tools: string[]
48
- }
49
-
50
- interface SenderToolOption {
51
- value: string
52
- label: React.ReactNode
53
- children?: SenderToolOption[]
54
- }
55
-
56
- const formatToolLabel = (tool: string) => {
57
- const parts = tool.split('__')
58
- return parts[parts.length - 1] || tool
59
- }
60
-
61
- const getToolGroupIcon = (groupKey: SenderToolGroup['key']) => {
62
- return groupKey === 'chrome-devtools' ? 'web_traffic' : 'memory'
63
- }
64
-
65
- export function Sender({
66
- onSend,
67
- onSendContent,
68
- adapterLocked = false,
69
- sessionStatus,
70
- onInterrupt,
71
- onClear,
72
- sessionInfo,
73
- connectionError,
74
- onRetryConnection,
75
- interactionRequest,
76
- onInteractionResponse,
77
- placeholder,
78
- modelOptions,
79
- selectedModel,
80
- onModelChange,
81
- effort,
82
- effortOptions,
83
- onEffortChange,
84
- permissionMode,
85
- permissionModeOptions,
86
- onPermissionModeChange,
87
- selectedAdapter,
88
- adapterOptions,
89
- onAdapterChange,
90
- modelUnavailable
91
- }: {
92
- onSend: (text: string) => void
93
- onSendContent: (content: ChatMessageContent[]) => void
94
- adapterLocked?: boolean
95
- sessionStatus?: SessionStatus
96
- onInterrupt: () => void
97
- onClear?: () => void
98
- sessionInfo?: SessionInfo | null
99
- connectionError?: string | null
100
- onRetryConnection?: () => void
101
- interactionRequest?: { id: string; payload: AskUserQuestionParams } | null
102
- onInteractionResponse?: (id: string, data: string | string[]) => void
103
- placeholder?: string
104
- modelOptions?: ModelSelectGroup[]
105
- selectedModel?: string
106
- onModelChange?: (model: string) => void
107
- effort: ChatEffort
108
- effortOptions: Array<{ value: ChatEffort; label: React.ReactNode }>
109
- onEffortChange: (effort: ChatEffort) => void
110
- permissionMode: PermissionMode
111
- permissionModeOptions: Array<{ value: PermissionMode; label: React.ReactNode }>
112
- onPermissionModeChange: (mode: PermissionMode) => void
113
- selectedAdapter?: string
114
- adapterOptions?: Array<{ value: string; label: React.ReactNode }>
115
- onAdapterChange?: (adapter: string) => void
116
- modelUnavailable?: boolean
117
- }) {
118
- const { t } = useTranslation()
119
- const { message } = App.useApp()
120
- const [input, setInput] = useState('')
121
- const [showCompletion, setShowCompletion] = useState(false)
122
- const [completionItems, setCompletionItems] = useState<CompletionItem[]>([])
123
- const [selectedIndex, setSelectedIndex] = useState(0)
124
- const [triggerChar, setTriggerChar] = useState<string | null>(null)
125
-
126
- const [showToolsList, setShowToolsList] = useState(false)
127
- const textareaRef = useRef<TextAreaRef>(null)
128
- const fileInputRef = useRef<HTMLInputElement>(null)
129
- const isMac = navigator.platform.includes('Mac')
130
- const [pendingImages, setPendingImages] = useState<PendingImage[]>([])
131
-
132
- const { data: configRes } = useSWR<{
133
- sources?: {
134
- merged?: {
135
- shortcuts?: {
136
- sendMessage?: string
137
- clearInput?: string
138
- }
139
- }
140
- }
141
- }>('/api/config')
142
- const sendShortcut = configRes?.sources?.merged?.shortcuts?.sendMessage
143
- const clearInputShortcut = configRes?.sources?.merged?.shortcuts?.clearInput
144
- const resolvedSendShortcut = sendShortcut != null && sendShortcut.trim() !== ''
145
- ? sendShortcut
146
- : 'mod+enter'
147
-
148
- const isThinking = sessionStatus === 'running'
149
- const supportsEffort = selectedAdapter === 'codex' || selectedAdapter === 'claude-code' ||
150
- selectedAdapter === 'opencode'
151
- const groupedTools: SenderToolGroup[] = sessionInfo != null && sessionInfo.type === 'init'
152
- ? ([
153
- {
154
- key: 'chrome-devtools',
155
- label: t('chat.toolGroupChromeDevtools'),
156
- tools: sessionInfo.tools.filter((tool: string) => tool.startsWith('mcp__ChromeDevtools__'))
157
- },
158
- {
159
- key: 'system',
160
- label: t('chat.toolGroupSystem'),
161
- tools: sessionInfo.tools.filter((tool: string) => !tool.startsWith('mcp__ChromeDevtools__'))
162
- }
163
- ] satisfies SenderToolGroup[]).filter((group): group is SenderToolGroup => group.tools.length > 0)
164
- : []
165
- const assetWarnings = sessionInfo != null && sessionInfo.type === 'init'
166
- ? (sessionInfo.assetDiagnostics ?? []).filter((diagnostic: SessionAssetDiagnostic) =>
167
- diagnostic.status === 'skipped'
168
- )
169
- : []
170
- const selectionWarnings = sessionInfo != null && sessionInfo.type === 'init'
171
- ? (sessionInfo.selectionWarnings ?? [])
172
- : []
173
- const toolCascaderOptions: SenderToolOption[] = groupedTools.map(group => ({
174
- value: group.key,
175
- label: (
176
- <span className='sender-tool-group-option'>
177
- <span className='sender-tool-group-option__icon material-symbols-rounded'>{getToolGroupIcon(group.key)}</span>
178
- <span className='sender-tool-group-option__text'>{group.label}</span>
179
- <span className='sender-tool-group-option__count'>{group.tools.length}</span>
180
- </span>
181
- ),
182
- children: group.tools.map(tool => ({
183
- value: tool,
184
- label: (
185
- <span className='sender-tool-option'>
186
- <span className='sender-tool-option__dot' />
187
- <span className='sender-tool-option__text'>{formatToolLabel(tool)}</span>
188
- </span>
189
- )
190
- }))
191
- }))
192
-
193
- const [historyIndex, setHistoryIndex] = useState(-1)
194
- const [draft, setDraft] = useState('')
195
-
196
- const formatSelectionWarning = (warning: SessionSelectionWarning) => {
197
- const reason = warning.reason === 'excluded'
198
- ? t('chat.selectionWarningReasonExcluded')
199
- : t('chat.selectionWarningReasonNotIncluded')
200
-
201
- return t('chat.selectionWarningFallback', {
202
- adapter: warning.adapter,
203
- requestedModel: warning.requestedModel,
204
- resolvedModel: warning.resolvedModel,
205
- reason
206
- })
207
- }
208
-
209
- const readFileAsDataUrl = (file: File) => {
210
- return new Promise<string>((resolve, reject) => {
211
- const reader = new FileReader()
212
- reader.onload = () => {
213
- resolve(typeof reader.result === 'string' ? reader.result : '')
214
- }
215
- reader.onerror = () => {
216
- reject(new Error('read_failed'))
217
- }
218
- reader.readAsDataURL(file)
219
- })
220
- }
221
-
222
- const addImageFiles = async (files: File[]) => {
223
- const maxSize = 5 * 1024 * 1024
224
- for (const file of files) {
225
- if (!file.type.startsWith('image/')) continue
226
- if (file.size > maxSize) {
227
- void message.error(t('chat.imageTooLarge'))
228
- continue
229
- }
230
- try {
231
- const url = await readFileAsDataUrl(file)
232
- if (url === '') {
233
- void message.error(t('chat.imageReadFailed'))
234
- continue
235
- }
236
- setPendingImages(prev => [
237
- ...prev,
238
- {
239
- id: globalThis.crypto?.randomUUID
240
- ? globalThis.crypto.randomUUID()
241
- : `img-${Date.now()}-${Math.random().toString(16).slice(2)}`,
242
- url,
243
- name: file.name,
244
- size: file.size,
245
- mimeType: file.type
246
- }
247
- ])
248
- } catch (err) {
249
- void message.error(t('chat.imageReadFailed'))
250
- }
251
- }
252
- }
253
-
254
- const handleSend = () => {
255
- if (isThinking) return
256
- if (input.trim() === '' && pendingImages.length === 0) return
257
-
258
- if (modelUnavailable) {
259
- void message.warning(t('chat.modelConfigRequired'))
260
- return
261
- }
262
-
263
- if (interactionRequest != null && onInteractionResponse != null) {
264
- if (pendingImages.length > 0) {
265
- void message.warning(t('chat.imageNotSupportedInInteraction'))
266
- return
267
- }
268
- onInteractionResponse(interactionRequest.id, input.trim())
269
- setInput('')
270
- return
271
- }
272
-
273
- if (pendingImages.length > 0) {
274
- const content: ChatMessageContent[] = []
275
- if (input.trim() !== '') {
276
- content.push({ type: 'text', text: input.trim() })
277
- }
278
- content.push(...pendingImages.map((img): ChatMessageContent => ({
279
- type: 'image',
280
- url: img.url,
281
- name: img.name,
282
- size: img.size,
283
- mimeType: img.mimeType
284
- })))
285
- onSendContent(content)
286
- } else {
287
- onSend(input)
288
- }
289
-
290
- // Save to local storage history
291
- try {
292
- const history = JSON.parse(localStorage.getItem('vf_chat_history') ?? '[]') as string[]
293
- const newHistory = [input, ...history.filter((h: string) => h !== input)].slice(0, 50)
294
- localStorage.setItem('vf_chat_history', JSON.stringify(newHistory))
295
- } catch (e) {
296
- console.error('Failed to save chat history', e)
297
- }
298
-
299
- setInput('')
300
- setPendingImages([])
301
- setDraft('')
302
- setShowCompletion(false)
303
- setHistoryIndex(-1)
304
- }
305
-
306
- const handleImageUpload = () => {
307
- if (isThinking) return
308
- if (modelUnavailable) {
309
- void message.warning(t('chat.modelConfigRequired'))
310
- return
311
- }
312
- if (interactionRequest != null) {
313
- void message.warning(t('chat.imageNotSupportedInInteraction'))
314
- return
315
- }
316
- fileInputRef.current?.click()
317
- }
318
-
319
- const handleImageFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
320
- const fileList = Array.from(event.target.files ?? [])
321
- if (fileList.length === 0) return
322
- await addImageFiles(fileList)
323
- event.target.value = ''
324
- }
325
-
326
- const handleRemovePendingImage = (id: string) => {
327
- setPendingImages(prev => prev.filter(img => img.id !== id))
328
- }
329
-
330
- const handlePaste = async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
331
- const items = Array.from(event.clipboardData?.items ?? [])
332
- const files = items
333
- .filter(item => item.kind === 'file' && item.type.startsWith('image/'))
334
- .map(item => item.getAsFile())
335
- .filter((file): file is File => file != null)
336
- if (files.length > 0) {
337
- event.preventDefault()
338
- await addImageFiles(files)
339
- }
340
- }
341
-
342
- const clearInputValue = () => {
343
- if (input === '') return
344
- setInput('')
345
- setHistoryIndex(-1)
346
- }
347
-
348
- const handleHistoryNavigation = (direction: 'up' | 'down') => {
349
- try {
350
- const history = JSON.parse(localStorage.getItem('vf_chat_history') ?? '[]') as string[]
351
- if (history.length === 0) return
352
-
353
- let nextIndex = historyIndex
354
- if (direction === 'up') {
355
- nextIndex = Math.min(historyIndex + 1, history.length - 1)
356
- } else {
357
- nextIndex = Math.max(historyIndex - 1, -1)
358
- }
359
-
360
- if (nextIndex !== historyIndex) {
361
- // Save draft when leaving -1
362
- if (historyIndex === -1) {
363
- setDraft(input)
364
- }
365
-
366
- setHistoryIndex(nextIndex)
367
- const nextValue = nextIndex === -1 ? draft : history[nextIndex]
368
- setInput(nextValue)
369
-
370
- // Set cursor to the end of the text
371
- setTimeout(() => {
372
- if (textareaRef.current?.resizableTextArea?.textArea != null) {
373
- const textArea = textareaRef.current.resizableTextArea.textArea
374
- const length = nextValue.length
375
- textArea.setSelectionRange(length, length)
376
- textArea.focus()
377
- }
378
- }, 0)
379
- }
380
- } catch (e) {
381
- console.error('Failed to navigate chat history', e)
382
- }
383
- }
384
-
385
- const handleSelectCompletion = (item: CompletionItem) => {
386
- if (triggerChar == null || textareaRef.current?.resizableTextArea?.textArea == null) return
387
-
388
- const textArea = textareaRef.current.resizableTextArea.textArea
389
- const cursorFallback = textArea.selectionStart
390
- const textBeforeTrigger = input.slice(0, input.lastIndexOf(triggerChar, cursorFallback - 1))
391
- const textAfterCursor = input.slice(cursorFallback)
392
-
393
- const newValue = `${textBeforeTrigger}${triggerChar}${item.value} ${textAfterCursor}`
394
- setInput(newValue)
395
- setShowCompletion(false)
396
-
397
- // Focus back and set cursor
398
- setTimeout(() => {
399
- if (textareaRef.current?.resizableTextArea?.textArea != null) {
400
- const textArea = textareaRef.current.resizableTextArea.textArea
401
- const newCursorPos = textBeforeTrigger.length + triggerChar.length + item.value.length + 1
402
- textArea.focus()
403
- textArea.setSelectionRange(newCursorPos, newCursorPos)
404
- }
405
- }, 0)
406
- }
407
-
408
- const handleTriggerClick = (char: string) => {
409
- if (textareaRef.current?.resizableTextArea?.textArea == null) return
410
- const textArea = textareaRef.current.resizableTextArea.textArea
411
- const cursor = textArea.selectionStart
412
- const textBefore = input.slice(0, cursor)
413
- const textAfter = input.slice(cursor)
414
-
415
- // Check if we need to add a space before the trigger char
416
- const needsSpaceBefore = textBefore.length > 0 && !textBefore.endsWith(' ')
417
- const trigger = needsSpaceBefore ? ` ${char}` : char
418
-
419
- const newValue = textBefore + trigger + textAfter
420
- setInput(newValue)
421
-
422
- setTimeout(() => {
423
- if (textareaRef.current?.resizableTextArea?.textArea != null) {
424
- const textArea = textareaRef.current.resizableTextArea.textArea
425
- const newPos = cursor + trigger.length
426
- textArea.focus()
427
- textArea.setSelectionRange(newPos, newPos)
428
-
429
- // Trigger handleInputChange logic manually
430
- const event = { target: textArea } as unknown as React.ChangeEvent<HTMLTextAreaElement>
431
- handleInputChange(event)
432
- }
433
- }, 0)
434
- }
435
-
436
- const handleKeyDown = (e: React.KeyboardEvent) => {
437
- if (isShortcutMatch(e, resolvedSendShortcut, isMac)) {
438
- e.preventDefault()
439
- handleSend()
440
- return
441
- }
442
- if (
443
- clearInputShortcut != null && clearInputShortcut.trim() !== '' && isShortcutMatch(e, clearInputShortcut, isMac)
444
- ) {
445
- e.preventDefault()
446
- clearInputValue()
447
- return
448
- }
449
- if (showCompletion) {
450
- if (e.key === 'ArrowDown') {
451
- e.preventDefault()
452
- setSelectedIndex(prev => (prev + 1) % completionItems.length)
453
- return
454
- }
455
- if (e.key === 'ArrowUp') {
456
- e.preventDefault()
457
- setSelectedIndex(prev => (prev - 1 + completionItems.length) % completionItems.length)
458
- return
459
- }
460
- if (e.key === 'Enter' || e.key === 'Tab') {
461
- e.preventDefault()
462
- const selectedItem = completionItems[selectedIndex]
463
- if (selectedItem != null) {
464
- handleSelectCompletion(selectedItem)
465
- }
466
- return
467
- }
468
- if (e.key === 'Escape') {
469
- e.preventDefault()
470
- setShowCompletion(false)
471
- return
472
- }
473
- }
474
-
475
- // History navigation logic
476
- if (e.key === 'ArrowUp') {
477
- const textarea = e.target as HTMLTextAreaElement
478
- const cursorPosition = textarea.selectionStart
479
- const textBeforeCursor = textarea.value.substring(0, cursorPosition)
480
-
481
- // Only navigate if cursor is at the first line
482
- if (!textBeforeCursor.includes('\n')) {
483
- const historyJson = localStorage.getItem('vf_chat_history')
484
- const history = (historyJson != null ? JSON.parse(historyJson) : []) as string[]
485
- const currentHistoryValue = historyIndex === -1 ? null : history[historyIndex]
486
-
487
- // If content is empty OR content matches the current history entry, allow navigation
488
- if (input.trim() === '' || input === currentHistoryValue) {
489
- e.preventDefault()
490
- handleHistoryNavigation('up')
491
- return
492
- }
493
- }
494
- }
495
-
496
- if (e.key === 'ArrowDown') {
497
- const textarea = e.target as HTMLTextAreaElement
498
- const cursorPosition = textarea.selectionEnd
499
- const textAfterCursor = textarea.value.substring(cursorPosition)
500
-
501
- // Only navigate if cursor is at the last line
502
- if (!textAfterCursor.includes('\n')) {
503
- const historyJson = localStorage.getItem('vf_chat_history')
504
- const history = (historyJson != null ? JSON.parse(historyJson) : []) as string[]
505
- const currentHistoryValue = historyIndex === -1 ? null : history[historyIndex]
506
-
507
- // If history navigation has started (index >= 0) OR content matches current history entry
508
- if (historyIndex !== -1 || input === currentHistoryValue) {
509
- e.preventDefault()
510
- handleHistoryNavigation('down')
511
- return
512
- }
513
- }
514
- }
515
-
516
- // More shortcuts
517
- if (e.key === 'Escape') {
518
- if (input !== '') {
519
- e.preventDefault()
520
- clearInputValue()
521
- }
522
- return
523
- }
524
-
525
- // Cmd/Ctrl + L to clear screen
526
- if (e.key === 'l' && (e.metaKey || e.ctrlKey)) {
527
- e.preventDefault()
528
- setInput('')
529
- setHistoryIndex(-1)
530
- if (onClear != null) {
531
- onClear()
532
- } else {
533
- void message.info('Clear screen is not supported in this context')
534
- }
535
- return
536
- }
537
-
538
- // Cmd/Ctrl + Enter to send
539
- if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
540
- e.preventDefault()
541
- handleSend()
542
- }
543
- }
544
-
545
- const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
546
- const value = e.target.value
547
- setInput(value)
548
-
549
- const cursor = e.target.selectionStart
550
- const charBeforeCursor = value[cursor - 1]
551
-
552
- if (['/', '@', '#'].includes(charBeforeCursor)) {
553
- setTriggerChar(charBeforeCursor)
554
- let items: CompletionItem[] = []
555
-
556
- if (sessionInfo?.type === 'init') {
557
- const info = sessionInfo
558
- if (charBeforeCursor === '/') {
559
- items = (info.slashCommands != null ? info.slashCommands : []).map((cmd: string) => ({
560
- label: `/${cmd}`,
561
- value: cmd,
562
- icon: 'terminal'
563
- }))
564
- } else if (charBeforeCursor === '@') {
565
- items = (info.agents != null ? info.agents : []).map((agent: string) => ({
566
- label: `@${agent}`,
567
- value: agent,
568
- icon: 'smart_toy'
569
- }))
570
- } else if (charBeforeCursor === '#') {
571
- items = (info.tools != null ? info.tools : []).map((tool: string) => ({
572
- label: `#${tool}`,
573
- value: tool,
574
- icon: 'check_box'
575
- }))
576
- }
577
- }
578
-
579
- if (items.length > 0) {
580
- setCompletionItems(items)
581
- setSelectedIndex(0)
582
- setShowCompletion(true)
583
- } else {
584
- setShowCompletion(false)
585
- }
586
- } else if (showCompletion) {
587
- // Filter logic could go here if needed
588
- if (!value.includes(triggerChar ?? '')) {
589
- setShowCompletion(false)
590
- }
591
- }
592
- }
593
-
594
- const permissionContext = interactionRequest?.payload.kind === 'permission'
595
- ? interactionRequest.payload.permissionContext
596
- : undefined
597
- const hideSender = shouldHideSenderForInteraction(interactionRequest)
598
- const deniedTools = permissionContext?.deniedTools?.filter(tool => tool.trim() !== '') ?? []
599
- const reasons = permissionContext?.reasons?.filter(reason => reason.trim() !== '') ?? []
10
+ export function Sender(props: SenderProps) {
11
+ const controller = useSenderController(props)
600
12
 
601
13
  return (
602
- <div className={`chat-input-wrapper ${hideSender ? 'chat-input-wrapper--permission' : ''}`.trim()}>
603
- {isThinking && <ThinkingStatus />}
604
- {interactionRequest != null && (
605
- <div className='interaction-panel'>
606
- {permissionContext != null && (
607
- <div className='interaction-panel__badge'>
608
- <span className='material-symbols-rounded'>lock</span>
609
- <span>{t('chat.permissionRequestBadge')}</span>
610
- </div>
611
- )}
612
- <div className='interaction-question'>
613
- {interactionRequest.payload.question}
614
- </div>
615
- {permissionContext != null && (
616
- <div className='interaction-panel__context'>
617
- <div className='interaction-panel__meta'>
618
- {permissionContext.currentMode != null && permissionContext.currentMode !== '' && (
619
- <div className='interaction-panel__meta-item'>
620
- <span className='interaction-panel__meta-label'>{t('chat.permissionCurrentMode')}</span>
621
- <code>{permissionContext.currentMode}</code>
622
- </div>
623
- )}
624
- {permissionContext.suggestedMode != null && permissionContext.suggestedMode !== '' && (
625
- <div className='interaction-panel__meta-item'>
626
- <span className='interaction-panel__meta-label'>{t('chat.permissionSuggestedMode')}</span>
627
- <code>{permissionContext.suggestedMode}</code>
628
- </div>
629
- )}
630
- </div>
631
- {deniedTools.length > 0 && (
632
- <div className='interaction-panel__section'>
633
- <div className='interaction-panel__section-title'>{t('chat.permissionDeniedTools')}</div>
634
- <div className='interaction-panel__chips'>
635
- {deniedTools.map(tool => (
636
- <code key={tool} className='interaction-panel__chip'>{tool}</code>
637
- ))}
638
- </div>
639
- </div>
640
- )}
641
- {reasons.length > 0 && (
642
- <div className='interaction-panel__section'>
643
- <div className='interaction-panel__section-title'>{t('chat.permissionReasons')}</div>
644
- <div className='interaction-panel__reasons'>
645
- {reasons.map(reason => (
646
- <div key={reason} className='interaction-panel__reason'>{reason}</div>
647
- ))}
648
- </div>
649
- </div>
650
- )}
651
- </div>
652
- )}
653
- {interactionRequest.payload.options?.map((option: NonNullable<AskUserQuestionParams['options']>[number]) => (
654
- <Button
655
- key={option.value ?? option.label}
656
- block
657
- className='interaction-panel__option'
658
- onClick={() => onInteractionResponse?.(interactionRequest.id, option.value ?? option.label)}
659
- >
660
- <div className='interaction-panel__option-label'>{option.label}</div>
661
- {option.description && (
662
- <div className='interaction-panel__option-description'>
663
- {option.description}
664
- </div>
665
- )}
666
- </Button>
667
- ))}
668
- </div>
669
- )}
670
- {!hideSender && (
671
- <div className='chat-input-container'>
672
- {connectionError && connectionError.trim() !== '' && (
673
- <div className='connection-error-banner'>
674
- <div className='connection-error-content'>
675
- <span className='material-symbols-rounded'>error</span>
676
- <div className='connection-error-copy'>
677
- <div className='connection-error-title'>{t('chat.connectionErrorTitle')}</div>
678
- <div className='connection-error-message'>{connectionError}</div>
679
- </div>
680
- </div>
681
- <Button size='small' onClick={onRetryConnection}>
682
- {t('chat.retryConnection')}
683
- </Button>
684
- </div>
685
- )}
686
- {modelUnavailable && (
687
- <div className='model-unavailable'>
688
- {t('chat.modelConfigRequired')}
689
- </div>
690
- )}
691
- {pendingImages.length > 0 && (
692
- <div className='pending-images'>
693
- {pendingImages.map(img => (
694
- <div key={img.id} className='pending-image'>
695
- <img src={img.url} alt={img.name ?? 'image'} />
696
- <div className='pending-image-remove' onClick={() => handleRemovePendingImage(img.id)}>
697
- <span className='material-symbols-rounded'>close</span>
698
- </div>
699
- </div>
700
- ))}
701
- </div>
702
- )}
703
- {showCompletion && (
704
- <CompletionMenu
705
- items={completionItems}
706
- selectedIndex={selectedIndex}
707
- onSelect={handleSelectCompletion}
708
- onClose={() => setShowCompletion(false)}
709
- />
710
- )}
711
- <TextArea
712
- ref={textareaRef}
713
- className='chat-input-textarea'
714
- placeholder={placeholder ?? interactionRequest?.payload.question ?? t('chat.inputPlaceholder')}
715
- value={input}
716
- onChange={handleInputChange}
717
- onKeyDown={handleKeyDown}
718
- onPaste={handlePaste}
719
- autoSize={{ minRows: 1, maxRows: 10 }}
720
- variant='borderless'
721
- disabled={modelUnavailable}
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}
722
29
  />
723
-
724
- <div className='chat-input-toolbar'>
725
- <input
726
- ref={fileInputRef}
727
- type='file'
728
- accept='image/*'
729
- multiple
730
- onChange={handleImageFileChange}
731
- className='file-input-hidden'
732
- />
733
- <div className='toolbar-left'>
734
- <Tooltip title={t('chat.tooltipSlashCommands')}>
735
- <span>
736
- <div className='toolbar-btn' onClick={() => handleTriggerClick('/')}>
737
- <span className='material-symbols-rounded'>terminal</span>
738
- </div>
739
- </span>
740
- </Tooltip>
741
- <Tooltip title={t('chat.tooltipMentionAgents')}>
742
- <span>
743
- <div className='toolbar-btn' onClick={() => handleTriggerClick('@')}>
744
- <span className='material-symbols-rounded'>smart_toy</span>
745
- </div>
746
- </span>
747
- </Tooltip>
748
- <Tooltip title={t('chat.tooltipInjectContext')}>
749
- <span>
750
- <div className='toolbar-btn' onClick={() => handleTriggerClick('#')}>
751
- <span className='material-symbols-rounded'>description</span>
752
- </div>
753
- </span>
754
- </Tooltip>
755
- <Tooltip title={t('chat.tooltipUploadImages')}>
756
- <span>
757
- <div className='toolbar-btn' onClick={handleImageUpload}>
758
- <span className='material-symbols-rounded'>image</span>
759
- </div>
760
- </span>
761
- </Tooltip>
762
-
763
- {sessionInfo != null && sessionInfo.type === 'init' && (
764
- <div className='session-info-toolbar'>
765
- {selectionWarnings.length > 0 && (
766
- <Tooltip
767
- placement='topLeft'
768
- title={
769
- <div className='asset-warning-tooltip'>
770
- <div className='asset-warning-tooltip__title'>{t('chat.selectionWarningsTitle')}</div>
771
- {selectionWarnings.slice(0, 5).map((warning: SessionSelectionWarning, index: number) => (
772
- <div
773
- key={`${warning.adapter}:${warning.requestedModel}:${index}`}
774
- className='asset-warning-tooltip__item'
775
- >
776
- <span>{formatSelectionWarning(warning)}</span>
777
- </div>
778
- ))}
779
- {selectionWarnings.length > 5 && (
780
- <div className='asset-warning-tooltip__more'>
781
- {t('chat.assetWarningsMore', { count: selectionWarnings.length - 5 })}
782
- </div>
783
- )}
784
- </div>
785
- }
786
- >
787
- <div className='info-item asset-warning-item'>
788
- <span className='info-item-leading'>
789
- <span className='material-symbols-rounded'>warning</span>
790
- </span>
791
- <span className='info-text'>
792
- {t('chat.selectionWarningsCount', { count: selectionWarnings.length })}
793
- </span>
794
- </div>
795
- </Tooltip>
796
- )}
797
- {assetWarnings.length > 0 && (
798
- <Tooltip
799
- placement='topLeft'
800
- title={
801
- <div className='asset-warning-tooltip'>
802
- <div className='asset-warning-tooltip__title'>{t('chat.assetWarningsTitle')}</div>
803
- {assetWarnings.slice(0, 5).map((warning: SessionAssetDiagnostic) => (
804
- <div key={warning.assetId} className='asset-warning-tooltip__item'>
805
- <code>{warning.assetId}</code>
806
- <span>{warning.reason}</span>
807
- </div>
808
- ))}
809
- {assetWarnings.length > 5 && (
810
- <div className='asset-warning-tooltip__more'>
811
- {t('chat.assetWarningsMore', { count: assetWarnings.length - 5 })}
812
- </div>
813
- )}
814
- </div>
815
- }
816
- >
817
- <div className='info-item asset-warning-item'>
818
- <span className='info-item-leading'>
819
- <span className='material-symbols-rounded'>warning</span>
820
- </span>
821
- <span className='info-text'>{t('chat.assetWarningsCount', { count: assetWarnings.length })}</span>
822
- </div>
823
- </Tooltip>
824
- )}
825
- <Cascader
826
- open={showToolsList}
827
- options={toolCascaderOptions}
828
- expandTrigger='hover'
829
- placement='topLeft'
830
- allowClear={false}
831
- popupClassName='sender-tools-cascader-popup'
832
- onOpenChange={setShowToolsList}
833
- onChange={() => setShowToolsList(false)}
834
- >
835
- <div className={`info-item ${showToolsList ? 'active' : ''}`}>
836
- <span className='info-item-leading'>
837
- <span className='material-symbols-rounded'>build</span>
838
- </span>
839
- <span className='info-text'>{t('chat.toolsCount', { count: sessionInfo.tools.length })}</span>
840
- <span className='material-symbols-rounded arrow-icon'>keyboard_arrow_up</span>
841
- </div>
842
- </Cascader>
843
- </div>
844
- )}
845
- </div>
846
-
847
- <div className='toolbar-right'>
848
- {adapterOptions && adapterOptions.length > 1 && (
849
- <Select
850
- className='adapter-select'
851
- classNames={{ popup: { root: 'adapter-select-popup' } }}
852
- value={selectedAdapter}
853
- options={adapterOptions}
854
- showSearch={false}
855
- allowClear={false}
856
- disabled={adapterLocked || modelUnavailable || isThinking}
857
- onChange={(value) => onAdapterChange?.(value)}
858
- placeholder={t('chat.adapterSelectPlaceholder', { defaultValue: 'Adapter' })}
859
- optionLabelProp='label'
860
- popupMatchSelectWidth={false}
861
- />
862
- )}
863
-
864
- <Select
865
- className='model-select'
866
- classNames={{ popup: { root: 'model-select-popup' } }}
867
- value={selectedModel}
868
- options={modelOptions ?? []}
869
- showSearch
870
- allowClear={false}
871
- disabled={modelUnavailable || isThinking}
872
- onChange={(value) => onModelChange?.(value)}
873
- placeholder={modelUnavailable ? t('chat.modelUnavailable') : t('chat.modelSelectPlaceholder')}
874
- optionLabelProp='displayLabel'
875
- filterOption={(input, option) => {
876
- const searchText = String((option as ModelSelectOption | undefined)?.searchText ?? '')
877
- return searchText.toLowerCase().includes(input.toLowerCase())
878
- }}
879
- popupMatchSelectWidth={false}
880
- />
881
-
882
- {supportsEffort && (
883
- <Select
884
- className='effort-select'
885
- classNames={{ popup: { root: 'effort-select-popup' } }}
886
- value={effort}
887
- options={effortOptions}
888
- showSearch={false}
889
- allowClear={false}
890
- disabled={modelUnavailable || isThinking}
891
- onChange={(value) => onEffortChange(value)}
892
- placeholder={t('chat.effortSelectPlaceholder')}
893
- optionLabelProp='label'
894
- popupMatchSelectWidth={false}
895
- />
896
- )}
897
-
898
- <Select
899
- className='permission-mode-select'
900
- classNames={{ popup: { root: 'permission-mode-select-popup' } }}
901
- value={permissionMode}
902
- options={permissionModeOptions}
903
- showSearch={false}
904
- allowClear={false}
905
- disabled={modelUnavailable || isThinking}
906
- onChange={(value) => onPermissionModeChange(value)}
907
- placeholder={t('chat.permissionModeSelectPlaceholder')}
908
- optionLabelProp='label'
909
- popupMatchSelectWidth={false}
910
- />
911
-
912
- <div
913
- className={`chat-send-btn ${input.trim() !== '' && !modelUnavailable ? 'active' : ''} ${
914
- isThinking ? 'thinking' : ''
915
- } ${modelUnavailable ? 'disabled' : ''}`}
916
- onClick={modelUnavailable ? undefined : (isThinking ? onInterrupt : handleSend)}
917
- >
918
- <span className='material-symbols-rounded'>
919
- {isThinking ? 'stop_circle' : 'send'}
920
- </span>
921
- </div>
922
- </div>
923
- </div>
924
- </div>
925
30
  )}
926
- {!hideSender && (
927
- <div className='chat-input-hint'>
928
- {t('chat.hint')}
929
- </div>
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}
61
+ />
930
62
  )}
931
63
  </div>
932
64
  )