hermium 0.1.10 → 0.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 (212) hide show
  1. package/README.md +56 -0
  2. package/bin/hermium.mjs +185 -164
  3. package/dist/api.mjs +3513 -0
  4. package/dist/public/assets/css/index-Dfs9RUU9.css +1 -0
  5. package/dist/public/assets/css/styles-B8p6jk5Z.css +1 -0
  6. package/dist/public/assets/js/ChatInputBlock-Bw7AL70H.js +1 -0
  7. package/dist/public/assets/js/MarkdownMessage-8d7Y6VL-.js +1 -0
  8. package/dist/public/assets/js/base-ui-BvQbAt_1.js +1 -0
  9. package/dist/public/assets/js/chat._sessionId-BG6lVraH.js +1 -0
  10. package/dist/public/assets/js/chat.index-D2zdMPTT.js +1 -0
  11. package/dist/public/assets/js/index-C0AK45FU.js +60 -0
  12. package/dist/public/assets/js/index-Cx5En4FK.js +1 -0
  13. package/dist/public/assets/js/memory-CeSRdTkW.js +3 -0
  14. package/dist/public/assets/js/router-8uDKazL-.js +1 -0
  15. package/dist/public/assets/js/settings-Bc3Y5zXO.js +1 -0
  16. package/dist/public/assets/js/skills-DZv7sA_5.js +1 -0
  17. package/dist/public/assets/js/theme-CPkdkpaj.js +1 -0
  18. package/dist/public/assets/js/usage-DXQsT9_b.js +1 -0
  19. package/dist/public/assets/woff2/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  20. package/dist/public/assets/woff2/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  21. package/dist/public/assets/woff2/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  22. package/dist/public/assets/woff2/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  23. package/dist/public/assets/woff2/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  24. package/dist/public/favicon.ico +0 -0
  25. package/dist/public/logo.png +0 -0
  26. package/package.json +1 -1
  27. package/dist/public/assets/IconAlertCircle-BW147gsG.js +0 -1
  28. package/dist/public/assets/IconAlertTriangle-DCoTLVSd.js +0 -1
  29. package/dist/public/assets/IconCheck-DAO7Fpl9.js +0 -1
  30. package/dist/public/assets/IconCode-BqfTl5wU.js +0 -1
  31. package/dist/public/assets/IconLoader2-B_pehSXN.js +0 -1
  32. package/dist/public/assets/IconRefresh-BbMGoMV8.js +0 -1
  33. package/dist/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  34. package/dist/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  35. package/dist/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  36. package/dist/public/assets/index-BL9a2Xg9.js +0 -1
  37. package/dist/public/assets/index-BSwXjgjr.js +0 -1
  38. package/dist/public/assets/index-BWl8tn18.js +0 -1
  39. package/dist/public/assets/index-CQh8SXb2.js +0 -94
  40. package/dist/public/assets/index-Cxd-kSUY.js +0 -1
  41. package/dist/public/assets/index-DA5SH--7.js +0 -2
  42. package/dist/public/assets/index-DCHbvtBS.js +0 -1
  43. package/dist/public/assets/index-DCYXJZEe.js +0 -1
  44. package/dist/public/assets/index-DFDfp0ca.js +0 -1
  45. package/dist/public/assets/index-GuAAqSCJ.js +0 -14
  46. package/dist/public/assets/index-WIDirTHx.js +0 -29
  47. package/dist/public/assets/index-X3XZcAzy.js +0 -1
  48. package/dist/public/assets/input-7TQEEJq6.js +0 -1
  49. package/dist/public/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  50. package/dist/public/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  51. package/dist/public/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  52. package/dist/public/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  53. package/dist/public/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  54. package/dist/public/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  55. package/dist/public/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  56. package/dist/public/assets/models-BlUb1eaf.js +0 -1
  57. package/dist/public/assets/styles-PHtUFHUr.css +0 -1
  58. package/dist/public/assets/switch-B_iYUUM3.js +0 -1
  59. package/dist/public/assets/syntax-highlighter-DcBYfnEK.js +0 -6
  60. package/dist/public/assets/textarea-Dm4aaRqS.js +0 -1
  61. package/dist/public/favicon.png +0 -0
  62. package/dist/public/nous-logo.png +0 -0
  63. package/dist/server/index.mjs +0 -244
  64. package/dist/web-server/__23tanstack-start-plugin-adapters-Cwee5PKy.mjs +0 -6
  65. package/dist/web-server/_chunks/ssr-renderer.mjs +0 -22
  66. package/dist/web-server/_libs/babel__runtime.mjs +0 -237
  67. package/dist/web-server/_libs/bail.mjs +0 -8
  68. package/dist/web-server/_libs/base-ui__react.mjs +0 -9554
  69. package/dist/web-server/_libs/base-ui__utils.mjs +0 -1101
  70. package/dist/web-server/_libs/ccount.mjs +0 -16
  71. package/dist/web-server/_libs/character-entities-legacy.mjs +0 -111
  72. package/dist/web-server/_libs/character-entities.mjs +0 -2130
  73. package/dist/web-server/_libs/character-reference-invalid.mjs +0 -33
  74. package/dist/web-server/_libs/class-variance-authority.mjs +0 -44
  75. package/dist/web-server/_libs/clsx.mjs +0 -16
  76. package/dist/web-server/_libs/comma-separated-tokens.mjs +0 -31
  77. package/dist/web-server/_libs/cookie-es.mjs +0 -44
  78. package/dist/web-server/_libs/croner.mjs +0 -1
  79. package/dist/web-server/_libs/crossws.mjs +0 -1
  80. package/dist/web-server/_libs/decode-named-character-reference+[...].mjs +0 -8
  81. package/dist/web-server/_libs/devlop.mjs +0 -8
  82. package/dist/web-server/_libs/escape-string-regexp.mjs +0 -9
  83. package/dist/web-server/_libs/estree-util-is-identifier-name.mjs +0 -11
  84. package/dist/web-server/_libs/extend.mjs +0 -97
  85. package/dist/web-server/_libs/fault.mjs +0 -1
  86. package/dist/web-server/_libs/floating-ui__core.mjs +0 -663
  87. package/dist/web-server/_libs/floating-ui__dom.mjs +0 -624
  88. package/dist/web-server/_libs/floating-ui__react-dom.mjs +0 -279
  89. package/dist/web-server/_libs/floating-ui__utils.mjs +0 -322
  90. package/dist/web-server/_libs/format.mjs +0 -1
  91. package/dist/web-server/_libs/h3.mjs +0 -408
  92. package/dist/web-server/_libs/hast-util-parse-selector.mjs +0 -39
  93. package/dist/web-server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
  94. package/dist/web-server/_libs/hast-util-whitespace.mjs +0 -10
  95. package/dist/web-server/_libs/hastscript.mjs +0 -200
  96. package/dist/web-server/_libs/highlight.js.mjs +0 -1
  97. package/dist/web-server/_libs/hookable.mjs +0 -1
  98. package/dist/web-server/_libs/html-url-attributes.mjs +0 -26
  99. package/dist/web-server/_libs/inline-style-parser.mjs +0 -142
  100. package/dist/web-server/_libs/is-alphabetical.mjs +0 -7
  101. package/dist/web-server/_libs/is-alphanumerical.mjs +0 -8
  102. package/dist/web-server/_libs/is-decimal.mjs +0 -7
  103. package/dist/web-server/_libs/is-hexadecimal.mjs +0 -7
  104. package/dist/web-server/_libs/is-plain-obj.mjs +0 -10
  105. package/dist/web-server/_libs/isbot.mjs +0 -21
  106. package/dist/web-server/_libs/longest-streak.mjs +0 -25
  107. package/dist/web-server/_libs/lowlight.mjs +0 -1
  108. package/dist/web-server/_libs/markdown-table.mjs +0 -142
  109. package/dist/web-server/_libs/mdast-util-find-and-replace.mjs +0 -109
  110. package/dist/web-server/_libs/mdast-util-from-markdown.mjs +0 -717
  111. package/dist/web-server/_libs/mdast-util-gfm-autolink-literal+[...].mjs +0 -156
  112. package/dist/web-server/_libs/mdast-util-gfm-footnote.mjs +0 -117
  113. package/dist/web-server/_libs/mdast-util-gfm-strikethrough.mjs +0 -54
  114. package/dist/web-server/_libs/mdast-util-gfm-table.mjs +0 -157
  115. package/dist/web-server/_libs/mdast-util-gfm-task-list-item.mjs +0 -77
  116. package/dist/web-server/_libs/mdast-util-gfm.mjs +0 -29
  117. package/dist/web-server/_libs/mdast-util-phrasing.mjs +0 -30
  118. package/dist/web-server/_libs/mdast-util-to-hast.mjs +0 -710
  119. package/dist/web-server/_libs/mdast-util-to-markdown.mjs +0 -798
  120. package/dist/web-server/_libs/mdast-util-to-string.mjs +0 -38
  121. package/dist/web-server/_libs/micromark-core-commonmark.mjs +0 -2259
  122. package/dist/web-server/_libs/micromark-extension-gfm-autolink-literal+[...].mjs +0 -344
  123. package/dist/web-server/_libs/micromark-extension-gfm-footnote+[...].mjs +0 -279
  124. package/dist/web-server/_libs/micromark-extension-gfm-strikethrough+[...].mjs +0 -98
  125. package/dist/web-server/_libs/micromark-extension-gfm-table.mjs +0 -491
  126. package/dist/web-server/_libs/micromark-extension-gfm-tagfilter+[...].mjs +0 -1
  127. package/dist/web-server/_libs/micromark-extension-gfm-task-list-item+[...].mjs +0 -77
  128. package/dist/web-server/_libs/micromark-extension-gfm.mjs +0 -18
  129. package/dist/web-server/_libs/micromark-factory-destination.mjs +0 -94
  130. package/dist/web-server/_libs/micromark-factory-label.mjs +0 -63
  131. package/dist/web-server/_libs/micromark-factory-space.mjs +0 -24
  132. package/dist/web-server/_libs/micromark-factory-title.mjs +0 -65
  133. package/dist/web-server/_libs/micromark-factory-whitespace.mjs +0 -22
  134. package/dist/web-server/_libs/micromark-util-character.mjs +0 -44
  135. package/dist/web-server/_libs/micromark-util-chunked.mjs +0 -36
  136. package/dist/web-server/_libs/micromark-util-classify-character+[...].mjs +0 -12
  137. package/dist/web-server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
  138. package/dist/web-server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
  139. package/dist/web-server/_libs/micromark-util-decode-string.mjs +0 -21
  140. package/dist/web-server/_libs/micromark-util-encode.mjs +0 -1
  141. package/dist/web-server/_libs/micromark-util-html-tag-name.mjs +0 -69
  142. package/dist/web-server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
  143. package/dist/web-server/_libs/micromark-util-resolve-all.mjs +0 -15
  144. package/dist/web-server/_libs/micromark-util-sanitize-uri.mjs +0 -41
  145. package/dist/web-server/_libs/micromark-util-subtokenize.mjs +0 -346
  146. package/dist/web-server/_libs/micromark.mjs +0 -906
  147. package/dist/web-server/_libs/ocache.mjs +0 -1
  148. package/dist/web-server/_libs/ohash.mjs +0 -1
  149. package/dist/web-server/_libs/parse-entities.mjs +0 -245
  150. package/dist/web-server/_libs/property-information.mjs +0 -1210
  151. package/dist/web-server/_libs/react-dom.mjs +0 -10779
  152. package/dist/web-server/_libs/react-markdown.mjs +0 -147
  153. package/dist/web-server/_libs/react-syntax-highlighter.mjs +0 -941
  154. package/dist/web-server/_libs/react.mjs +0 -513
  155. package/dist/web-server/_libs/refractor.mjs +0 -2425
  156. package/dist/web-server/_libs/remark-gfm.mjs +0 -20
  157. package/dist/web-server/_libs/remark-parse.mjs +0 -19
  158. package/dist/web-server/_libs/remark-rehype.mjs +0 -21
  159. package/dist/web-server/_libs/reselect.mjs +0 -1
  160. package/dist/web-server/_libs/rou3.mjs +0 -8
  161. package/dist/web-server/_libs/seroval-plugins.mjs +0 -58
  162. package/dist/web-server/_libs/seroval.mjs +0 -1775
  163. package/dist/web-server/_libs/space-separated-tokens.mjs +0 -11
  164. package/dist/web-server/_libs/srvx.mjs +0 -781
  165. package/dist/web-server/_libs/style-to-js.mjs +0 -72
  166. package/dist/web-server/_libs/style-to-object.mjs +0 -38
  167. package/dist/web-server/_libs/tabler__icons-react.mjs +0 -233
  168. package/dist/web-server/_libs/tanstack__history.mjs +0 -204
  169. package/dist/web-server/_libs/tanstack__query-core.mjs +0 -2552
  170. package/dist/web-server/_libs/tanstack__react-query.mjs +0 -190
  171. package/dist/web-server/_libs/tanstack__react-router.mjs +0 -1120
  172. package/dist/web-server/_libs/tanstack__react-store.mjs +0 -2
  173. package/dist/web-server/_libs/tanstack__router-core.mjs +0 -4288
  174. package/dist/web-server/_libs/tanstack__store.mjs +0 -1
  175. package/dist/web-server/_libs/trim-lines.mjs +0 -41
  176. package/dist/web-server/_libs/trough.mjs +0 -85
  177. package/dist/web-server/_libs/ufo.mjs +0 -54
  178. package/dist/web-server/_libs/unctx.mjs +0 -1
  179. package/dist/web-server/_libs/ungap__structured-clone.mjs +0 -224
  180. package/dist/web-server/_libs/unified.mjs +0 -661
  181. package/dist/web-server/_libs/unist-util-is.mjs +0 -100
  182. package/dist/web-server/_libs/unist-util-position.mjs +0 -27
  183. package/dist/web-server/_libs/unist-util-stringify-position.mjs +0 -27
  184. package/dist/web-server/_libs/unist-util-visit-parents.mjs +0 -83
  185. package/dist/web-server/_libs/unist-util-visit.mjs +0 -24
  186. package/dist/web-server/_libs/unstorage.mjs +0 -1
  187. package/dist/web-server/_libs/use-sync-external-store.mjs +0 -139
  188. package/dist/web-server/_libs/vfile-message.mjs +0 -138
  189. package/dist/web-server/_libs/vfile.mjs +0 -467
  190. package/dist/web-server/_libs/zod.mjs +0 -3915
  191. package/dist/web-server/_libs/zustand.mjs +0 -343
  192. package/dist/web-server/_libs/zwitch.mjs +0 -1
  193. package/dist/web-server/_ssr/index--Bo2_ipW.mjs +0 -277
  194. package/dist/web-server/_ssr/index-BEvygh5x.mjs +0 -612
  195. package/dist/web-server/_ssr/index-BFaKxYfN.mjs +0 -40
  196. package/dist/web-server/_ssr/index-BdrMzRTd.mjs +0 -513
  197. package/dist/web-server/_ssr/index-CoDfv1vI.mjs +0 -66
  198. package/dist/web-server/_ssr/index-Cwp8Gyl1.mjs +0 -1812
  199. package/dist/web-server/_ssr/index-DQsfKYjV.mjs +0 -449
  200. package/dist/web-server/_ssr/index-Dbs9k8Ea.mjs +0 -251
  201. package/dist/web-server/_ssr/index-Dk93oyZD.mjs +0 -213
  202. package/dist/web-server/_ssr/index-KNJ7huw_.mjs +0 -369
  203. package/dist/web-server/_ssr/index.mjs +0 -1558
  204. package/dist/web-server/_ssr/input-BV1DMASc.mjs +0 -20
  205. package/dist/web-server/_ssr/models-MzrvbL2i.mjs +0 -43
  206. package/dist/web-server/_ssr/router-CS6Zq3md.mjs +0 -2134
  207. package/dist/web-server/_ssr/start-HYkvq4Ni.mjs +0 -4
  208. package/dist/web-server/_ssr/switch-Bd-Sg0HG.mjs +0 -33
  209. package/dist/web-server/_ssr/syntax-highlighter-5vezNTce.mjs +0 -62
  210. package/dist/web-server/_ssr/textarea-CB4kQp9w.mjs +0 -18
  211. package/dist/web-server/_tanstack-start-manifest_v-m5lY48LR.mjs +0 -4
  212. package/dist/web-server/index.mjs +0 -611
@@ -1,1812 +0,0 @@
1
- import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
2
- import { u as useShallow } from "../_libs/zustand.mjs";
3
- import { q as Route, u as useChatStore, c as createSession, D as DropdownMenu, k as DropdownMenuTrigger, l as DropdownMenuContent, m as DropdownMenuItem, r as renameSession, B as Button, a as cn, n as deleteSessionApi, o as respondApproval } from "./router-CS6Zq3md.mjs";
4
- import { T as Textarea } from "./textarea-CB4kQp9w.mjs";
5
- import { d as useNavigate } from "../_libs/tanstack__react-router.mjs";
6
- import { Q as IconPlus, v as IconTrash, r as IconDots, s as IconPencil, I as IconSearch, _ as IconBell, $ as IconMail, z as IconCode, R as IconClock, a0 as IconChartLine, a1 as IconShieldCheck, O as IconAlertTriangle, L as IconCheck, a as IconX, a2 as IconCommand, a3 as IconPaperclip, a4 as IconPlayerStop, C as IconArrowUp, E as IconLoader2, a5 as IconPhoto, a6 as IconFile, V as IconChevronRight, u as IconCopy } from "../_libs/tabler__icons-react.mjs";
7
- import { M as Markdown } from "../_libs/react-markdown.mjs";
8
- import { r as remarkGfm } from "../_libs/remark-gfm.mjs";
9
- import "../_libs/tanstack__query-core.mjs";
10
- import "../_libs/tanstack__react-query.mjs";
11
- import "../_libs/clsx.mjs";
12
- import "../_libs/class-variance-authority.mjs";
13
- import "../_libs/base-ui__react.mjs";
14
- import "../_libs/base-ui__utils.mjs";
15
- import "../_libs/use-sync-external-store.mjs";
16
- import "../_libs/react-dom.mjs";
17
- import "util";
18
- import "crypto";
19
- import "async_hooks";
20
- import "stream";
21
- import "../_libs/floating-ui__utils.mjs";
22
- import "../_libs/floating-ui__react-dom.mjs";
23
- import "../_libs/floating-ui__dom.mjs";
24
- import "../_libs/floating-ui__core.mjs";
25
- import "../_libs/zod.mjs";
26
- import "../_libs/tanstack__router-core.mjs";
27
- import "../_libs/tanstack__history.mjs";
28
- import "../_libs/cookie-es.mjs";
29
- import "../_libs/seroval.mjs";
30
- import "../_libs/seroval-plugins.mjs";
31
- import "node:stream/web";
32
- import "node:stream";
33
- import "../_libs/isbot.mjs";
34
- import "../_libs/devlop.mjs";
35
- import "../_libs/unified.mjs";
36
- import "../_libs/bail.mjs";
37
- import "../_libs/extend.mjs";
38
- import "../_libs/is-plain-obj.mjs";
39
- import "../_libs/trough.mjs";
40
- import "../_libs/vfile.mjs";
41
- import "../_libs/vfile-message.mjs";
42
- import "../_libs/unist-util-stringify-position.mjs";
43
- import "node:process";
44
- import "node:path";
45
- import "node:url";
46
- import "../_libs/remark-parse.mjs";
47
- import "../_libs/mdast-util-from-markdown.mjs";
48
- import "../_libs/micromark-util-decode-numeric-character-reference+[...].mjs";
49
- import "../_libs/micromark-util-decode-string.mjs";
50
- import "../_libs/decode-named-character-reference+[...].mjs";
51
- import "../_libs/character-entities.mjs";
52
- import "../_libs/micromark-util-normalize-identifier+[...].mjs";
53
- import "../_libs/micromark.mjs";
54
- import "../_libs/micromark-util-combine-extensions+[...].mjs";
55
- import "../_libs/micromark-util-chunked.mjs";
56
- import "../_libs/micromark-factory-space.mjs";
57
- import "../_libs/micromark-util-character.mjs";
58
- import "../_libs/micromark-core-commonmark.mjs";
59
- import "../_libs/micromark-util-classify-character+[...].mjs";
60
- import "../_libs/micromark-util-resolve-all.mjs";
61
- import "../_libs/micromark-util-subtokenize.mjs";
62
- import "../_libs/micromark-factory-destination.mjs";
63
- import "../_libs/micromark-factory-label.mjs";
64
- import "../_libs/micromark-factory-title.mjs";
65
- import "../_libs/micromark-factory-whitespace.mjs";
66
- import "../_libs/micromark-util-html-tag-name.mjs";
67
- import "../_libs/mdast-util-to-string.mjs";
68
- import "../_libs/remark-rehype.mjs";
69
- import "../_libs/mdast-util-to-hast.mjs";
70
- import "../_libs/ungap__structured-clone.mjs";
71
- import "../_libs/micromark-util-sanitize-uri.mjs";
72
- import "../_libs/unist-util-position.mjs";
73
- import "../_libs/trim-lines.mjs";
74
- import "../_libs/unist-util-visit.mjs";
75
- import "../_libs/unist-util-visit-parents.mjs";
76
- import "../_libs/unist-util-is.mjs";
77
- import "../_libs/hast-util-to-jsx-runtime.mjs";
78
- import "../_libs/comma-separated-tokens.mjs";
79
- import "../_libs/property-information.mjs";
80
- import "../_libs/space-separated-tokens.mjs";
81
- import "../_libs/style-to-js.mjs";
82
- import "../_libs/style-to-object.mjs";
83
- import "../_libs/inline-style-parser.mjs";
84
- import "../_libs/hast-util-whitespace.mjs";
85
- import "../_libs/estree-util-is-identifier-name.mjs";
86
- import "../_libs/html-url-attributes.mjs";
87
- import "../_libs/micromark-extension-gfm.mjs";
88
- import "../_libs/micromark-extension-gfm-autolink-literal+[...].mjs";
89
- import "../_libs/micromark-extension-gfm-footnote+[...].mjs";
90
- import "../_libs/micromark-extension-gfm-strikethrough+[...].mjs";
91
- import "../_libs/micromark-extension-gfm-table.mjs";
92
- import "../_libs/micromark-extension-gfm-task-list-item+[...].mjs";
93
- import "../_libs/mdast-util-gfm.mjs";
94
- import "../_libs/mdast-util-gfm-autolink-literal+[...].mjs";
95
- import "../_libs/ccount.mjs";
96
- import "../_libs/mdast-util-find-and-replace.mjs";
97
- import "../_libs/escape-string-regexp.mjs";
98
- import "../_libs/mdast-util-gfm-footnote.mjs";
99
- import "../_libs/mdast-util-gfm-strikethrough.mjs";
100
- import "../_libs/mdast-util-gfm-table.mjs";
101
- import "../_libs/markdown-table.mjs";
102
- import "../_libs/mdast-util-to-markdown.mjs";
103
- import "../_libs/longest-streak.mjs";
104
- import "../_libs/mdast-util-phrasing.mjs";
105
- import "../_libs/mdast-util-gfm-task-list-item.mjs";
106
- function isImeComposing(e) {
107
- return e.nativeEvent?.isComposing === true;
108
- }
109
- const SLASH_COMMANDS = [
110
- // Chat control (local)
111
- { name: "/new", description: "Start a new chat", category: "chat", local: true },
112
- { name: "/clear", description: "Clear conversation history", category: "chat", local: true },
113
- // Agent commands (sent to backend)
114
- { name: "/btw", description: "Ask a side question without affecting context", category: "agent" },
115
- { name: "/approve", description: "Approve a pending action", category: "agent" },
116
- { name: "/deny", description: "Deny a pending action", category: "agent" },
117
- { name: "/status", description: "Show current agent status", category: "agent" },
118
- { name: "/reset", description: "Reset conversation context", category: "agent" },
119
- { name: "/compact", description: "Compact and summarize the conversation", category: "agent" },
120
- { name: "/undo", description: "Undo the last action", category: "agent" },
121
- { name: "/retry", description: "Retry the last failed action", category: "agent" },
122
- { name: "/compress", description: "Compress conversation with optional focus topic", category: "agent" },
123
- { name: "/debug", description: "Show diagnostics and debug info", category: "agent" },
124
- { name: "/goal", description: "Lock the agent onto a persistent cross-turn goal", category: "agent" },
125
- { name: "/steer", description: "Steer the in-flight agent without interrupting it", category: "agent" },
126
- { name: "/queue", description: "Queue a follow-up to run after the current turn", category: "agent" },
127
- // Tools
128
- { name: "/web", description: "Search the web", category: "tools" },
129
- { name: "/image", description: "Generate an image", category: "tools" },
130
- { name: "/browse", description: "Browse a URL", category: "tools" },
131
- { name: "/code", description: "Write or execute code", category: "tools" },
132
- { name: "/file", description: "Read or write files", category: "tools" },
133
- { name: "/shell", description: "Run a shell command", category: "tools" },
134
- // Info (local)
135
- { name: "/help", description: "Show available commands and help", category: "info", local: true },
136
- { name: "/model", description: "Show or switch the current model", category: "info", local: true },
137
- { name: "/memory", description: "Show agent memory", category: "info", local: true },
138
- { name: "/persona", description: "Show current persona", category: "info", local: true },
139
- { name: "/version", description: "Show Hermes version", category: "info", local: true },
140
- { name: "/usage", description: "Show token usage and cost", category: "info", local: true },
141
- { name: "/tools", description: "List available tools", category: "info", local: true },
142
- { name: "/skills", description: "List installed skills", category: "info", local: true }
143
- ];
144
- function isLocalCommand(text) {
145
- if (!text.startsWith("/")) return false;
146
- const cmd = text.split(/\s+/)[0].toLowerCase();
147
- return SLASH_COMMANDS.some((c) => c.name === cmd && c.local);
148
- }
149
- function useInputHistory({
150
- currentInput,
151
- applyText
152
- }) {
153
- const [history, setHistory] = reactExports.useState([]);
154
- const indexRef = reactExports.useRef(-1);
155
- const draftRef = reactExports.useRef("");
156
- const push = reactExports.useCallback((text) => {
157
- setHistory((prev) => [...prev, text]);
158
- indexRef.current = -1;
159
- draftRef.current = "";
160
- }, []);
161
- const recallPrev = reactExports.useCallback(() => {
162
- if (history.length === 0) return false;
163
- const cur = indexRef.current;
164
- const next = cur === -1 ? history.length - 1 : Math.max(0, cur - 1);
165
- if (cur === -1) draftRef.current = currentInput;
166
- indexRef.current = next;
167
- applyText(history[next]);
168
- return true;
169
- }, [history, currentInput, applyText]);
170
- const recallNext = reactExports.useCallback(() => {
171
- const cur = indexRef.current;
172
- if (cur === -1) return false;
173
- if (cur < history.length - 1) {
174
- indexRef.current = cur + 1;
175
- applyText(history[cur + 1]);
176
- } else {
177
- indexRef.current = -1;
178
- applyText(draftRef.current);
179
- }
180
- return true;
181
- }, [history, applyText]);
182
- const isNavigating = reactExports.useCallback(() => indexRef.current !== -1, []);
183
- const size = reactExports.useCallback(() => history.length, [history.length]);
184
- return { push, recallPrev, recallNext, isNavigating, size };
185
- }
186
- function formatSize(bytes) {
187
- if (bytes < 1024) return `${bytes} B`;
188
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
189
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
190
- }
191
- function AttachmentThumb({ attachment, onRemove }) {
192
- const isImage = attachment.type.startsWith("image/");
193
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group relative flex shrink-0 items-center gap-2 rounded-lg border border-border bg-muted/40 px-2 py-1.5 text-xs", children: [
194
- isImage && attachment.previewUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx(
195
- "img",
196
- {
197
- src: attachment.previewUrl,
198
- alt: attachment.name,
199
- className: "h-10 w-10 rounded object-cover"
200
- }
201
- ) : isImage ? /* @__PURE__ */ jsxRuntimeExports.jsx(IconPhoto, { className: "size-5 text-muted-foreground" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(IconFile, { className: "size-5 text-muted-foreground" }),
202
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col min-w-0", children: [
203
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate max-w-[120px] font-medium text-foreground", children: attachment.name }),
204
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: formatSize(attachment.size) })
205
- ] }),
206
- /* @__PURE__ */ jsxRuntimeExports.jsx(
207
- "button",
208
- {
209
- onClick: (e) => {
210
- e.stopPropagation();
211
- onRemove();
212
- },
213
- className: "absolute -top-1.5 -right-1.5 flex size-4 items-center justify-center rounded-full bg-muted-foreground/20 text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-destructive hover:text-destructive-foreground transition-all",
214
- "aria-label": `Remove ${attachment.name}`,
215
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconX, { className: "size-2.5" })
216
- }
217
- )
218
- ] });
219
- }
220
- function AttachmentPreview({ attachments, onRemove, compact = false }) {
221
- if (attachments.length === 0) return null;
222
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
223
- "div",
224
- {
225
- className: cn(
226
- "flex gap-2 overflow-x-auto",
227
- compact ? "px-3 pt-3" : "px-3 pt-3"
228
- ),
229
- children: attachments.map((a) => /* @__PURE__ */ jsxRuntimeExports.jsx(
230
- AttachmentThumb,
231
- {
232
- attachment: a,
233
- onRemove: () => onRemove(a.id)
234
- },
235
- a.id
236
- ))
237
- }
238
- );
239
- }
240
- function uid() {
241
- return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
242
- }
243
- const ChatInput = reactExports.forwardRef(
244
- function ChatInput2({ sessionId, isLoading, onSubmit, onAbort }, ref) {
245
- const [input, setInput] = reactExports.useState("");
246
- const [isComposing, setIsComposing] = reactExports.useState(false);
247
- const [slashMenuOpen, setSlashMenuOpen] = reactExports.useState(false);
248
- const [slashFilter, setSlashFilter] = reactExports.useState("");
249
- const [slashSelectedIndex, setSlashSelectedIndex] = reactExports.useState(0);
250
- const [attachments, setAttachments] = reactExports.useState([]);
251
- const [isDragOver, setIsDragOver] = reactExports.useState(false);
252
- const inputRef = reactExports.useRef(null);
253
- const slashMenuRef = reactExports.useRef(null);
254
- const fileInputRef = reactExports.useRef(null);
255
- const autoResize = reactExports.useCallback(() => {
256
- const el = inputRef.current;
257
- if (!el) return;
258
- el.style.height = "auto";
259
- el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
260
- }, []);
261
- const applyHistoryText = reactExports.useCallback(
262
- (text) => {
263
- setInput(text);
264
- requestAnimationFrame(() => {
265
- autoResize();
266
- inputRef.current?.setSelectionRange(text.length, text.length);
267
- });
268
- },
269
- [autoResize]
270
- );
271
- const history = useInputHistory({ currentInput: input, applyText: applyHistoryText });
272
- reactExports.useImperativeHandle(
273
- ref,
274
- () => ({
275
- setText(text) {
276
- setInput(text);
277
- requestAnimationFrame(() => {
278
- autoResize();
279
- inputRef.current?.focus();
280
- });
281
- },
282
- clear() {
283
- setInput("");
284
- if (inputRef.current) inputRef.current.style.height = "auto";
285
- },
286
- focus() {
287
- inputRef.current?.focus();
288
- }
289
- }),
290
- [autoResize]
291
- );
292
- reactExports.useEffect(() => {
293
- if (!isLoading) inputRef.current?.focus();
294
- }, [isLoading]);
295
- reactExports.useEffect(() => {
296
- if (!slashMenuOpen) return;
297
- function handleClick(e) {
298
- if (slashMenuRef.current && !slashMenuRef.current.contains(e.target)) {
299
- setSlashMenuOpen(false);
300
- }
301
- }
302
- document.addEventListener("mousedown", handleClick);
303
- return () => document.removeEventListener("mousedown", handleClick);
304
- }, [slashMenuOpen]);
305
- reactExports.useEffect(() => {
306
- if (!slashMenuOpen) return;
307
- const active = slashMenuRef.current?.querySelector(".slash-item-active");
308
- active?.scrollIntoView({ block: "nearest" });
309
- }, [slashSelectedIndex, slashMenuOpen]);
310
- const filteredCommands = reactExports.useMemo(
311
- () => slashMenuOpen ? SLASH_COMMANDS.filter(
312
- (cmd) => cmd.name.toLowerCase().startsWith(slashFilter.toLowerCase())
313
- ) : [],
314
- [slashMenuOpen, slashFilter]
315
- );
316
- const addFiles = reactExports.useCallback((files) => {
317
- const newAttachments = Array.from(files).map((file) => ({
318
- id: uid(),
319
- name: file.name,
320
- type: file.type || "application/octet-stream",
321
- size: file.size,
322
- file,
323
- previewUrl: file.type.startsWith("image/") ? URL.createObjectURL(file) : void 0
324
- }));
325
- setAttachments((prev) => [...prev, ...newAttachments]);
326
- }, []);
327
- const removeAttachment = reactExports.useCallback((id) => {
328
- setAttachments((prev) => {
329
- const removed = prev.find((a) => a.id === id);
330
- if (removed?.previewUrl) URL.revokeObjectURL(removed.previewUrl);
331
- return prev.filter((a) => a.id !== id);
332
- });
333
- }, []);
334
- reactExports.useEffect(() => {
335
- return () => {
336
- attachments.forEach((a) => {
337
- if (a.previewUrl) URL.revokeObjectURL(a.previewUrl);
338
- });
339
- };
340
- }, []);
341
- const handleDragOver = reactExports.useCallback((e) => {
342
- e.preventDefault();
343
- e.stopPropagation();
344
- setIsDragOver(true);
345
- }, []);
346
- const handleDragLeave = reactExports.useCallback((e) => {
347
- e.preventDefault();
348
- e.stopPropagation();
349
- if (e.currentTarget === e.target || !e.currentTarget.contains(e.relatedTarget)) {
350
- setIsDragOver(false);
351
- }
352
- }, []);
353
- const handleDrop = reactExports.useCallback(
354
- (e) => {
355
- e.preventDefault();
356
- e.stopPropagation();
357
- setIsDragOver(false);
358
- if (e.dataTransfer.files.length > 0) addFiles(e.dataTransfer.files);
359
- },
360
- [addFiles]
361
- );
362
- const handlePaste = reactExports.useCallback(
363
- (e) => {
364
- const items = e.clipboardData?.items;
365
- if (!items) return;
366
- const imageFiles = [];
367
- for (let i = 0; i < items.length; i++) {
368
- const item = items[i];
369
- if (item.type.startsWith("image/")) {
370
- const file = item.getAsFile();
371
- if (file) {
372
- const ext = item.type.split("/")[1] || "png";
373
- const renamed = new File([file], `pasted-image-${Date.now()}.${ext}`, {
374
- type: item.type
375
- });
376
- imageFiles.push(renamed);
377
- }
378
- }
379
- }
380
- if (imageFiles.length > 0) {
381
- e.preventDefault();
382
- addFiles(imageFiles);
383
- }
384
- },
385
- [addFiles]
386
- );
387
- function clearAfterSend(text) {
388
- history.push(text);
389
- setInput("");
390
- if (inputRef.current) inputRef.current.style.height = "auto";
391
- }
392
- function handleSend() {
393
- const text = input.trim();
394
- if (!text || isLoading) return;
395
- setSlashMenuOpen(false);
396
- clearAfterSend(text);
397
- onSubmit(text, attachments);
398
- setAttachments([]);
399
- }
400
- function handleSlashSelect(cmd) {
401
- setSlashMenuOpen(false);
402
- if (cmd.local || cmd.category === "info") {
403
- setInput("");
404
- if (inputRef.current) inputRef.current.style.height = "auto";
405
- onSubmit(cmd.name);
406
- return;
407
- }
408
- setInput(cmd.name + " ");
409
- inputRef.current?.focus();
410
- }
411
- function handleInputChange(e) {
412
- const value = e.target.value;
413
- setInput(value);
414
- autoResize();
415
- if (value.startsWith("/") && !value.includes(" ")) {
416
- setSlashMenuOpen(true);
417
- setSlashFilter(value);
418
- setSlashSelectedIndex(0);
419
- } else if (slashMenuOpen) {
420
- setSlashMenuOpen(false);
421
- }
422
- }
423
- function handleKeyDown(e) {
424
- if (isImeComposing(e)) return;
425
- if (slashMenuOpen && filteredCommands.length > 0) {
426
- if (e.key === "ArrowDown") {
427
- e.preventDefault();
428
- setSlashSelectedIndex(
429
- (i) => i < filteredCommands.length - 1 ? i + 1 : 0
430
- );
431
- return;
432
- }
433
- if (e.key === "ArrowUp") {
434
- e.preventDefault();
435
- setSlashSelectedIndex(
436
- (i) => i > 0 ? i - 1 : filteredCommands.length - 1
437
- );
438
- return;
439
- }
440
- if (e.key === "Enter" || e.key === "Tab") {
441
- e.preventDefault();
442
- handleSlashSelect(filteredCommands[slashSelectedIndex]);
443
- return;
444
- }
445
- if (e.key === "Escape") {
446
- e.preventDefault();
447
- setSlashMenuOpen(false);
448
- return;
449
- }
450
- }
451
- if (!slashMenuOpen && (history.isNavigating() || !input.includes("\n"))) {
452
- if (e.key === "ArrowUp" && history.size() > 0) {
453
- if (history.recallPrev()) {
454
- e.preventDefault();
455
- return;
456
- }
457
- }
458
- if (e.key === "ArrowDown" && history.isNavigating()) {
459
- if (history.recallNext()) {
460
- e.preventDefault();
461
- return;
462
- }
463
- }
464
- }
465
- if (e.key === "Enter" && !e.shiftKey) {
466
- e.preventDefault();
467
- handleSend();
468
- }
469
- }
470
- const canSend = input.trim().length > 0;
471
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-3xl mx-auto flex flex-col gap-2 relative", children: [
472
- slashMenuOpen && filteredCommands.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
473
- "div",
474
- {
475
- ref: slashMenuRef,
476
- className: "absolute bottom-full left-0 right-0 mb-1 rounded-xl border border-border bg-card shadow-lg z-50 overflow-hidden",
477
- children: [
478
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 text-[11px] font-medium text-muted-foreground border-b border-border/60 bg-muted/30", children: [
479
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconCommand, { className: "size-3" }),
480
- "Commands"
481
- ] }),
482
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-[240px] overflow-y-auto py-1", children: filteredCommands.map((cmd, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
483
- "button",
484
- {
485
- className: cn(
486
- "w-full flex items-center gap-2 px-3 py-2 text-sm transition-colors text-left",
487
- i === slashSelectedIndex ? "bg-accent text-accent-foreground slash-item-active" : "hover:bg-accent/50"
488
- ),
489
- onMouseEnter: () => setSlashSelectedIndex(i),
490
- onClick: () => handleSlashSelect(cmd),
491
- children: [
492
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs text-primary", children: cmd.name }),
493
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground text-xs", children: cmd.description })
494
- ]
495
- },
496
- cmd.name
497
- )) })
498
- ]
499
- }
500
- ),
501
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
502
- "div",
503
- {
504
- className: cn(
505
- "flex min-h-[80px] flex-col rounded-2xl cursor-text bg-card border shadow-lg transition-colors",
506
- isDragOver ? "border-primary border-dashed" : "border-border"
507
- ),
508
- onDragOver: handleDragOver,
509
- onDragLeave: handleDragLeave,
510
- onDrop: handleDrop,
511
- children: [
512
- /* @__PURE__ */ jsxRuntimeExports.jsx(
513
- AttachmentPreview,
514
- {
515
- attachments,
516
- onRemove: removeAttachment,
517
- compact: true
518
- }
519
- ),
520
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 relative overflow-y-auto max-h-[200px]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
521
- Textarea,
522
- {
523
- ref: inputRef,
524
- value: input,
525
- onChange: handleInputChange,
526
- onKeyDown: handleKeyDown,
527
- onPaste: handlePaste,
528
- onCompositionStart: () => setIsComposing(true),
529
- onCompositionEnd: () => setIsComposing(false),
530
- placeholder: "Ask anything…",
531
- rows: 1,
532
- className: cn(
533
- "w-full !rounded-none border-0 p-3 outline-none text-[15px] text-foreground resize-none shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-transparent bg-transparent whitespace-pre-wrap break-words",
534
- "min-h-[48px] max-h-[200px]"
535
- )
536
- }
537
- ) }),
538
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-h-[40px] items-center gap-2 p-2 pb-1", children: [
539
- /* @__PURE__ */ jsxRuntimeExports.jsx(
540
- Button,
541
- {
542
- variant: "ghost",
543
- size: "icon-sm",
544
- className: "text-muted-foreground hover:text-foreground transition-colors",
545
- title: "Attach images",
546
- onClick: () => fileInputRef.current?.click(),
547
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconPaperclip, { className: "h-5 w-5" })
548
- }
549
- ),
550
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-auto flex items-center gap-2", children: isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(
551
- Button,
552
- {
553
- variant: "ghost",
554
- size: "icon-sm",
555
- className: "rounded-full bg-destructive hover:bg-destructive/90 text-destructive-foreground transition-colors cursor-pointer",
556
- onClick: onAbort,
557
- title: "Stop generating",
558
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconPlayerStop, { className: "h-4 w-4" })
559
- }
560
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
561
- Button,
562
- {
563
- variant: "ghost",
564
- size: "icon-sm",
565
- className: cn(
566
- "rounded-full transition-colors cursor-pointer",
567
- canSend ? "bg-primary hover:bg-primary/90 text-primary-foreground" : "bg-primary text-primary-foreground opacity-40"
568
- ),
569
- disabled: !canSend,
570
- onClick: handleSend,
571
- title: "Send message",
572
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconArrowUp, { className: "h-4 w-4" })
573
- }
574
- ) })
575
- ] })
576
- ]
577
- }
578
- ),
579
- /* @__PURE__ */ jsxRuntimeExports.jsx(
580
- "input",
581
- {
582
- ref: fileInputRef,
583
- type: "file",
584
- multiple: true,
585
- accept: "image/*",
586
- className: "hidden",
587
- onChange: (e) => {
588
- if (e.target.files?.length) {
589
- addFiles(e.target.files);
590
- e.target.value = "";
591
- }
592
- }
593
- }
594
- )
595
- ] });
596
- }
597
- );
598
- const ChatHeader = reactExports.memo(function ChatHeader2({
599
- sessionId,
600
- title,
601
- hasMessages,
602
- onNewChat,
603
- onClear
604
- }) {
605
- const navigate = useNavigate();
606
- const deleteStoreSession = useChatStore((s) => s.deleteSession);
607
- const handleDelete = async () => {
608
- try {
609
- await deleteSessionApi(sessionId);
610
- deleteStoreSession(sessionId);
611
- navigate({ to: "/chat" });
612
- } catch {
613
- }
614
- };
615
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group/header shrink-0 px-2 py-1 flex items-center gap-1.5 min-w-0", children: [
616
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-medium text-muted-foreground truncate flex-1 select-none", children: title || "Conversation" }),
617
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-0.5 opacity-0 group-hover/header:opacity-100 transition-opacity", children: [
618
- /* @__PURE__ */ jsxRuntimeExports.jsx(
619
- "button",
620
- {
621
- onClick: onNewChat,
622
- className: "flex items-center justify-center rounded-lg p-1 text-muted-foreground hover:bg-sidebar-muted hover:text-foreground transition-colors",
623
- title: "New chat",
624
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconPlus, { className: "size-3.5" })
625
- }
626
- ),
627
- hasMessages && /* @__PURE__ */ jsxRuntimeExports.jsx(
628
- "button",
629
- {
630
- onClick: onClear,
631
- className: "flex items-center justify-center rounded-lg p-1 text-muted-foreground hover:bg-sidebar-muted hover:text-foreground transition-colors",
632
- title: "Clear chat",
633
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconTrash, { className: "size-3.5" })
634
- }
635
- ),
636
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
637
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "flex items-center justify-center rounded-lg p-1 text-muted-foreground hover:bg-sidebar-muted hover:text-foreground transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconDots, { className: "size-3.5" }) }) }),
638
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "end", side: "bottom", sideOffset: 4, children: [
639
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
640
- DropdownMenuItem,
641
- {
642
- className: "gap-2 text-xs",
643
- onClick: () => {
644
- const newTitle = prompt("Rename conversation", title || "");
645
- if (newTitle?.trim()) {
646
- renameSession(sessionId, newTitle.trim());
647
- useChatStore.getState().updateSession(sessionId, { title: newTitle.trim() });
648
- }
649
- },
650
- children: [
651
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconPencil, { className: "size-3" }),
652
- "Rename"
653
- ]
654
- }
655
- ),
656
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
657
- DropdownMenuItem,
658
- {
659
- className: "gap-2 text-xs text-destructive",
660
- onClick: handleDelete,
661
- children: [
662
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconTrash, { className: "size-3" }),
663
- "Delete"
664
- ]
665
- }
666
- )
667
- ] })
668
- ] })
669
- ] })
670
- ] });
671
- });
672
- const SUGGESTIONS = [
673
- {
674
- label: "Search the web",
675
- text: "Search the web for today's top tech news",
676
- Icon: IconSearch
677
- },
678
- {
679
- label: "Set a reminder",
680
- text: "Set a reminder to check emails every day at 9 AM",
681
- Icon: IconBell
682
- },
683
- {
684
- label: "Read emails",
685
- text: "Read my latest emails and summarize them",
686
- Icon: IconMail
687
- },
688
- {
689
- label: "Write a script",
690
- text: "Write a Python script to rename all files in a folder",
691
- Icon: IconCode
692
- },
693
- {
694
- label: "Schedule a job",
695
- text: "Schedule a cron job to back up my database every night",
696
- Icon: IconClock
697
- },
698
- {
699
- label: "Analyze data",
700
- text: "Analyze this CSV file and show key insights",
701
- Icon: IconChartLine
702
- }
703
- ];
704
- const ChatEmptyState = reactExports.memo(function ChatEmptyState2({
705
- onSelectSuggestion
706
- }) {
707
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full px-6 py-12", children: [
708
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-6 flex items-center justify-center w-16 h-16 rounded-2xl bg-gradient-to-br from-primary/20 to-primary/5 text-primary", children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconSearch, { className: "w-8 h-8", strokeWidth: 1.5 }) }),
709
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold text-foreground mb-2", children: "How can I help?" }),
710
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground mb-8 max-w-sm text-center", children: "Ask anything or pick a suggestion to get started." }),
711
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-2 w-full max-w-lg", children: SUGGESTIONS.map(({ text, label, Icon }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
712
- "button",
713
- {
714
- className: "flex items-center gap-3 rounded-xl border border-border/60 bg-card px-4 py-3 text-left text-sm text-foreground hover:bg-accent hover:border-accent transition-colors group",
715
- onClick: () => onSelectSuggestion(text),
716
- children: [
717
- /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { className: "w-4 h-4 shrink-0 text-muted-foreground group-hover:text-foreground transition-colors" }),
718
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: label })
719
- ]
720
- },
721
- text
722
- )) })
723
- ] });
724
- });
725
- const SyncHighlighter = reactExports.lazy(() => import("./syntax-highlighter-5vezNTce.mjs").then((m) => ({ default: m.SyncHighlighter })));
726
- function CopyButton({ text }) {
727
- const [copied, setCopied] = reactExports.useState(false);
728
- const handleCopy = async () => {
729
- await navigator.clipboard.writeText(text);
730
- setCopied(true);
731
- setTimeout(() => setCopied(false), 2e3);
732
- };
733
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
734
- "button",
735
- {
736
- onClick: handleCopy,
737
- className: "absolute right-2 top-2 flex items-center gap-1 rounded px-1.5 py-1 text-xs text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors",
738
- "aria-label": "Copy code",
739
- children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(IconCheck, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(IconCopy, { className: "size-3" })
740
- }
741
- );
742
- }
743
- function BubbleCopyButton({ text }) {
744
- const [copied, setCopied] = reactExports.useState(false);
745
- const handleCopy = async () => {
746
- await navigator.clipboard.writeText(text);
747
- setCopied(true);
748
- setTimeout(() => setCopied(false), 2e3);
749
- };
750
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
751
- "button",
752
- {
753
- onClick: handleCopy,
754
- className: "inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] text-muted-foreground/50 hover:text-foreground hover:bg-muted transition-colors",
755
- "aria-label": "Copy message",
756
- children: [
757
- copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(IconCheck, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(IconCopy, { className: "size-3" }),
758
- copied ? "Copied" : "Copy"
759
- ]
760
- }
761
- );
762
- }
763
- function formatThinkingDuration(ms) {
764
- const s = Math.floor(ms / 1e3);
765
- if (s < 60) return `${s}s`;
766
- const m = Math.floor(s / 60);
767
- const r = s % 60;
768
- return r === 0 ? `${m}m` : `${m}m ${r}s`;
769
- }
770
- function ThinkingCard({
771
- reasoning,
772
- isStreaming,
773
- startedAt,
774
- endedAt
775
- }) {
776
- const [open, setOpen] = reactExports.useState(isStreaming ?? false);
777
- reactExports.useEffect(() => {
778
- setOpen(isStreaming ?? false);
779
- }, [isStreaming]);
780
- const [now, setNow] = reactExports.useState(Date.now());
781
- const tickRef = reactExports.useRef(null);
782
- reactExports.useEffect(() => {
783
- if (isStreaming && startedAt && !endedAt) {
784
- tickRef.current = setInterval(() => setNow(Date.now()), 1e3);
785
- return () => {
786
- if (tickRef.current) clearInterval(tickRef.current);
787
- };
788
- }
789
- if (tickRef.current) {
790
- clearInterval(tickRef.current);
791
- tickRef.current = null;
792
- }
793
- return void 0;
794
- }, [isStreaming, startedAt, endedAt]);
795
- if (!reasoning.trim()) return null;
796
- const durationMs = startedAt ? Math.max(0, (endedAt ?? (isStreaming ? now : startedAt)) - startedAt) : null;
797
- const charCount = [...reasoning].length;
798
- const hasThinking = isStreaming && startedAt && !endedAt;
799
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1", children: [
800
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
801
- "button",
802
- {
803
- onClick: () => setOpen(!open),
804
- className: cn(
805
- "inline-flex items-center gap-1.5 text-[11px] transition-colors",
806
- hasThinking ? "text-amber-500 dark:text-amber-400 font-medium" : "text-muted-foreground/70 hover:text-foreground"
807
- ),
808
- children: [
809
- /* @__PURE__ */ jsxRuntimeExports.jsx(
810
- IconChevronRight,
811
- {
812
- className: cn(
813
- "size-3 shrink-0 transition-transform",
814
- open && "rotate-90"
815
- )
816
- }
817
- ),
818
- hasThinking ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1", children: [
819
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconLoader2, { className: "size-2.5 animate-spin" }),
820
- "thinking…"
821
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "thought" }),
822
- durationMs !== null && durationMs > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60 tabular-nums", children: [
823
- "· ",
824
- formatThinkingDuration(durationMs)
825
- ] }),
826
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/60", children: [
827
- "· ",
828
- charCount.toLocaleString(),
829
- " chars"
830
- ] })
831
- ]
832
- }
833
- ),
834
- open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 rounded-md bg-amber-50 dark:bg-amber-950/20 px-2.5 py-1.5 text-[11px] text-muted-foreground leading-relaxed whitespace-pre-wrap font-mono border border-amber-200 dark:border-amber-800/30", children: reasoning })
835
- ] });
836
- }
837
- function ToolCard({ name, content }) {
838
- const [open, setOpen] = reactExports.useState(false);
839
- const preview = content.length > 60 ? content.slice(0, 60) + "..." : content;
840
- if (!content) return null;
841
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1", children: [
842
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
843
- "button",
844
- {
845
- onClick: () => setOpen(!open),
846
- className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground/70 hover:text-foreground transition-colors truncate max-w-full",
847
- children: [
848
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconChevronRight, { className: cn("size-3 shrink-0 transition-transform", open && "rotate-90") }),
849
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "truncate", children: [
850
- name || "tool",
851
- ': "',
852
- preview,
853
- '"'
854
- ] })
855
- ]
856
- }
857
- ),
858
- open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 rounded-md bg-muted/40 px-2.5 py-1.5 text-[11px] text-muted-foreground leading-relaxed whitespace-pre-wrap font-mono border border-border/50 max-h-[300px] overflow-auto", children: content })
859
- ] });
860
- }
861
- function formatToolPayload(raw) {
862
- if (!raw) return null;
863
- try {
864
- const parsed = JSON.parse(raw);
865
- return { text: JSON.stringify(parsed, null, 2), isJson: true };
866
- } catch {
867
- return { text: raw, isJson: false };
868
- }
869
- }
870
- function ToolInvocationCard({ tools }) {
871
- const [open, setOpen] = reactExports.useState(false);
872
- if (!tools?.length) return null;
873
- const runningCount = tools.filter((t) => t.status === "running").length;
874
- const doneCount = tools.filter((t) => t.status === "done").length;
875
- const errorCount = tools.filter((t) => t.status === "error").length;
876
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1", children: [
877
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
878
- "button",
879
- {
880
- onClick: () => setOpen(!open),
881
- className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground/70 hover:text-foreground transition-colors",
882
- children: [
883
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconChevronRight, { className: cn("size-3 shrink-0 transition-transform", open && "rotate-90") }),
884
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
885
- "used ",
886
- tools.length,
887
- " tool",
888
- tools.length > 1 ? "s" : ""
889
- ] }),
890
- runningCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-0.5 text-amber-500", children: [
891
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconLoader2, { className: "size-2.5 animate-spin" }),
892
- runningCount
893
- ] }),
894
- doneCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-emerald-500", children: [
895
- doneCount,
896
- " done"
897
- ] }),
898
- errorCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-500", children: [
899
- errorCount,
900
- " error"
901
- ] }),
902
- tools.length > 0 && runningCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground/50 truncate max-w-[200px]", children: [
903
- "— ",
904
- tools.map((t) => t.name).join(", ")
905
- ] })
906
- ]
907
- }
908
- ),
909
- open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 flex flex-col gap-1.5", children: tools.map((t, i) => {
910
- const formattedArgs = formatToolPayload(t.preview);
911
- const formattedOutput = formatToolPayload(t.output);
912
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
913
- "div",
914
- {
915
- className: "rounded-md bg-muted/40 border border-border/50 overflow-hidden",
916
- children: [
917
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-2.5 py-1.5", children: [
918
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-semibold text-foreground/80 font-mono", children: t.name }),
919
- t.status === "running" && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-amber-500", children: [
920
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconLoader2, { className: "size-2.5 animate-spin" }),
921
- "running"
922
- ] }),
923
- t.status === "done" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-emerald-500", children: "✓ done" }),
924
- t.status === "error" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-red-500", children: "✗ error" })
925
- ] }),
926
- formattedArgs && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-border/30", children: [
927
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-2.5 py-0.5", children: [
928
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] uppercase tracking-wider text-muted-foreground/60", children: "Arguments" }),
929
- /* @__PURE__ */ jsxRuntimeExports.jsx(CopyButton, { text: formattedArgs.text })
930
- ] }),
931
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2.5 pb-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: cn(
932
- "text-[10px] leading-relaxed overflow-x-auto font-mono whitespace-pre-wrap max-h-[200px] overflow-y-auto rounded",
933
- formattedArgs.isJson ? "bg-slate-900/10 dark:bg-slate-900/40 text-foreground/80 px-2 py-1" : "text-muted-foreground/70"
934
- ), children: formattedArgs.text }) })
935
- ] }),
936
- formattedOutput && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-border/30", children: [
937
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-2.5 py-0.5", children: [
938
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] uppercase tracking-wider text-muted-foreground/60", children: "Result" }),
939
- /* @__PURE__ */ jsxRuntimeExports.jsx(CopyButton, { text: formattedOutput.text })
940
- ] }),
941
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2.5 pb-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: cn(
942
- "text-[10px] leading-relaxed overflow-x-auto font-mono whitespace-pre-wrap max-h-[200px] overflow-y-auto rounded",
943
- formattedOutput.isJson ? "bg-slate-900/10 dark:bg-slate-900/40 text-foreground/80 px-2 py-1" : "text-muted-foreground/70"
944
- ), children: formattedOutput.text }) })
945
- ] })
946
- ]
947
- },
948
- t.callId || `${t.name}-${i}`
949
- );
950
- }) })
951
- ] });
952
- }
953
- function CodeBlock({ language, value }) {
954
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group/code my-2", children: [
955
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between rounded-t-lg bg-muted/80 px-3 py-1.5 border border-border border-b-0", children: [
956
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-medium text-muted-foreground uppercase tracking-wide", children: language || "text" }),
957
- /* @__PURE__ */ jsxRuntimeExports.jsx(CopyButton, { text: value })
958
- ] }),
959
- /* @__PURE__ */ jsxRuntimeExports.jsx(
960
- reactExports.Suspense,
961
- {
962
- fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "bg-[#282c34] text-gray-300 rounded-b-lg px-4 py-3 text-[13px] leading-relaxed overflow-x-auto font-mono", children: /* @__PURE__ */ jsxRuntimeExports.jsx("code", { children: value }) }),
963
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(SyncHighlighter, { language, value })
964
- }
965
- )
966
- ] });
967
- }
968
- const markdownComponents = {
969
- pre({ children }) {
970
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto max-w-full", children });
971
- },
972
- code({ className, children, ...props }) {
973
- const match = /language-(\w+)/.exec(className || "");
974
- const value = String(children).replace(/\n$/, "");
975
- if (!match) {
976
- return /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "rounded bg-muted px-1 py-0.5 text-[13px] font-mono text-foreground/90", ...props, children });
977
- }
978
- if (match[1] === "plaintext") {
979
- return /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "rounded-lg bg-muted/50 border border-border/50 px-4 py-3 text-[13px] leading-relaxed overflow-x-auto font-mono whitespace-pre-wrap", children: value });
980
- }
981
- return /* @__PURE__ */ jsxRuntimeExports.jsx(CodeBlock, { language: match[1], value });
982
- },
983
- a({ href, children }) {
984
- return /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href, target: "_blank", rel: "noopener noreferrer", className: "text-blue-600 dark:text-blue-400 underline underline-offset-2", children });
985
- },
986
- img({ src, alt }) {
987
- return /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src, alt, className: "rounded-lg max-w-full my-2", loading: "lazy" });
988
- },
989
- table({ children }) {
990
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto my-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full text-sm border-collapse border border-border rounded-lg overflow-hidden", children }) });
991
- },
992
- th({ children }) {
993
- return /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "border border-border bg-muted px-3 py-1.5 text-left font-medium", children });
994
- },
995
- td({ children }) {
996
- return /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "border border-border px-3 py-1.5", children });
997
- },
998
- blockquote({ children }) {
999
- return /* @__PURE__ */ jsxRuntimeExports.jsx("blockquote", { className: "border-l-2 border-amber-400 dark:border-amber-600 pl-3 my-2 text-muted-foreground italic", children });
1000
- }
1001
- };
1002
- const BOX_DRAWING_RE = /[\u2500-\u257F]/;
1003
- function hasBoxDrawing(text) {
1004
- return BOX_DRAWING_RE.test(text);
1005
- }
1006
- function wrapAsciiArtCodeBlocks(content) {
1007
- const lines = content.split("\n");
1008
- const result = [];
1009
- let inArt = false;
1010
- for (let i = 0; i < lines.length; i++) {
1011
- const line = lines[i];
1012
- const isArtLine = hasBoxDrawing(line);
1013
- if (isArtLine && !inArt) {
1014
- if (result.length > 0 && result[result.length - 1] !== "") {
1015
- result.push("");
1016
- }
1017
- result.push("```plaintext");
1018
- inArt = true;
1019
- } else if (!isArtLine && inArt) {
1020
- result.push("```");
1021
- if (line !== "") {
1022
- result.push("");
1023
- }
1024
- inArt = false;
1025
- }
1026
- result.push(line);
1027
- }
1028
- if (inArt) {
1029
- result.push("```");
1030
- }
1031
- return result.join("\n");
1032
- }
1033
- function MessageBody({
1034
- content,
1035
- isStreaming
1036
- }) {
1037
- const processed = wrapAsciiArtCodeBlocks(content);
1038
- if (isStreaming && content) {
1039
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-w-none leading-relaxed", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "whitespace-pre-wrap text-base leading-relaxed", children: [
1040
- content,
1041
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block w-0.5 h-4 bg-foreground/70 ml-0.5 align-text-bottom animate-pulse" })
1042
- ] }) });
1043
- }
1044
- if (isStreaming && !content) {
1045
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block w-0.5 h-4 bg-foreground/70 ml-0.5 align-text-bottom animate-pulse" });
1046
- }
1047
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-w-none leading-relaxed [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_p]:my-3 [&_p]:leading-relaxed [&_ul]:my-3 [&_ul]:pl-5 [&_ul]:list-disc [&_ol]:my-3 [&_ol]:pl-5 [&_ol]:list-decimal [&_li]:my-1 [&_li]:leading-relaxed [&_h1]:mt-6 [&_h1]:mb-3 [&_h2]:mt-5 [&_h2]:mb-2 [&_h3]:mt-4 [&_h3]:mb-1.5 [&_h4]:mt-3 [&_h4]:mb-1 [&_blockquote]:my-3 [&_hr]:my-6 [&_table]:my-4 [&_pre]:my-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { remarkPlugins: [remarkGfm], components: markdownComponents, children: processed }) });
1048
- }
1049
- function formatTime(ts) {
1050
- if (!ts || isNaN(ts)) return null;
1051
- try {
1052
- return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
1053
- } catch {
1054
- return null;
1055
- }
1056
- }
1057
- function ChatMessageRaw({ message }) {
1058
- const isUser = message.role === "user";
1059
- const isTool = message.role === "tool";
1060
- const hasContent = !!message.content?.trim();
1061
- const hasReasoning = !!message.reasoning?.trim();
1062
- const hasToolCalls = !!message.toolCalls?.length;
1063
- const timeLabel = formatTime(message.timestamp);
1064
- const isMinimal = isTool || !isUser && !hasContent && (hasReasoning || hasToolCalls);
1065
- if (isMinimal) {
1066
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-1", children: [
1067
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
1068
- hasReasoning && /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingCard, { reasoning: message.reasoning, isStreaming: message.isStreaming, startedAt: message.reasoningStartedAt, endedAt: message.reasoningEndedAt }),
1069
- !isTool && hasToolCalls && /* @__PURE__ */ jsxRuntimeExports.jsx(ToolInvocationCard, { tools: message.toolCalls }),
1070
- isTool && /* @__PURE__ */ jsxRuntimeExports.jsx(ToolCard, { name: message.toolName || "", content: message.content })
1071
- ] }),
1072
- timeLabel && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mt-0.5 text-[10px] text-muted-foreground/40", children: timeLabel })
1073
- ] });
1074
- }
1075
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("flex px-4", isUser ? "py-2 justify-end" : "py-4 justify-start"), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("flex min-w-0 flex-col", isUser ? "max-w-[85%] items-end" : "w-full"), children: [
1076
- message.isStreaming && !hasContent && !hasReasoning ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-sm text-muted-foreground py-1", children: [
1077
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }),
1078
- "Thinking..."
1079
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 w-full", children: [
1080
- hasReasoning && /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingCard, { reasoning: message.reasoning, isStreaming: message.isStreaming, startedAt: message.reasoningStartedAt, endedAt: message.reasoningEndedAt }),
1081
- !isTool && !isUser && hasToolCalls && /* @__PURE__ */ jsxRuntimeExports.jsx(ToolInvocationCard, { tools: message.toolCalls }),
1082
- hasContent && (isUser ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-2xl px-4 py-2.5 text-sm leading-relaxed overflow-x-auto bg-primary text-primary-foreground", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1083
- MessageBody,
1084
- {
1085
- content: message.content,
1086
- isStreaming: message.isStreaming
1087
- }
1088
- ) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-base leading-relaxed", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1089
- MessageBody,
1090
- {
1091
- content: message.content,
1092
- isStreaming: message.isStreaming
1093
- }
1094
- ) }))
1095
- ] }),
1096
- hasContent && !isUser && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 flex items-center gap-2 px-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(BubbleCopyButton, { text: message.content }) }),
1097
- timeLabel && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mt-1 text-[10px] text-muted-foreground/50 px-1", children: timeLabel })
1098
- ] }) });
1099
- }
1100
- const ChatMessage = reactExports.memo(ChatMessageRaw);
1101
- const MessageList = reactExports.memo(function MessageList2({
1102
- messages,
1103
- isLoading
1104
- }) {
1105
- const mapped = reactExports.useMemo(
1106
- () => messages.map(
1107
- (msg) => ({
1108
- id: msg.id,
1109
- role: msg.role === "user" ? "user" : msg.role === "tool" ? "tool" : "assistant",
1110
- content: msg.content,
1111
- timestamp: msg.timestamp,
1112
- isStreaming: msg.isStreaming,
1113
- reasoning: msg.reasoning,
1114
- reasoningStartedAt: msg.reasoningStartedAt,
1115
- reasoningEndedAt: msg.reasoningEndedAt,
1116
- toolCalls: msg.toolCalls
1117
- })
1118
- ),
1119
- [messages]
1120
- );
1121
- return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: mapped.map((msg) => /* @__PURE__ */ jsxRuntimeExports.jsx(ChatMessage, { message: msg }, msg.id)) });
1122
- });
1123
- function useChatScroll(messages) {
1124
- const containerRef = reactExports.useRef(null);
1125
- const bottomRef = reactExports.useRef(null);
1126
- const userScrolledUpRef = reactExports.useRef(false);
1127
- const prevCountRef = reactExports.useRef(messages.length);
1128
- const scrollToBottom = reactExports.useCallback((force) => {
1129
- if (!force && userScrolledUpRef.current) return;
1130
- bottomRef.current?.scrollIntoView({ behavior: "smooth" });
1131
- }, []);
1132
- const onScroll = reactExports.useCallback(() => {
1133
- const el = containerRef.current;
1134
- if (!el) return;
1135
- const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
1136
- userScrolledUpRef.current = !atBottom;
1137
- }, []);
1138
- reactExports.useEffect(() => {
1139
- const prevCount = prevCountRef.current;
1140
- prevCountRef.current = messages.length;
1141
- const userJustSent = messages.length > prevCount && messages[messages.length - 1]?.role === "user";
1142
- if (userJustSent) {
1143
- userScrolledUpRef.current = false;
1144
- scrollToBottom(true);
1145
- } else {
1146
- scrollToBottom();
1147
- }
1148
- }, [messages, scrollToBottom]);
1149
- return { containerRef, bottomRef, onScroll };
1150
- }
1151
- const BASE_URL = "http://localhost:47474";
1152
- async function* postChatStream(options) {
1153
- const { input, conversation, model, signal } = options;
1154
- const body = { input };
1155
- if (conversation) body.conversation = conversation;
1156
- if (model) body.model = model;
1157
- if (options.messages && options.messages.length > 0) {
1158
- body.messages = options.messages;
1159
- }
1160
- if (options.attachments && options.attachments.length > 0) {
1161
- body.attachments = options.attachments;
1162
- }
1163
- const res = await fetch(`${BASE_URL}/api/chat/stream`, {
1164
- method: "POST",
1165
- headers: { "Content-Type": "application/json" },
1166
- body: JSON.stringify(body),
1167
- signal
1168
- });
1169
- if (!res.ok) {
1170
- const text = await res.text().catch(() => "Stream request failed");
1171
- throw new Error(text);
1172
- }
1173
- if (!res.body) {
1174
- throw new Error("No response body");
1175
- }
1176
- const reader = res.body.getReader();
1177
- const decoder = new TextDecoder();
1178
- let buffer = "";
1179
- try {
1180
- while (true) {
1181
- const { done, value } = await reader.read();
1182
- if (done) break;
1183
- buffer += decoder.decode(value, { stream: true });
1184
- let boundary = buffer.indexOf("\n\n");
1185
- while (boundary >= 0) {
1186
- const frame = buffer.slice(0, boundary);
1187
- buffer = buffer.slice(boundary + 2);
1188
- let eventType = "";
1189
- let dataStr = "";
1190
- for (const line of frame.split("\n")) {
1191
- if (line.startsWith("event: ")) eventType = line.slice(7).trim();
1192
- else if (line.startsWith("data: ")) {
1193
- dataStr += (dataStr ? "\n" : "") + line.slice(6);
1194
- }
1195
- }
1196
- if (dataStr) {
1197
- let parsed = {};
1198
- try {
1199
- parsed = JSON.parse(dataStr);
1200
- } catch {
1201
- parsed = { raw: dataStr };
1202
- }
1203
- yield { type: eventType || "unknown", data: parsed };
1204
- }
1205
- boundary = buffer.indexOf("\n\n");
1206
- }
1207
- }
1208
- } finally {
1209
- reader.releaseLock();
1210
- }
1211
- }
1212
- let msgCounter = 0;
1213
- function nextMsgId() {
1214
- return `msg-${++msgCounter}-${Math.random().toString(36).slice(2, 6)}`;
1215
- }
1216
- function useChatStream() {
1217
- const abortRef = reactExports.useRef(null);
1218
- const setStreaming = reactExports.useCallback((sessionId, loading) => {
1219
- useChatStore.setState((s) => ({
1220
- streamingSessions: loading ? /* @__PURE__ */ new Set([...s.streamingSessions, sessionId]) : new Set([...s.streamingSessions].filter((id) => id !== sessionId))
1221
- }));
1222
- }, []);
1223
- const abortChat = reactExports.useCallback(() => {
1224
- if (abortRef.current) {
1225
- abortRef.current.abort();
1226
- abortRef.current = null;
1227
- }
1228
- }, []);
1229
- const sendMessage = reactExports.useCallback(
1230
- async (text, sessionId, model, options) => {
1231
- if (useChatStore.getState().streamingSessions.has(sessionId)) return;
1232
- const userMsg = {
1233
- id: nextMsgId(),
1234
- role: "user",
1235
- content: text,
1236
- timestamp: Date.now()
1237
- };
1238
- useChatStore.setState((s) => {
1239
- const existing = s.sessions.find((sx) => sx.id === sessionId);
1240
- if (!existing) {
1241
- const now = Date.now();
1242
- const seeded = {
1243
- id: sessionId,
1244
- title: text.slice(0, 64),
1245
- model: model || "",
1246
- messages: [userMsg],
1247
- createdAt: now,
1248
- updatedAt: now
1249
- };
1250
- return { sessions: [seeded, ...s.sessions] };
1251
- }
1252
- return {
1253
- sessions: s.sessions.map(
1254
- (sx) => sx.id === sessionId ? {
1255
- ...sx,
1256
- messages: [...sx.messages, userMsg],
1257
- model: model || sx.model || "",
1258
- title: !sx.title || sx.title === "New Conversation" || sx.title === "Untitled" ? text.slice(0, 64) : sx.title
1259
- } : sx
1260
- )
1261
- };
1262
- });
1263
- const history = useChatStore.getState().sessions.find((sx) => sx.id === sessionId)?.messages.slice(0, -1).filter((m) => !m.isStreaming && m.content).map((m) => ({ role: m.role, content: m.content })) || [];
1264
- const asstId = nextMsgId();
1265
- const placeholder = {
1266
- id: asstId,
1267
- role: "assistant",
1268
- content: "",
1269
- timestamp: Date.now(),
1270
- isStreaming: true
1271
- };
1272
- useChatStore.setState((s) => ({
1273
- sessions: s.sessions.map(
1274
- (sx) => sx.id === sessionId ? { ...sx, messages: [...sx.messages, placeholder] } : sx
1275
- )
1276
- }));
1277
- setStreaming(sessionId, true);
1278
- const abortController = new AbortController();
1279
- abortRef.current = abortController;
1280
- let lastEventMs = Date.now();
1281
- const hangTimeout = setInterval(() => {
1282
- if (Date.now() - lastEventMs > 3e5) {
1283
- console.warn("[stream] hang timeout — aborting", sessionId);
1284
- abortController.abort();
1285
- }
1286
- }, 3e4);
1287
- try {
1288
- const stream = postChatStream({
1289
- input: text,
1290
- conversation: sessionId,
1291
- model,
1292
- signal: abortController.signal,
1293
- messages: history,
1294
- attachments: options?.attachments?.map((a) => ({
1295
- id: a.id,
1296
- name: a.name,
1297
- type: a.type,
1298
- size: a.size
1299
- }))
1300
- });
1301
- for await (const ev of stream) {
1302
- lastEventMs = Date.now();
1303
- switch (ev.type) {
1304
- case "token": {
1305
- const delta = ev.data.delta || "";
1306
- if (!delta) continue;
1307
- useChatStore.setState((s) => ({
1308
- sessions: s.sessions.map((sx) => {
1309
- if (sx.id !== sessionId) return sx;
1310
- const msgs = sx.messages.slice();
1311
- const last = msgs[msgs.length - 1];
1312
- if (last && last.role === "assistant" && last.isStreaming) {
1313
- msgs[msgs.length - 1] = { ...last, content: last.content + delta };
1314
- }
1315
- return { ...sx, messages: msgs };
1316
- })
1317
- }));
1318
- break;
1319
- }
1320
- case "reasoning": {
1321
- const text2 = ev.data.text || "";
1322
- if (!text2) continue;
1323
- useChatStore.setState((s) => ({
1324
- sessions: s.sessions.map((sx) => {
1325
- if (sx.id !== sessionId) return sx;
1326
- const msgs = sx.messages.slice();
1327
- const last = msgs[msgs.length - 1];
1328
- if (last && last.role === "assistant" && last.isStreaming) {
1329
- const prevReasoning = last.reasoning || "";
1330
- msgs[msgs.length - 1] = {
1331
- ...last,
1332
- reasoning: prevReasoning + text2,
1333
- reasoningStartedAt: last.reasoningStartedAt || Date.now()
1334
- };
1335
- }
1336
- return { ...sx, messages: msgs };
1337
- })
1338
- }));
1339
- break;
1340
- }
1341
- case "tool_start": {
1342
- const name = ev.data.name || "tool";
1343
- const preview = ev.data.preview || "";
1344
- const callId = ev.data.tool_call_id || void 0;
1345
- useChatStore.setState((s) => ({
1346
- sessions: s.sessions.map((sx) => {
1347
- if (sx.id !== sessionId) return sx;
1348
- const msgs = sx.messages.slice();
1349
- const last = msgs[msgs.length - 1];
1350
- if (last && last.role === "assistant" && last.isStreaming) {
1351
- const calls = [
1352
- ...last.toolCalls || [],
1353
- { name, preview, callId, status: "running" }
1354
- ];
1355
- msgs[msgs.length - 1] = { ...last, toolCalls: calls };
1356
- }
1357
- return { ...sx, messages: msgs };
1358
- })
1359
- }));
1360
- break;
1361
- }
1362
- case "tool_end": {
1363
- const callId = ev.data.tool_call_id;
1364
- const toolName = ev.data.tool;
1365
- const output = ev.data.output;
1366
- useChatStore.setState((s) => ({
1367
- sessions: s.sessions.map((sx) => {
1368
- if (sx.id !== sessionId) return sx;
1369
- const msgs = sx.messages.slice();
1370
- const last = msgs[msgs.length - 1];
1371
- if (!last?.toolCalls) return sx;
1372
- const calls = last.toolCalls.map((tc) => {
1373
- if (callId && tc.callId === callId) {
1374
- return { ...tc, status: "done", output: output || tc.output };
1375
- }
1376
- if (!callId && tc.name === toolName && tc.status === "running") {
1377
- return { ...tc, status: "done", output: output || tc.output };
1378
- }
1379
- return tc;
1380
- });
1381
- msgs[msgs.length - 1] = { ...last, toolCalls: calls };
1382
- return { ...sx, messages: msgs };
1383
- })
1384
- }));
1385
- break;
1386
- }
1387
- case "done": {
1388
- useChatStore.setState((s) => ({
1389
- sessions: s.sessions.map((sx) => {
1390
- if (sx.id !== sessionId) return sx;
1391
- const msgs = sx.messages.map(
1392
- (m) => m.role === "assistant" && m.isStreaming ? { ...m, isStreaming: false, reasoningEndedAt: m.reasoning ? Date.now() : void 0 } : m
1393
- );
1394
- return { ...sx, messages: msgs };
1395
- })
1396
- }));
1397
- setStreaming(sessionId, false);
1398
- break;
1399
- }
1400
- case "error": {
1401
- const err = ev.data.error;
1402
- useChatStore.setState((s) => ({
1403
- sessions: s.sessions.map((sx) => {
1404
- if (sx.id !== sessionId) return sx;
1405
- const msgs = sx.messages.map(
1406
- (m) => m.role === "assistant" && m.isStreaming ? { ...m, isStreaming: false, content: m.content || `**Error:** ${err || "Run failed"}` } : m
1407
- );
1408
- return { ...sx, messages: msgs };
1409
- })
1410
- }));
1411
- setStreaming(sessionId, false);
1412
- break;
1413
- }
1414
- case "approval": {
1415
- useChatStore.setState((s) => ({
1416
- sessions: s.sessions.map(
1417
- (sx) => sx.id === sessionId ? { ...sx, pendingApproval: ev.data } : sx
1418
- )
1419
- }));
1420
- break;
1421
- }
1422
- case "title": {
1423
- const newTitle = ev.data.title;
1424
- if (newTitle) {
1425
- useChatStore.setState((s) => ({
1426
- sessions: s.sessions.map(
1427
- (sx) => sx.id === sessionId ? { ...sx, title: newTitle } : sx
1428
- )
1429
- }));
1430
- }
1431
- break;
1432
- }
1433
- }
1434
- }
1435
- } catch (err) {
1436
- const errMsg = err instanceof Error ? err.message : "Connection error";
1437
- useChatStore.setState((s) => ({
1438
- sessions: s.sessions.map((sx) => {
1439
- if (sx.id !== sessionId) return sx;
1440
- const msgs = sx.messages.map(
1441
- (m) => m.role === "assistant" && m.isStreaming ? { ...m, isStreaming: false, content: m.content || `**Error:** ${errMsg}` } : m
1442
- );
1443
- return { ...sx, messages: msgs };
1444
- })
1445
- }));
1446
- setStreaming(sessionId, false);
1447
- } finally {
1448
- clearInterval(hangTimeout);
1449
- abortRef.current = null;
1450
- setStreaming(sessionId, false);
1451
- useChatStore.setState((s) => ({
1452
- sessions: s.sessions.map((sx) => {
1453
- if (sx.id !== sessionId) return sx;
1454
- const hasStreaming = sx.messages.some((m) => m.role === "assistant" && m.isStreaming);
1455
- if (!hasStreaming) return sx;
1456
- return {
1457
- ...sx,
1458
- messages: sx.messages.map(
1459
- (m) => m.role === "assistant" && m.isStreaming ? { ...m, isStreaming: false } : m
1460
- )
1461
- };
1462
- })
1463
- }));
1464
- }
1465
- },
1466
- [setStreaming]
1467
- );
1468
- return { sendMessage, abortChat };
1469
- }
1470
- function useChatActions({
1471
- sessionId,
1472
- model,
1473
- isLoading,
1474
- sendMessage,
1475
- abortChat
1476
- }) {
1477
- const navigate = useNavigate();
1478
- const isLoadingRef = reactExports.useRef(isLoading);
1479
- isLoadingRef.current = isLoading;
1480
- const handleSend = reactExports.useCallback(
1481
- async (text, attachments) => {
1482
- const trimmed = text.trim();
1483
- if (!trimmed || isLoadingRef.current) return;
1484
- if (isLocalCommand(trimmed)) {
1485
- const cmd = trimmed.split(/\s+/)[0].toLowerCase();
1486
- if (cmd === "/new") {
1487
- const id = await createSession({ model });
1488
- navigate({ to: "/chat/$sessionId", params: { sessionId: id.id } });
1489
- return;
1490
- }
1491
- if (cmd === "/clear") {
1492
- useChatStore.setState((s) => ({
1493
- sessions: s.sessions.map(
1494
- (sx) => sx.id === sessionId ? { ...sx, messages: [] } : sx
1495
- )
1496
- }));
1497
- return;
1498
- }
1499
- if (cmd === "/help") {
1500
- const lines = SLASH_COMMANDS.map(
1501
- (c) => `\`${c.name}\` — ${c.description}`
1502
- ).join("\n");
1503
- useChatStore.setState((s) => ({
1504
- sessions: s.sessions.map(
1505
- (sx) => sx.id === sessionId ? {
1506
- ...sx,
1507
- messages: [
1508
- ...sx.messages,
1509
- {
1510
- id: `agent-${Date.now()}`,
1511
- role: "assistant",
1512
- content: `**Available Commands**
1513
-
1514
- ${lines}`,
1515
- timestamp: Date.now()
1516
- }
1517
- ]
1518
- } : sx
1519
- )
1520
- }));
1521
- return;
1522
- }
1523
- useChatStore.setState((s) => ({
1524
- sessions: s.sessions.map(
1525
- (sx) => sx.id === sessionId ? {
1526
- ...sx,
1527
- messages: [
1528
- ...sx.messages,
1529
- {
1530
- id: `agent-${Date.now()}`,
1531
- role: "assistant",
1532
- content: `Command \`${cmd}\` is not yet implemented in the web UI.`,
1533
- timestamp: Date.now()
1534
- }
1535
- ]
1536
- } : sx
1537
- )
1538
- }));
1539
- return;
1540
- }
1541
- await sendMessage(trimmed, sessionId, model, { attachments });
1542
- },
1543
- [sessionId, model, sendMessage, navigate]
1544
- );
1545
- const handleAbort = reactExports.useCallback(() => {
1546
- abortChat();
1547
- }, [abortChat]);
1548
- const handleApprove = reactExports.useCallback(async () => {
1549
- if (isLoadingRef.current) return;
1550
- await sendMessage("/approve", sessionId, model);
1551
- }, [sessionId, model, sendMessage]);
1552
- const handleDeny = reactExports.useCallback(async () => {
1553
- if (isLoadingRef.current) return;
1554
- await sendMessage("/deny", sessionId, model);
1555
- }, [sessionId, model, sendMessage]);
1556
- const handleNewChat = reactExports.useCallback(async () => {
1557
- const id = await createSession({ model });
1558
- navigate({ to: "/chat/$sessionId", params: { sessionId: id.id } });
1559
- }, [model, navigate]);
1560
- const handleClear = reactExports.useCallback(() => {
1561
- if (isLoadingRef.current) {
1562
- abortChat();
1563
- }
1564
- useChatStore.setState((s) => ({
1565
- sessions: s.sessions.map(
1566
- (sx) => sx.id === sessionId ? { ...sx, messages: [] } : sx
1567
- )
1568
- }));
1569
- }, [sessionId, abortChat]);
1570
- return { handleSend, handleAbort, handleApprove, handleDeny, handleNewChat, handleClear };
1571
- }
1572
- function ApprovalRequest({
1573
- sessionId,
1574
- request,
1575
- onResolved
1576
- }) {
1577
- const [responding, setResponding] = reactExports.useState(false);
1578
- const handleChoice = async (choice) => {
1579
- setResponding(true);
1580
- try {
1581
- await respondApproval({ session_id: sessionId, choice });
1582
- onResolved();
1583
- } catch {
1584
- } finally {
1585
- setResponding(false);
1586
- }
1587
- };
1588
- const toolName = request.tool_name || "unknown tool";
1589
- const command = typeof request.command === "string" ? request.command : typeof request.arguments === "string" ? request.arguments : null;
1590
- const description = typeof request.description === "string" ? request.description : typeof request.preview === "string" ? request.preview : null;
1591
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-auto max-w-3xl px-4 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4", children: [
1592
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
1593
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconShieldCheck, { className: "h-4 w-4 text-amber-500" }),
1594
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium text-amber-600 dark:text-amber-400", children: "Approval Required" })
1595
- ] }),
1596
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-sm text-foreground mb-1", children: [
1597
- "The agent wants to use ",
1598
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium", children: toolName })
1599
- ] }),
1600
- command && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 rounded-lg bg-muted/60 border border-border px-3 py-2 text-xs font-mono text-muted-foreground whitespace-pre-wrap break-all max-h-32 overflow-y-auto", children: command }),
1601
- description && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 text-xs text-muted-foreground leading-relaxed", children: description }),
1602
- request.risk === "high" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex items-center gap-1.5 text-xs text-red-500", children: [
1603
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconAlertTriangle, { className: "h-3.5 w-3.5" }),
1604
- "High risk operation"
1605
- ] }),
1606
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
1607
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1608
- Button,
1609
- {
1610
- size: "sm",
1611
- variant: "default",
1612
- className: "gap-1.5 bg-emerald-600 hover:bg-emerald-700 text-white",
1613
- disabled: responding,
1614
- onClick: () => handleChoice("once"),
1615
- children: [
1616
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconCheck, { className: "h-3.5 w-3.5" }),
1617
- "Approve once"
1618
- ]
1619
- }
1620
- ),
1621
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1622
- Button,
1623
- {
1624
- size: "sm",
1625
- variant: "secondary",
1626
- className: "gap-1.5",
1627
- disabled: responding,
1628
- onClick: () => handleChoice("session"),
1629
- children: [
1630
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconCheck, { className: "h-3.5 w-3.5" }),
1631
- "Approve all"
1632
- ]
1633
- }
1634
- ),
1635
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1636
- Button,
1637
- {
1638
- size: "sm",
1639
- variant: "outline",
1640
- className: "gap-1.5",
1641
- disabled: responding,
1642
- onClick: () => handleChoice("always"),
1643
- children: [
1644
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconShieldCheck, { className: "h-3.5 w-3.5" }),
1645
- "Always allow"
1646
- ]
1647
- }
1648
- ),
1649
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1650
- Button,
1651
- {
1652
- size: "sm",
1653
- variant: "ghost",
1654
- className: "gap-1.5 text-destructive hover:text-destructive",
1655
- disabled: responding,
1656
- onClick: () => handleChoice("deny"),
1657
- children: [
1658
- /* @__PURE__ */ jsxRuntimeExports.jsx(IconX, { className: "h-3.5 w-3.5" }),
1659
- "Deny"
1660
- ]
1661
- }
1662
- )
1663
- ] })
1664
- ] }) });
1665
- }
1666
- function Chat({
1667
- sessionId,
1668
- messages,
1669
- title,
1670
- model,
1671
- pendingApproval
1672
- }) {
1673
- const chatInputRef = reactExports.useRef(null);
1674
- const pendingSentRef = reactExports.useRef(false);
1675
- const { containerRef, bottomRef, onScroll } = useChatScroll(messages);
1676
- const { sendMessage, abortChat } = useChatStream();
1677
- const isLoading = useChatStore((s) => s.streamingSessions.has(sessionId));
1678
- const actions = useChatActions({
1679
- sessionId,
1680
- model,
1681
- isLoading,
1682
- sendMessage,
1683
- abortChat
1684
- });
1685
- reactExports.useEffect(() => {
1686
- if (!sessionId || pendingSentRef.current) return;
1687
- const msgKey = `hermium_pending_msg_${sessionId}`;
1688
- const modelKey = `hermium_pending_model_${sessionId}`;
1689
- const pending = sessionStorage.getItem(msgKey);
1690
- if (!pending) return;
1691
- pendingSentRef.current = true;
1692
- const pendingModel = sessionStorage.getItem(modelKey) || model;
1693
- sessionStorage.removeItem(msgKey);
1694
- sessionStorage.removeItem(modelKey);
1695
- sendMessage(pending, sessionId, pendingModel);
1696
- }, [sessionId, model, sendMessage]);
1697
- reactExports.useEffect(() => {
1698
- function onKey(e) {
1699
- if ((e.metaKey || e.ctrlKey) && e.key === "n") {
1700
- e.preventDefault();
1701
- actions.handleNewChat();
1702
- }
1703
- }
1704
- window.addEventListener("keydown", onKey);
1705
- return () => window.removeEventListener("keydown", onKey);
1706
- }, [actions]);
1707
- const handleSuggestion = reactExports.useCallback((text) => {
1708
- chatInputRef.current?.setText(text);
1709
- }, []);
1710
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full overflow-hidden bg-background", children: [
1711
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1712
- ChatHeader,
1713
- {
1714
- sessionId,
1715
- title,
1716
- hasMessages: messages.length > 0,
1717
- onNewChat: actions.handleNewChat,
1718
- onClear: actions.handleClear
1719
- }
1720
- ),
1721
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1722
- "div",
1723
- {
1724
- className: "flex-1 min-w-0 overflow-y-auto",
1725
- ref: containerRef,
1726
- onScroll,
1727
- children: [
1728
- messages.length === 0 && !isLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChatEmptyState, { onSelectSuggestion: handleSuggestion }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-auto max-w-3xl", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1729
- MessageList,
1730
- {
1731
- messages,
1732
- isLoading,
1733
- onApprove: actions.handleApprove,
1734
- onDeny: actions.handleDeny
1735
- }
1736
- ) }),
1737
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: bottomRef }),
1738
- pendingApproval && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-auto max-w-3xl", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1739
- ApprovalRequest,
1740
- {
1741
- sessionId,
1742
- request: pendingApproval,
1743
- onResolved: () => {
1744
- useChatStore.getState().updateSession(sessionId, {
1745
- pendingApproval: null
1746
- });
1747
- }
1748
- }
1749
- ) })
1750
- ]
1751
- }
1752
- ),
1753
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 px-4 py-3 border-t bg-card/50 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1754
- ChatInput,
1755
- {
1756
- ref: chatInputRef,
1757
- sessionId,
1758
- isLoading,
1759
- onSubmit: actions.handleSend,
1760
- onAbort: actions.handleAbort
1761
- }
1762
- ) })
1763
- ] });
1764
- }
1765
- function SessionChatPage() {
1766
- const {
1767
- sessionId
1768
- } = Route.useParams();
1769
- const switchSession = useChatStore((s) => s.switchSession);
1770
- const loadSessions = useChatStore((s) => s.loadSessions);
1771
- const loadSessionMessages = useChatStore((s) => s.loadSessionMessages);
1772
- const sessionsLoaded = useChatStore((s) => s.sessionsLoaded);
1773
- const isLoadingMessages = useChatStore((s) => s.isLoadingMessages);
1774
- const sessionMeta = useChatStore(useShallow((s) => {
1775
- const sess = s.sessions.find((sx) => sx.id === sessionId);
1776
- return {
1777
- title: sess?.title,
1778
- model: sess?.model || "",
1779
- pendingApproval: sess?.pendingApproval ?? null
1780
- };
1781
- }));
1782
- const messages = useChatStore(useShallow((s) => {
1783
- const sess = s.sessions.find((sx) => sx.id === sessionId);
1784
- return sess?.messages ?? [];
1785
- }));
1786
- reactExports.useEffect(() => {
1787
- if (!sessionsLoaded) {
1788
- loadSessions();
1789
- }
1790
- }, [sessionsLoaded, loadSessions]);
1791
- reactExports.useEffect(() => {
1792
- if (!sessionId) return;
1793
- switchSession(sessionId);
1794
- const sess = useChatStore.getState().getSession(sessionId);
1795
- if (!sess || sess.messages.length === 0) {
1796
- loadSessionMessages(sessionId);
1797
- }
1798
- }, [sessionId, switchSession, loadSessionMessages]);
1799
- if (isLoadingMessages && messages.length === 0) {
1800
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col h-full overflow-hidden", children: [
1801
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "shrink-0 border-b px-4 py-2.5 flex items-center gap-2 min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-muted-foreground truncate flex-1", children: "Loading…" }) }),
1802
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-4 p-4 mx-auto max-w-3xl", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "animate-pulse space-y-2", children: [
1803
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `h-4 rounded bg-muted ${i % 2 === 0 ? "w-2/3" : "w-1/2 ml-auto"}` }),
1804
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `h-3 rounded bg-muted ${i % 2 === 0 ? "w-1/2" : "w-1/3 ml-auto"}` })
1805
- ] }, i)) }) })
1806
- ] });
1807
- }
1808
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Chat, { sessionId, messages, title: sessionMeta.title, model: sessionMeta.model, pendingApproval: sessionMeta.pendingApproval });
1809
- }
1810
- export {
1811
- SessionChatPage as component
1812
- };