@draht/web-ui 2026.3.2-2

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 (372) hide show
  1. package/CHANGELOG.md +276 -0
  2. package/README.md +601 -0
  3. package/dist/ChatPanel.d.ts +28 -0
  4. package/dist/ChatPanel.d.ts.map +1 -0
  5. package/dist/ChatPanel.js +193 -0
  6. package/dist/ChatPanel.js.map +1 -0
  7. package/dist/app.css +2 -0
  8. package/dist/components/AgentInterface.d.ts +39 -0
  9. package/dist/components/AgentInterface.d.ts.map +1 -0
  10. package/dist/components/AgentInterface.js +384 -0
  11. package/dist/components/AgentInterface.js.map +1 -0
  12. package/dist/components/AttachmentTile.d.ts +12 -0
  13. package/dist/components/AttachmentTile.d.ts.map +1 -0
  14. package/dist/components/AttachmentTile.js +110 -0
  15. package/dist/components/AttachmentTile.js.map +1 -0
  16. package/dist/components/ConsoleBlock.d.ts +12 -0
  17. package/dist/components/ConsoleBlock.d.ts.map +1 -0
  18. package/dist/components/ConsoleBlock.js +81 -0
  19. package/dist/components/ConsoleBlock.js.map +1 -0
  20. package/dist/components/CustomProviderCard.d.ts +17 -0
  21. package/dist/components/CustomProviderCard.d.ts.map +1 -0
  22. package/dist/components/CustomProviderCard.js +112 -0
  23. package/dist/components/CustomProviderCard.js.map +1 -0
  24. package/dist/components/ExpandableSection.d.ts +15 -0
  25. package/dist/components/ExpandableSection.d.ts.map +1 -0
  26. package/dist/components/ExpandableSection.js +61 -0
  27. package/dist/components/ExpandableSection.js.map +1 -0
  28. package/dist/components/Input.d.ts +26 -0
  29. package/dist/components/Input.d.ts.map +1 -0
  30. package/dist/components/Input.js +57 -0
  31. package/dist/components/Input.js.map +1 -0
  32. package/dist/components/MessageEditor.d.ts +44 -0
  33. package/dist/components/MessageEditor.d.ts.map +1 -0
  34. package/dist/components/MessageEditor.js +418 -0
  35. package/dist/components/MessageEditor.js.map +1 -0
  36. package/dist/components/MessageList.d.ts +14 -0
  37. package/dist/components/MessageList.d.ts.map +1 -0
  38. package/dist/components/MessageList.js +104 -0
  39. package/dist/components/MessageList.js.map +1 -0
  40. package/dist/components/Messages.d.ts +95 -0
  41. package/dist/components/Messages.d.ts.map +1 -0
  42. package/dist/components/Messages.js +363 -0
  43. package/dist/components/Messages.js.map +1 -0
  44. package/dist/components/ProviderKeyInput.d.ts +16 -0
  45. package/dist/components/ProviderKeyInput.d.ts.map +1 -0
  46. package/dist/components/ProviderKeyInput.js +168 -0
  47. package/dist/components/ProviderKeyInput.js.map +1 -0
  48. package/dist/components/SandboxedIframe.d.ts +85 -0
  49. package/dist/components/SandboxedIframe.d.ts.map +1 -0
  50. package/dist/components/SandboxedIframe.js +518 -0
  51. package/dist/components/SandboxedIframe.js.map +1 -0
  52. package/dist/components/StreamingMessageContainer.d.ts +19 -0
  53. package/dist/components/StreamingMessageContainer.d.ts.map +1 -0
  54. package/dist/components/StreamingMessageContainer.js +117 -0
  55. package/dist/components/StreamingMessageContainer.js.map +1 -0
  56. package/dist/components/ThinkingBlock.d.ts +11 -0
  57. package/dist/components/ThinkingBlock.d.ts.map +1 -0
  58. package/dist/components/ThinkingBlock.js +56 -0
  59. package/dist/components/ThinkingBlock.js.map +1 -0
  60. package/dist/components/message-renderer-registry.d.ts +12 -0
  61. package/dist/components/message-renderer-registry.d.ts.map +1 -0
  62. package/dist/components/message-renderer-registry.js +12 -0
  63. package/dist/components/message-renderer-registry.js.map +1 -0
  64. package/dist/components/sandbox/ArtifactsRuntimeProvider.d.ts +35 -0
  65. package/dist/components/sandbox/ArtifactsRuntimeProvider.d.ts.map +1 -0
  66. package/dist/components/sandbox/ArtifactsRuntimeProvider.js +192 -0
  67. package/dist/components/sandbox/ArtifactsRuntimeProvider.js.map +1 -0
  68. package/dist/components/sandbox/AttachmentsRuntimeProvider.d.ts +17 -0
  69. package/dist/components/sandbox/AttachmentsRuntimeProvider.d.ts.map +1 -0
  70. package/dist/components/sandbox/AttachmentsRuntimeProvider.js +65 -0
  71. package/dist/components/sandbox/AttachmentsRuntimeProvider.js.map +1 -0
  72. package/dist/components/sandbox/ConsoleRuntimeProvider.d.ts +42 -0
  73. package/dist/components/sandbox/ConsoleRuntimeProvider.d.ts.map +1 -0
  74. package/dist/components/sandbox/ConsoleRuntimeProvider.js +159 -0
  75. package/dist/components/sandbox/ConsoleRuntimeProvider.js.map +1 -0
  76. package/dist/components/sandbox/FileDownloadRuntimeProvider.d.ts +30 -0
  77. package/dist/components/sandbox/FileDownloadRuntimeProvider.d.ts.map +1 -0
  78. package/dist/components/sandbox/FileDownloadRuntimeProvider.js +95 -0
  79. package/dist/components/sandbox/FileDownloadRuntimeProvider.js.map +1 -0
  80. package/dist/components/sandbox/RuntimeMessageBridge.d.ts +19 -0
  81. package/dist/components/sandbox/RuntimeMessageBridge.d.ts.map +1 -0
  82. package/dist/components/sandbox/RuntimeMessageBridge.js +74 -0
  83. package/dist/components/sandbox/RuntimeMessageBridge.js.map +1 -0
  84. package/dist/components/sandbox/RuntimeMessageRouter.d.ts +65 -0
  85. package/dist/components/sandbox/RuntimeMessageRouter.d.ts.map +1 -0
  86. package/dist/components/sandbox/RuntimeMessageRouter.js +166 -0
  87. package/dist/components/sandbox/RuntimeMessageRouter.js.map +1 -0
  88. package/dist/components/sandbox/SandboxRuntimeProvider.d.ts +48 -0
  89. package/dist/components/sandbox/SandboxRuntimeProvider.d.ts.map +1 -0
  90. package/dist/components/sandbox/SandboxRuntimeProvider.js +2 -0
  91. package/dist/components/sandbox/SandboxRuntimeProvider.js.map +1 -0
  92. package/dist/dialogs/ApiKeyPromptDialog.d.ts +15 -0
  93. package/dist/dialogs/ApiKeyPromptDialog.d.ts.map +1 -0
  94. package/dist/dialogs/ApiKeyPromptDialog.js +77 -0
  95. package/dist/dialogs/ApiKeyPromptDialog.js.map +1 -0
  96. package/dist/dialogs/AttachmentOverlay.d.ts +32 -0
  97. package/dist/dialogs/AttachmentOverlay.d.ts.map +1 -0
  98. package/dist/dialogs/AttachmentOverlay.js +576 -0
  99. package/dist/dialogs/AttachmentOverlay.js.map +1 -0
  100. package/dist/dialogs/CustomProviderDialog.d.ts +25 -0
  101. package/dist/dialogs/CustomProviderDialog.d.ts.map +1 -0
  102. package/dist/dialogs/CustomProviderDialog.js +270 -0
  103. package/dist/dialogs/CustomProviderDialog.js.map +1 -0
  104. package/dist/dialogs/ModelSelector.d.ts +27 -0
  105. package/dist/dialogs/ModelSelector.d.ts.map +1 -0
  106. package/dist/dialogs/ModelSelector.js +320 -0
  107. package/dist/dialogs/ModelSelector.js.map +1 -0
  108. package/dist/dialogs/PersistentStorageDialog.d.ts +17 -0
  109. package/dist/dialogs/PersistentStorageDialog.d.ts.map +1 -0
  110. package/dist/dialogs/PersistentStorageDialog.js +144 -0
  111. package/dist/dialogs/PersistentStorageDialog.js.map +1 -0
  112. package/dist/dialogs/ProvidersModelsTab.d.ts +20 -0
  113. package/dist/dialogs/ProvidersModelsTab.d.ts.map +1 -0
  114. package/dist/dialogs/ProvidersModelsTab.js +188 -0
  115. package/dist/dialogs/ProvidersModelsTab.js.map +1 -0
  116. package/dist/dialogs/SessionListDialog.d.ts +19 -0
  117. package/dist/dialogs/SessionListDialog.d.ts.map +1 -0
  118. package/dist/dialogs/SessionListDialog.js +152 -0
  119. package/dist/dialogs/SessionListDialog.js.map +1 -0
  120. package/dist/dialogs/SettingsDialog.d.ts +30 -0
  121. package/dist/dialogs/SettingsDialog.d.ts.map +1 -0
  122. package/dist/dialogs/SettingsDialog.js +222 -0
  123. package/dist/dialogs/SettingsDialog.js.map +1 -0
  124. package/dist/index.d.ts +67 -0
  125. package/dist/index.d.ts.map +1 -0
  126. package/dist/index.js +70 -0
  127. package/dist/index.js.map +1 -0
  128. package/dist/prompts/prompts.d.ts +11 -0
  129. package/dist/prompts/prompts.d.ts.map +1 -0
  130. package/dist/prompts/prompts.js +272 -0
  131. package/dist/prompts/prompts.js.map +1 -0
  132. package/dist/storage/app-storage.d.ts +33 -0
  133. package/dist/storage/app-storage.d.ts.map +1 -0
  134. package/dist/storage/app-storage.js +43 -0
  135. package/dist/storage/app-storage.js.map +1 -0
  136. package/dist/storage/backends/indexeddb-storage-backend.d.ts +27 -0
  137. package/dist/storage/backends/indexeddb-storage-backend.d.ts.map +1 -0
  138. package/dist/storage/backends/indexeddb-storage-backend.js +167 -0
  139. package/dist/storage/backends/indexeddb-storage-backend.js.map +1 -0
  140. package/dist/storage/store.d.ts +23 -0
  141. package/dist/storage/store.d.ts.map +1 -0
  142. package/dist/storage/store.js +24 -0
  143. package/dist/storage/store.js.map +1 -0
  144. package/dist/storage/stores/custom-providers-store.d.ts +25 -0
  145. package/dist/storage/stores/custom-providers-store.d.ts.map +1 -0
  146. package/dist/storage/stores/custom-providers-store.js +35 -0
  147. package/dist/storage/stores/custom-providers-store.js.map +1 -0
  148. package/dist/storage/stores/provider-keys-store.d.ts +14 -0
  149. package/dist/storage/stores/provider-keys-store.d.ts.map +1 -0
  150. package/dist/storage/stores/provider-keys-store.js +27 -0
  151. package/dist/storage/stores/provider-keys-store.js.map +1 -0
  152. package/dist/storage/stores/sessions-store.d.ts +32 -0
  153. package/dist/storage/stores/sessions-store.d.ts.map +1 -0
  154. package/dist/storage/stores/sessions-store.js +113 -0
  155. package/dist/storage/stores/sessions-store.js.map +1 -0
  156. package/dist/storage/stores/settings-store.d.ts +14 -0
  157. package/dist/storage/stores/settings-store.d.ts.map +1 -0
  158. package/dist/storage/stores/settings-store.js +28 -0
  159. package/dist/storage/stores/settings-store.js.map +1 -0
  160. package/dist/storage/types.d.ts +176 -0
  161. package/dist/storage/types.d.ts.map +1 -0
  162. package/dist/storage/types.js +2 -0
  163. package/dist/storage/types.js.map +1 -0
  164. package/dist/tools/artifacts/ArtifactElement.d.ts +9 -0
  165. package/dist/tools/artifacts/ArtifactElement.d.ts.map +1 -0
  166. package/dist/tools/artifacts/ArtifactElement.js +8 -0
  167. package/dist/tools/artifacts/ArtifactElement.js.map +1 -0
  168. package/dist/tools/artifacts/ArtifactPill.d.ts +4 -0
  169. package/dist/tools/artifacts/ArtifactPill.d.ts.map +1 -0
  170. package/dist/tools/artifacts/ArtifactPill.js +23 -0
  171. package/dist/tools/artifacts/ArtifactPill.js.map +1 -0
  172. package/dist/tools/artifacts/Console.d.ts +18 -0
  173. package/dist/tools/artifacts/Console.d.ts.map +1 -0
  174. package/dist/tools/artifacts/Console.js +92 -0
  175. package/dist/tools/artifacts/Console.js.map +1 -0
  176. package/dist/tools/artifacts/DocxArtifact.d.ts +22 -0
  177. package/dist/tools/artifacts/DocxArtifact.d.ts.map +1 -0
  178. package/dist/tools/artifacts/DocxArtifact.js +205 -0
  179. package/dist/tools/artifacts/DocxArtifact.js.map +1 -0
  180. package/dist/tools/artifacts/ExcelArtifact.d.ts +24 -0
  181. package/dist/tools/artifacts/ExcelArtifact.d.ts.map +1 -0
  182. package/dist/tools/artifacts/ExcelArtifact.js +213 -0
  183. package/dist/tools/artifacts/ExcelArtifact.js.map +1 -0
  184. package/dist/tools/artifacts/GenericArtifact.d.ts +19 -0
  185. package/dist/tools/artifacts/GenericArtifact.d.ts.map +1 -0
  186. package/dist/tools/artifacts/GenericArtifact.js +114 -0
  187. package/dist/tools/artifacts/GenericArtifact.js.map +1 -0
  188. package/dist/tools/artifacts/HtmlArtifact.d.ts +27 -0
  189. package/dist/tools/artifacts/HtmlArtifact.d.ts.map +1 -0
  190. package/dist/tools/artifacts/HtmlArtifact.js +187 -0
  191. package/dist/tools/artifacts/HtmlArtifact.js.map +1 -0
  192. package/dist/tools/artifacts/ImageArtifact.d.ts +20 -0
  193. package/dist/tools/artifacts/ImageArtifact.d.ts.map +1 -0
  194. package/dist/tools/artifacts/ImageArtifact.js +117 -0
  195. package/dist/tools/artifacts/ImageArtifact.js.map +1 -0
  196. package/dist/tools/artifacts/MarkdownArtifact.d.ts +19 -0
  197. package/dist/tools/artifacts/MarkdownArtifact.d.ts.map +1 -0
  198. package/dist/tools/artifacts/MarkdownArtifact.js +79 -0
  199. package/dist/tools/artifacts/MarkdownArtifact.js.map +1 -0
  200. package/dist/tools/artifacts/PdfArtifact.d.ts +25 -0
  201. package/dist/tools/artifacts/PdfArtifact.d.ts.map +1 -0
  202. package/dist/tools/artifacts/PdfArtifact.js +181 -0
  203. package/dist/tools/artifacts/PdfArtifact.js.map +1 -0
  204. package/dist/tools/artifacts/SvgArtifact.d.ts +18 -0
  205. package/dist/tools/artifacts/SvgArtifact.d.ts.map +1 -0
  206. package/dist/tools/artifacts/SvgArtifact.js +75 -0
  207. package/dist/tools/artifacts/SvgArtifact.js.map +1 -0
  208. package/dist/tools/artifacts/TextArtifact.d.ts +19 -0
  209. package/dist/tools/artifacts/TextArtifact.d.ts.map +1 -0
  210. package/dist/tools/artifacts/TextArtifact.js +141 -0
  211. package/dist/tools/artifacts/TextArtifact.js.map +1 -0
  212. package/dist/tools/artifacts/artifacts-tool-renderer.d.ts +11 -0
  213. package/dist/tools/artifacts/artifacts-tool-renderer.d.ts.map +1 -0
  214. package/dist/tools/artifacts/artifacts-tool-renderer.js +273 -0
  215. package/dist/tools/artifacts/artifacts-tool-renderer.js.map +1 -0
  216. package/dist/tools/artifacts/artifacts.d.ts +63 -0
  217. package/dist/tools/artifacts/artifacts.d.ts.map +1 -0
  218. package/dist/tools/artifacts/artifacts.js +664 -0
  219. package/dist/tools/artifacts/artifacts.js.map +1 -0
  220. package/dist/tools/artifacts/index.d.ts +8 -0
  221. package/dist/tools/artifacts/index.d.ts.map +1 -0
  222. package/dist/tools/artifacts/index.js +8 -0
  223. package/dist/tools/artifacts/index.js.map +1 -0
  224. package/dist/tools/extract-document.d.ts +24 -0
  225. package/dist/tools/extract-document.d.ts.map +1 -0
  226. package/dist/tools/extract-document.js +216 -0
  227. package/dist/tools/extract-document.js.map +1 -0
  228. package/dist/tools/index.d.ts +16 -0
  229. package/dist/tools/index.d.ts.map +1 -0
  230. package/dist/tools/index.js +33 -0
  231. package/dist/tools/index.js.map +1 -0
  232. package/dist/tools/javascript-repl.d.ts +44 -0
  233. package/dist/tools/javascript-repl.d.ts.map +1 -0
  234. package/dist/tools/javascript-repl.js +224 -0
  235. package/dist/tools/javascript-repl.js.map +1 -0
  236. package/dist/tools/renderer-registry.d.ts +23 -0
  237. package/dist/tools/renderer-registry.d.ts.map +1 -0
  238. package/dist/tools/renderer-registry.js +107 -0
  239. package/dist/tools/renderer-registry.js.map +1 -0
  240. package/dist/tools/renderers/BashRenderer.d.ts +10 -0
  241. package/dist/tools/renderers/BashRenderer.d.ts.map +1 -0
  242. package/dist/tools/renderers/BashRenderer.js +42 -0
  243. package/dist/tools/renderers/BashRenderer.js.map +1 -0
  244. package/dist/tools/renderers/CalculateRenderer.d.ts +10 -0
  245. package/dist/tools/renderers/CalculateRenderer.d.ts.map +1 -0
  246. package/dist/tools/renderers/CalculateRenderer.js +45 -0
  247. package/dist/tools/renderers/CalculateRenderer.js.map +1 -0
  248. package/dist/tools/renderers/DefaultRenderer.d.ts +6 -0
  249. package/dist/tools/renderers/DefaultRenderer.d.ts.map +1 -0
  250. package/dist/tools/renderers/DefaultRenderer.js +94 -0
  251. package/dist/tools/renderers/DefaultRenderer.js.map +1 -0
  252. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts +10 -0
  253. package/dist/tools/renderers/GetCurrentTimeRenderer.d.ts.map +1 -0
  254. package/dist/tools/renderers/GetCurrentTimeRenderer.js +72 -0
  255. package/dist/tools/renderers/GetCurrentTimeRenderer.js.map +1 -0
  256. package/dist/tools/types.d.ts +10 -0
  257. package/dist/tools/types.d.ts.map +1 -0
  258. package/dist/tools/types.js +2 -0
  259. package/dist/tools/types.js.map +1 -0
  260. package/dist/utils/attachment-utils.d.ts +19 -0
  261. package/dist/utils/attachment-utils.d.ts.map +1 -0
  262. package/dist/utils/attachment-utils.js +415 -0
  263. package/dist/utils/attachment-utils.js.map +1 -0
  264. package/dist/utils/auth-token.d.ts +3 -0
  265. package/dist/utils/auth-token.d.ts.map +1 -0
  266. package/dist/utils/auth-token.js +19 -0
  267. package/dist/utils/auth-token.js.map +1 -0
  268. package/dist/utils/format.d.ts +6 -0
  269. package/dist/utils/format.d.ts.map +1 -0
  270. package/dist/utils/format.js +47 -0
  271. package/dist/utils/format.js.map +1 -0
  272. package/dist/utils/i18n.d.ts +636 -0
  273. package/dist/utils/i18n.d.ts.map +1 -0
  274. package/dist/utils/i18n.js +418 -0
  275. package/dist/utils/i18n.js.map +1 -0
  276. package/dist/utils/model-discovery.d.ts +38 -0
  277. package/dist/utils/model-discovery.d.ts.map +1 -0
  278. package/dist/utils/model-discovery.js +243 -0
  279. package/dist/utils/model-discovery.js.map +1 -0
  280. package/dist/utils/proxy-utils.d.ts +45 -0
  281. package/dist/utils/proxy-utils.d.ts.map +1 -0
  282. package/dist/utils/proxy-utils.js +116 -0
  283. package/dist/utils/proxy-utils.js.map +1 -0
  284. package/dist/utils/test-sessions.d.ts +359 -0
  285. package/dist/utils/test-sessions.d.ts.map +1 -0
  286. package/dist/utils/test-sessions.js +2325 -0
  287. package/dist/utils/test-sessions.js.map +1 -0
  288. package/example/README.md +61 -0
  289. package/example/index.html +13 -0
  290. package/example/package.json +25 -0
  291. package/example/src/app.css +1 -0
  292. package/example/src/custom-messages.ts +99 -0
  293. package/example/src/env.d.ts +1 -0
  294. package/example/src/main.ts +421 -0
  295. package/example/tsconfig.json +23 -0
  296. package/example/vite.config.ts +6 -0
  297. package/package.json +53 -0
  298. package/scripts/count-prompt-tokens.ts +88 -0
  299. package/src/ChatPanel.ts +207 -0
  300. package/src/app.css +68 -0
  301. package/src/components/AgentInterface.ts +388 -0
  302. package/src/components/AttachmentTile.ts +107 -0
  303. package/src/components/ConsoleBlock.ts +72 -0
  304. package/src/components/CustomProviderCard.ts +100 -0
  305. package/src/components/ExpandableSection.ts +46 -0
  306. package/src/components/Input.ts +113 -0
  307. package/src/components/MessageEditor.ts +400 -0
  308. package/src/components/MessageList.ts +95 -0
  309. package/src/components/Messages.ts +383 -0
  310. package/src/components/ProviderKeyInput.ts +153 -0
  311. package/src/components/SandboxedIframe.ts +626 -0
  312. package/src/components/StreamingMessageContainer.ts +103 -0
  313. package/src/components/ThinkingBlock.ts +43 -0
  314. package/src/components/message-renderer-registry.ts +28 -0
  315. package/src/components/sandbox/ArtifactsRuntimeProvider.ts +219 -0
  316. package/src/components/sandbox/AttachmentsRuntimeProvider.ts +66 -0
  317. package/src/components/sandbox/ConsoleRuntimeProvider.ts +186 -0
  318. package/src/components/sandbox/FileDownloadRuntimeProvider.ts +110 -0
  319. package/src/components/sandbox/RuntimeMessageBridge.ts +82 -0
  320. package/src/components/sandbox/RuntimeMessageRouter.ts +216 -0
  321. package/src/components/sandbox/SandboxRuntimeProvider.ts +52 -0
  322. package/src/dialogs/ApiKeyPromptDialog.ts +75 -0
  323. package/src/dialogs/AttachmentOverlay.ts +636 -0
  324. package/src/dialogs/CustomProviderDialog.ts +274 -0
  325. package/src/dialogs/ModelSelector.ts +313 -0
  326. package/src/dialogs/PersistentStorageDialog.ts +144 -0
  327. package/src/dialogs/ProvidersModelsTab.ts +212 -0
  328. package/src/dialogs/SessionListDialog.ts +150 -0
  329. package/src/dialogs/SettingsDialog.ts +214 -0
  330. package/src/index.ts +119 -0
  331. package/src/prompts/prompts.ts +282 -0
  332. package/src/storage/app-storage.ts +60 -0
  333. package/src/storage/backends/indexeddb-storage-backend.ts +193 -0
  334. package/src/storage/store.ts +33 -0
  335. package/src/storage/stores/custom-providers-store.ts +62 -0
  336. package/src/storage/stores/provider-keys-store.ts +33 -0
  337. package/src/storage/stores/sessions-store.ts +136 -0
  338. package/src/storage/stores/settings-store.ts +34 -0
  339. package/src/storage/types.ts +206 -0
  340. package/src/tools/artifacts/ArtifactElement.ts +14 -0
  341. package/src/tools/artifacts/ArtifactPill.ts +26 -0
  342. package/src/tools/artifacts/Console.ts +93 -0
  343. package/src/tools/artifacts/DocxArtifact.ts +213 -0
  344. package/src/tools/artifacts/ExcelArtifact.ts +231 -0
  345. package/src/tools/artifacts/GenericArtifact.ts +117 -0
  346. package/src/tools/artifacts/HtmlArtifact.ts +195 -0
  347. package/src/tools/artifacts/ImageArtifact.ts +116 -0
  348. package/src/tools/artifacts/MarkdownArtifact.ts +82 -0
  349. package/src/tools/artifacts/PdfArtifact.ts +201 -0
  350. package/src/tools/artifacts/SvgArtifact.ts +78 -0
  351. package/src/tools/artifacts/TextArtifact.ts +148 -0
  352. package/src/tools/artifacts/artifacts-tool-renderer.ts +310 -0
  353. package/src/tools/artifacts/artifacts.ts +713 -0
  354. package/src/tools/artifacts/index.ts +7 -0
  355. package/src/tools/extract-document.ts +275 -0
  356. package/src/tools/index.ts +46 -0
  357. package/src/tools/javascript-repl.ts +293 -0
  358. package/src/tools/renderer-registry.ts +130 -0
  359. package/src/tools/renderers/BashRenderer.ts +52 -0
  360. package/src/tools/renderers/CalculateRenderer.ts +58 -0
  361. package/src/tools/renderers/DefaultRenderer.ts +103 -0
  362. package/src/tools/renderers/GetCurrentTimeRenderer.ts +92 -0
  363. package/src/tools/types.ts +15 -0
  364. package/src/utils/attachment-utils.ts +472 -0
  365. package/src/utils/auth-token.ts +22 -0
  366. package/src/utils/format.ts +42 -0
  367. package/src/utils/i18n.ts +653 -0
  368. package/src/utils/model-discovery.ts +277 -0
  369. package/src/utils/proxy-utils.ts +134 -0
  370. package/src/utils/test-sessions.ts +2357 -0
  371. package/tsconfig.build.json +20 -0
  372. package/tsconfig.json +7 -0
@@ -0,0 +1,207 @@
1
+ import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
2
+ import { html, LitElement } from "lit";
3
+ import { customElement, state } from "lit/decorators.js";
4
+ import "./components/AgentInterface.js";
5
+ import type { Agent, AgentTool } from "@draht/agent-core";
6
+ import type { AgentInterface } from "./components/AgentInterface.js";
7
+ import { ArtifactsRuntimeProvider } from "./components/sandbox/ArtifactsRuntimeProvider.js";
8
+ import { AttachmentsRuntimeProvider } from "./components/sandbox/AttachmentsRuntimeProvider.js";
9
+ import type { SandboxRuntimeProvider } from "./components/sandbox/SandboxRuntimeProvider.js";
10
+ import { ArtifactsPanel, ArtifactsToolRenderer } from "./tools/artifacts/index.js";
11
+ import { registerToolRenderer } from "./tools/renderer-registry.js";
12
+ import type { Attachment } from "./utils/attachment-utils.js";
13
+ import { i18n } from "./utils/i18n.js";
14
+
15
+ const BREAKPOINT = 800; // px - switch between overlay and side-by-side
16
+
17
+ @customElement("pi-chat-panel")
18
+ export class ChatPanel extends LitElement {
19
+ @state() public agent?: Agent;
20
+ @state() public agentInterface?: AgentInterface;
21
+ @state() public artifactsPanel?: ArtifactsPanel;
22
+ @state() private hasArtifacts = false;
23
+ @state() private artifactCount = 0;
24
+ @state() private showArtifactsPanel = false;
25
+ @state() private windowWidth = 0;
26
+
27
+ private resizeHandler = () => {
28
+ this.windowWidth = window.innerWidth;
29
+ this.requestUpdate();
30
+ };
31
+
32
+ createRenderRoot() {
33
+ return this;
34
+ }
35
+
36
+ override connectedCallback() {
37
+ super.connectedCallback();
38
+ this.windowWidth = window.innerWidth; // Set initial width after connection
39
+ window.addEventListener("resize", this.resizeHandler);
40
+ this.style.display = "flex";
41
+ this.style.flexDirection = "column";
42
+ this.style.height = "100%";
43
+ this.style.minHeight = "0";
44
+ // Update width after initial render
45
+ requestAnimationFrame(() => {
46
+ this.windowWidth = window.innerWidth;
47
+ this.requestUpdate();
48
+ });
49
+ }
50
+
51
+ override disconnectedCallback() {
52
+ super.disconnectedCallback();
53
+ window.removeEventListener("resize", this.resizeHandler);
54
+ }
55
+
56
+ async setAgent(
57
+ agent: Agent,
58
+ config?: {
59
+ onApiKeyRequired?: (provider: string) => Promise<boolean>;
60
+ onBeforeSend?: () => void | Promise<void>;
61
+ onCostClick?: () => void;
62
+ sandboxUrlProvider?: () => string;
63
+ toolsFactory?: (
64
+ agent: Agent,
65
+ agentInterface: AgentInterface,
66
+ artifactsPanel: ArtifactsPanel,
67
+ runtimeProvidersFactory: () => SandboxRuntimeProvider[],
68
+ ) => AgentTool<any>[];
69
+ },
70
+ ) {
71
+ this.agent = agent;
72
+
73
+ // Create AgentInterface
74
+ this.agentInterface = document.createElement("agent-interface") as AgentInterface;
75
+ this.agentInterface.session = agent;
76
+ this.agentInterface.enableAttachments = true;
77
+ this.agentInterface.enableModelSelector = true;
78
+ this.agentInterface.enableThinkingSelector = true;
79
+ this.agentInterface.showThemeToggle = false;
80
+ this.agentInterface.onApiKeyRequired = config?.onApiKeyRequired;
81
+ this.agentInterface.onBeforeSend = config?.onBeforeSend;
82
+ this.agentInterface.onCostClick = config?.onCostClick;
83
+
84
+ // Set up artifacts panel
85
+ this.artifactsPanel = new ArtifactsPanel();
86
+ this.artifactsPanel.agent = agent; // Pass agent for HTML artifact runtime providers
87
+ if (config?.sandboxUrlProvider) {
88
+ this.artifactsPanel.sandboxUrlProvider = config.sandboxUrlProvider;
89
+ }
90
+ // Register the standalone tool renderer (not the panel itself)
91
+ registerToolRenderer("artifacts", new ArtifactsToolRenderer(this.artifactsPanel));
92
+
93
+ // Runtime providers factory for REPL tools (read-write access)
94
+ const runtimeProvidersFactory = () => {
95
+ const attachments: Attachment[] = [];
96
+ for (const message of this.agent!.state.messages) {
97
+ if (message.role === "user-with-attachments") {
98
+ message.attachments?.forEach((a: any) => {
99
+ attachments.push(a);
100
+ });
101
+ }
102
+ }
103
+ const providers: SandboxRuntimeProvider[] = [];
104
+
105
+ // Add attachments provider if there are attachments
106
+ if (attachments.length > 0) {
107
+ providers.push(new AttachmentsRuntimeProvider(attachments));
108
+ }
109
+
110
+ // Add artifacts provider with read-write access (for REPL)
111
+ providers.push(new ArtifactsRuntimeProvider(this.artifactsPanel!, this.agent!, true));
112
+
113
+ return providers;
114
+ };
115
+
116
+ this.artifactsPanel.onArtifactsChange = () => {
117
+ const count = this.artifactsPanel?.artifacts?.size ?? 0;
118
+ const created = count > this.artifactCount;
119
+ this.hasArtifacts = count > 0;
120
+ this.artifactCount = count;
121
+ if (this.hasArtifacts && created) {
122
+ this.showArtifactsPanel = true;
123
+ }
124
+ this.requestUpdate();
125
+ };
126
+
127
+ this.artifactsPanel.onClose = () => {
128
+ this.showArtifactsPanel = false;
129
+ this.requestUpdate();
130
+ };
131
+
132
+ this.artifactsPanel.onOpen = () => {
133
+ this.showArtifactsPanel = true;
134
+ this.requestUpdate();
135
+ };
136
+
137
+ // Set tools on the agent
138
+ // Pass runtimeProvidersFactory so consumers can configure their own REPL tools
139
+ const additionalTools =
140
+ config?.toolsFactory?.(agent, this.agentInterface, this.artifactsPanel, runtimeProvidersFactory) || [];
141
+ const tools = [this.artifactsPanel.tool, ...additionalTools];
142
+ this.agent.setTools(tools);
143
+
144
+ // Reconstruct artifacts from existing messages
145
+ // Temporarily disable the onArtifactsChange callback to prevent auto-opening on load
146
+ const originalCallback = this.artifactsPanel.onArtifactsChange;
147
+ this.artifactsPanel.onArtifactsChange = undefined;
148
+ await this.artifactsPanel.reconstructFromMessages(this.agent.state.messages);
149
+ this.artifactsPanel.onArtifactsChange = originalCallback;
150
+
151
+ this.hasArtifacts = this.artifactsPanel.artifacts.size > 0;
152
+ this.artifactCount = this.artifactsPanel.artifacts.size;
153
+
154
+ this.requestUpdate();
155
+ }
156
+
157
+ render() {
158
+ if (!this.agent || !this.agentInterface) {
159
+ return html`<div class="flex items-center justify-center h-full">
160
+ <div class="text-muted-foreground">No agent set</div>
161
+ </div>`;
162
+ }
163
+
164
+ const isMobile = this.windowWidth < BREAKPOINT;
165
+
166
+ // Set panel props
167
+ if (this.artifactsPanel) {
168
+ this.artifactsPanel.collapsed = !this.showArtifactsPanel;
169
+ this.artifactsPanel.overlay = isMobile;
170
+ }
171
+
172
+ return html`
173
+ <div class="relative w-full h-full overflow-hidden flex">
174
+ <div class="h-full" style="${!isMobile && this.showArtifactsPanel && this.hasArtifacts ? "width: 50%;" : "width: 100%;"}">
175
+ ${this.agentInterface}
176
+ </div>
177
+
178
+ <!-- Floating pill when artifacts exist and panel is collapsed -->
179
+ ${
180
+ this.hasArtifacts && !this.showArtifactsPanel
181
+ ? html`
182
+ <button
183
+ class="absolute z-30 top-4 left-1/2 -translate-x-1/2 pointer-events-auto"
184
+ @click=${() => {
185
+ this.showArtifactsPanel = true;
186
+ this.requestUpdate();
187
+ }}
188
+ title=${i18n("Show artifacts")}
189
+ >
190
+ ${Badge(html`
191
+ <span class="inline-flex items-center gap-1">
192
+ <span>${i18n("Artifacts")}</span>
193
+ <span class="text-[10px] leading-none bg-primary-foreground/20 text-primary-foreground rounded px-1 font-mono tabular-nums">${this.artifactCount}</span>
194
+ </span>
195
+ `)}
196
+ </button>
197
+ `
198
+ : ""
199
+ }
200
+
201
+ <div class="h-full ${isMobile ? "absolute inset-0 pointer-events-none" : ""}" style="${!isMobile ? (!this.hasArtifacts || !this.showArtifactsPanel ? "display: none;" : "width: 50%;") : ""}">
202
+ ${this.artifactsPanel}
203
+ </div>
204
+ </div>
205
+ `;
206
+ }
207
+ }
package/src/app.css ADDED
@@ -0,0 +1,68 @@
1
+ /* Import Claude theme from mini-lit */
2
+ @import "@mariozechner/mini-lit/styles/themes/default.css";
3
+
4
+ /* Tell Tailwind to scan mini-lit components */
5
+ /* biome-ignore lint/suspicious/noUnknownAtRules: Tailwind 4 source directive */
6
+ @source "../../../node_modules/@mariozechner/mini-lit/dist";
7
+
8
+ /* Import Tailwind */
9
+ /* biome-ignore lint/correctness/noInvalidPositionAtImportRule: fuck you */
10
+ @import "tailwindcss";
11
+
12
+ body {
13
+ font-size: 16px;
14
+ -webkit-font-smoothing: antialiased;
15
+ }
16
+
17
+ * {
18
+ scrollbar-width: thin;
19
+ scrollbar-color: var(--color-border) rgba(0, 0, 0, 0);
20
+ }
21
+
22
+ *::-webkit-scrollbar {
23
+ width: 8px;
24
+ height: 8px;
25
+ }
26
+
27
+ *::-webkit-scrollbar-track {
28
+ background: transparent;
29
+ }
30
+
31
+ *::-webkit-scrollbar-thumb {
32
+ background-color: var(--color-border);
33
+ border-radius: 4px;
34
+ }
35
+
36
+ *::-webkit-scrollbar-thumb:hover {
37
+ background-color: rgba(0, 0, 0, 0);
38
+ }
39
+
40
+ /* Fix cursor for dialog close buttons */
41
+ .fixed.inset-0 button[aria-label*="Close"],
42
+ .fixed.inset-0 button[type="button"] {
43
+ cursor: pointer;
44
+ }
45
+
46
+ /* Shimmer animation for thinking text */
47
+ @keyframes shimmer {
48
+ 0% {
49
+ background-position: -200% 0;
50
+ }
51
+ 100% {
52
+ background-position: 200% 0;
53
+ }
54
+ }
55
+
56
+ .animate-shimmer {
57
+ animation: shimmer 2s ease-in-out infinite;
58
+ }
59
+
60
+ /* User message with fancy pill styling */
61
+ .user-message-container {
62
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
63
+ position: relative;
64
+ background: linear-gradient(135deg, rgba(217, 79, 0, 0.12), rgba(255, 107, 0, 0.12), rgba(212, 165, 0, 0.12));
65
+ border: 1px solid rgba(255, 107, 0, 0.25);
66
+ backdrop-filter: blur(10px);
67
+ max-width: 100%;
68
+ }
@@ -0,0 +1,388 @@
1
+ import { streamSimple, type ToolResultMessage, type Usage } from "@draht/ai";
2
+ import { html, LitElement } from "lit";
3
+ import { customElement, property, query } from "lit/decorators.js";
4
+ import { ModelSelector } from "../dialogs/ModelSelector.js";
5
+ import type { MessageEditor } from "./MessageEditor.js";
6
+ import "./MessageEditor.js";
7
+ import "./MessageList.js";
8
+ import "./Messages.js"; // Import for side effects to register the custom elements
9
+ import { getAppStorage } from "../storage/app-storage.js";
10
+ import "./StreamingMessageContainer.js";
11
+ import type { Agent, AgentEvent } from "@draht/agent-core";
12
+ import type { Attachment } from "../utils/attachment-utils.js";
13
+ import { formatUsage } from "../utils/format.js";
14
+ import { i18n } from "../utils/i18n.js";
15
+ import { createStreamFn } from "../utils/proxy-utils.js";
16
+ import type { UserMessageWithAttachments } from "./Messages.js";
17
+ import type { StreamingMessageContainer } from "./StreamingMessageContainer.js";
18
+
19
+ @customElement("agent-interface")
20
+ export class AgentInterface extends LitElement {
21
+ // Optional external session: when provided, this component becomes a view over the session
22
+ @property({ attribute: false }) session?: Agent;
23
+ @property({ type: Boolean }) enableAttachments = true;
24
+ @property({ type: Boolean }) enableModelSelector = true;
25
+ @property({ type: Boolean }) enableThinkingSelector = true;
26
+ @property({ type: Boolean }) showThemeToggle = false;
27
+ // Optional custom API key prompt handler - if not provided, uses default dialog
28
+ @property({ attribute: false }) onApiKeyRequired?: (provider: string) => Promise<boolean>;
29
+ // Optional callback called before sending a message
30
+ @property({ attribute: false }) onBeforeSend?: () => void | Promise<void>;
31
+ // Optional callback called before executing a tool call - return false to prevent execution
32
+ @property({ attribute: false }) onBeforeToolCall?: (toolName: string, args: any) => boolean | Promise<boolean>;
33
+ // Optional callback called when cost display is clicked
34
+ @property({ attribute: false }) onCostClick?: () => void;
35
+
36
+ // References
37
+ @query("message-editor") private _messageEditor!: MessageEditor;
38
+ @query("streaming-message-container") private _streamingContainer!: StreamingMessageContainer;
39
+
40
+ private _autoScroll = true;
41
+ private _lastScrollTop = 0;
42
+ private _lastClientHeight = 0;
43
+ private _scrollContainer?: HTMLElement;
44
+ private _resizeObserver?: ResizeObserver;
45
+ private _unsubscribeSession?: () => void;
46
+
47
+ public setInput(text: string, attachments?: Attachment[]) {
48
+ const update = () => {
49
+ if (!this._messageEditor) requestAnimationFrame(update);
50
+ else {
51
+ this._messageEditor.value = text;
52
+ this._messageEditor.attachments = attachments || [];
53
+ }
54
+ };
55
+ update();
56
+ }
57
+
58
+ public setAutoScroll(enabled: boolean) {
59
+ this._autoScroll = enabled;
60
+ }
61
+
62
+ protected override createRenderRoot(): HTMLElement | DocumentFragment {
63
+ return this;
64
+ }
65
+
66
+ override willUpdate(changedProperties: Map<string, any>) {
67
+ super.willUpdate(changedProperties);
68
+
69
+ // Re-subscribe when session property changes
70
+ if (changedProperties.has("session")) {
71
+ this.setupSessionSubscription();
72
+ }
73
+ }
74
+
75
+ override async connectedCallback() {
76
+ super.connectedCallback();
77
+
78
+ this.style.display = "flex";
79
+ this.style.flexDirection = "column";
80
+ this.style.height = "100%";
81
+ this.style.minHeight = "0";
82
+
83
+ // Wait for first render to get scroll container
84
+ await this.updateComplete;
85
+ this._scrollContainer = this.querySelector(".overflow-y-auto") as HTMLElement;
86
+
87
+ if (this._scrollContainer) {
88
+ // Set up ResizeObserver to detect content changes
89
+ this._resizeObserver = new ResizeObserver(() => {
90
+ if (this._autoScroll && this._scrollContainer) {
91
+ this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight;
92
+ }
93
+ });
94
+
95
+ // Observe the content container inside the scroll container
96
+ const contentContainer = this._scrollContainer.querySelector(".max-w-3xl");
97
+ if (contentContainer) {
98
+ this._resizeObserver.observe(contentContainer);
99
+ }
100
+
101
+ // Set up scroll listener with better detection
102
+ this._scrollContainer.addEventListener("scroll", this._handleScroll);
103
+ }
104
+
105
+ // Subscribe to external session if provided
106
+ this.setupSessionSubscription();
107
+ }
108
+
109
+ override disconnectedCallback() {
110
+ super.disconnectedCallback();
111
+
112
+ // Clean up observers and listeners
113
+ if (this._resizeObserver) {
114
+ this._resizeObserver.disconnect();
115
+ this._resizeObserver = undefined;
116
+ }
117
+
118
+ if (this._scrollContainer) {
119
+ this._scrollContainer.removeEventListener("scroll", this._handleScroll);
120
+ }
121
+
122
+ if (this._unsubscribeSession) {
123
+ this._unsubscribeSession();
124
+ this._unsubscribeSession = undefined;
125
+ }
126
+ }
127
+
128
+ private setupSessionSubscription() {
129
+ if (this._unsubscribeSession) {
130
+ this._unsubscribeSession();
131
+ this._unsubscribeSession = undefined;
132
+ }
133
+ if (!this.session) return;
134
+
135
+ // Set default streamFn with proxy support if not already set
136
+ if (this.session.streamFn === streamSimple) {
137
+ this.session.streamFn = createStreamFn(async () => {
138
+ const enabled = await getAppStorage().settings.get<boolean>("proxy.enabled");
139
+ return enabled ? (await getAppStorage().settings.get<string>("proxy.url")) || undefined : undefined;
140
+ });
141
+ }
142
+
143
+ // Set default getApiKey if not already set
144
+ if (!this.session.getApiKey) {
145
+ this.session.getApiKey = async (provider: string) => {
146
+ const key = await getAppStorage().providerKeys.get(provider);
147
+ return key ?? undefined;
148
+ };
149
+ }
150
+
151
+ this._unsubscribeSession = this.session.subscribe(async (ev: AgentEvent) => {
152
+ switch (ev.type) {
153
+ case "message_start":
154
+ case "message_end":
155
+ case "turn_start":
156
+ case "turn_end":
157
+ case "agent_start":
158
+ this.requestUpdate();
159
+ break;
160
+ case "agent_end":
161
+ // Clear streaming container when agent finishes
162
+ if (this._streamingContainer) {
163
+ this._streamingContainer.isStreaming = false;
164
+ this._streamingContainer.setMessage(null, true);
165
+ }
166
+ this.requestUpdate();
167
+ break;
168
+ case "message_update":
169
+ if (this._streamingContainer) {
170
+ const isStreaming = this.session?.state.isStreaming || false;
171
+ this._streamingContainer.isStreaming = isStreaming;
172
+ this._streamingContainer.setMessage(ev.message, !isStreaming);
173
+ }
174
+ this.requestUpdate();
175
+ break;
176
+ }
177
+ });
178
+ }
179
+
180
+ private _handleScroll = (_ev: any) => {
181
+ if (!this._scrollContainer) return;
182
+
183
+ const currentScrollTop = this._scrollContainer.scrollTop;
184
+ const scrollHeight = this._scrollContainer.scrollHeight;
185
+ const clientHeight = this._scrollContainer.clientHeight;
186
+ const distanceFromBottom = scrollHeight - currentScrollTop - clientHeight;
187
+
188
+ // Ignore relayout due to message editor getting pushed up by stats
189
+ if (clientHeight < this._lastClientHeight) {
190
+ this._lastClientHeight = clientHeight;
191
+ return;
192
+ }
193
+
194
+ // Only disable auto-scroll if user scrolled UP or is far from bottom
195
+ if (currentScrollTop !== 0 && currentScrollTop < this._lastScrollTop && distanceFromBottom > 50) {
196
+ this._autoScroll = false;
197
+ } else if (distanceFromBottom < 10) {
198
+ // Re-enable if very close to bottom
199
+ this._autoScroll = true;
200
+ }
201
+
202
+ this._lastScrollTop = currentScrollTop;
203
+ this._lastClientHeight = clientHeight;
204
+ };
205
+
206
+ public async sendMessage(input: string, attachments?: Attachment[]) {
207
+ if ((!input.trim() && attachments?.length === 0) || this.session?.state.isStreaming) return;
208
+ const session = this.session;
209
+ if (!session) throw new Error("No session set on AgentInterface");
210
+ if (!session.state.model) throw new Error("No model set on AgentInterface");
211
+
212
+ // Check if API key exists for the provider (only needed in direct mode)
213
+ const provider = session.state.model.provider;
214
+ const apiKey = await getAppStorage().providerKeys.get(provider);
215
+
216
+ // If no API key, prompt for it
217
+ if (!apiKey) {
218
+ if (!this.onApiKeyRequired) {
219
+ console.error("No API key configured and no onApiKeyRequired handler set");
220
+ return;
221
+ }
222
+
223
+ const success = await this.onApiKeyRequired(provider);
224
+
225
+ // If still no API key, abort the send
226
+ if (!success) {
227
+ return;
228
+ }
229
+ }
230
+
231
+ // Call onBeforeSend hook before sending
232
+ if (this.onBeforeSend) {
233
+ await this.onBeforeSend();
234
+ }
235
+
236
+ // Only clear editor after we know we can send
237
+ this._messageEditor.value = "";
238
+ this._messageEditor.attachments = [];
239
+ this._autoScroll = true; // Enable auto-scroll when sending a message
240
+
241
+ // Compose message with attachments if any
242
+ if (attachments && attachments.length > 0) {
243
+ const message: UserMessageWithAttachments = {
244
+ role: "user-with-attachments",
245
+ content: input,
246
+ attachments,
247
+ timestamp: Date.now(),
248
+ };
249
+ await this.session?.prompt(message);
250
+ } else {
251
+ await this.session?.prompt(input);
252
+ }
253
+ }
254
+
255
+ private renderMessages() {
256
+ if (!this.session)
257
+ return html`<div class="p-4 text-center text-muted-foreground">${i18n("No session available")}</div>`;
258
+ const state = this.session.state;
259
+ // Build a map of tool results to allow inline rendering in assistant messages
260
+ const toolResultsById = new Map<string, ToolResultMessage<any>>();
261
+ for (const message of state.messages) {
262
+ if (message.role === "toolResult") {
263
+ toolResultsById.set(message.toolCallId, message);
264
+ }
265
+ }
266
+ return html`
267
+ <div class="flex flex-col gap-3">
268
+ <!-- Stable messages list - won't re-render during streaming -->
269
+ <message-list
270
+ .messages=${this.session.state.messages}
271
+ .tools=${state.tools}
272
+ .pendingToolCalls=${this.session ? this.session.state.pendingToolCalls : new Set<string>()}
273
+ .isStreaming=${state.isStreaming}
274
+ .onCostClick=${this.onCostClick}
275
+ ></message-list>
276
+
277
+ <!-- Streaming message container - manages its own updates -->
278
+ <streaming-message-container
279
+ class="${state.isStreaming ? "" : "hidden"}"
280
+ .tools=${state.tools}
281
+ .isStreaming=${state.isStreaming}
282
+ .pendingToolCalls=${state.pendingToolCalls}
283
+ .toolResultsById=${toolResultsById}
284
+ .onCostClick=${this.onCostClick}
285
+ ></streaming-message-container>
286
+ </div>
287
+ `;
288
+ }
289
+
290
+ private renderStats() {
291
+ if (!this.session) return html`<div class="text-xs h-5"></div>`;
292
+
293
+ const state = this.session.state;
294
+ const totals = state.messages
295
+ .filter((m) => m.role === "assistant")
296
+ .reduce(
297
+ (acc, msg: any) => {
298
+ const usage = msg.usage;
299
+ if (usage) {
300
+ acc.input += usage.input;
301
+ acc.output += usage.output;
302
+ acc.cacheRead += usage.cacheRead;
303
+ acc.cacheWrite += usage.cacheWrite;
304
+ acc.cost.total += usage.cost.total;
305
+ }
306
+ return acc;
307
+ },
308
+ {
309
+ input: 0,
310
+ output: 0,
311
+ cacheRead: 0,
312
+ cacheWrite: 0,
313
+ totalTokens: 0,
314
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
315
+ } satisfies Usage,
316
+ );
317
+
318
+ const hasTotals = totals.input || totals.output || totals.cacheRead || totals.cacheWrite;
319
+ const totalsText = hasTotals ? formatUsage(totals) : "";
320
+
321
+ return html`
322
+ <div class="text-xs text-muted-foreground flex justify-between items-center h-5">
323
+ <div class="flex items-center gap-1">
324
+ ${this.showThemeToggle ? html`<theme-toggle></theme-toggle>` : html``}
325
+ </div>
326
+ <div class="flex ml-auto items-center gap-3">
327
+ ${
328
+ totalsText
329
+ ? this.onCostClick
330
+ ? html`<span class="cursor-pointer hover:text-foreground transition-colors" @click=${this.onCostClick}>${totalsText}</span>`
331
+ : html`<span>${totalsText}</span>`
332
+ : ""
333
+ }
334
+ </div>
335
+ </div>
336
+ `;
337
+ }
338
+
339
+ override render() {
340
+ if (!this.session)
341
+ return html`<div class="p-4 text-center text-muted-foreground">${i18n("No session set")}</div>`;
342
+
343
+ const session = this.session;
344
+ const state = this.session.state;
345
+ return html`
346
+ <div class="flex flex-col h-full bg-background text-foreground">
347
+ <!-- Messages Area -->
348
+ <div class="flex-1 overflow-y-auto">
349
+ <div class="max-w-3xl mx-auto p-4 pb-0">${this.renderMessages()}</div>
350
+ </div>
351
+
352
+ <!-- Input Area -->
353
+ <div class="shrink-0">
354
+ <div class="max-w-3xl mx-auto px-2">
355
+ <message-editor
356
+ .isStreaming=${state.isStreaming}
357
+ .currentModel=${state.model}
358
+ .thinkingLevel=${state.thinkingLevel}
359
+ .showAttachmentButton=${this.enableAttachments}
360
+ .showModelSelector=${this.enableModelSelector}
361
+ .showThinkingSelector=${this.enableThinkingSelector}
362
+ .onSend=${(input: string, attachments: Attachment[]) => {
363
+ this.sendMessage(input, attachments);
364
+ }}
365
+ .onAbort=${() => session.abort()}
366
+ .onModelSelect=${() => {
367
+ ModelSelector.open(state.model, (model) => session.setModel(model));
368
+ }}
369
+ .onThinkingChange=${
370
+ this.enableThinkingSelector
371
+ ? (level: "off" | "minimal" | "low" | "medium" | "high") => {
372
+ session.setThinkingLevel(level);
373
+ }
374
+ : undefined
375
+ }
376
+ ></message-editor>
377
+ ${this.renderStats()}
378
+ </div>
379
+ </div>
380
+ </div>
381
+ `;
382
+ }
383
+ }
384
+
385
+ // Register custom element with guard
386
+ if (!customElements.get("agent-interface")) {
387
+ customElements.define("agent-interface", AgentInterface);
388
+ }