opencami 1.0.1 → 1.2.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 (346) hide show
  1. package/README.md +20 -3
  2. package/dist/client/analytics-dashboard.html +509 -0
  3. package/dist/client/analytics.json +598 -0
  4. package/dist/client/assets/_sessionKey-xUqdePGW.js +97 -0
  5. package/dist/client/assets/agents-BiadNAEz.js +2 -0
  6. package/dist/client/assets/agents-screen-C5TPJbry.js +1 -0
  7. package/dist/client/assets/button-seqh98ae.js +1 -0
  8. package/dist/client/assets/{connect-BRasuRbB.js → connect-DlVsi-ya.js} +2 -2
  9. package/dist/client/assets/file-explorer-screen-B0hwrjdF.js +1 -0
  10. package/dist/client/assets/files-dkr2U72N.js +2 -0
  11. package/dist/client/assets/index-CwoJpeG1.js +153 -0
  12. package/dist/client/assets/{index-DHGnKfAU.js → index-D1Et4XlM.js} +1 -1
  13. package/dist/client/assets/keyboard-shortcuts-dialog-zdnAmDiH.js +1 -0
  14. package/dist/client/assets/{main-BAHT5yqU.js → main-DOBfHXDF.js} +12 -12
  15. package/dist/client/assets/opencami-logo-Bxp_4d3X.js +1 -0
  16. package/dist/client/assets/react-DD8SkMJn.js +1 -0
  17. package/dist/client/assets/search-dialog-Ck1AQreu.js +1 -0
  18. package/dist/client/assets/session-export-dialog-Bh3zH4nZ.js +1 -0
  19. package/dist/client/assets/settings-dialog-CvUsgnnZ.js +1 -0
  20. package/dist/client/assets/styles-DogPvrGY.css +1 -0
  21. package/dist/client/assets/switch-BoGcWcnr.js +1 -0
  22. package/dist/client/assets/use-file-explorer-state-B5JLNsSa.js +12 -0
  23. package/dist/client/assets/useButton-csSB2uQJ.js +9 -0
  24. package/dist/client/manifest.json +8 -2
  25. package/dist/server/assets/{_sessionKey-qlAPF_Ft.js → _sessionKey-DQwkBUS7.js} +981 -229
  26. package/dist/server/assets/_tanstack-start-manifest_v-cW8K1Mnv.js +4 -0
  27. package/dist/server/assets/agents-Dz_i76VW.js +11 -0
  28. package/dist/server/assets/agents-screen-CqQPJndp.js +587 -0
  29. package/dist/server/assets/{connect-BWI_6rCm.js → connect-CIDOw12K.js} +34 -2
  30. package/dist/server/assets/{file-explorer-screen-DJXPEG_J.js → file-explorer-screen-BzvgvV8m.js} +7 -58
  31. package/dist/server/assets/{files-CONoVTGD.js → files-BxvRDIWU.js} +1 -1
  32. package/dist/server/assets/{index-Bp8QskbI.js → index-BwD7ufHE.js} +4 -3
  33. package/dist/server/assets/{index-COu2idHm.js → index-CmbNTqa2.js} +105 -14
  34. package/dist/server/assets/{keyboard-shortcuts-dialog-D5hqVX2v.js → keyboard-shortcuts-dialog-7OEtXUlW.js} +2 -2
  35. package/dist/server/assets/opencami-logo-C-43FL3R.js +24 -0
  36. package/dist/server/assets/{router-DbxyvprK.js → router-5qObg5aX.js} +368 -74
  37. package/dist/server/assets/{search-dialog-JY8C3mqa.js → search-dialog-BE2ZBkj0.js} +44 -12
  38. package/dist/server/assets/{session-export-dialog-D87uafPD.js → session-export-dialog-DRVbC8Q-.js} +2 -2
  39. package/dist/server/assets/{settings-dialog-CBj7njLM.js → settings-dialog-BBQcU55J.js} +204 -41
  40. package/dist/server/assets/switch-DnX0MjGS.js +28 -0
  41. package/dist/server/assets/tooltip-gbV6rEVv.js +60 -0
  42. package/dist/server/assets/{menu-D6n4DB0U.js → use-file-explorer-state-DMHdtb7D.js} +56 -57
  43. package/dist/server/server.js +2 -2
  44. package/package.json +1 -1
  45. package/dist/client/assets/_sessionKey-B-MBYHgD.js +0 -95
  46. package/dist/client/assets/abap-BdImnpbu.js +0 -1
  47. package/dist/client/assets/actionscript-3-CfeIJUat.js +0 -1
  48. package/dist/client/assets/ada-bCR0ucgS.js +0 -1
  49. package/dist/client/assets/andromeeda-C-Jbm3Hp.js +0 -1
  50. package/dist/client/assets/angular-html-CU67Zn6k.js +0 -1
  51. package/dist/client/assets/angular-ts-BwZT4LLn.js +0 -1
  52. package/dist/client/assets/apache-Pmp26Uib.js +0 -1
  53. package/dist/client/assets/apex-D8_7TLub.js +0 -1
  54. package/dist/client/assets/apl-dKokRX4l.js +0 -1
  55. package/dist/client/assets/applescript-Co6uUVPk.js +0 -1
  56. package/dist/client/assets/ara-BRHolxvo.js +0 -1
  57. package/dist/client/assets/asciidoc-Dv7Oe6Be.js +0 -1
  58. package/dist/client/assets/asm-D_Q5rh1f.js +0 -1
  59. package/dist/client/assets/astro-CbQHKStN.js +0 -1
  60. package/dist/client/assets/aurora-x-D-2ljcwZ.js +0 -1
  61. package/dist/client/assets/awk-DMzUqQB5.js +0 -1
  62. package/dist/client/assets/ayu-dark-CmMr59Fi.js +0 -1
  63. package/dist/client/assets/ballerina-BFfxhgS-.js +0 -1
  64. package/dist/client/assets/bat-BkioyH1T.js +0 -1
  65. package/dist/client/assets/beancount-k_qm7-4y.js +0 -1
  66. package/dist/client/assets/berry-uYugtg8r.js +0 -1
  67. package/dist/client/assets/bibtex-CHM0blh-.js +0 -1
  68. package/dist/client/assets/bicep-Bmn6On1c.js +0 -1
  69. package/dist/client/assets/blade-D4QpJJKB.js +0 -1
  70. package/dist/client/assets/bsl-BO_Y6i37.js +0 -1
  71. package/dist/client/assets/button-D_9OBT_f.js +0 -1
  72. package/dist/client/assets/c-BIGW1oBm.js +0 -1
  73. package/dist/client/assets/c3-VCDPK7BO.js +0 -1
  74. package/dist/client/assets/cadence-Bv_4Rxtq.js +0 -1
  75. package/dist/client/assets/cairo-KRGpt6FW.js +0 -1
  76. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +0 -1
  77. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +0 -1
  78. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +0 -1
  79. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +0 -1
  80. package/dist/client/assets/clarity-D53aC0YG.js +0 -1
  81. package/dist/client/assets/clojure-P80f7IUj.js +0 -1
  82. package/dist/client/assets/cmake-D1j8_8rp.js +0 -1
  83. package/dist/client/assets/cobol-nwyudZeR.js +0 -1
  84. package/dist/client/assets/codeowners-Bp6g37R7.js +0 -1
  85. package/dist/client/assets/codeql-DsOJ9woJ.js +0 -1
  86. package/dist/client/assets/coffee-Ch7k5sss.js +0 -1
  87. package/dist/client/assets/common-lisp-Cg-RD9OK.js +0 -1
  88. package/dist/client/assets/coq-DkFqJrB1.js +0 -1
  89. package/dist/client/assets/cpp-CofmeUqb.js +0 -1
  90. package/dist/client/assets/crystal-tKQVLTB8.js +0 -1
  91. package/dist/client/assets/csharp-K5feNrxe.js +0 -1
  92. package/dist/client/assets/css-DPfMkruS.js +0 -1
  93. package/dist/client/assets/csv-fuZLfV_i.js +0 -1
  94. package/dist/client/assets/cue-D82EKSYY.js +0 -1
  95. package/dist/client/assets/cypher-COkxafJQ.js +0 -1
  96. package/dist/client/assets/d-85-TOEBH.js +0 -1
  97. package/dist/client/assets/dark-plus-C3mMm8J8.js +0 -1
  98. package/dist/client/assets/dart-CF10PKvl.js +0 -1
  99. package/dist/client/assets/dax-CEL-wOlO.js +0 -1
  100. package/dist/client/assets/desktop-BmXAJ9_W.js +0 -1
  101. package/dist/client/assets/diff-D97Zzqfu.js +0 -1
  102. package/dist/client/assets/docker-BcOcwvcX.js +0 -1
  103. package/dist/client/assets/dotenv-Da5cRb03.js +0 -1
  104. package/dist/client/assets/dracula-BzJJZx-M.js +0 -1
  105. package/dist/client/assets/dracula-soft-BXkSAIEj.js +0 -1
  106. package/dist/client/assets/dream-maker-BtqSS_iP.js +0 -1
  107. package/dist/client/assets/edge-BkV0erSs.js +0 -1
  108. package/dist/client/assets/elixir-CDX3lj18.js +0 -1
  109. package/dist/client/assets/elm-DbKCFpqz.js +0 -1
  110. package/dist/client/assets/emacs-lisp-C9XAeP06.js +0 -1
  111. package/dist/client/assets/erb-BOJIQeun.js +0 -1
  112. package/dist/client/assets/erlang-DsQrWhSR.js +0 -1
  113. package/dist/client/assets/everforest-dark-BgDCqdQA.js +0 -1
  114. package/dist/client/assets/everforest-light-C8M2exoo.js +0 -1
  115. package/dist/client/assets/fennel-BYunw83y.js +0 -1
  116. package/dist/client/assets/file-explorer-screen-Daf4rIq9.js +0 -1
  117. package/dist/client/assets/files-ByeIMLfw.js +0 -2
  118. package/dist/client/assets/fish-BvzEVeQv.js +0 -1
  119. package/dist/client/assets/fluent-C4IJs8-o.js +0 -1
  120. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +0 -1
  121. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +0 -1
  122. package/dist/client/assets/fsharp-CXgrBDvD.js +0 -1
  123. package/dist/client/assets/gdresource-B7Tvp0Sc.js +0 -1
  124. package/dist/client/assets/gdscript-DTMYz4Jt.js +0 -1
  125. package/dist/client/assets/gdshader-DkwncUOv.js +0 -1
  126. package/dist/client/assets/genie-D0YGMca9.js +0 -1
  127. package/dist/client/assets/gherkin-DyxjwDmM.js +0 -1
  128. package/dist/client/assets/git-commit-F4YmCXRG.js +0 -1
  129. package/dist/client/assets/git-rebase-r7XF79zn.js +0 -1
  130. package/dist/client/assets/github-dark-DHJKELXO.js +0 -1
  131. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +0 -1
  132. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
  133. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
  134. package/dist/client/assets/github-light-DAi9KRSo.js +0 -1
  135. package/dist/client/assets/github-light-default-D7oLnXFd.js +0 -1
  136. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
  137. package/dist/client/assets/gleam-BspZqrRM.js +0 -1
  138. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +0 -1
  139. package/dist/client/assets/glimmer-ts-U6CK756n.js +0 -1
  140. package/dist/client/assets/glsl-DplSGwfg.js +0 -1
  141. package/dist/client/assets/gn-n2N0HUVH.js +0 -1
  142. package/dist/client/assets/gnuplot-DdkO51Og.js +0 -1
  143. package/dist/client/assets/go-Dn2_MT6a.js +0 -1
  144. package/dist/client/assets/graphql-ChdNCCLP.js +0 -1
  145. package/dist/client/assets/groovy-gcz8RCvz.js +0 -1
  146. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
  147. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
  148. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
  149. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
  150. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
  151. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
  152. package/dist/client/assets/hack-CaT9iCJl.js +0 -1
  153. package/dist/client/assets/haml-B8DHNrY2.js +0 -1
  154. package/dist/client/assets/handlebars-BL8al0AC.js +0 -1
  155. package/dist/client/assets/haskell-Df6bDoY_.js +0 -1
  156. package/dist/client/assets/haxe-CzTSHFRz.js +0 -1
  157. package/dist/client/assets/hcl-BWvSN4gD.js +0 -1
  158. package/dist/client/assets/hjson-D5-asLiD.js +0 -1
  159. package/dist/client/assets/hlsl-D3lLCCz7.js +0 -1
  160. package/dist/client/assets/houston-DnULxvSX.js +0 -1
  161. package/dist/client/assets/html-GMplVEZG.js +0 -1
  162. package/dist/client/assets/html-derivative-BFtXZ54Q.js +0 -1
  163. package/dist/client/assets/http-jrhK8wxY.js +0 -1
  164. package/dist/client/assets/hurl-irOxFIW8.js +0 -1
  165. package/dist/client/assets/hxml-Bvhsp5Yf.js +0 -1
  166. package/dist/client/assets/hy-DFXneXwc.js +0 -1
  167. package/dist/client/assets/imba-DGztddWO.js +0 -1
  168. package/dist/client/assets/index-ByUDBI-n.js +0 -14
  169. package/dist/client/assets/ini-BEwlwnbL.js +0 -1
  170. package/dist/client/assets/java-CylS5w8V.js +0 -1
  171. package/dist/client/assets/javascript-wDzz0qaB.js +0 -1
  172. package/dist/client/assets/jinja-4LBKfQ-Z.js +0 -1
  173. package/dist/client/assets/jison-wvAkD_A8.js +0 -1
  174. package/dist/client/assets/json-Cp-IABpG.js +0 -1
  175. package/dist/client/assets/json5-C9tS-k6U.js +0 -1
  176. package/dist/client/assets/jsonc-Des-eS-w.js +0 -1
  177. package/dist/client/assets/jsonl-DcaNXYhu.js +0 -1
  178. package/dist/client/assets/jsonnet-DFQXde-d.js +0 -1
  179. package/dist/client/assets/jssm-C2t-YnRu.js +0 -1
  180. package/dist/client/assets/jsx-g9-lgVsj.js +0 -1
  181. package/dist/client/assets/julia-CxzCAyBv.js +0 -1
  182. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +0 -1
  183. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +0 -1
  184. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +0 -1
  185. package/dist/client/assets/kdl-DV7GczEv.js +0 -1
  186. package/dist/client/assets/keyboard-shortcuts-dialog-agsWJ36q.js +0 -1
  187. package/dist/client/assets/kotlin-BdnUsdx6.js +0 -1
  188. package/dist/client/assets/kusto-DZf3V79B.js +0 -1
  189. package/dist/client/assets/laserwave-DUszq2jm.js +0 -1
  190. package/dist/client/assets/latex-B4uzh10-.js +0 -1
  191. package/dist/client/assets/lean-BZvkOJ9d.js +0 -1
  192. package/dist/client/assets/less-B1dDrJ26.js +0 -1
  193. package/dist/client/assets/light-plus-B7mTdjB0.js +0 -1
  194. package/dist/client/assets/liquid-DYVedYrR.js +0 -1
  195. package/dist/client/assets/llvm-BtvRca6l.js +0 -1
  196. package/dist/client/assets/log-2UxHyX5q.js +0 -1
  197. package/dist/client/assets/logo-BtOb2qkB.js +0 -1
  198. package/dist/client/assets/lua-BbnMAYS6.js +0 -1
  199. package/dist/client/assets/luau-C-HG3fhB.js +0 -1
  200. package/dist/client/assets/make-CHLpvVh8.js +0 -1
  201. package/dist/client/assets/markdown-Cvjx9yec.js +0 -1
  202. package/dist/client/assets/marko-DZsq8hO1.js +0 -1
  203. package/dist/client/assets/material-theme-D5KoaKCx.js +0 -1
  204. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +0 -1
  205. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +0 -1
  206. package/dist/client/assets/material-theme-ocean-CyktbL80.js +0 -1
  207. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
  208. package/dist/client/assets/matlab-D7o27uSR.js +0 -1
  209. package/dist/client/assets/mdc-DUICxH0z.js +0 -1
  210. package/dist/client/assets/mdx-Cmh6b_Ma.js +0 -1
  211. package/dist/client/assets/menu-BhVaz8Ly.js +0 -20
  212. package/dist/client/assets/mermaid-mWjccvbQ.js +0 -1
  213. package/dist/client/assets/min-dark-CafNBF8u.js +0 -1
  214. package/dist/client/assets/min-light-CTRr51gU.js +0 -1
  215. package/dist/client/assets/mipsasm-CKIfxQSi.js +0 -1
  216. package/dist/client/assets/mojo-B93PlW-d.js +0 -1
  217. package/dist/client/assets/monokai-D4h5O-jR.js +0 -1
  218. package/dist/client/assets/moonbit-Ba13S78F.js +0 -1
  219. package/dist/client/assets/move-Bu9oaDYs.js +0 -1
  220. package/dist/client/assets/narrat-DRg8JJMk.js +0 -1
  221. package/dist/client/assets/nextflow-BrzmwbiE.js +0 -1
  222. package/dist/client/assets/nginx-DknmC5AR.js +0 -1
  223. package/dist/client/assets/night-owl-C39BiMTA.js +0 -1
  224. package/dist/client/assets/nim-CVrawwO9.js +0 -1
  225. package/dist/client/assets/nix-CwoSXNpI.js +0 -1
  226. package/dist/client/assets/nord-Ddv68eIx.js +0 -1
  227. package/dist/client/assets/nushell-C-sUppwS.js +0 -1
  228. package/dist/client/assets/objective-c-DXmwc3jG.js +0 -1
  229. package/dist/client/assets/objective-cpp-CLxacb5B.js +0 -1
  230. package/dist/client/assets/ocaml-C0hk2d4L.js +0 -1
  231. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +0 -1
  232. package/dist/client/assets/one-light-PoHY5YXO.js +0 -1
  233. package/dist/client/assets/openscad-C4EeE6gA.js +0 -1
  234. package/dist/client/assets/pascal-D93ZcfNL.js +0 -1
  235. package/dist/client/assets/perl-C0TMdlhV.js +0 -1
  236. package/dist/client/assets/php-CDn_0X-4.js +0 -1
  237. package/dist/client/assets/pkl-u5AG7uiY.js +0 -1
  238. package/dist/client/assets/plastic-3e1v2bzS.js +0 -1
  239. package/dist/client/assets/plsql-ChMvpjG-.js +0 -1
  240. package/dist/client/assets/po-BTJTHyun.js +0 -1
  241. package/dist/client/assets/poimandres-CS3Unz2-.js +0 -1
  242. package/dist/client/assets/polar-C0HS_06l.js +0 -1
  243. package/dist/client/assets/postcss-CXtECtnM.js +0 -1
  244. package/dist/client/assets/powerquery-CEu0bR-o.js +0 -1
  245. package/dist/client/assets/powershell-Dpen1YoG.js +0 -1
  246. package/dist/client/assets/prisma-Dd19v3D-.js +0 -1
  247. package/dist/client/assets/prolog-CbFg5uaA.js +0 -1
  248. package/dist/client/assets/proto-C7zT0LnQ.js +0 -1
  249. package/dist/client/assets/pug-CGlum2m_.js +0 -1
  250. package/dist/client/assets/puppet-BMWR74SV.js +0 -1
  251. package/dist/client/assets/purescript-CklMAg4u.js +0 -1
  252. package/dist/client/assets/python-B6aJPvgy.js +0 -1
  253. package/dist/client/assets/qml-3beO22l8.js +0 -1
  254. package/dist/client/assets/qmldir-C8lEn-DE.js +0 -1
  255. package/dist/client/assets/qss-IeuSbFQv.js +0 -1
  256. package/dist/client/assets/r-Dspwwk_N.js +0 -1
  257. package/dist/client/assets/racket-BqYA7rlc.js +0 -1
  258. package/dist/client/assets/raku-DXvB9xmW.js +0 -1
  259. package/dist/client/assets/razor-C1TweQQi.js +0 -1
  260. package/dist/client/assets/red-bN70gL4F.js +0 -1
  261. package/dist/client/assets/reg-C-SQnVFl.js +0 -1
  262. package/dist/client/assets/regexp-CDVJQ6XC.js +0 -1
  263. package/dist/client/assets/rel-C3B-1QV4.js +0 -1
  264. package/dist/client/assets/riscv-BM1_JUlF.js +0 -1
  265. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +0 -1
  266. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +0 -1
  267. package/dist/client/assets/rose-pine-qdsjHGoJ.js +0 -1
  268. package/dist/client/assets/rosmsg-BJDFO7_C.js +0 -1
  269. package/dist/client/assets/rst-B0xPkSld.js +0 -1
  270. package/dist/client/assets/ruby-BvKwtOVI.js +0 -1
  271. package/dist/client/assets/rust-B1yitclQ.js +0 -1
  272. package/dist/client/assets/sas-cz2c8ADy.js +0 -1
  273. package/dist/client/assets/sass-Cj5Yp3dK.js +0 -1
  274. package/dist/client/assets/scala-C151Ov-r.js +0 -1
  275. package/dist/client/assets/scheme-C98Dy4si.js +0 -1
  276. package/dist/client/assets/scss-OYdSNvt2.js +0 -1
  277. package/dist/client/assets/sdbl-DVxCFoDh.js +0 -1
  278. package/dist/client/assets/search-dialog-CHwnOyPS.js +0 -1
  279. package/dist/client/assets/session-export-dialog-uOlDUzgX.js +0 -1
  280. package/dist/client/assets/settings-dialog-D0aYDkAM.js +0 -1
  281. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +0 -1
  282. package/dist/client/assets/shellscript-Yzrsuije.js +0 -1
  283. package/dist/client/assets/shellsession-BADoaaVG.js +0 -1
  284. package/dist/client/assets/slack-dark-BthQWCQV.js +0 -1
  285. package/dist/client/assets/slack-ochin-DqwNpetd.js +0 -1
  286. package/dist/client/assets/smalltalk-BERRCDM3.js +0 -1
  287. package/dist/client/assets/snazzy-light-Bw305WKR.js +0 -1
  288. package/dist/client/assets/solarized-dark-DXbdFlpD.js +0 -1
  289. package/dist/client/assets/solarized-light-L9t79GZl.js +0 -1
  290. package/dist/client/assets/solidity-rGO070M0.js +0 -1
  291. package/dist/client/assets/soy-Brmx7dQM.js +0 -1
  292. package/dist/client/assets/sparql-rVzFXLq3.js +0 -1
  293. package/dist/client/assets/splunk-BtCnVYZw.js +0 -1
  294. package/dist/client/assets/sql-BLtJtn59.js +0 -1
  295. package/dist/client/assets/ssh-config-_ykCGR6B.js +0 -1
  296. package/dist/client/assets/stata-BH5u7GGu.js +0 -1
  297. package/dist/client/assets/styles-7aVSlb6l.css +0 -1
  298. package/dist/client/assets/stylus-BEDo0Tqx.js +0 -1
  299. package/dist/client/assets/svelte-zxCyuUbr.js +0 -1
  300. package/dist/client/assets/swift-Dg5xB15N.js +0 -1
  301. package/dist/client/assets/synthwave-84-CbfX1IO0.js +0 -1
  302. package/dist/client/assets/system-verilog-CnnmHF94.js +0 -1
  303. package/dist/client/assets/systemd-4A_iFExJ.js +0 -1
  304. package/dist/client/assets/talonscript-CkByrt1z.js +0 -1
  305. package/dist/client/assets/tasl-QIJgUcNo.js +0 -1
  306. package/dist/client/assets/tcl-dwOrl1Do.js +0 -1
  307. package/dist/client/assets/templ-W15q3VgB.js +0 -1
  308. package/dist/client/assets/terraform-BETggiCN.js +0 -1
  309. package/dist/client/assets/tex-CvyZ59Mk.js +0 -1
  310. package/dist/client/assets/tokyo-night-hegEt444.js +0 -1
  311. package/dist/client/assets/toml-vGWfd6FD.js +0 -1
  312. package/dist/client/assets/ts-tags-zn1MmPIZ.js +0 -1
  313. package/dist/client/assets/tsv-B_m7g4N7.js +0 -1
  314. package/dist/client/assets/tsx-COt5Ahok.js +0 -1
  315. package/dist/client/assets/turtle-BsS91CYL.js +0 -1
  316. package/dist/client/assets/twig-CO9l9SDP.js +0 -1
  317. package/dist/client/assets/typescript-BPQ3VLAy.js +0 -1
  318. package/dist/client/assets/typespec-BGHnOYBU.js +0 -1
  319. package/dist/client/assets/typst-DHCkPAjA.js +0 -1
  320. package/dist/client/assets/v-BcVCzyr7.js +0 -1
  321. package/dist/client/assets/vala-CsfeWuGM.js +0 -1
  322. package/dist/client/assets/vb-D17OF-Vu.js +0 -1
  323. package/dist/client/assets/verilog-BQ8w6xss.js +0 -1
  324. package/dist/client/assets/vesper-DU1UobuO.js +0 -1
  325. package/dist/client/assets/vhdl-CeAyd5Ju.js +0 -1
  326. package/dist/client/assets/viml-CJc9bBzg.js +0 -1
  327. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +0 -1
  328. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +0 -1
  329. package/dist/client/assets/vitesse-light-CVO1_9PV.js +0 -1
  330. package/dist/client/assets/vue-DN_0RTcg.js +0 -1
  331. package/dist/client/assets/vue-html-AaS7Mt5G.js +0 -1
  332. package/dist/client/assets/vue-vine-CQOfvN7w.js +0 -1
  333. package/dist/client/assets/vyper-CDx5xZoG.js +0 -1
  334. package/dist/client/assets/wasm-CG6Dc4jp.js +0 -1
  335. package/dist/client/assets/wasm-MzD3tlZU.js +0 -1
  336. package/dist/client/assets/wenyan-BV7otONQ.js +0 -1
  337. package/dist/client/assets/wgsl-Dx-B1_4e.js +0 -1
  338. package/dist/client/assets/wikitext-BhOHFoWU.js +0 -1
  339. package/dist/client/assets/wit-5i3qLPDT.js +0 -1
  340. package/dist/client/assets/wolfram-lXgVvXCa.js +0 -1
  341. package/dist/client/assets/xml-sdJ4AIDG.js +0 -1
  342. package/dist/client/assets/xsl-CtQFsRM5.js +0 -1
  343. package/dist/client/assets/yaml-Buea-lGh.js +0 -1
  344. package/dist/client/assets/zenscript-DVFEvuxE.js +0 -1
  345. package/dist/client/assets/zig-VOosw3JB.js +0 -1
  346. package/dist/server/assets/_tanstack-start-manifest_v-El8F-kd-.js +0 -4
@@ -3,23 +3,25 @@ import { Link, useNavigate } from "@tanstack/react-router";
3
3
  import * as React from "react";
4
4
  import React__default, { memo, useDeferredValue, useState, useMemo, useCallback, Suspense, lazy, useRef, useEffect, useId, useLayoutEffect, createContext, useContext } from "react";
5
5
  import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
6
- import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose, M as MenuRoot, e as MenuTrigger, f as MenuContent, g as MenuItem, T as TooltipProvider, h as TooltipRoot, i as TooltipTrigger, j as TooltipContent, s as setChatUiState, k as chatUiQueryKey, l as getChatUiState } from "./menu-D6n4DB0U.js";
6
+ import { T as TooltipProvider, a as TooltipRoot, b as TooltipTrigger, c as TooltipContent, s as setChatUiState, d as chatUiQueryKey, g as getChatUiState } from "./tooltip-gbV6rEVv.js";
7
7
  import { HugeiconsIcon } from "@hugeicons/react";
8
- import { MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, ArtificialIntelligence02Icon, CommandIcon, Cancel01Icon, Attachment01Icon, File01Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
8
+ import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, File01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
9
9
  import { motion, AnimatePresence } from "motion/react";
10
+ import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose, M as MenuRoot, e as MenuTrigger, f as MenuContent, g as MenuItem, u as useFileExplorerState } from "./use-file-explorer-state-DMHdtb7D.js";
10
11
  import { B as Button, c as cn, b as buttonVariants } from "./button-DtQ3rV1m.js";
11
12
  import { AlertDialog } from "@base-ui/react/alert-dialog";
12
13
  import { Collapsible as Collapsible$1 } from "@base-ui/react/collapsible";
13
14
  import { ScrollArea } from "@base-ui/react/scroll-area";
15
+ import { O as OpenCamiLogo, a as OpenCamiText } from "./opencami-logo-C-43FL3R.js";
14
16
  import { marked } from "marked";
15
17
  import ReactMarkdown from "react-markdown";
16
18
  import remarkBreaks from "remark-breaks";
17
19
  import remarkGfm from "remark-gfm";
18
- import { C as CodeBlock, u as useChatSettings$1 } from "./index-COu2idHm.js";
20
+ import { r as resolveLanguage, u as useChatSettingsStore, C as CodeBlock, a as useChatSettings$1 } from "./index-CmbNTqa2.js";
19
21
  import { create } from "zustand";
20
22
  import { persist } from "zustand/middleware";
21
23
  import { createPortal } from "react-dom";
22
- import { a as Route } from "./router-DbxyvprK.js";
24
+ import { a as Route } from "./router-5qObg5aX.js";
23
25
  function deriveFriendlyIdFromKey(key) {
24
26
  if (!key) return "main";
25
27
  const trimmed = key.trim();
@@ -39,7 +41,7 @@ function getToolCallsFromMessage(msg) {
39
41
  (part) => part.type === "toolCall"
40
42
  );
41
43
  }
42
- function normalizeTimestamp$1(value) {
44
+ function normalizeTimestamp$2(value) {
43
45
  if (typeof value === "number" && Number.isFinite(value)) {
44
46
  if (value < 1e12) return value * 1e3;
45
47
  return value;
@@ -59,7 +61,7 @@ function getMessageTimestamp(message) {
59
61
  message.ts
60
62
  ];
61
63
  for (const candidate of candidates) {
62
- const normalized = normalizeTimestamp$1(candidate);
64
+ const normalized = normalizeTimestamp$2(candidate);
63
65
  if (normalized) return normalized;
64
66
  }
65
67
  return Date.now();
@@ -93,6 +95,7 @@ function normalizeSessions(rows) {
93
95
  const rawSession = session;
94
96
  const totalTokens = typeof rawSession.totalTokens === "number" ? rawSession.totalTokens : void 0;
95
97
  const contextTokens = typeof rawSession.contextTokens === "number" ? rawSession.contextTokens : void 0;
98
+ const status = typeof rawSession.status === "string" ? rawSession.status : void 0;
96
99
  return {
97
100
  key,
98
101
  friendlyId: friendlyIdCandidate,
@@ -102,6 +105,7 @@ function normalizeSessions(rows) {
102
105
  updatedAt: typeof session.updatedAt === "number" ? session.updatedAt : void 0,
103
106
  lastMessage: session.lastMessage ?? null,
104
107
  kind,
108
+ status,
105
109
  totalTokens,
106
110
  contextTokens
107
111
  };
@@ -652,9 +656,74 @@ function ScrollAreaCorner({ className, ...props }) {
652
656
  }
653
657
  );
654
658
  }
659
+ function getKindIcon(kind) {
660
+ if (kind === "subagent") return BotIcon;
661
+ if (kind === "cron") return Clock01Icon;
662
+ return Chat01Icon;
663
+ }
664
+ function previewFromMessage(message) {
665
+ if (!message || !Array.isArray(message.content)) return "";
666
+ const text = message.content.map((part) => part.type === "text" ? String(part.text ?? "") : "").join(" ").replace(/\s+/g, " ").trim();
667
+ return text;
668
+ }
669
+ function normalizeTimestamp$1(value) {
670
+ if (typeof value === "number" && Number.isFinite(value)) {
671
+ if (value < 1e12) return value * 1e3;
672
+ return value;
673
+ }
674
+ if (typeof value === "string") {
675
+ const parsed = Date.parse(value);
676
+ if (!Number.isNaN(parsed)) return parsed;
677
+ }
678
+ return null;
679
+ }
680
+ function getLastMessageTimestamp(message) {
681
+ if (!message) return null;
682
+ const candidates = [
683
+ message.createdAt,
684
+ message.created_at,
685
+ message.timestamp,
686
+ message.time,
687
+ message.ts
688
+ ];
689
+ for (const candidate of candidates) {
690
+ const normalized = normalizeTimestamp$1(candidate);
691
+ if (normalized) return normalized;
692
+ }
693
+ return null;
694
+ }
695
+ function formatRelativeTime(timestamp) {
696
+ const diffMs = timestamp - Date.now();
697
+ const absMs = Math.abs(diffMs);
698
+ const units = [
699
+ ["minute", 6e4],
700
+ ["hour", 36e5],
701
+ ["day", 864e5]
702
+ ];
703
+ let unit = "minute";
704
+ let divisor = 6e4;
705
+ if (absMs >= units[2][1]) {
706
+ [unit, divisor] = units[2];
707
+ } else if (absMs >= units[1][1]) {
708
+ [unit, divisor] = units[1];
709
+ }
710
+ const value = Math.round(diffMs / divisor);
711
+ const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
712
+ return rtf.format(value, unit);
713
+ }
714
+ function subagentStatusTone(status) {
715
+ const normalized = String(status ?? "").toLowerCase().trim();
716
+ if (!normalized) return null;
717
+ if (normalized === "error" || normalized === "failed") return "error";
718
+ if (normalized === "completed" || normalized === "ended" || normalized === "done") {
719
+ return "success";
720
+ }
721
+ return null;
722
+ }
655
723
  function SessionItemComponent({
656
724
  session,
657
725
  active,
726
+ isGenerating = false,
658
727
  isPinned,
659
728
  selectionMode = false,
660
729
  selected = false,
@@ -666,6 +735,11 @@ function SessionItemComponent({
666
735
  onExport
667
736
  }) {
668
737
  const label = session.label || session.title || session.derivedTitle || session.friendlyId;
738
+ const KindIcon = getKindIcon(session.kind);
739
+ const subagentPreviewRaw = session.kind === "subagent" && (session.lastMessage?.role === "assistant" || session.lastMessage?.role === "toolResult") ? previewFromMessage(session.lastMessage) : "";
740
+ const subagentPreview = subagentPreviewRaw.length > 50 ? `${subagentPreviewRaw.slice(0, 50).trimEnd()}…` : subagentPreviewRaw;
741
+ const subagentStatus = session.kind === "subagent" ? subagentStatusTone(session.status) : null;
742
+ const cronLastRun = session.kind === "cron" ? getLastMessageTimestamp(session.lastMessage) : null;
669
743
  return /* @__PURE__ */ jsxs(
670
744
  Link,
671
745
  {
@@ -682,7 +756,7 @@ function SessionItemComponent({
682
756
  },
683
757
  className: cn(
684
758
  "group inline-flex items-center justify-between",
685
- "w-full text-left pl-1.5 pr-0.5 h-8 rounded-lg transition-colors duration-0",
759
+ "w-full text-left pl-1.5 pr-0.5 min-h-8 py-1 rounded-lg transition-colors duration-0",
686
760
  "select-none",
687
761
  active ? "bg-primary-200 text-primary-950" : "bg-transparent text-primary-950 [&:hover:not(:has(button:hover))]:bg-primary-200"
688
762
  ),
@@ -699,10 +773,48 @@ function SessionItemComponent({
699
773
  children: selected ? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", className: "size-3", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { d: "M2.5 6l2.5 2.5 4.5-5" }) }) : null
700
774
  }
701
775
  ) : null,
702
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "text-sm font-[450] line-clamp-1", children: [
703
- isPinned ? /* @__PURE__ */ jsx("span", { className: "mr-1 text-xs text-primary-700", "aria-hidden": "true", children: "📌" }) : null,
704
- label
705
- ] }) })
776
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
777
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [
778
+ /* @__PURE__ */ jsx(
779
+ HugeiconsIcon,
780
+ {
781
+ icon: KindIcon,
782
+ size: 12,
783
+ strokeWidth: 1.75,
784
+ className: "text-primary-500/70 shrink-0"
785
+ }
786
+ ),
787
+ /* @__PURE__ */ jsxs("div", { className: "text-sm font-[450] line-clamp-1 min-w-0", children: [
788
+ isPinned ? /* @__PURE__ */ jsx("span", { className: "mr-1 text-xs text-primary-700", "aria-hidden": "true", children: "📌" }) : null,
789
+ label
790
+ ] }),
791
+ session.kind === "subagent" ? isGenerating ? /* @__PURE__ */ jsx(
792
+ "span",
793
+ {
794
+ "aria-label": "Session active",
795
+ className: "size-1.5 rounded-full bg-green-500 shrink-0"
796
+ }
797
+ ) : subagentStatus === "success" ? /* @__PURE__ */ jsx(
798
+ HugeiconsIcon,
799
+ {
800
+ icon: Tick01Icon,
801
+ size: 12,
802
+ strokeWidth: 1.8,
803
+ className: "text-green-600/80 shrink-0"
804
+ }
805
+ ) : subagentStatus === "error" ? /* @__PURE__ */ jsx(
806
+ HugeiconsIcon,
807
+ {
808
+ icon: Cancel01Icon,
809
+ size: 12,
810
+ strokeWidth: 1.8,
811
+ className: "text-red-600/80 shrink-0"
812
+ }
813
+ ) : null : null
814
+ ] }),
815
+ subagentPreview ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-primary-600/80 line-clamp-1 mt-0.5 pl-[18px]", children: subagentPreview }) : null,
816
+ session.kind === "cron" && cronLastRun ? /* @__PURE__ */ jsx("div", { className: "text-[10px] text-primary-500/75 line-clamp-1 mt-0.5 pl-[18px] font-mono", children: formatRelativeTime(cronLastRun) }) : null
817
+ ] })
706
818
  ] }),
707
819
  selectionMode ? null : /* @__PURE__ */ jsxs(MenuRoot, { children: [
708
820
  /* @__PURE__ */ jsx(
@@ -801,6 +913,7 @@ function SessionItemComponent({
801
913
  }
802
914
  function areSessionItemsEqual(prev, next) {
803
915
  if (prev.active !== next.active) return false;
916
+ if (prev.isGenerating !== next.isGenerating) return false;
804
917
  if (prev.isPinned !== next.isPinned) return false;
805
918
  if (prev.selectionMode !== next.selectionMode) return false;
806
919
  if (prev.selected !== next.selected) return false;
@@ -811,7 +924,7 @@ function areSessionItemsEqual(prev, next) {
811
924
  if (prev.onDelete !== next.onDelete) return false;
812
925
  if (prev.onExport !== next.onExport) return false;
813
926
  if (prev.session === next.session) return true;
814
- return prev.session.key === next.session.key && prev.session.friendlyId === next.session.friendlyId && prev.session.label === next.session.label && prev.session.title === next.session.title && prev.session.derivedTitle === next.session.derivedTitle && prev.session.updatedAt === next.session.updatedAt;
927
+ return prev.session.key === next.session.key && prev.session.friendlyId === next.session.friendlyId && prev.session.label === next.session.label && prev.session.title === next.session.title && prev.session.derivedTitle === next.session.derivedTitle && prev.session.updatedAt === next.session.updatedAt && prev.session.kind === next.session.kind && prev.session.status === next.session.status && prev.session.lastMessage === next.session.lastMessage;
815
928
  }
816
929
  const SessionItem = memo(SessionItemComponent, areSessionItemsEqual);
817
930
  const PINNED_SESSIONS_KEY = "opencami-pinned-sessions";
@@ -900,10 +1013,22 @@ function isSessionActive(session, activeFriendlyId, activeSessionKey) {
900
1013
  }
901
1014
  return session.friendlyId === activeFriendlyId;
902
1015
  }
1016
+ function isStatusGenerating(status) {
1017
+ if (!status) return false;
1018
+ const normalized = status.toLowerCase();
1019
+ return normalized.includes("running") || normalized.includes("active") || normalized.includes("stream") || normalized.includes("generat");
1020
+ }
1021
+ function isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming) {
1022
+ if (isSessionActive(session, activeFriendlyId, activeSessionKey) && isStreaming) {
1023
+ return true;
1024
+ }
1025
+ return isStatusGenerating(session.status);
1026
+ }
903
1027
  const SidebarSessions = memo(function SidebarSessions2({
904
1028
  sessions,
905
1029
  activeFriendlyId,
906
1030
  activeSessionKey,
1031
+ isStreaming = false,
907
1032
  defaultOpen = true,
908
1033
  onSelect,
909
1034
  onRename,
@@ -1053,6 +1178,7 @@ const SidebarSessions = memo(function SidebarSessions2({
1053
1178
  {
1054
1179
  session,
1055
1180
  active: isSessionActive(session, activeFriendlyId, activeSessionKey),
1181
+ isGenerating: isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming),
1056
1182
  isPinned: false,
1057
1183
  selectionMode,
1058
1184
  selected: selectedSessionKeys.has(session.key),
@@ -1111,6 +1237,7 @@ const SidebarSessions = memo(function SidebarSessions2({
1111
1237
  {
1112
1238
  session,
1113
1239
  active: isSessionActive(session, activeFriendlyId, activeSessionKey),
1240
+ isGenerating: isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming),
1114
1241
  isPinned: true,
1115
1242
  selectionMode,
1116
1243
  selected: selectedSessionKeys.has(session.key),
@@ -1139,6 +1266,7 @@ const SidebarSessions = memo(function SidebarSessions2({
1139
1266
  {
1140
1267
  session,
1141
1268
  active: isSessionActive(session, activeFriendlyId, activeSessionKey),
1269
+ isGenerating: isSessionGenerating(session, activeFriendlyId, activeSessionKey, isStreaming),
1142
1270
  isPinned: false,
1143
1271
  selectionMode,
1144
1272
  selected: selectedSessionKeys.has(session.key),
@@ -1224,6 +1352,7 @@ const SidebarSessions = memo(function SidebarSessions2({
1224
1352
  function areSidebarSessionsEqual(prev, next) {
1225
1353
  if (prev.activeFriendlyId !== next.activeFriendlyId) return false;
1226
1354
  if (prev.activeSessionKey !== next.activeSessionKey) return false;
1355
+ if (prev.isStreaming !== next.isStreaming) return false;
1227
1356
  if (prev.defaultOpen !== next.defaultOpen) return false;
1228
1357
  if (prev.onSelect !== next.onSelect) return false;
1229
1358
  if (prev.onRename !== next.onRename) return false;
@@ -1241,6 +1370,8 @@ function areSidebarSessionsEqual(prev, next) {
1241
1370
  if (prevSession.derivedTitle !== nextSession.derivedTitle) return false;
1242
1371
  if (prevSession.updatedAt !== nextSession.updatedAt) return false;
1243
1372
  if (prevSession.kind !== nextSession.kind) return false;
1373
+ if (prevSession.status !== nextSession.status) return false;
1374
+ if (prevSession.lastMessage !== nextSession.lastMessage) return false;
1244
1375
  }
1245
1376
  return true;
1246
1377
  }
@@ -1517,30 +1648,11 @@ function useRenameSession() {
1517
1648
  );
1518
1649
  return { renameSession, renaming, error };
1519
1650
  }
1520
- function OpenCamiLogo({ className }) {
1521
- return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("span", { className: "text-xl", children: "🦎" }) });
1522
- }
1523
- function OpenCamiText({ className }) {
1524
- return /* @__PURE__ */ jsx(
1525
- "span",
1526
- {
1527
- className,
1528
- style: {
1529
- background: "linear-gradient(90deg, #10b981, #14b8a6, #06b6d4, #8b5cf6, #ec4899)",
1530
- WebkitBackgroundClip: "text",
1531
- WebkitTextFillColor: "transparent",
1532
- backgroundClip: "text",
1533
- fontWeight: 600
1534
- },
1535
- children: "OpenCami"
1536
- }
1537
- );
1538
- }
1539
1651
  const SettingsDialog = lazy(
1540
- () => import("./settings-dialog-CBj7njLM.js").then((m) => ({ default: m.SettingsDialog }))
1652
+ () => import("./settings-dialog-BBQcU55J.js").then((m) => ({ default: m.SettingsDialog }))
1541
1653
  );
1542
1654
  const SessionExportDialog = lazy(
1543
- () => import("./session-export-dialog-D87uafPD.js").then((m) => ({
1655
+ () => import("./session-export-dialog-DRVbC8Q-.js").then((m) => ({
1544
1656
  default: m.SessionExportDialog
1545
1657
  }))
1546
1658
  );
@@ -1548,6 +1660,7 @@ function ChatSidebarComponent({
1548
1660
  sessions,
1549
1661
  activeFriendlyId,
1550
1662
  activeSessionKey,
1663
+ isStreaming = false,
1551
1664
  creatingSession,
1552
1665
  onCreateSession,
1553
1666
  isCollapsed,
@@ -1779,6 +1892,48 @@ function ChatSidebarComponent({
1779
1892
  }
1780
1893
  ) }),
1781
1894
  isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Files" })
1895
+ ] }) }),
1896
+ (() => {
1897
+ try {
1898
+ return localStorage.getItem("opencami-agent-manager") === "true";
1899
+ } catch {
1900
+ return false;
1901
+ }
1902
+ })() && /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { children: [
1903
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1904
+ Link,
1905
+ {
1906
+ to: "/agents",
1907
+ className: cn(
1908
+ buttonVariants({ variant: "ghost", size: "sm" }),
1909
+ "w-full pl-1.5 justify-start"
1910
+ ),
1911
+ onClick: onSelectSession,
1912
+ children: [
1913
+ /* @__PURE__ */ jsx(
1914
+ HugeiconsIcon,
1915
+ {
1916
+ icon: AiBrain01Icon,
1917
+ size: 20,
1918
+ strokeWidth: 1.5,
1919
+ className: "min-w-5"
1920
+ }
1921
+ ),
1922
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: !isCollapsed && /* @__PURE__ */ jsx(
1923
+ motion.span,
1924
+ {
1925
+ initial: { opacity: 0 },
1926
+ animate: { opacity: 1 },
1927
+ exit: { opacity: 0 },
1928
+ transition,
1929
+ className: "overflow-hidden whitespace-nowrap",
1930
+ children: "Agents"
1931
+ }
1932
+ ) })
1933
+ ]
1934
+ }
1935
+ ) }),
1936
+ isCollapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Agents" })
1782
1937
  ] }) })
1783
1938
  ]
1784
1939
  }
@@ -1837,6 +1992,7 @@ function ChatSidebarComponent({
1837
1992
  sessions,
1838
1993
  activeFriendlyId,
1839
1994
  activeSessionKey,
1995
+ isStreaming,
1840
1996
  onSelect: onSelectSession,
1841
1997
  onRename: handleOpenRename,
1842
1998
  onDelete: handleOpenDelete,
@@ -1945,12 +2101,16 @@ function areSessionsEqual(prevSessions, nextSessions) {
1945
2101
  if (prev.title !== next.title) return false;
1946
2102
  if (prev.derivedTitle !== next.derivedTitle) return false;
1947
2103
  if (prev.updatedAt !== next.updatedAt) return false;
2104
+ if (prev.kind !== next.kind) return false;
2105
+ if (prev.status !== next.status) return false;
2106
+ if (prev.lastMessage !== next.lastMessage) return false;
1948
2107
  }
1949
2108
  return true;
1950
2109
  }
1951
2110
  function areSidebarPropsEqual(prevProps, nextProps) {
1952
2111
  if (prevProps.activeFriendlyId !== nextProps.activeFriendlyId) return false;
1953
2112
  if (prevProps.activeSessionKey !== nextProps.activeSessionKey) return false;
2113
+ if (prevProps.isStreaming !== nextProps.isStreaming) return false;
1954
2114
  if (prevProps.creatingSession !== nextProps.creatingSession) return false;
1955
2115
  if (prevProps.isCollapsed !== nextProps.isCollapsed) return false;
1956
2116
  if (!areSessionsEqual(prevProps.sessions, nextProps.sessions)) return false;
@@ -2352,6 +2512,57 @@ function MessageActionsBar({
2352
2512
  }
2353
2513
  );
2354
2514
  }
2515
+ function markdownHrefToFilePath(href) {
2516
+ if (!href?.startsWith("openclaw-file://")) return null;
2517
+ try {
2518
+ return decodeURIComponent(href.slice("openclaw-file://".length));
2519
+ } catch {
2520
+ return null;
2521
+ }
2522
+ }
2523
+ const EXTENSION_LANGUAGE_MAP = {
2524
+ py: "python",
2525
+ ts: "typescript",
2526
+ js: "javascript",
2527
+ jsx: "jsx",
2528
+ tsx: "tsx",
2529
+ json: "json",
2530
+ md: "markdown",
2531
+ yml: "yaml",
2532
+ yaml: "yaml",
2533
+ sh: "bash",
2534
+ bash: "bash",
2535
+ zsh: "bash",
2536
+ html: "html",
2537
+ css: "css",
2538
+ sql: "sql",
2539
+ xml: "xml",
2540
+ toml: "toml",
2541
+ rs: "rust",
2542
+ go: "go",
2543
+ java: "java",
2544
+ c: "c",
2545
+ cpp: "cpp",
2546
+ cs: "csharp",
2547
+ php: "php",
2548
+ rb: "ruby",
2549
+ graphql: "graphql",
2550
+ diff: "diff",
2551
+ patch: "diff",
2552
+ env: "text"
2553
+ };
2554
+ function languageFromFilePath(path) {
2555
+ if (!path) return "text";
2556
+ const filename = path.split("/").pop() || "";
2557
+ const lower = filename.toLowerCase();
2558
+ if (lower === "dockerfile") return "dockerfile";
2559
+ if (lower === "makefile") return "text";
2560
+ const parts = lower.split(".");
2561
+ const extension = parts.length > 1 ? parts.pop() || "" : "";
2562
+ const mapped = EXTENSION_LANGUAGE_MAP[extension] || extension || "text";
2563
+ return resolveLanguage(mapped);
2564
+ }
2565
+ const INLINE_PREVIEW_MAX_BYTES = 100 * 1024;
2355
2566
  function parseMarkdownIntoBlocks(markdown) {
2356
2567
  const tokens = marked.lexer(markdown);
2357
2568
  return tokens.map((token) => token.raw);
@@ -2361,7 +2572,7 @@ function extractLanguage(className) {
2361
2572
  const match = className.match(/language-(\w+)/);
2362
2573
  return match ? match[1] : "text";
2363
2574
  }
2364
- const INITIAL_COMPONENTS = {
2575
+ const BASE_COMPONENTS = {
2365
2576
  code: function CodeComponent({ className, children }) {
2366
2577
  const isInline = !className?.includes("language-");
2367
2578
  if (isInline) {
@@ -2401,18 +2612,6 @@ const INITIAL_COMPONENTS = {
2401
2612
  li: function LiComponent({ children }) {
2402
2613
  return /* @__PURE__ */ jsx("li", { className: "leading-relaxed", children });
2403
2614
  },
2404
- a: function AComponent({ children, href }) {
2405
- return /* @__PURE__ */ jsx(
2406
- "a",
2407
- {
2408
- href,
2409
- className: "text-primary-950 underline decoration-primary-300 underline-offset-4 transition-colors hover:text-primary-950 hover:decoration-primary-500",
2410
- target: "_blank",
2411
- rel: "noopener noreferrer",
2412
- children
2413
- }
2414
- );
2415
- },
2416
2615
  blockquote: function BlockquoteComponent({ children }) {
2417
2616
  return /* @__PURE__ */ jsx("blockquote", { className: "border-l-2 border-primary-300 pl-4 text-primary-900 italic", children });
2418
2617
  },
@@ -2444,10 +2643,56 @@ const INITIAL_COMPONENTS = {
2444
2643
  return /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-primary-950", children });
2445
2644
  }
2446
2645
  };
2646
+ function createDefaultComponents(onOpenFilePreview, inlineFilePreviewEnabled) {
2647
+ const FILE_PATH_RE = /^(?:~\/[\w.\-\/]+|\/(?:[\w.\-]+\/)+[\w.\-]+)$/;
2648
+ return {
2649
+ ...BASE_COMPONENTS,
2650
+ a: function AComponent({ children, href }) {
2651
+ const filePath = markdownHrefToFilePath(href);
2652
+ if (inlineFilePreviewEnabled && filePath) {
2653
+ return /* @__PURE__ */ jsx(
2654
+ "button",
2655
+ {
2656
+ type: "button",
2657
+ onClick: () => onOpenFilePreview(filePath),
2658
+ className: "font-mono text-primary-900 underline decoration-primary-300 underline-offset-4 hover:decoration-primary-600 cursor-pointer",
2659
+ children
2660
+ }
2661
+ );
2662
+ }
2663
+ return /* @__PURE__ */ jsx(
2664
+ "a",
2665
+ {
2666
+ href,
2667
+ className: "text-primary-950 underline decoration-primary-300 underline-offset-4 transition-colors hover:text-primary-950 hover:decoration-primary-500",
2668
+ target: "_blank",
2669
+ rel: "noopener noreferrer",
2670
+ children
2671
+ }
2672
+ );
2673
+ },
2674
+ code: function InlineCodeComponent({ children, className }) {
2675
+ if (className) return /* @__PURE__ */ jsx("code", { className, children });
2676
+ const text = typeof children === "string" ? children : Array.isArray(children) ? children.filter((c) => typeof c === "string").join("") : String(children ?? "");
2677
+ if (inlineFilePreviewEnabled && text && FILE_PATH_RE.test(text)) {
2678
+ return /* @__PURE__ */ jsx(
2679
+ "button",
2680
+ {
2681
+ type: "button",
2682
+ onClick: () => onOpenFilePreview(text),
2683
+ className: "font-mono text-sm bg-primary-100 rounded px-1.5 py-0.5 text-primary-900 underline decoration-primary-300 underline-offset-4 hover:decoration-primary-600 cursor-pointer",
2684
+ children
2685
+ }
2686
+ );
2687
+ }
2688
+ return /* @__PURE__ */ jsx("code", { className: "font-mono text-sm bg-primary-100 rounded px-1.5 py-0.5 text-primary-900", children });
2689
+ }
2690
+ };
2691
+ }
2447
2692
  const MemoizedMarkdownBlock = memo(
2448
2693
  function MarkdownBlock({
2449
2694
  content,
2450
- components = INITIAL_COMPONENTS
2695
+ components
2451
2696
  }) {
2452
2697
  return /* @__PURE__ */ jsx(
2453
2698
  ReactMarkdown,
@@ -2463,23 +2708,132 @@ const MemoizedMarkdownBlock = memo(
2463
2708
  }
2464
2709
  );
2465
2710
  MemoizedMarkdownBlock.displayName = "MemoizedMarkdownBlock";
2711
+ function fileErrorMessageFromResponse(status, code) {
2712
+ if (code === "NOT_FOUND" || status === 404) return "File not found";
2713
+ if (code === "UNSUPPORTED_TYPE") return "Binary file";
2714
+ if (code === "FILE_TOO_LARGE" || status === 413) return "File too large";
2715
+ return "Failed to load file preview";
2716
+ }
2466
2717
  function MarkdownComponent({
2467
2718
  children,
2468
2719
  id,
2469
2720
  className,
2470
- components = INITIAL_COMPONENTS
2721
+ components
2471
2722
  }) {
2472
2723
  const generatedId = useId();
2473
2724
  const blockId = id ?? generatedId;
2474
2725
  const blocks = useMemo(() => parseMarkdownIntoBlocks(children), [children]);
2475
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-2", className), children: blocks.map((block, index) => /* @__PURE__ */ jsx(
2476
- MemoizedMarkdownBlock,
2477
- {
2478
- content: block,
2479
- components
2480
- },
2481
- `${blockId}-block-${index}`
2482
- )) });
2726
+ const [filePreview, setFilePreview] = useState({ status: "idle" });
2727
+ const inlineFilePreviewEnabled = useChatSettingsStore(
2728
+ (state) => state.settings.inlineFilePreview
2729
+ );
2730
+ const defaultComponents = useMemo(
2731
+ () => createDefaultComponents(
2732
+ (path) => setFilePreview({ status: "loading", path }),
2733
+ inlineFilePreviewEnabled
2734
+ ),
2735
+ [inlineFilePreviewEnabled]
2736
+ );
2737
+ const mergedComponents = useMemo(
2738
+ () => ({ ...defaultComponents, ...components || {} }),
2739
+ [defaultComponents, components]
2740
+ );
2741
+ useEffect(() => {
2742
+ if (filePreview.status !== "loading") return;
2743
+ const path = filePreview.path;
2744
+ const controller = new AbortController();
2745
+ fetch(`/api/files/read?path=${encodeURIComponent(path)}`, {
2746
+ signal: controller.signal
2747
+ }).then(async (response) => {
2748
+ const payload = await response.json().catch(() => ({}));
2749
+ if (!response.ok) {
2750
+ const error = fileErrorMessageFromResponse(response.status, payload.code);
2751
+ setFilePreview({ status: "error", path, message: error });
2752
+ return;
2753
+ }
2754
+ const size = Number(payload.size ?? 0);
2755
+ if (size > INLINE_PREVIEW_MAX_BYTES) {
2756
+ setFilePreview({ status: "error", path, message: "File too large" });
2757
+ return;
2758
+ }
2759
+ const content = String(payload.content ?? "");
2760
+ setFilePreview({
2761
+ status: "success",
2762
+ path,
2763
+ content,
2764
+ language: languageFromFilePath(path)
2765
+ });
2766
+ }).catch(() => {
2767
+ if (controller.signal.aborted) return;
2768
+ setFilePreview({ status: "error", path, message: "Failed to load file preview" });
2769
+ });
2770
+ return () => controller.abort();
2771
+ }, [filePreview]);
2772
+ const previewOpen = filePreview.status !== "idle";
2773
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2774
+ /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-2", className), children: blocks.map((block, index) => /* @__PURE__ */ jsx(
2775
+ MemoizedMarkdownBlock,
2776
+ {
2777
+ content: block,
2778
+ components: mergedComponents
2779
+ },
2780
+ `${blockId}-block-${index}`
2781
+ )) }),
2782
+ /* @__PURE__ */ jsx(
2783
+ DialogRoot,
2784
+ {
2785
+ open: previewOpen,
2786
+ onOpenChange: (open) => {
2787
+ if (!open) setFilePreview({ status: "idle" });
2788
+ },
2789
+ children: /* @__PURE__ */ jsxs(DialogContent, { className: "w-[min(1000px,95vw)] max-h-[88vh] overflow-hidden p-0", children: [
2790
+ /* @__PURE__ */ jsxs("div", { className: "border-b border-primary-200 px-4 py-3 flex items-start justify-between gap-3", children: [
2791
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2792
+ /* @__PURE__ */ jsx(DialogTitle, { className: "text-base", children: "File Preview" }),
2793
+ filePreview.status !== "idle" && /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-600 font-mono truncate", children: filePreview.path })
2794
+ ] }),
2795
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2796
+ filePreview.status !== "idle" && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(
2797
+ Link,
2798
+ {
2799
+ to: "/files",
2800
+ onClick: () => {
2801
+ let p = filePreview.status !== "idle" ? filePreview.path : "";
2802
+ if (p) {
2803
+ const prefixes = ["/root/clawd/", "/root/"];
2804
+ for (const prefix of prefixes) {
2805
+ if (p.startsWith(prefix)) {
2806
+ p = "/" + p.slice(prefix.length);
2807
+ break;
2808
+ }
2809
+ }
2810
+ const dir = p.includes("/") ? p.slice(0, p.lastIndexOf("/")) || "/" : "/";
2811
+ useFileExplorerState.getState().navigateTo(dir);
2812
+ }
2813
+ setFilePreview({ status: "idle" });
2814
+ },
2815
+ children: "Open in File Explorer"
2816
+ }
2817
+ ) }),
2818
+ /* @__PURE__ */ jsx(DialogClose, { children: "Close" })
2819
+ ] })
2820
+ ] }),
2821
+ /* @__PURE__ */ jsxs("div", { className: "p-4 overflow-auto max-h-[calc(88vh-72px)]", children: [
2822
+ filePreview.status === "loading" && /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-600", children: "Loading preview…" }),
2823
+ filePreview.status === "error" && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: filePreview.message }),
2824
+ filePreview.status === "success" && /* @__PURE__ */ jsx(
2825
+ CodeBlock,
2826
+ {
2827
+ content: filePreview.content,
2828
+ language: filePreview.language,
2829
+ className: "w-full"
2830
+ }
2831
+ )
2832
+ ] })
2833
+ ] })
2834
+ }
2835
+ )
2836
+ ] });
2483
2837
  }
2484
2838
  const Markdown = memo(MarkdownComponent);
2485
2839
  Markdown.displayName = "Markdown";
@@ -2544,27 +2898,25 @@ function Thinking({ content }) {
2544
2898
  }
2545
2899
  function Tool({ toolPart, defaultOpen = false }) {
2546
2900
  const { state, input, output, toolCallId } = toolPart;
2547
- const formatValue = (value) => {
2548
- if (value === null) return "null";
2549
- if (value === void 0) return "undefined";
2550
- if (typeof value === "string") {
2901
+ const serialize = (value, maxLength = 3200) => {
2902
+ const normalized = value === void 0 ? "undefined" : value;
2903
+ let raw;
2904
+ if (typeof normalized === "string") {
2551
2905
  try {
2552
- const parsed = JSON.parse(value);
2553
- return parsed;
2906
+ raw = JSON.stringify(JSON.parse(normalized), null, 2);
2554
2907
  } catch {
2555
- return value;
2908
+ raw = normalized;
2556
2909
  }
2910
+ } else {
2911
+ raw = JSON.stringify(normalized, null, 2);
2557
2912
  }
2558
- return value;
2559
- };
2560
- const renderValue = (value) => {
2561
- const formatted = formatValue(value);
2562
- if (typeof formatted === "object" && formatted !== null) {
2563
- return /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap break-all font-mono text-xs leading-relaxed", children: JSON.stringify(formatted, null, 2) });
2564
- }
2565
- return /* @__PURE__ */ jsx("span", { className: "break-all", children: String(formatted) });
2913
+ if (raw.length <= maxLength) return raw;
2914
+ return `${raw.slice(0, maxLength).trimEnd()}
2915
+ …[truncated]`;
2566
2916
  };
2567
- return /* @__PURE__ */ jsx("div", { className: "inline-flex flex-col", children: /* @__PURE__ */ jsxs(Collapsible, { defaultOpen, children: [
2917
+ const statusIcon = state === "output-error" ? Cancel01Icon : state === "output-available" ? Tick01Icon : Loading03Icon;
2918
+ const statusClassName = state === "output-error" ? "text-red-600/80" : state === "output-available" ? "text-green-600/80" : "text-primary-500/80";
2919
+ return /* @__PURE__ */ jsx("div", { className: "inline-flex flex-col w-full", children: /* @__PURE__ */ jsxs(Collapsible, { defaultOpen, children: [
2568
2920
  /* @__PURE__ */ jsxs(
2569
2921
  CollapsibleTrigger,
2570
2922
  {
@@ -2572,10 +2924,19 @@ function Tool({ toolPart, defaultOpen = false }) {
2572
2924
  Button,
2573
2925
  {
2574
2926
  variant: "ghost",
2575
- className: "h-auto gap-1.5 px-1.5 py-0.5 -mx-2"
2927
+ className: "h-auto w-full justify-start gap-1.5 rounded-md px-1.5 py-1"
2576
2928
  }
2577
2929
  ),
2578
2930
  children: [
2931
+ /* @__PURE__ */ jsx(
2932
+ HugeiconsIcon,
2933
+ {
2934
+ icon: statusIcon,
2935
+ size: 12,
2936
+ strokeWidth: 1.7,
2937
+ className: statusClassName
2938
+ }
2939
+ ),
2579
2940
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-primary-900", children: toolPart.type }),
2580
2941
  /* @__PURE__ */ jsx(
2581
2942
  HugeiconsIcon,
@@ -2583,41 +2944,130 @@ function Tool({ toolPart, defaultOpen = false }) {
2583
2944
  icon: ArrowDown01Icon,
2584
2945
  size: 14,
2585
2946
  strokeWidth: 1.5,
2586
- className: "text-primary-900 transition-transform duration-150 group-data-panel-open:rotate-180"
2947
+ className: "ml-auto text-primary-700/80 transition-transform duration-150 group-data-panel-open:rotate-180"
2587
2948
  }
2588
2949
  )
2589
2950
  ]
2590
2951
  }
2591
2952
  ),
2592
- /* @__PURE__ */ jsx(CollapsiblePanel, { className: "mt-1", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 bg-primary-100 p-2 border border-primary-200", children: [
2593
- input && Object.keys(input).length > 0 && /* @__PURE__ */ jsxs("div", { className: "border border-primary-200 bg-primary-50 p-3", children: [
2594
- /* @__PURE__ */ jsx("h4", { className: "text-primary-600 mb-2 text-xs font-medium", children: "Input" }),
2595
- /* @__PURE__ */ jsx("div", { className: "max-h-40 overflow-auto space-y-2 font-mono text-xs text-primary-800", children: Object.entries(input).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "break-all", children: [
2596
- /* @__PURE__ */ jsxs("span", { className: "text-primary-500", children: [
2597
- key,
2598
- ":"
2599
- ] }),
2600
- " ",
2601
- /* @__PURE__ */ jsx("span", { className: "text-primary-700", children: renderValue(value) })
2602
- ] }, key)) })
2953
+ /* @__PURE__ */ jsx(CollapsiblePanel, { className: "mt-1", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-primary-200 bg-primary-100/70 p-2", children: [
2954
+ /* @__PURE__ */ jsxs("div", { className: "rounded border border-primary-200 bg-primary-50 px-2 py-1.5", children: [
2955
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium text-primary-600", children: "Tool" }),
2956
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-xs text-primary-800 break-all", children: toolPart.type })
2957
+ ] }),
2958
+ input && Object.keys(input).length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded border border-primary-200 bg-primary-50 px-2 py-1.5", children: [
2959
+ /* @__PURE__ */ jsx("h4", { className: "mb-1 text-[11px] font-medium text-primary-600", children: "Input" }),
2960
+ /* @__PURE__ */ jsx("pre", { className: "max-h-44 overflow-auto whitespace-pre-wrap break-all font-mono text-xs leading-relaxed text-primary-800", children: serialize(input) })
2603
2961
  ] }),
2604
- output && /* @__PURE__ */ jsxs("div", { className: "border border-primary-200 bg-primary-50 p-3", children: [
2605
- /* @__PURE__ */ jsx("h4", { className: "text-primary-600 mb-2 text-xs font-medium", children: "Output" }),
2606
- /* @__PURE__ */ jsx("div", { className: "max-h-40 overflow-auto font-mono text-xs text-primary-800", children: renderValue(output) })
2962
+ output !== void 0 && output !== null && /* @__PURE__ */ jsxs("div", { className: "rounded border border-primary-200 bg-primary-50 px-2 py-1.5", children: [
2963
+ /* @__PURE__ */ jsx("h4", { className: "mb-1 text-[11px] font-medium text-primary-600", children: "Output" }),
2964
+ /* @__PURE__ */ jsx("pre", { className: "max-h-44 overflow-auto whitespace-pre-wrap break-all font-mono text-xs leading-relaxed text-primary-800", children: serialize(output) })
2607
2965
  ] }),
2608
- state === "output-error" && toolPart.errorText && /* @__PURE__ */ jsxs("div", { className: "rounded-md bg-red-50 p-2", children: [
2609
- /* @__PURE__ */ jsx("h4", { className: "mb-1 text-xs font-medium text-red-600", children: "Error" }),
2966
+ state === "output-error" && toolPart.errorText && /* @__PURE__ */ jsxs("div", { className: "rounded border border-red-200 bg-red-50 px-2 py-1.5", children: [
2967
+ /* @__PURE__ */ jsx("h4", { className: "mb-1 text-[11px] font-medium text-red-600", children: "Error" }),
2610
2968
  /* @__PURE__ */ jsx("div", { className: "text-xs text-red-700", children: toolPart.errorText })
2611
2969
  ] }),
2612
- state === "input-streaming" && /* @__PURE__ */ jsx("div", { className: "text-primary-500 text-xs", children: "Processing..." }),
2613
- toolCallId && /* @__PURE__ */ jsx("div", { className: "text-primary-400 text-xs", children: /* @__PURE__ */ jsxs("span", { className: "font-mono tabular-nums", children: [
2970
+ toolCallId && /* @__PURE__ */ jsxs("div", { className: "text-[11px] text-primary-500/80 font-mono tabular-nums", children: [
2614
2971
  "ID: ",
2615
2972
  toolCallId.slice(0, 16),
2616
2973
  "..."
2617
- ] }) })
2974
+ ] })
2618
2975
  ] }) })
2619
2976
  ] }) });
2620
2977
  }
2978
+ function getDomain(url) {
2979
+ try {
2980
+ return new URL(url).hostname.replace("www.", "");
2981
+ } catch {
2982
+ return url;
2983
+ }
2984
+ }
2985
+ function FaviconCircle({ domain }) {
2986
+ return /* @__PURE__ */ jsx(
2987
+ "div",
2988
+ {
2989
+ className: "flex items-center justify-center w-5 h-5 rounded-full bg-white dark:bg-zinc-800 border border-cyan-500/20 overflow-hidden",
2990
+ title: domain,
2991
+ children: /* @__PURE__ */ jsx(
2992
+ "img",
2993
+ {
2994
+ src: `https://www.google.com/s2/favicons?domain=${domain}&sz=32`,
2995
+ alt: "",
2996
+ className: "w-4 h-4 object-contain",
2997
+ loading: "lazy",
2998
+ onError: (e) => {
2999
+ const target = e.target;
3000
+ target.style.display = "none";
3001
+ const parent = target.parentElement;
3002
+ if (parent) {
3003
+ parent.textContent = domain.charAt(0).toUpperCase();
3004
+ parent.classList.add("text-[10px]", "font-medium", "text-cyan-400");
3005
+ }
3006
+ }
3007
+ }
3008
+ )
3009
+ }
3010
+ );
3011
+ }
3012
+ function SearchSourcesBadge({ sources }) {
3013
+ const [expanded, setExpanded] = useState(false);
3014
+ if (!sources.length) return null;
3015
+ const uniqueDomains = [...new Set(sources.map((s) => getDomain(s.url)))];
3016
+ return /* @__PURE__ */ jsxs("div", { className: "mt-2 w-full", children: [
3017
+ /* @__PURE__ */ jsxs(
3018
+ "button",
3019
+ {
3020
+ onClick: () => setExpanded(!expanded),
3021
+ className: cn(
3022
+ "inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs",
3023
+ "bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30",
3024
+ "text-cyan-300 transition-colors"
3025
+ ),
3026
+ children: [
3027
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Search01Icon, size: 14 }),
3028
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Sources" }),
3029
+ /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded-full bg-cyan-500/20 text-cyan-400 font-semibold", children: sources.length }),
3030
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 ml-1", children: [
3031
+ uniqueDomains.slice(0, 3).map((domain) => /* @__PURE__ */ jsx(FaviconCircle, { domain }, domain)),
3032
+ uniqueDomains.length > 3 && /* @__PURE__ */ jsxs("span", { className: "text-cyan-500/70 text-[10px] ml-0.5", children: [
3033
+ "+",
3034
+ uniqueDomains.length - 3
3035
+ ] })
3036
+ ] }),
3037
+ /* @__PURE__ */ jsx(
3038
+ HugeiconsIcon,
3039
+ {
3040
+ icon: ArrowRight01Icon,
3041
+ size: 12,
3042
+ className: cn("transition-transform", expanded && "rotate-90")
3043
+ }
3044
+ )
3045
+ ]
3046
+ }
3047
+ ),
3048
+ expanded && /* @__PURE__ */ jsx("div", { className: "mt-2 rounded-lg border border-cyan-500/20 bg-cyan-500/5 max-h-80 overflow-y-auto", children: sources.map((source, i) => {
3049
+ const domain = getDomain(source.url);
3050
+ return /* @__PURE__ */ jsxs(
3051
+ "a",
3052
+ {
3053
+ href: source.url,
3054
+ target: "_blank",
3055
+ rel: "noopener noreferrer",
3056
+ className: "flex items-start gap-2 p-2.5 border-b border-cyan-500/10 last:border-b-0 hover:bg-cyan-500/5 transition-colors",
3057
+ children: [
3058
+ /* @__PURE__ */ jsx(FaviconCircle, { domain }),
3059
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
3060
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-primary-900 hover:underline line-clamp-1", children: source.title || domain }),
3061
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: domain }),
3062
+ source.snippet && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground/80 line-clamp-2 mt-0.5", children: source.snippet })
3063
+ ] })
3064
+ ]
3065
+ },
3066
+ `${source.url}-${i}`
3067
+ );
3068
+ }) })
3069
+ ] });
3070
+ }
2621
3071
  function mapToolCallToToolPart(toolCall, resultMessage) {
2622
3072
  const hasResult = resultMessage !== void 0;
2623
3073
  const isError = resultMessage?.isError ?? false;
@@ -2633,11 +3083,13 @@ function mapToolCallToToolPart(toolCall, resultMessage) {
2633
3083
  if (isError && resultMessage?.content?.[0]?.type === "text") {
2634
3084
  errorText = resultMessage.content[0].text || "Unknown error";
2635
3085
  }
3086
+ const outputText = resultMessage?.content?.map((part) => part.type === "text" ? String(part.text ?? "") : "").join("").trim();
3087
+ const output = resultMessage?.details ?? (outputText && outputText.length > 0 ? outputText : void 0);
2636
3088
  return {
2637
3089
  type: toolCall.name || "unknown",
2638
3090
  state,
2639
3091
  input: toolCall.arguments,
2640
- output: resultMessage?.details,
3092
+ output,
2641
3093
  toolCallId: toolCall.id,
2642
3094
  errorText
2643
3095
  };
@@ -2716,6 +3168,8 @@ function MessageItemComponent({
2716
3168
  toolResultsByCallId,
2717
3169
  forceActionsVisible = false,
2718
3170
  isStreaming = false,
3171
+ isLastAssistant = false,
3172
+ aggregatedSearchSources,
2719
3173
  wrapperRef,
2720
3174
  wrapperClassName,
2721
3175
  wrapperScrollMarginTop
@@ -2729,6 +3183,7 @@ function MessageItemComponent({
2729
3183
  const timestamp = getMessageTimestamp(message);
2730
3184
  const toolCalls = role === "assistant" ? getToolCallsFromMessage(message) : [];
2731
3185
  const hasToolCalls = toolCalls.length > 0;
3186
+ const searchSources = isLastAssistant && !isStreaming && settings.showSearchSources && aggregatedSearchSources ? aggregatedSearchSources : [];
2732
3187
  return /* @__PURE__ */ jsxs(
2733
3188
  "div",
2734
3189
  {
@@ -2783,6 +3238,7 @@ function MessageItemComponent({
2783
3238
  toolCall.id || toolCall.name
2784
3239
  );
2785
3240
  }) }),
3241
+ searchSources.length > 0 && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[900px]", children: /* @__PURE__ */ jsx(SearchSourcesBadge, { sources: searchSources }) }),
2786
3242
  !hasToolCalls && /* @__PURE__ */ jsx(
2787
3243
  MessageActionsBar,
2788
3244
  {
@@ -2801,6 +3257,8 @@ function areMessagesEqual(prevProps, nextProps) {
2801
3257
  return false;
2802
3258
  }
2803
3259
  if (prevProps.isStreaming !== nextProps.isStreaming) return false;
3260
+ if (prevProps.isLastAssistant !== nextProps.isLastAssistant) return false;
3261
+ if (prevProps.aggregatedSearchSources !== nextProps.aggregatedSearchSources) return false;
2804
3262
  if (prevProps.wrapperClassName !== nextProps.wrapperClassName) return false;
2805
3263
  if (prevProps.wrapperRef !== nextProps.wrapperRef) return false;
2806
3264
  if (prevProps.wrapperScrollMarginTop !== nextProps.wrapperScrollMarginTop) {
@@ -3473,6 +3931,75 @@ function ChatMessageListComponent({
3473
3931
  }
3474
3932
  return map;
3475
3933
  }, [messages]);
3934
+ const aggregatedSearchSources = useMemo(() => {
3935
+ const strip = (s) => {
3936
+ if (!s) return "";
3937
+ return s.replace(/SECURITY NOTICE:[\s\S]*?<<<EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/<<<\/?EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/<<<\/?END_EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/Source: Web (?:Search|Fetch)\n---/g, "").replace(/\n{2,}/g, "\n").trim();
3938
+ };
3939
+ const extractResults = (text, sources2, seenUrls2) => {
3940
+ try {
3941
+ const parsed = JSON.parse(text);
3942
+ const items = Array.isArray(parsed) ? parsed : parsed?.results ?? parsed?.web?.results ?? [];
3943
+ if (!Array.isArray(items)) return false;
3944
+ let found = false;
3945
+ for (const item of items) {
3946
+ if (item?.url && item?.title && !seenUrls2.has(item.url)) {
3947
+ seenUrls2.add(item.url);
3948
+ sources2.push({
3949
+ title: strip(item.title),
3950
+ url: item.url,
3951
+ snippet: strip(item.description || item.snippet || item.content || "")
3952
+ });
3953
+ found = true;
3954
+ }
3955
+ }
3956
+ return found;
3957
+ } catch {
3958
+ return false;
3959
+ }
3960
+ };
3961
+ const sources = [];
3962
+ const seenUrls = /* @__PURE__ */ new Set();
3963
+ for (const msg of displayMessages) {
3964
+ if (msg.role !== "assistant") continue;
3965
+ const toolCalls = getToolCallsFromMessage(msg);
3966
+ for (const tc of toolCalls) {
3967
+ if (!tc.id) continue;
3968
+ const isSearch = tc.name === "web_search";
3969
+ const isFetch = tc.name === "web_fetch";
3970
+ const isExec = tc.name === "exec";
3971
+ if (!isSearch && !isFetch && !isExec) continue;
3972
+ const result = toolResultsByCallId.get(tc.id);
3973
+ if (!result) continue;
3974
+ const text = result.content?.map((p) => p.type === "text" ? String(p.text ?? "") : "").join("").trim();
3975
+ if (!text) continue;
3976
+ if (isSearch || isExec) {
3977
+ if (isExec) {
3978
+ if (!text.includes('"results"') || !text.includes('"url"')) continue;
3979
+ }
3980
+ let jsonText = text;
3981
+ const jsonStart = text.indexOf("{");
3982
+ if (jsonStart > 0) jsonText = text.slice(jsonStart);
3983
+ const jsonEnd = jsonText.lastIndexOf("}");
3984
+ if (jsonEnd > 0) jsonText = jsonText.slice(0, jsonEnd + 1);
3985
+ extractResults(jsonText, sources, seenUrls);
3986
+ } else if (isFetch) {
3987
+ const url = tc.arguments?.url;
3988
+ if (url && !seenUrls.has(url)) {
3989
+ seenUrls.add(url);
3990
+ let title;
3991
+ try {
3992
+ title = new URL(url).hostname;
3993
+ } catch {
3994
+ title = url;
3995
+ }
3996
+ sources.push({ title, url });
3997
+ }
3998
+ }
3999
+ }
4000
+ }
4001
+ return sources;
4002
+ }, [displayMessages, toolResultsByCallId]);
3476
4003
  const lastAssistantIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role !== "user").map(({ index }) => index).pop();
3477
4004
  const lastUserIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user").map(({ index }) => index).pop();
3478
4005
  const showTypingIndicator = waitingForResponse && (typeof lastUserIndex !== "number" || typeof lastAssistantIndex !== "number" || lastAssistantIndex < lastUserIndex);
@@ -3480,7 +4007,7 @@ function ChatMessageListComponent({
3480
4007
  const hasGroup = pinToTop && groupStartIndex >= 0;
3481
4008
  const lastAssistantMessage = typeof lastAssistantIndex === "number" ? displayMessages[lastAssistantIndex] : void 0;
3482
4009
  const lastAssistantText = lastAssistantMessage ? textFromMessage(lastAssistantMessage) : "";
3483
- const showFollowUps = !waitingForResponse && lastAssistantText.length > 0 && onFollowUpClick !== void 0 && (typeof lastUserIndex !== "number" || typeof lastAssistantIndex !== "number" || lastAssistantIndex > lastUserIndex);
4010
+ const showFollowUps = !waitingForResponse && !isStreaming && lastAssistantText.length > 0 && onFollowUpClick !== void 0 && (typeof lastUserIndex !== "number" || typeof lastAssistantIndex !== "number" || lastAssistantIndex > lastUserIndex);
3484
4011
  useLayoutEffect(() => {
3485
4012
  if (loading) return;
3486
4013
  if (pinToTop) {
@@ -3512,7 +4039,7 @@ function ChatMessageListComponent({
3512
4039
  notice && noticePosition === "start" ? notice : null,
3513
4040
  empty && !notice ? emptyState ?? /* @__PURE__ */ jsx("div", { "aria-hidden": true }) : hasGroup ? /* @__PURE__ */ jsxs(Fragment, { children: [
3514
4041
  displayMessages.slice(0, groupStartIndex).map((chatMessage, index) => {
3515
- const messageKey = chatMessage.__optimisticId || chatMessage.id || index;
4042
+ const messageKey = index;
3516
4043
  const forceActionsVisible = typeof lastAssistantIndex === "number" && index === lastAssistantIndex;
3517
4044
  const isLastAssistant = typeof lastAssistantIndex === "number" && index === lastAssistantIndex;
3518
4045
  const hasToolCalls = chatMessage.role === "assistant" && getToolCallsFromMessage(chatMessage).length > 0;
@@ -3522,7 +4049,9 @@ function ChatMessageListComponent({
3522
4049
  message: chatMessage,
3523
4050
  toolResultsByCallId: hasToolCalls ? toolResultsByCallId : void 0,
3524
4051
  forceActionsVisible,
3525
- isStreaming: isLastAssistant && isStreaming
4052
+ isStreaming: isLastAssistant && isStreaming,
4053
+ isLastAssistant,
4054
+ aggregatedSearchSources: isLastAssistant ? aggregatedSearchSources : void 0
3526
4055
  },
3527
4056
  messageKey
3528
4057
  );
@@ -3535,7 +4064,7 @@ function ChatMessageListComponent({
3535
4064
  children: [
3536
4065
  displayMessages.slice(groupStartIndex).map((chatMessage, index) => {
3537
4066
  const realIndex = groupStartIndex + index;
3538
- const messageKey = chatMessage.__optimisticId || chatMessage.id || realIndex;
4067
+ const messageKey = realIndex;
3539
4068
  const forceActionsVisible = typeof lastAssistantIndex === "number" && realIndex === lastAssistantIndex;
3540
4069
  const isLastAssistant = typeof lastAssistantIndex === "number" && realIndex === lastAssistantIndex;
3541
4070
  const wrapperRef = realIndex === lastUserIndex ? lastUserRef : void 0;
@@ -3549,6 +4078,8 @@ function ChatMessageListComponent({
3549
4078
  toolResultsByCallId: hasToolCalls ? toolResultsByCallId : void 0,
3550
4079
  forceActionsVisible,
3551
4080
  isStreaming: isLastAssistant && isStreaming,
4081
+ isLastAssistant,
4082
+ aggregatedSearchSources: isLastAssistant ? aggregatedSearchSources : void 0,
3552
4083
  wrapperRef,
3553
4084
  wrapperClassName,
3554
4085
  wrapperScrollMarginTop
@@ -3570,7 +4101,7 @@ function ChatMessageListComponent({
3570
4101
  )
3571
4102
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3572
4103
  displayMessages.map((chatMessage, index) => {
3573
- const messageKey = chatMessage.__optimisticId || chatMessage.id || index;
4104
+ const messageKey = index;
3574
4105
  const forceActionsVisible = typeof lastAssistantIndex === "number" && index === lastAssistantIndex;
3575
4106
  const isLastAssistant = typeof lastAssistantIndex === "number" && index === lastAssistantIndex;
3576
4107
  const hasToolCalls = chatMessage.role === "assistant" && getToolCallsFromMessage(chatMessage).length > 0;
@@ -3580,7 +4111,9 @@ function ChatMessageListComponent({
3580
4111
  message: chatMessage,
3581
4112
  toolResultsByCallId: hasToolCalls ? toolResultsByCallId : void 0,
3582
4113
  forceActionsVisible,
3583
- isStreaming: isLastAssistant && isStreaming
4114
+ isStreaming: isLastAssistant && isStreaming,
4115
+ isLastAssistant,
4116
+ aggregatedSearchSources: isLastAssistant ? aggregatedSearchSources : void 0
3584
4117
  },
3585
4118
  messageKey
3586
4119
  );
@@ -4256,6 +4789,49 @@ async function compressImage(file) {
4256
4789
  function isAcceptedImage(file) {
4257
4790
  return ACCEPTED_IMAGE_TYPES.includes(file.type);
4258
4791
  }
4792
+ async function createAttachmentFromFile(file) {
4793
+ const id = crypto.randomUUID();
4794
+ if (!isAcceptedImage(file)) {
4795
+ return {
4796
+ id,
4797
+ file,
4798
+ preview: null,
4799
+ type: "image",
4800
+ base64: null,
4801
+ error: "Unsupported file type. Please use PNG, JPG, GIF, or WebP images."
4802
+ };
4803
+ }
4804
+ if (file.size > MAX_FILE_SIZE) {
4805
+ return {
4806
+ id,
4807
+ file,
4808
+ preview: null,
4809
+ type: "image",
4810
+ base64: null,
4811
+ error: "Image is too large. Maximum size is 10MB."
4812
+ };
4813
+ }
4814
+ try {
4815
+ const base64 = await compressImage(file);
4816
+ const preview = URL.createObjectURL(file);
4817
+ return {
4818
+ id,
4819
+ file,
4820
+ preview,
4821
+ type: "image",
4822
+ base64
4823
+ };
4824
+ } catch (err) {
4825
+ return {
4826
+ id,
4827
+ file,
4828
+ preview: null,
4829
+ type: "image",
4830
+ base64: null,
4831
+ error: err instanceof Error ? err.message : "Failed to process image"
4832
+ };
4833
+ }
4834
+ }
4259
4835
  function AttachmentButton({
4260
4836
  onFileSelect,
4261
4837
  disabled = false,
@@ -4270,49 +4846,8 @@ function AttachmentButton({
4270
4846
  const file = event.target.files?.[0];
4271
4847
  if (!file) return;
4272
4848
  event.target.value = "";
4273
- const id = crypto.randomUUID();
4274
- if (!isAcceptedImage(file)) {
4275
- onFileSelect({
4276
- id,
4277
- file,
4278
- preview: null,
4279
- type: "image",
4280
- base64: null,
4281
- error: "Unsupported file type. Please use PNG, JPG, GIF, or WebP images."
4282
- });
4283
- return;
4284
- }
4285
- if (file.size > MAX_FILE_SIZE) {
4286
- onFileSelect({
4287
- id,
4288
- file,
4289
- preview: null,
4290
- type: "image",
4291
- base64: null,
4292
- error: "Image is too large. Maximum size is 10MB."
4293
- });
4294
- return;
4295
- }
4296
- try {
4297
- const base64 = await compressImage(file);
4298
- const preview = URL.createObjectURL(file);
4299
- onFileSelect({
4300
- id,
4301
- file,
4302
- preview,
4303
- type: "image",
4304
- base64
4305
- });
4306
- } catch (err) {
4307
- onFileSelect({
4308
- id,
4309
- file,
4310
- preview: null,
4311
- type: "image",
4312
- base64: null,
4313
- error: err instanceof Error ? err.message : "Failed to process image"
4314
- });
4315
- }
4849
+ const attachment = await createAttachmentFromFile(file);
4850
+ onFileSelect(attachment);
4316
4851
  },
4317
4852
  [onFileSelect]
4318
4853
  );
@@ -4537,6 +5072,14 @@ function ChatComposerComponent({
4537
5072
  const [attachments, setAttachments] = useState([]);
4538
5073
  const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
4539
5074
  const [slashMenuDismissed, setSlashMenuDismissed] = useState(false);
5075
+ const [isDragActive, setIsDragActive] = useState(false);
5076
+ const [isRecording, setIsRecording] = useState(false);
5077
+ const [recordingTime, setRecordingTime] = useState(0);
5078
+ const [sttLoading, setSttLoading] = useState(false);
5079
+ const mediaRecorderRef = useRef(null);
5080
+ const recordingChunksRef = useRef([]);
5081
+ const recordingTimerRef = useRef(null);
5082
+ const webSpeechRef = useRef(null);
4540
5083
  const promptRef = useRef(null);
4541
5084
  const showSlashCommands = useMemo(() => /^\/\S*$/.test(value) && !slashMenuDismissed, [value, slashMenuDismissed]);
4542
5085
  const slashQuery = useMemo(() => showSlashCommands ? value.slice(1).toLowerCase() : "", [showSlashCommands, value]);
@@ -4577,6 +5120,30 @@ function ChatComposerComponent({
4577
5120
  const handleRemoveAttachment = useCallback((id) => {
4578
5121
  setAttachments((prev) => prev.filter((a) => a.id !== id));
4579
5122
  }, []);
5123
+ const handleDragOver = useCallback((event) => {
5124
+ const hasFiles = Array.from(event.dataTransfer.types).includes("Files");
5125
+ if (!hasFiles) return;
5126
+ event.preventDefault();
5127
+ event.dataTransfer.dropEffect = "copy";
5128
+ setIsDragActive(true);
5129
+ }, []);
5130
+ const handleDragLeave = useCallback((event) => {
5131
+ event.preventDefault();
5132
+ const nextTarget = event.relatedTarget;
5133
+ if (nextTarget && event.currentTarget.contains(nextTarget)) return;
5134
+ setIsDragActive(false);
5135
+ }, []);
5136
+ const handleDrop = useCallback(async (event) => {
5137
+ event.preventDefault();
5138
+ setIsDragActive(false);
5139
+ const files = Array.from(event.dataTransfer.files ?? []);
5140
+ if (files.length === 0) return;
5141
+ const imageFiles = files.filter((file) => isAcceptedImage(file));
5142
+ if (imageFiles.length === 0) return;
5143
+ const newAttachments = await Promise.all(imageFiles.map((file) => createAttachmentFromFile(file)));
5144
+ setAttachments((prev) => [...prev, ...newAttachments]);
5145
+ focusPrompt();
5146
+ }, [focusPrompt]);
4580
5147
  const setComposerValue = useCallback(
4581
5148
  (nextValue) => {
4582
5149
  setValue(nextValue);
@@ -4661,75 +5228,252 @@ function ChatComposerComponent({
4661
5228
  },
4662
5229
  [showSlashCommands, filteredSlashCommands, selectedCommandIndex, selectSlashCommand]
4663
5230
  );
5231
+ useEffect(() => {
5232
+ return () => {
5233
+ if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
5234
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
5235
+ mediaRecorderRef.current.stop();
5236
+ }
5237
+ if (webSpeechRef.current) webSpeechRef.current.abort();
5238
+ };
5239
+ }, []);
5240
+ const getSttProvider = useCallback(() => {
5241
+ if (typeof window === "undefined") return "auto";
5242
+ try {
5243
+ return localStorage.getItem("opencami-stt-provider") || "auto";
5244
+ } catch {
5245
+ return "auto";
5246
+ }
5247
+ }, []);
5248
+ const stopRecording = useCallback(() => {
5249
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
5250
+ mediaRecorderRef.current.stop();
5251
+ }
5252
+ if (webSpeechRef.current) {
5253
+ webSpeechRef.current.stop();
5254
+ }
5255
+ if (recordingTimerRef.current) {
5256
+ clearInterval(recordingTimerRef.current);
5257
+ recordingTimerRef.current = null;
5258
+ }
5259
+ setIsRecording(false);
5260
+ setRecordingTime(0);
5261
+ }, []);
5262
+ const startRecording = useCallback(async () => {
5263
+ const provider = getSttProvider();
5264
+ if (provider === "browser") {
5265
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
5266
+ if (!SpeechRecognition) {
5267
+ alert("Web Speech API is not supported in this browser.");
5268
+ return;
5269
+ }
5270
+ const recognition = new SpeechRecognition();
5271
+ recognition.continuous = true;
5272
+ recognition.interimResults = false;
5273
+ recognition.lang = navigator.language || "en-US";
5274
+ webSpeechRef.current = recognition;
5275
+ setIsRecording(true);
5276
+ setRecordingTime(0);
5277
+ recordingTimerRef.current = setInterval(() => {
5278
+ setRecordingTime((t) => {
5279
+ if (t >= 119) {
5280
+ stopRecording();
5281
+ return 0;
5282
+ }
5283
+ return t + 1;
5284
+ });
5285
+ }, 1e3);
5286
+ let transcript = "";
5287
+ recognition.onresult = (event) => {
5288
+ for (let i = event.resultIndex; i < event.results.length; i++) {
5289
+ if (event.results[i].isFinal) {
5290
+ transcript += event.results[i][0].transcript;
5291
+ }
5292
+ }
5293
+ };
5294
+ recognition.onend = () => {
5295
+ setIsRecording(false);
5296
+ setRecordingTime(0);
5297
+ if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
5298
+ if (transcript.trim()) {
5299
+ setValue((prev) => prev + (prev ? " " : "") + transcript.trim());
5300
+ focusPrompt();
5301
+ }
5302
+ };
5303
+ recognition.onerror = () => {
5304
+ setIsRecording(false);
5305
+ setRecordingTime(0);
5306
+ if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
5307
+ };
5308
+ recognition.start();
5309
+ return;
5310
+ }
5311
+ try {
5312
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
5313
+ const mimeType = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
5314
+ const recorder = new MediaRecorder(stream, { mimeType });
5315
+ mediaRecorderRef.current = recorder;
5316
+ recordingChunksRef.current = [];
5317
+ recorder.ondataavailable = (e) => {
5318
+ if (e.data.size > 0) recordingChunksRef.current.push(e.data);
5319
+ };
5320
+ recorder.onstop = async () => {
5321
+ stream.getTracks().forEach((t) => t.stop());
5322
+ const audioBlob = new Blob(recordingChunksRef.current, { type: mimeType });
5323
+ if (audioBlob.size === 0) return;
5324
+ setSttLoading(true);
5325
+ try {
5326
+ const formData = new FormData();
5327
+ formData.append("audio", audioBlob, `recording.${mimeType === "audio/webm" ? "webm" : "mp4"}`);
5328
+ if (provider !== "auto") formData.append("provider", provider);
5329
+ const res = await fetch("/api/stt", { method: "POST", body: formData });
5330
+ const data = await res.json();
5331
+ if (data.ok && data.text) {
5332
+ setValue((prev) => prev + (prev ? " " : "") + data.text);
5333
+ focusPrompt();
5334
+ } else if (!data.ok) {
5335
+ console.warn("STT failed:", data.error);
5336
+ alert(data.error || "Speech-to-text failed. Try the Browser provider in Settings.");
5337
+ }
5338
+ } catch (err) {
5339
+ console.warn("STT request failed:", err);
5340
+ alert("Could not reach speech-to-text service.");
5341
+ } finally {
5342
+ setSttLoading(false);
5343
+ }
5344
+ };
5345
+ setIsRecording(true);
5346
+ setRecordingTime(0);
5347
+ recordingTimerRef.current = setInterval(() => {
5348
+ setRecordingTime((t) => {
5349
+ if (t >= 119) {
5350
+ stopRecording();
5351
+ return 0;
5352
+ }
5353
+ return t + 1;
5354
+ });
5355
+ }, 1e3);
5356
+ recorder.start();
5357
+ } catch (err) {
5358
+ const msg = err instanceof Error ? err.message : String(err);
5359
+ if (msg.includes("NotAllowedError") || msg.includes("Permission")) {
5360
+ try {
5361
+ const status = await navigator.permissions.query({ name: "microphone" });
5362
+ if (status.state === "denied") {
5363
+ alert("Microphone access is blocked. Please enable it in your browser/app settings.");
5364
+ } else {
5365
+ alert("Microphone permission was not granted. Please try again and allow access when prompted.");
5366
+ }
5367
+ } catch {
5368
+ alert("Could not access microphone. Please check your browser settings and allow microphone access for this site.");
5369
+ }
5370
+ } else {
5371
+ alert("Could not access microphone: " + msg);
5372
+ }
5373
+ }
5374
+ }, [getSttProvider, stopRecording, focusPrompt]);
5375
+ const handleMicClick = useCallback(() => {
5376
+ if (isRecording) {
5377
+ stopRecording();
5378
+ } else {
5379
+ startRecording();
5380
+ }
5381
+ }, [isRecording, stopRecording, startRecording]);
4664
5382
  const validAttachments = attachments.filter((a) => !a.error && a.base64);
4665
5383
  const submitDisabled = disabled || value.trim().length === 0 && validAttachments.length === 0;
4666
- return /* @__PURE__ */ jsx(
5384
+ return /* @__PURE__ */ jsxs(
4667
5385
  "div",
4668
5386
  {
4669
5387
  className: "mx-auto w-full max-w-full px-2 md:px-5 sm:max-w-[768px] sm:min-w-[400px] relative pb-1 md:pb-3",
4670
5388
  ref: wrapperRef,
4671
- children: /* @__PURE__ */ jsxs(
4672
- PromptInput,
4673
- {
4674
- value,
4675
- onValueChange: handleValueChange,
4676
- onSubmit: handleSubmit,
4677
- isLoading,
4678
- disabled,
4679
- children: [
4680
- /* @__PURE__ */ jsx(
4681
- AttachmentPreviewList,
4682
- {
4683
- attachments,
4684
- onRemove: handleRemoveAttachment
4685
- }
4686
- ),
4687
- showSlashCommands && filteredSlashCommands.length > 0 && /* @__PURE__ */ jsx(
4688
- SlashCommandMenu,
4689
- {
4690
- commands: filteredSlashCommands,
4691
- selectedIndex: Math.min(selectedCommandIndex, filteredSlashCommands.length - 1),
4692
- onSelect: selectSlashCommand
4693
- }
4694
- ),
4695
- /* @__PURE__ */ jsx(
4696
- PromptInputTextarea,
4697
- {
4698
- placeholder: "Type a message…",
4699
- inputRef: setPromptRef,
4700
- onKeyDown: handleComposerKeyDown
4701
- }
4702
- ),
4703
- /* @__PURE__ */ jsxs(PromptInputActions, { className: "justify-between px-3", children: [
4704
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
4705
- /* @__PURE__ */ jsx(ModelSelector, { onModelChange: setSelectedModel }),
4706
- /* @__PURE__ */ jsx(PersonaPicker, { onSelect: handlePersonaSelect }),
4707
- /* @__PURE__ */ jsx(CommandHelp, { onCommandSelect: (cmd) => handleValueChange(cmd + " ") })
4708
- ] }),
4709
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
4710
- /* @__PURE__ */ jsx(PromptInputAction, { tooltip: "Attach file", children: /* @__PURE__ */ jsx(
4711
- AttachmentButton,
4712
- {
4713
- onFileSelect: handleFileSelect,
4714
- disabled
4715
- }
4716
- ) }),
4717
- /* @__PURE__ */ jsx(PromptInputAction, { tooltip: "Send message", children: /* @__PURE__ */ jsx(
4718
- Button,
4719
- {
4720
- onClick: handleSubmit,
4721
- disabled: submitDisabled,
4722
- size: "icon-sm",
4723
- className: "rounded-full",
4724
- "aria-label": "Send message",
4725
- children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArrowUp02Icon, size: 18, strokeWidth: 2 })
4726
- }
4727
- ) })
5389
+ onDragOver: handleDragOver,
5390
+ onDragLeave: handleDragLeave,
5391
+ onDrop: handleDrop,
5392
+ children: [
5393
+ isDragActive && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-2 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary-400 bg-primary-50/90 text-sm font-medium text-primary-700", children: "Drop image here" }),
5394
+ /* @__PURE__ */ jsxs(
5395
+ PromptInput,
5396
+ {
5397
+ value,
5398
+ onValueChange: handleValueChange,
5399
+ onSubmit: handleSubmit,
5400
+ isLoading,
5401
+ disabled,
5402
+ children: [
5403
+ /* @__PURE__ */ jsx(
5404
+ AttachmentPreviewList,
5405
+ {
5406
+ attachments,
5407
+ onRemove: handleRemoveAttachment
5408
+ }
5409
+ ),
5410
+ showSlashCommands && filteredSlashCommands.length > 0 && /* @__PURE__ */ jsx(
5411
+ SlashCommandMenu,
5412
+ {
5413
+ commands: filteredSlashCommands,
5414
+ selectedIndex: Math.min(selectedCommandIndex, filteredSlashCommands.length - 1),
5415
+ onSelect: selectSlashCommand
5416
+ }
5417
+ ),
5418
+ /* @__PURE__ */ jsx(
5419
+ PromptInputTextarea,
5420
+ {
5421
+ placeholder: "Type a message…",
5422
+ inputRef: setPromptRef,
5423
+ onKeyDown: handleComposerKeyDown
5424
+ }
5425
+ ),
5426
+ /* @__PURE__ */ jsxs(PromptInputActions, { className: "justify-between px-3", children: [
5427
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5428
+ /* @__PURE__ */ jsx(ModelSelector, { onModelChange: setSelectedModel }),
5429
+ /* @__PURE__ */ jsx(PersonaPicker, { onSelect: handlePersonaSelect }),
5430
+ /* @__PURE__ */ jsx(CommandHelp, { onCommandSelect: (cmd) => handleValueChange(cmd + " ") })
5431
+ ] }),
5432
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5433
+ /* @__PURE__ */ jsx(PromptInputAction, { tooltip: "Attach file", children: /* @__PURE__ */ jsx(
5434
+ AttachmentButton,
5435
+ {
5436
+ onFileSelect: handleFileSelect,
5437
+ disabled
5438
+ }
5439
+ ) }),
5440
+ /* @__PURE__ */ jsx(PromptInputAction, { tooltip: isRecording ? "Stop recording" : "Voice input", children: /* @__PURE__ */ jsx(
5441
+ Button,
5442
+ {
5443
+ onClick: handleMicClick,
5444
+ disabled: disabled || sttLoading,
5445
+ size: "icon-sm",
5446
+ variant: isRecording ? "destructive" : "ghost",
5447
+ className: `rounded-full ${isRecording ? "animate-pulse" : ""}`,
5448
+ "aria-label": isRecording ? "Stop recording" : "Voice input",
5449
+ children: sttLoading ? /* @__PURE__ */ jsx("span", { className: "size-4 animate-spin rounded-full border-2 border-current border-t-transparent" }) : isRecording ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
5450
+ /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-red-500" }),
5451
+ /* @__PURE__ */ jsxs("span", { className: "text-xs tabular-nums", children: [
5452
+ Math.floor(recordingTime / 60),
5453
+ ":",
5454
+ String(recordingTime % 60).padStart(2, "0")
5455
+ ] }),
5456
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StopIcon, size: 14, strokeWidth: 2 })
5457
+ ] }) : /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Mic02Icon, size: 18, strokeWidth: 2 })
5458
+ }
5459
+ ) }),
5460
+ /* @__PURE__ */ jsx(PromptInputAction, { tooltip: "Send message", children: /* @__PURE__ */ jsx(
5461
+ Button,
5462
+ {
5463
+ onClick: handleSubmit,
5464
+ disabled: submitDisabled,
5465
+ size: "icon-sm",
5466
+ className: "rounded-full",
5467
+ "aria-label": "Send message",
5468
+ children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArrowUp02Icon, size: 18, strokeWidth: 2 })
5469
+ }
5470
+ ) })
5471
+ ] })
4728
5472
  ] })
4729
- ] })
4730
- ]
4731
- }
4732
- )
5473
+ ]
5474
+ }
5475
+ )
5476
+ ]
4733
5477
  }
4734
5478
  );
4735
5479
  }
@@ -5102,11 +5846,15 @@ function useStreaming(options) {
5102
5846
  const onErrorRef = useRef(options.onError);
5103
5847
  onDoneRef.current = options.onDone;
5104
5848
  onErrorRef.current = options.onError;
5105
- const stop = useCallback(() => {
5849
+ const stop = useCallback((options2) => {
5106
5850
  if (eventSourceRef.current) {
5107
5851
  eventSourceRef.current.close();
5108
5852
  eventSourceRef.current = null;
5109
5853
  }
5854
+ if (options2?.preserveState) {
5855
+ setState((prev) => ({ ...prev, active: false }));
5856
+ return;
5857
+ }
5110
5858
  setState(INITIAL_STATE);
5111
5859
  }, []);
5112
5860
  const start = useCallback(
@@ -5154,7 +5902,7 @@ function useStreaming(options) {
5154
5902
  const data = JSON.parse(e.data);
5155
5903
  es.close();
5156
5904
  eventSourceRef.current = null;
5157
- setState(INITIAL_STATE);
5905
+ setState((prev) => ({ ...prev, active: false }));
5158
5906
  onDoneRef.current(data.sessionKey);
5159
5907
  } catch {
5160
5908
  }
@@ -5313,12 +6061,12 @@ function useSwipeGesture(options) {
5313
6061
  };
5314
6062
  }
5315
6063
  const KeyboardShortcutsDialog = lazy(
5316
- () => import("./keyboard-shortcuts-dialog-D5hqVX2v.js").then((m) => ({
6064
+ () => import("./keyboard-shortcuts-dialog-7OEtXUlW.js").then((m) => ({
5317
6065
  default: m.KeyboardShortcutsDialog
5318
6066
  }))
5319
6067
  );
5320
6068
  const SearchDialog = lazy(
5321
- () => import("./search-dialog-JY8C3mqa.js").then((m) => ({
6069
+ () => import("./search-dialog-BE2ZBkj0.js").then((m) => ({
5322
6070
  default: m.SearchDialog
5323
6071
  }))
5324
6072
  );
@@ -5462,26 +6210,31 @@ function ChatScreen({
5462
6210
  refreshHistoryRef.current();
5463
6211
  }, FAST_POLL_MS);
5464
6212
  }, [activeFriendlyId, isNewChat]);
5465
- const handleStreamDone = useCallback(
5466
- (_sk) => {
5467
- void historyQuery.refetch();
5468
- void queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions });
6213
+ const handleStreamDoneRef = useRef(() => {
6214
+ });
6215
+ const handleStreamErrorRef = useRef(() => {
6216
+ });
6217
+ const { streaming, startStream, stopStream } = useStreaming({
6218
+ onDone: (sk) => handleStreamDoneRef.current(sk),
6219
+ onError: (err) => handleStreamErrorRef.current(err)
6220
+ });
6221
+ handleStreamDoneRef.current = useCallback(
6222
+ async (_sk) => {
6223
+ await historyQuery.refetch();
6224
+ stopStream({ preserveState: true });
5469
6225
  streamFinish();
6226
+ void queryClient.invalidateQueries({ queryKey: chatQueryKeys.sessions });
5470
6227
  },
5471
- [historyQuery, queryClient, streamFinish]
6228
+ [historyQuery, queryClient, stopStream, streamFinish]
5472
6229
  );
5473
- const handleStreamError = useCallback(
6230
+ handleStreamErrorRef.current = useCallback(
5474
6231
  (_err) => {
5475
6232
  console.warn("[stream] SSE error, falling back to polling");
5476
6233
  },
5477
6234
  []
5478
6235
  );
5479
- const { streaming, startStream, stopStream } = useStreaming({
5480
- onDone: handleStreamDone,
5481
- onError: handleStreamError
5482
- });
5483
6236
  const streamingMessage = useMemo(() => {
5484
- if (!streaming.active || !streaming.text) return null;
6237
+ if (!streaming.text) return null;
5485
6238
  const content = [];
5486
6239
  for (const tool of streaming.tools) {
5487
6240
  content.push({
@@ -5494,23 +6247,22 @@ function ChatScreen({
5494
6247
  return {
5495
6248
  role: "assistant",
5496
6249
  content,
6250
+ id: "__streaming__",
5497
6251
  __streaming: true,
5498
6252
  timestamp: Date.now()
5499
6253
  };
5500
- }, [streaming.active, streaming.text, streaming.tools]);
6254
+ }, [streaming.text, streaming.tools]);
5501
6255
  const messagesWithStreaming = useMemo(() => {
5502
6256
  if (!streamingMessage) return displayMessages;
5503
- const msgs = [...displayMessages];
5504
- const lastMsg = msgs[msgs.length - 1];
5505
- if (lastMsg?.role === "assistant" && streamingMessage) {
6257
+ const lastMsg = displayMessages[displayMessages.length - 1];
6258
+ if (lastMsg?.role === "assistant") {
5506
6259
  const lastText = textFromMessage(lastMsg);
5507
- if (streaming.text.length > lastText.length) {
5508
- msgs[msgs.length - 1] = streamingMessage;
5509
- return msgs;
5510
- }
6260
+ if (lastText.length >= streaming.text.length) return displayMessages;
6261
+ const msgs = [...displayMessages];
6262
+ msgs[msgs.length - 1] = streamingMessage;
5511
6263
  return msgs;
5512
6264
  }
5513
- return [...msgs, streamingMessage];
6265
+ return [...displayMessages, streamingMessage];
5514
6266
  }, [displayMessages, streamingMessage, streaming.text]);
5515
6267
  const stableContentStyle = useMemo(() => ({}), []);
5516
6268
  refreshHistoryRef.current = function refreshHistory() {