cognova 0.2.12 → 0.2.14

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 (398) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/-mGU8_Hv.js +1 -0
  3. package/.output/public/_nuxt/0GqCtQ9S.js +1 -0
  4. package/.output/public/_nuxt/{CLfF6dSn.js → 1wCoXZ14.js} +1 -1
  5. package/.output/public/_nuxt/2J1KxdHv.js +1 -0
  6. package/.output/public/_nuxt/6VDUZECX.js +1 -0
  7. package/.output/public/_nuxt/{DEd2xVbS.js → 8WMJQbWG.js} +1 -1
  8. package/.output/public/_nuxt/9Rw88G6h.js +1 -0
  9. package/.output/public/_nuxt/{PP_4ebzl.js → B-yW4SD3.js} +1 -1
  10. package/.output/public/_nuxt/B20jNAhm.js +1 -0
  11. package/.output/public/_nuxt/B3OpTH_e.js +1 -0
  12. package/.output/public/_nuxt/B9Y3_B5G.js +1 -0
  13. package/.output/public/_nuxt/B9_vmatf.js +1 -0
  14. package/.output/public/_nuxt/B9o6sFST.js +1 -0
  15. package/.output/public/_nuxt/{D8M722pn.js → BAY3_Hrq.js} +1 -1
  16. package/.output/public/_nuxt/{0yk-pS3R.js → BD5rPkc2.js} +3 -3
  17. package/.output/public/_nuxt/BGYoW3kO.js +1 -0
  18. package/.output/public/_nuxt/BHUy5EAj.js +1 -0
  19. package/.output/public/_nuxt/BLdMRpJD.js +1 -0
  20. package/.output/public/_nuxt/{2dNDtTiv.js → BOCn_SGf.js} +12 -12
  21. package/.output/public/_nuxt/BQi8LIrn.js +1 -0
  22. package/.output/public/_nuxt/BS1t375R.js +1 -0
  23. package/.output/public/_nuxt/BTGeP1bA.js +1 -0
  24. package/.output/public/_nuxt/{4K03TkGA.js → BVYYP4eQ.js} +5 -5
  25. package/.output/public/_nuxt/BWQ7zE01.js +1 -0
  26. package/.output/public/_nuxt/BWqJInD3.js +1 -0
  27. package/.output/public/_nuxt/BXdQ9ASJ.js +1 -0
  28. package/.output/public/_nuxt/{BHtY0l0l.js → BXxZokD_.js} +2 -2
  29. package/.output/public/_nuxt/BZ7DAtPi.js +1 -0
  30. package/.output/public/_nuxt/BZm9w7en.js +1 -0
  31. package/.output/public/_nuxt/B_hNqC64.js +1 -0
  32. package/.output/public/_nuxt/Be47YE2L.js +1 -0
  33. package/.output/public/_nuxt/BgIS9_ti.js +39 -0
  34. package/.output/public/_nuxt/BgavRUl_.js +1 -0
  35. package/.output/public/_nuxt/Bh0yGTzr.js +1 -0
  36. package/.output/public/_nuxt/BhhMUror.js +1 -0
  37. package/.output/public/_nuxt/Bj5e7QPz.js +1 -0
  38. package/.output/public/_nuxt/{7oCGSglN.js → Bnn59-FS.js} +1 -1
  39. package/.output/public/_nuxt/BoCU3mWu.js +1 -0
  40. package/.output/public/_nuxt/Br6epbzY.js +1 -0
  41. package/.output/public/_nuxt/Br81Sr2R.js +1 -0
  42. package/.output/public/_nuxt/BrkEOLHu.js +9 -0
  43. package/.output/public/_nuxt/BtrNCc2S.js +1 -0
  44. package/.output/public/_nuxt/BxsQlbgl.js +1 -0
  45. package/.output/public/_nuxt/BzrhNhdT.js +1 -0
  46. package/.output/public/_nuxt/{CVqlefTY.js → C-78dDNd.js} +1 -1
  47. package/.output/public/_nuxt/C1oaoi1p.js +1 -0
  48. package/.output/public/_nuxt/C2-_hcwB.js +1 -0
  49. package/.output/public/_nuxt/{rfGRTJJW.js → C2Gh3qqr.js} +1 -1
  50. package/.output/public/_nuxt/C2OHaW6l.js +1 -0
  51. package/.output/public/_nuxt/C3szZmvA.js +1 -0
  52. package/.output/public/_nuxt/C3xzleKP.js +1 -0
  53. package/.output/public/_nuxt/C4sG6rdW.js +1 -0
  54. package/.output/public/_nuxt/C5Cu9Fx6.js +1 -0
  55. package/.output/public/_nuxt/{JX1oqJI9.js → C5gfF7J1.js} +1 -1
  56. package/.output/public/_nuxt/C6Eoiclo.js +1 -0
  57. package/.output/public/_nuxt/C7hFztZL.js +1 -0
  58. package/.output/public/_nuxt/CAVUAxNU.js +2 -0
  59. package/.output/public/_nuxt/{t8aDAkZ5.js → CCeM5lW2.js} +1 -1
  60. package/.output/public/_nuxt/CDcQ31Eh.js +1 -0
  61. package/.output/public/_nuxt/CHbHVtCy.js +1 -0
  62. package/.output/public/_nuxt/CHnVFnBW.js +1 -0
  63. package/.output/public/_nuxt/CLNcmNQS.js +1 -0
  64. package/.output/public/_nuxt/{EgKnQnf-.js → CPwCTHun.js} +1 -1
  65. package/.output/public/_nuxt/{ixlNW2So.js → CS8SdmH2.js} +1 -1
  66. package/.output/public/_nuxt/CTGm_mc8.js +1 -0
  67. package/.output/public/_nuxt/{CPutXj8l.js → CTQFGHjS.js} +1 -1
  68. package/.output/public/_nuxt/{BS2ZNXI1.js → CVVtVzXC.js} +1 -1
  69. package/.output/public/_nuxt/CZNOaqeX.js +1 -0
  70. package/.output/public/_nuxt/C_A_NkeC.js +2 -0
  71. package/.output/public/_nuxt/{gTrVszwd.js → C_doutX-.js} +1 -1
  72. package/.output/public/_nuxt/{CY-QVcA5.js → CbDLhrNA.js} +2 -2
  73. package/.output/public/_nuxt/CePKCqSb.js +1 -0
  74. package/.output/public/_nuxt/Cet4T_yO.js +1 -0
  75. package/.output/public/_nuxt/{fbyIeNkc.js → CjJPkhbc.js} +1 -1
  76. package/.output/public/_nuxt/CkZT4LGq.js +1 -0
  77. package/.output/public/_nuxt/CkrW8sGL.js +1 -0
  78. package/.output/public/_nuxt/Co0UxVsx.js +1 -0
  79. package/.output/public/_nuxt/CqS0zXYQ.js +1 -0
  80. package/.output/public/_nuxt/CskGv_Km.js +1 -0
  81. package/.output/public/_nuxt/{Bk6JUtIo.js → CuvkTsqs.js} +1 -1
  82. package/.output/public/_nuxt/{Db4KMnt8.js → CxMuZEwX.js} +1 -1
  83. package/.output/public/_nuxt/{vIOxcXKR.js → CxSijT71.js} +8 -8
  84. package/.output/public/_nuxt/Cywo6tZI.js +1 -0
  85. package/.output/public/_nuxt/D1reIfpX.js +1 -0
  86. package/.output/public/_nuxt/D1uV2uir.js +1 -0
  87. package/.output/public/_nuxt/{CuxrHsu-.js → D2edadpd.js} +1 -1
  88. package/.output/public/_nuxt/D3cEeQh7.js +1 -0
  89. package/.output/public/_nuxt/D41A2mE2.js +1 -0
  90. package/.output/public/_nuxt/D49P2RhG.js +1 -0
  91. package/.output/public/_nuxt/D867g6Ds.js +1 -0
  92. package/.output/public/_nuxt/DEFWIjWl.js +1 -0
  93. package/.output/public/_nuxt/DFTEBTzZ.js +1 -0
  94. package/.output/public/_nuxt/{DwY7rCd_.js → DHRexj9X.js} +2 -2
  95. package/.output/public/_nuxt/DL4EpgqQ.js +1 -0
  96. package/.output/public/_nuxt/DLHnP6tw.js +1 -0
  97. package/.output/public/_nuxt/DM20WaLq.js +1 -0
  98. package/.output/public/_nuxt/{BYHCP8x7.js → DNAMjHbb.js} +1 -1
  99. package/.output/public/_nuxt/{C6qKcgOY.js → DP8t9VWs.js} +26 -26
  100. package/.output/public/_nuxt/DTR31rjp.js +1 -0
  101. package/.output/public/_nuxt/{CZoEPC_Q.js → DTlSLSo1.js} +3 -3
  102. package/.output/public/_nuxt/{DtjjnHnt.js → DVdnhfXT.js} +1 -1
  103. package/.output/public/_nuxt/DZikIgHK.js +1 -0
  104. package/.output/public/_nuxt/D_YGqvw_.js +1 -0
  105. package/.output/public/_nuxt/D_k1YwXm.js +1 -0
  106. package/.output/public/_nuxt/Daf2MbWG.js +7 -0
  107. package/.output/public/_nuxt/DcJ0GstJ.js +1 -0
  108. package/.output/public/_nuxt/DcYKh6QA.js +1 -0
  109. package/.output/public/_nuxt/DhN3_DYH.js +1 -0
  110. package/.output/public/_nuxt/DhSvBSt3.js +1 -0
  111. package/.output/public/_nuxt/DhlIWT8K.js +1 -0
  112. package/.output/public/_nuxt/DjsqyRqt.js +1 -0
  113. package/.output/public/_nuxt/DpSM97eH.js +1 -0
  114. package/.output/public/_nuxt/DqR9SdeU.js +1 -0
  115. package/.output/public/_nuxt/DqmQtg6Y.js +1 -0
  116. package/.output/public/_nuxt/Dsuag_iW.js +1 -0
  117. package/.output/public/_nuxt/Dtr_0IbE.js +1 -0
  118. package/.output/public/_nuxt/DwrmFEZN.js +1 -0
  119. package/.output/public/_nuxt/DxE8r3r4.js +1 -0
  120. package/.output/public/_nuxt/G_CsVlOR.js +1 -0
  121. package/.output/public/_nuxt/Gyw_2f6x.js +1 -0
  122. package/.output/public/_nuxt/HStC1ZyE.js +1 -0
  123. package/.output/public/_nuxt/Hh9mv0Ek.js +1 -0
  124. package/.output/public/_nuxt/LR0kCjbe.js +1 -0
  125. package/.output/public/_nuxt/MYeLvJGV.js +1 -0
  126. package/.output/public/_nuxt/NKs0EGrh.js +1 -0
  127. package/.output/public/_nuxt/PY5EGujn.js +1 -0
  128. package/.output/public/_nuxt/PamJNol9.js +1 -0
  129. package/.output/public/_nuxt/RlvwEbHv.js +1 -0
  130. package/.output/public/_nuxt/{BKXg-alD.js → TNpdJOlc.js} +1 -1
  131. package/.output/public/_nuxt/TSYrPbLR.js +1 -0
  132. package/.output/public/_nuxt/U-eBrbR4.js +1 -0
  133. package/.output/public/_nuxt/UuqXScSw.js +1 -0
  134. package/.output/public/_nuxt/V04-MrLN.js +1 -0
  135. package/.output/public/_nuxt/X0o5GcBb.js +1 -0
  136. package/.output/public/_nuxt/XQNzmQy9.js +1 -0
  137. package/.output/public/_nuxt/Y59XKprr.js +1 -0
  138. package/.output/public/_nuxt/YZQjZgsH.js +1 -0
  139. package/.output/public/_nuxt/_-TGPiLQ.js +1 -0
  140. package/.output/public/_nuxt/builds/latest.json +1 -1
  141. package/.output/public/_nuxt/builds/meta/d7188f50-b42c-4d2c-afab-066d318f0af8.json +1 -0
  142. package/.output/public/_nuxt/dashboard.ByJQ-Wzh.css +1 -0
  143. package/.output/public/_nuxt/entry.ZJxWCrU6.css +1 -0
  144. package/.output/public/_nuxt/{DhI5cA_n.js → hCeWb_38.js} +1 -1
  145. package/.output/public/_nuxt/{CQqCBrXj.js → hrPTRWjM.js} +1 -1
  146. package/.output/public/_nuxt/jC4pIK1p.js +1 -0
  147. package/.output/public/_nuxt/jTT1R-Vz.js +1 -0
  148. package/.output/public/_nuxt/{BLnYhy_t.js → kkAMoB43.js} +1 -1
  149. package/.output/public/_nuxt/{DzGy77Vr.js → mzM4BGtv.js} +1 -1
  150. package/.output/public/_nuxt/o-s8JBm2.js +1 -0
  151. package/.output/public/_nuxt/pwJTkIQ4.js +1 -0
  152. package/.output/public/_nuxt/{BDyn4ApB.js → rIgg2Arf.js} +3 -3
  153. package/.output/public/_nuxt/subdxgSQ.js +1 -0
  154. package/.output/public/_nuxt/t8B72leH.js +1 -0
  155. package/.output/public/_nuxt/vCFAHwdt.js +1 -0
  156. package/.output/public/_nuxt/z-MP0bB7.js +1 -0
  157. package/.output/server/chunks/build/CodeIcon-CWD5HcV7.mjs +1 -1
  158. package/.output/server/chunks/build/DropdownMenu-BBrV9nXz.mjs +1 -1
  159. package/.output/server/chunks/build/{EditorToolbar-DIfb5arC.mjs → EditorToolbar-B9OwSI5e.mjs} +3 -3
  160. package/.output/server/chunks/build/{EditorToolbar-DIfb5arC.mjs.map → EditorToolbar-B9OwSI5e.mjs.map} +1 -1
  161. package/.output/server/chunks/build/Img-CWLmvN1t.mjs +1 -1
  162. package/.output/server/chunks/build/MDC-Dx0YPDhe.mjs +1 -1
  163. package/.output/server/chunks/build/Select-BB1oLrCD.mjs +1 -1
  164. package/.output/server/chunks/build/SelectMenu-DPssg6zD.mjs +1 -1
  165. package/.output/server/chunks/build/Table-DCwTlhCj.mjs +1 -1
  166. package/.output/server/chunks/build/Tooltip-TRyl6dje.mjs +1 -1
  167. package/.output/server/chunks/build/Tree-DUhXKd8y.mjs +1 -1
  168. package/.output/server/chunks/build/{_id_-DN00UDdO.mjs → _id_-CwBeklIM.mjs} +296 -430
  169. package/.output/server/chunks/build/_id_-CwBeklIM.mjs.map +1 -0
  170. package/.output/server/chunks/build/{_name_-BnS_KEfX.mjs → _name_-D7MtW4Vu.mjs} +2 -2
  171. package/.output/server/chunks/build/{_name_-BnS_KEfX.mjs.map → _name_-D7MtW4Vu.mjs.map} +1 -1
  172. package/.output/server/chunks/build/{_uuid_-DfJaumTE.mjs → _uuid_-lvG92ein.mjs} +4 -4
  173. package/.output/server/chunks/build/{_uuid_-DfJaumTE.mjs.map → _uuid_-lvG92ein.mjs.map} +1 -1
  174. package/.output/server/chunks/build/account-LDZ4TL6x.mjs +513 -0
  175. package/.output/server/chunks/build/account-LDZ4TL6x.mjs.map +1 -0
  176. package/.output/server/chunks/build/app-CGhRZPNT.mjs +252 -0
  177. package/.output/server/chunks/build/app-CGhRZPNT.mjs.map +1 -0
  178. package/.output/server/chunks/build/{chat-CR3JIVEq.mjs → chat-BphuYhvA.mjs} +532 -39
  179. package/.output/server/chunks/build/chat-BphuYhvA.mjs.map +1 -0
  180. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  181. package/.output/server/chunks/build/cookie-C_iulBi6.mjs +1 -1
  182. package/.output/server/chunks/build/{dashboard-CiVTAZuF.mjs → dashboard-nDujDyOg.mjs} +55 -16
  183. package/.output/server/chunks/build/{dashboard-CiVTAZuF.mjs.map → dashboard-nDujDyOg.mjs.map} +1 -1
  184. package/.output/server/chunks/build/{docs-ChGwOPg5.mjs → docs-Bwucwp0r.mjs} +664 -592
  185. package/.output/server/chunks/build/docs-Bwucwp0r.mjs.map +1 -0
  186. package/.output/server/chunks/build/fetch-BB7Qzkwe.mjs +1 -1
  187. package/.output/server/chunks/build/{hooks-D328DcO6.mjs → hooks-D6PmTth8.mjs} +19 -29
  188. package/.output/server/chunks/build/hooks-D6PmTth8.mjs.map +1 -0
  189. package/.output/server/chunks/build/{index-CAnGTRlu.mjs → index-CJkn2jiM.mjs} +2 -2
  190. package/.output/server/chunks/build/{index-CAnGTRlu.mjs.map → index-CJkn2jiM.mjs.map} +1 -1
  191. package/.output/server/chunks/build/index-CxDxc9fm.mjs +1 -1
  192. package/.output/server/chunks/build/index-Cz9Y6NwN.mjs +723 -0
  193. package/.output/server/chunks/build/index-Cz9Y6NwN.mjs.map +1 -0
  194. package/.output/server/chunks/build/index-hwhuKhuZ.mjs +76 -0
  195. package/.output/server/chunks/build/index-hwhuKhuZ.mjs.map +1 -0
  196. package/.output/server/chunks/build/integrations-D5JWURiJ.mjs +1499 -0
  197. package/.output/server/chunks/build/integrations-D5JWURiJ.mjs.map +1 -0
  198. package/.output/server/chunks/build/{library-Aeg7_Lsb.mjs → library-DxRdgP6X.mjs} +2 -2
  199. package/.output/server/chunks/build/{library-Aeg7_Lsb.mjs.map → library-DxRdgP6X.mjs.map} +1 -1
  200. package/.output/server/chunks/build/{login-DnnElTl2.mjs → login-BmER_VoU.mjs} +2 -2
  201. package/.output/server/chunks/build/{login-DnnElTl2.mjs.map → login-BmER_VoU.mjs.map} +1 -1
  202. package/.output/server/chunks/build/manage-secrets-BWzqc_-f.mjs +669 -0
  203. package/.output/server/chunks/build/manage-secrets-BWzqc_-f.mjs.map +1 -0
  204. package/.output/server/chunks/build/server.mjs +65 -34
  205. package/.output/server/chunks/build/server.mjs.map +1 -1
  206. package/.output/server/chunks/build/settings-neokAVE5.mjs +154 -0
  207. package/.output/server/chunks/build/settings-neokAVE5.mjs.map +1 -0
  208. package/.output/server/chunks/build/styles.mjs +2 -2
  209. package/.output/server/chunks/build/{tasks-DnAFqbtt.mjs → tasks-CnpIQNpS.mjs} +27 -37
  210. package/.output/server/chunks/build/tasks-CnpIQNpS.mjs.map +1 -0
  211. package/.output/server/chunks/build/{useAgents-DHEXiFSc.mjs → useAgents-nmbkB9-_.mjs} +5 -26
  212. package/.output/server/chunks/build/useAgents-nmbkB9-_.mjs.map +1 -0
  213. package/.output/server/chunks/build/{view-n2sYa4Zh.mjs → view-mb0ISqfM.mjs} +3 -3
  214. package/.output/server/chunks/build/{view-n2sYa4Zh.mjs.map → view-mb0ISqfM.mjs.map} +1 -1
  215. package/.output/server/chunks/nitro/nitro.mjs +1021 -867
  216. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  217. package/.output/server/chunks/routes/_ws/chat.mjs +39 -5
  218. package/.output/server/chunks/routes/_ws/chat.mjs.map +1 -1
  219. package/.output/server/chunks/routes/api/index.get.mjs +17 -2
  220. package/.output/server/chunks/routes/api/index.get.mjs.map +1 -1
  221. package/.output/server/chunks/routes/api/index.get3.mjs +21 -3
  222. package/.output/server/chunks/routes/api/index.get3.mjs.map +1 -1
  223. package/.output/server/chunks/routes/api/index.get4.mjs +10 -9
  224. package/.output/server/chunks/routes/api/index.get4.mjs.map +1 -1
  225. package/.output/server/chunks/routes/api/index.get9.mjs +10 -8
  226. package/.output/server/chunks/routes/api/index.get9.mjs.map +1 -1
  227. package/.output/server/chunks/routes/api/memory/search.get.mjs +10 -5
  228. package/.output/server/chunks/routes/api/memory/search.get.mjs.map +1 -1
  229. package/.output/server/chunks/routes/renderer.mjs +1 -1
  230. package/.output/server/index.mjs +1 -1
  231. package/.output/server/package.json +1 -1
  232. package/Claude/skills/_lib/api.py +3 -2
  233. package/app/components/AssistantPanel.client.vue +16 -314
  234. package/app/components/agents/AgentCard.vue +111 -0
  235. package/app/components/agents/AgentInfoCard.vue +54 -0
  236. package/app/components/agents/AgentRunHistory.vue +103 -0
  237. package/app/components/agents/AgentRunModal.vue +1 -24
  238. package/app/components/agents/AgentStatsCards.vue +1 -26
  239. package/app/components/assistant/AssistantChat.vue +156 -0
  240. package/app/components/assistant/AssistantTerminal.client.vue +178 -0
  241. package/app/components/chat/ChatInput.vue +133 -5
  242. package/app/components/chat/ConversationList.vue +2 -12
  243. package/app/components/chat/MessageBubble.vue +188 -6
  244. package/app/components/files/FileTree.vue +23 -202
  245. package/app/components/files/FileTreeModals.vue +226 -0
  246. package/app/components/hooks/HookStatsCards.vue +1 -6
  247. package/app/components/hooks/ToolBreakdownTable.vue +1 -6
  248. package/app/components/search/DashboardSearch.vue +38 -1
  249. package/app/components/tasks/TaskCard.vue +2 -7
  250. package/app/components/tasks/TaskDetail.vue +4 -9
  251. package/app/composables/useAttachments.ts +196 -0
  252. package/app/composables/useChat.ts +14 -2
  253. package/app/composables/useSearch.ts +21 -11
  254. package/app/layouts/dashboard.vue +1 -1
  255. package/app/pages/agents/[id].vue +6 -197
  256. package/app/pages/agents/index.vue +9 -142
  257. package/app/pages/chat.vue +4 -2
  258. package/app/pages/settings/account.vue +243 -0
  259. package/app/pages/settings/app.vue +230 -0
  260. package/app/pages/settings/index.vue +12 -0
  261. package/app/pages/settings/integrations.vue +794 -0
  262. package/app/pages/settings/manage-secrets.vue +295 -0
  263. package/app/pages/settings.vue +20 -1581
  264. package/dist/cli/index.js +26 -2
  265. package/package.json +1 -1
  266. package/server/api/agents/index.get.ts +20 -1
  267. package/server/api/conversations/index.get.ts +23 -5
  268. package/server/api/documents/index.get.ts +11 -9
  269. package/server/api/memory/search.get.ts +11 -6
  270. package/server/api/tasks/index.get.ts +11 -9
  271. package/server/bridge/adapters/imessage.ts +9 -2
  272. package/server/drizzle/migrations/0016_full_text_search.sql +127 -0
  273. package/server/drizzle/migrations/meta/_journal.json +7 -0
  274. package/server/routes/_ws/chat.ts +61 -7
  275. package/server/utils/chat-session-manager.ts +20 -2
  276. package/shared/types/index.ts +34 -2
  277. package/shared/utils/formatting.ts +105 -0
  278. package/.output/public/_nuxt/0NJ3PaRM.js +0 -1
  279. package/.output/public/_nuxt/10_wwHSz.js +0 -1
  280. package/.output/public/_nuxt/1zUTf4AP.js +0 -1
  281. package/.output/public/_nuxt/2nOqGUPr.js +0 -1
  282. package/.output/public/_nuxt/3jQMk_5H.js +0 -1
  283. package/.output/public/_nuxt/6boMs_nF.js +0 -1
  284. package/.output/public/_nuxt/8q5NepGW.js +0 -9
  285. package/.output/public/_nuxt/B93pdGAW.js +0 -1
  286. package/.output/public/_nuxt/BA_pRWwX.js +0 -1
  287. package/.output/public/_nuxt/BGgwYWjH.js +0 -1
  288. package/.output/public/_nuxt/BH0QDWEm.js +0 -1
  289. package/.output/public/_nuxt/BIXrSYwR.js +0 -1
  290. package/.output/public/_nuxt/BK8S2ony.js +0 -1
  291. package/.output/public/_nuxt/BR8bKz8h.js +0 -1
  292. package/.output/public/_nuxt/BUghTae1.js +0 -1
  293. package/.output/public/_nuxt/BXuWCWsJ.js +0 -1
  294. package/.output/public/_nuxt/BZrSPX0S.js +0 -1
  295. package/.output/public/_nuxt/B_wilgcd.js +0 -1
  296. package/.output/public/_nuxt/BaZwjF8o.js +0 -1
  297. package/.output/public/_nuxt/Bciqk4dX.js +0 -1
  298. package/.output/public/_nuxt/Beom-INj.js +0 -1
  299. package/.output/public/_nuxt/BfcLZ_fC.js +0 -1
  300. package/.output/public/_nuxt/BkfI94R8.js +0 -1
  301. package/.output/public/_nuxt/BmOtR1wX.js +0 -1
  302. package/.output/public/_nuxt/BmVLxnDU.js +0 -1
  303. package/.output/public/_nuxt/BtHQ1T0f.js +0 -1
  304. package/.output/public/_nuxt/ByYh0uRg.js +0 -1
  305. package/.output/public/_nuxt/C27WoGNZ.js +0 -1
  306. package/.output/public/_nuxt/C2FxZbO_.js +0 -1
  307. package/.output/public/_nuxt/C2vq6Te8.js +0 -1
  308. package/.output/public/_nuxt/C39dQ88F.js +0 -1
  309. package/.output/public/_nuxt/C3NST0BU.js +0 -1
  310. package/.output/public/_nuxt/C4mwL7SE.js +0 -7
  311. package/.output/public/_nuxt/C5R5QaCA.js +0 -1
  312. package/.output/public/_nuxt/C71_a1IX.js +0 -1
  313. package/.output/public/_nuxt/C9WIgRRL.js +0 -1
  314. package/.output/public/_nuxt/CBTkrk2M.js +0 -1
  315. package/.output/public/_nuxt/CEnSeCqn.js +0 -1
  316. package/.output/public/_nuxt/CKxSHyww.js +0 -1
  317. package/.output/public/_nuxt/CSKJ-Ahh.js +0 -1
  318. package/.output/public/_nuxt/CTCcEJU3.js +0 -1
  319. package/.output/public/_nuxt/CVFwOzbl.js +0 -1
  320. package/.output/public/_nuxt/CVdCqaby.js +0 -1
  321. package/.output/public/_nuxt/CWyMCJQH.js +0 -1
  322. package/.output/public/_nuxt/CY-cjAwJ.js +0 -1
  323. package/.output/public/_nuxt/CYPO5o_C.js +0 -1
  324. package/.output/public/_nuxt/CZRnNmU8.js +0 -1
  325. package/.output/public/_nuxt/CZVzFlpI.js +0 -1
  326. package/.output/public/_nuxt/Cc-2ziaZ.js +0 -1
  327. package/.output/public/_nuxt/ChcO9s3o.js +0 -1
  328. package/.output/public/_nuxt/Cl-LOSDV.js +0 -1
  329. package/.output/public/_nuxt/CobqYwkp.js +0 -1
  330. package/.output/public/_nuxt/CqNwHCpo.js +0 -1
  331. package/.output/public/_nuxt/CwPdIZ9J.js +0 -1
  332. package/.output/public/_nuxt/D0ifH682.js +0 -1
  333. package/.output/public/_nuxt/D3AZaldL.js +0 -1
  334. package/.output/public/_nuxt/D4UJwDQJ.js +0 -1
  335. package/.output/public/_nuxt/D5jZq8b3.js +0 -1
  336. package/.output/public/_nuxt/D5q8SnqE.js +0 -1
  337. package/.output/public/_nuxt/D8lwrAYS.js +0 -1
  338. package/.output/public/_nuxt/D9nmzBAw.js +0 -1
  339. package/.output/public/_nuxt/DC4idAGt.js +0 -1
  340. package/.output/public/_nuxt/DH4YkDAi.js +0 -1
  341. package/.output/public/_nuxt/DJTCT0bl.js +0 -1
  342. package/.output/public/_nuxt/DNP5E1bC.js +0 -39
  343. package/.output/public/_nuxt/DO9SFIh1.js +0 -1
  344. package/.output/public/_nuxt/DPklr_tJ.js +0 -2
  345. package/.output/public/_nuxt/DQM4eBdt.js +0 -1
  346. package/.output/public/_nuxt/DQh6I9z9.js +0 -1
  347. package/.output/public/_nuxt/DS2wStH1.js +0 -1
  348. package/.output/public/_nuxt/DTAStixR.js +0 -1
  349. package/.output/public/_nuxt/DTGenhcA.js +0 -1
  350. package/.output/public/_nuxt/DUAAXJ6Q.js +0 -1
  351. package/.output/public/_nuxt/DUVzIl3o.js +0 -1
  352. package/.output/public/_nuxt/DXdwpJ-I.js +0 -16
  353. package/.output/public/_nuxt/DY3uK7wM.js +0 -1
  354. package/.output/public/_nuxt/DYbZBZet.js +0 -1
  355. package/.output/public/_nuxt/D_4LPm1U.js +0 -1
  356. package/.output/public/_nuxt/DcJbYLTp.js +0 -1
  357. package/.output/public/_nuxt/DfF81NlA.js +0 -1
  358. package/.output/public/_nuxt/DhuOKJda.js +0 -1
  359. package/.output/public/_nuxt/Di-Nc75e.js +0 -1
  360. package/.output/public/_nuxt/Djs0Tlpa.js +0 -1
  361. package/.output/public/_nuxt/DmmdPt_5.js +0 -1
  362. package/.output/public/_nuxt/Dn5a-guE.js +0 -1
  363. package/.output/public/_nuxt/DuvzM-P1.js +0 -1
  364. package/.output/public/_nuxt/Dya5oK8u.js +0 -1
  365. package/.output/public/_nuxt/DzA58_Lm.js +0 -1
  366. package/.output/public/_nuxt/E3rXPwU8.js +0 -1
  367. package/.output/public/_nuxt/GtEM7xVU.js +0 -1
  368. package/.output/public/_nuxt/H6JbrRBU.js +0 -1
  369. package/.output/public/_nuxt/ILEvizzp.js +0 -1
  370. package/.output/public/_nuxt/JbHa4oXq.js +0 -1
  371. package/.output/public/_nuxt/Kw0zy3FG.js +0 -1
  372. package/.output/public/_nuxt/N5XtbYVD.js +0 -1
  373. package/.output/public/_nuxt/SrncdpaW.js +0 -1
  374. package/.output/public/_nuxt/U1MWjQMi.js +0 -1
  375. package/.output/public/_nuxt/Um1vPiAz.js +0 -1
  376. package/.output/public/_nuxt/XCjS70z4.js +0 -1
  377. package/.output/public/_nuxt/YX8avsvq.js +0 -2
  378. package/.output/public/_nuxt/_cy8R3nk.js +0 -1
  379. package/.output/public/_nuxt/apYB9dr5.js +0 -1
  380. package/.output/public/_nuxt/builds/meta/a892969f-e07d-4f44-8d7e-57a4b7f33d94.json +0 -1
  381. package/.output/public/_nuxt/dashboard.BxCOkJwt.css +0 -1
  382. package/.output/public/_nuxt/entry.NKPfH2kE.css +0 -1
  383. package/.output/public/_nuxt/g5MjDvm5.js +0 -1
  384. package/.output/public/_nuxt/nnQqD5pb.js +0 -1
  385. package/.output/public/_nuxt/vScW1Zgm.js +0 -1
  386. package/.output/public/_nuxt/wO6z2ugJ.js +0 -1
  387. package/.output/public/_nuxt/x6FRJ5ac.js +0 -1
  388. package/.output/public/_nuxt/zq-a1TeT.js +0 -1
  389. package/.output/server/chunks/build/_id_-DN00UDdO.mjs.map +0 -1
  390. package/.output/server/chunks/build/chat-CR3JIVEq.mjs.map +0 -1
  391. package/.output/server/chunks/build/docs-ChGwOPg5.mjs.map +0 -1
  392. package/.output/server/chunks/build/hooks-D328DcO6.mjs.map +0 -1
  393. package/.output/server/chunks/build/index-C9PuieXh.mjs +0 -820
  394. package/.output/server/chunks/build/index-C9PuieXh.mjs.map +0 -1
  395. package/.output/server/chunks/build/settings-B2KXoGcz.mjs +0 -3232
  396. package/.output/server/chunks/build/settings-B2KXoGcz.mjs.map +0 -1
  397. package/.output/server/chunks/build/tasks-DnAFqbtt.mjs.map +0 -1
  398. package/.output/server/chunks/build/useAgents-DHEXiFSc.mjs.map +0 -1
@@ -0,0 +1,226 @@
1
+ <script setup lang="ts">
2
+ import type { TreeItem } from '~/composables/useFileTree'
3
+
4
+ const showNewFile = defineModel<boolean>('showNewFile', { required: true })
5
+ const showNewFolder = defineModel<boolean>('showNewFolder', { required: true })
6
+ const showRename = defineModel<boolean>('showRename', { required: true })
7
+ const showDelete = defineModel<boolean>('showDelete', { required: true })
8
+
9
+ const props = defineProps<{
10
+ contextTarget: TreeItem | null
11
+ }>()
12
+
13
+ const emit = defineEmits<{
14
+ createFile: [name: string]
15
+ createFolder: [name: string]
16
+ rename: [name: string]
17
+ delete: []
18
+ }>()
19
+
20
+ const newItemName = ref('')
21
+
22
+ // Pre-fill name when rename opens
23
+ watch(showRename, (open) => {
24
+ if (open && props.contextTarget)
25
+ newItemName.value = props.contextTarget.label
26
+ })
27
+
28
+ // Clear name when new file/folder opens
29
+ watch(showNewFile, (open) => {
30
+ if (open) newItemName.value = ''
31
+ })
32
+ watch(showNewFolder, (open) => {
33
+ if (open) newItemName.value = ''
34
+ })
35
+
36
+ function handleCreateFile() {
37
+ if (!newItemName.value.trim()) return
38
+ emit('createFile', newItemName.value.trim())
39
+ showNewFile.value = false
40
+ newItemName.value = ''
41
+ }
42
+
43
+ function handleCreateFolder() {
44
+ if (!newItemName.value.trim()) return
45
+ emit('createFolder', newItemName.value.trim())
46
+ showNewFolder.value = false
47
+ newItemName.value = ''
48
+ }
49
+
50
+ function handleRename() {
51
+ if (!newItemName.value.trim()) return
52
+ emit('rename', newItemName.value.trim())
53
+ showRename.value = false
54
+ newItemName.value = ''
55
+ }
56
+
57
+ function handleDelete() {
58
+ emit('delete')
59
+ showDelete.value = false
60
+ }
61
+ </script>
62
+
63
+ <template>
64
+ <!-- New File Modal -->
65
+ <UModal v-model:open="showNewFile">
66
+ <template #content>
67
+ <UCard>
68
+ <template #header>
69
+ <div class="flex items-center gap-2">
70
+ <UIcon
71
+ name="i-lucide-file-plus"
72
+ class="size-5"
73
+ />
74
+ <span class="font-semibold">New File</span>
75
+ </div>
76
+ </template>
77
+
78
+ <UInput
79
+ v-model="newItemName"
80
+ placeholder="filename.md"
81
+ autofocus
82
+ @keyup.enter="handleCreateFile"
83
+ />
84
+
85
+ <template #footer>
86
+ <div class="flex justify-end gap-2">
87
+ <UButton
88
+ color="neutral"
89
+ variant="ghost"
90
+ @click="showNewFile = false"
91
+ >
92
+ Cancel
93
+ </UButton>
94
+ <UButton @click="handleCreateFile">
95
+ Create
96
+ </UButton>
97
+ </div>
98
+ </template>
99
+ </UCard>
100
+ </template>
101
+ </UModal>
102
+
103
+ <!-- New Folder Modal -->
104
+ <UModal v-model:open="showNewFolder">
105
+ <template #content>
106
+ <UCard>
107
+ <template #header>
108
+ <div class="flex items-center gap-2">
109
+ <UIcon
110
+ name="i-lucide-folder-plus"
111
+ class="size-5"
112
+ />
113
+ <span class="font-semibold">New Folder</span>
114
+ </div>
115
+ </template>
116
+
117
+ <UInput
118
+ v-model="newItemName"
119
+ placeholder="folder-name"
120
+ autofocus
121
+ @keyup.enter="handleCreateFolder"
122
+ />
123
+
124
+ <template #footer>
125
+ <div class="flex justify-end gap-2">
126
+ <UButton
127
+ color="neutral"
128
+ variant="ghost"
129
+ @click="showNewFolder = false"
130
+ >
131
+ Cancel
132
+ </UButton>
133
+ <UButton @click="handleCreateFolder">
134
+ Create
135
+ </UButton>
136
+ </div>
137
+ </template>
138
+ </UCard>
139
+ </template>
140
+ </UModal>
141
+
142
+ <!-- Rename Modal -->
143
+ <UModal v-model:open="showRename">
144
+ <template #content>
145
+ <UCard>
146
+ <template #header>
147
+ <div class="flex items-center gap-2">
148
+ <UIcon
149
+ name="i-lucide-pencil"
150
+ class="size-5"
151
+ />
152
+ <span class="font-semibold">Rename</span>
153
+ </div>
154
+ </template>
155
+
156
+ <UInput
157
+ v-model="newItemName"
158
+ placeholder="new-name"
159
+ autofocus
160
+ @keyup.enter="handleRename"
161
+ />
162
+
163
+ <template #footer>
164
+ <div class="flex justify-end gap-2">
165
+ <UButton
166
+ color="neutral"
167
+ variant="ghost"
168
+ @click="showRename = false"
169
+ >
170
+ Cancel
171
+ </UButton>
172
+ <UButton @click="handleRename">
173
+ Rename
174
+ </UButton>
175
+ </div>
176
+ </template>
177
+ </UCard>
178
+ </template>
179
+ </UModal>
180
+
181
+ <!-- Delete Confirmation -->
182
+ <UModal v-model:open="showDelete">
183
+ <template #content>
184
+ <UCard>
185
+ <template #header>
186
+ <div class="flex items-center gap-2 text-error">
187
+ <UIcon
188
+ name="i-lucide-trash-2"
189
+ class="size-5"
190
+ />
191
+ <span class="font-semibold">Delete</span>
192
+ </div>
193
+ </template>
194
+
195
+ <p>
196
+ Are you sure you want to delete
197
+ <strong>{{ contextTarget?.label }}</strong>?
198
+ </p>
199
+ <p
200
+ v-if="contextTarget?.type === 'directory'"
201
+ class="text-sm text-dimmed mt-1"
202
+ >
203
+ This will delete all files and folders inside.
204
+ </p>
205
+
206
+ <template #footer>
207
+ <div class="flex justify-end gap-2">
208
+ <UButton
209
+ color="neutral"
210
+ variant="ghost"
211
+ @click="showDelete = false"
212
+ >
213
+ Cancel
214
+ </UButton>
215
+ <UButton
216
+ color="error"
217
+ @click="handleDelete"
218
+ >
219
+ Delete
220
+ </UButton>
221
+ </div>
222
+ </template>
223
+ </UCard>
224
+ </template>
225
+ </UModal>
226
+ </template>
@@ -1,17 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  import type { HookEventStats } from '~~/shared/types'
3
+ import { formatDuration } from '~~/shared/utils/formatting'
3
4
 
4
5
  const props = defineProps<{
5
6
  stats: HookEventStats | null
6
7
  loading?: boolean
7
8
  }>()
8
9
 
9
- function formatDuration(ms: number): string {
10
- if (ms < 1000) return `${ms}ms`
11
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
12
- return `${(ms / 60000).toFixed(1)}m`
13
- }
14
-
15
10
  const displayStats = computed(() => {
16
11
  if (!props.stats) return []
17
12
  const s = props.stats
@@ -1,17 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  import type { HookToolBreakdown } from '~~/shared/types'
3
+ import { formatDuration } from '~~/shared/utils/formatting'
3
4
 
4
5
  defineProps<{
5
6
  data: HookToolBreakdown[]
6
7
  loading?: boolean
7
8
  }>()
8
9
 
9
- function formatDuration(ms: number): string {
10
- if (ms < 1000) return `${ms}ms`
11
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
12
- return `${(ms / 60000).toFixed(1)}m`
13
- }
14
-
15
10
  function getBlockRateColor(blocked: number, total: number): string {
16
11
  const rate = total > 0 ? (blocked / total) * 100 : 0
17
12
  if (rate > 50) return 'text-error'
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { CommandPaletteGroup, CommandPaletteItem } from '@nuxt/ui'
3
+ import { formatRelativeTime } from '~~/shared/utils/formatting'
3
4
 
4
5
  const router = useRouter()
5
6
  const toast = useToast()
@@ -88,6 +89,42 @@ const groups = computed<CommandPaletteGroup[]>(() => {
88
89
  })
89
90
  }
90
91
 
92
+ // Agents group (only show if we have results)
93
+ if (results.value.agents.length) {
94
+ g.push({
95
+ id: 'agents',
96
+ label: 'Agents',
97
+ ignoreFilter: true,
98
+ items: results.value.agents.map(a => ({
99
+ label: a.name,
100
+ icon: a.enabled ? 'i-lucide-bot' : 'i-lucide-bot-off',
101
+ suffix: a.schedule,
102
+ onSelect: () => {
103
+ router.push(`/agents/${a.id}`)
104
+ reset()
105
+ }
106
+ }))
107
+ })
108
+ }
109
+
110
+ // Conversations group (only show if we have results)
111
+ if (results.value.conversations.length) {
112
+ g.push({
113
+ id: 'conversations',
114
+ label: 'Conversations',
115
+ ignoreFilter: true,
116
+ items: results.value.conversations.map(c => ({
117
+ label: c.title || 'Untitled chat',
118
+ icon: 'i-lucide-message-square',
119
+ suffix: formatRelativeTime(c.startedAt),
120
+ onSelect: () => {
121
+ router.push({ path: '/chat', query: { conversation: c.id } })
122
+ reset()
123
+ }
124
+ }))
125
+ })
126
+ }
127
+
91
128
  // Navigation group (always visible)
92
129
  g.push({
93
130
  id: 'navigation',
@@ -116,7 +153,7 @@ const groups = computed<CommandPaletteGroup[]>(() => {
116
153
  v-model:search-term="searchTerm"
117
154
  :groups="groups"
118
155
  :loading="loading"
119
- placeholder="Search tasks, docs, or type a command..."
156
+ placeholder="Search tasks, docs, agents, chats..."
120
157
  :fuse="{ resultLimit: 10 }"
121
158
  />
122
159
  </template>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { Task } from '~~/shared/types'
3
+ import { formatDateShort } from '~~/shared/utils/formatting'
3
4
 
4
5
  const props = defineProps<{
5
6
  task: Task
@@ -35,12 +36,6 @@ const isOverdue = computed(() => {
35
36
  return new Date(props.task.dueDate) < new Date()
36
37
  })
37
38
 
38
- function formatDate(date: Date | string | undefined) {
39
- if (!date) return null
40
- const d = new Date(date)
41
- return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
42
- }
43
-
44
39
  const menuItems = computed(() => [[
45
40
  {
46
41
  label: 'Edit',
@@ -146,7 +141,7 @@ const menuItems = computed(() => [[
146
141
  name="i-lucide-calendar"
147
142
  class="size-3"
148
143
  />
149
- {{ formatDate(task.dueDate) }}
144
+ {{ formatDateShort(task.dueDate) }}
150
145
  </span>
151
146
 
152
147
  <!-- Tags -->
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { Task } from '~~/shared/types'
3
+ import { formatDateLong } from '~~/shared/utils/formatting'
3
4
 
4
5
  const props = defineProps<{
5
6
  task: Task
@@ -27,12 +28,6 @@ const statusConfig = {
27
28
  const priority = computed(() => priorityConfig[props.task.priority as 1 | 2 | 3] || priorityConfig[2])
28
29
  const status = computed(() => statusConfig[props.task.status])
29
30
  const isComplete = computed(() => props.task.status === 'done')
30
-
31
- function formatDate(date: Date | string | undefined) {
32
- if (!date) return null
33
- const d = new Date(date)
34
- return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' })
35
- }
36
31
  </script>
37
32
 
38
33
  <template>
@@ -87,7 +82,7 @@ function formatDate(date: Date | string | undefined) {
87
82
  name="i-lucide-calendar"
88
83
  class="size-4 text-dimmed"
89
84
  />
90
- <span class="text-dimmed">{{ formatDate(task.dueDate) }}</span>
85
+ <span class="text-dimmed">{{ formatDateLong(task.dueDate) }}</span>
91
86
  </div>
92
87
  </div>
93
88
 
@@ -120,11 +115,11 @@ function formatDate(date: Date | string | undefined) {
120
115
  <!-- Timestamps & Audit -->
121
116
  <div class="pt-2 border-t border-default text-xs text-dimmed space-y-1">
122
117
  <p>
123
- Created: {{ formatDate(task.createdAt) }}
118
+ Created: {{ formatDateLong(task.createdAt) }}
124
119
  <span v-if="task.creator"> by {{ task.creator.name }}</span>
125
120
  </p>
126
121
  <p v-if="task.completedAt">
127
- Completed: {{ formatDate(task.completedAt) }}
122
+ Completed: {{ formatDateLong(task.completedAt) }}
128
123
  </p>
129
124
  </div>
130
125
  </div>
@@ -0,0 +1,196 @@
1
+ import type { ChatImageBlock, ChatImageMediaType, ChatDocumentBlock } from '~~/shared/types'
2
+
3
+ const IMAGE_TYPES: ChatImageMediaType[] = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
4
+
5
+ const TEXT_MIME_TYPES = new Set([
6
+ 'application/json',
7
+ 'application/javascript',
8
+ 'application/typescript',
9
+ 'application/xml',
10
+ 'application/x-yaml',
11
+ 'application/x-sh',
12
+ 'application/sql',
13
+ 'application/toml',
14
+ 'application/x-httpd-php'
15
+ ])
16
+
17
+ const MAX_FILE_SIZE = 20 * 1024 * 1024 // 20MB
18
+
19
+ export interface PendingImageAttachment {
20
+ id: string
21
+ kind: 'image'
22
+ previewUrl: string
23
+ base64: string
24
+ mediaType: ChatImageMediaType
25
+ name: string
26
+ }
27
+
28
+ export interface PendingDocumentAttachment {
29
+ id: string
30
+ kind: 'document'
31
+ data: string
32
+ sourceType: 'base64' | 'text'
33
+ name: string
34
+ }
35
+
36
+ export type PendingAttachment = PendingImageAttachment | PendingDocumentAttachment
37
+
38
+ function generateId(): string {
39
+ if (typeof crypto !== 'undefined' && crypto.randomUUID)
40
+ return crypto.randomUUID()
41
+ return Math.random().toString(36).slice(2)
42
+ }
43
+
44
+ function readFileAsBase64(file: File): Promise<string> {
45
+ return new Promise((resolve, reject) => {
46
+ const reader = new FileReader()
47
+ reader.onload = () => {
48
+ const result = reader.result as string
49
+ const base64 = result.split(',')[1]
50
+ if (base64) resolve(base64)
51
+ else reject(new Error('Failed to read file as base64'))
52
+ }
53
+ reader.onerror = () => reject(reader.error)
54
+ reader.readAsDataURL(file)
55
+ })
56
+ }
57
+
58
+ function readFileAsText(file: File): Promise<string> {
59
+ return new Promise((resolve, reject) => {
60
+ const reader = new FileReader()
61
+ reader.onload = () => resolve(reader.result as string)
62
+ reader.onerror = () => reject(reader.error)
63
+ reader.readAsText(file)
64
+ })
65
+ }
66
+
67
+ function isImageType(mime: string): mime is ChatImageMediaType {
68
+ return IMAGE_TYPES.includes(mime as ChatImageMediaType)
69
+ }
70
+
71
+ function isTextType(mime: string): boolean {
72
+ return mime.startsWith('text/') || TEXT_MIME_TYPES.has(mime)
73
+ }
74
+
75
+ function isPdfType(mime: string): boolean {
76
+ return mime === 'application/pdf'
77
+ }
78
+
79
+ export function useAttachments() {
80
+ const attachments = ref<PendingAttachment[]>([])
81
+ const toast = useToast()
82
+
83
+ async function addFiles(files: FileList | File[]) {
84
+ for (const file of Array.from(files)) {
85
+ if (file.size > MAX_FILE_SIZE) {
86
+ toast.add({ title: 'File too large', description: `${file.name} exceeds 20MB limit`, color: 'error' })
87
+ continue
88
+ }
89
+
90
+ const mime = file.type || ''
91
+
92
+ try {
93
+ if (isImageType(mime)) {
94
+ const base64 = await readFileAsBase64(file)
95
+ const previewUrl = URL.createObjectURL(file)
96
+ attachments.value.push({
97
+ id: generateId(),
98
+ kind: 'image',
99
+ previewUrl,
100
+ base64,
101
+ mediaType: mime,
102
+ name: file.name
103
+ })
104
+ } else if (isPdfType(mime)) {
105
+ const base64 = await readFileAsBase64(file)
106
+ attachments.value.push({
107
+ id: generateId(),
108
+ kind: 'document',
109
+ data: base64,
110
+ sourceType: 'base64',
111
+ name: file.name
112
+ })
113
+ } else if (isTextType(mime) || isLikelyTextFile(file.name)) {
114
+ const text = await readFileAsText(file)
115
+ attachments.value.push({
116
+ id: generateId(),
117
+ kind: 'document',
118
+ data: text,
119
+ sourceType: 'text',
120
+ name: file.name
121
+ })
122
+ } else {
123
+ toast.add({ title: 'Unsupported file type', description: `${file.name} (${mime || 'unknown type'})`, color: 'error' })
124
+ }
125
+ } catch {
126
+ toast.add({ title: 'Failed to read file', description: file.name, color: 'error' })
127
+ }
128
+ }
129
+ }
130
+
131
+ function removeAttachment(id: string) {
132
+ const idx = attachments.value.findIndex(a => a.id === id)
133
+ if (idx !== -1) {
134
+ const att = attachments.value[idx]!
135
+ if (att.kind === 'image') URL.revokeObjectURL(att.previewUrl)
136
+ attachments.value.splice(idx, 1)
137
+ }
138
+ }
139
+
140
+ function clearAttachments() {
141
+ for (const att of attachments.value)
142
+ if (att.kind === 'image') URL.revokeObjectURL(att.previewUrl)
143
+ attachments.value = []
144
+ }
145
+
146
+ function toImageBlocks(): ChatImageBlock[] {
147
+ return attachments.value
148
+ .filter((a): a is PendingImageAttachment => a.kind === 'image')
149
+ .map(att => ({
150
+ type: 'image' as const,
151
+ source: {
152
+ type: 'base64' as const,
153
+ media_type: att.mediaType,
154
+ data: att.base64
155
+ }
156
+ }))
157
+ }
158
+
159
+ function toDocumentBlocks(): ChatDocumentBlock[] {
160
+ return attachments.value
161
+ .filter((a): a is PendingDocumentAttachment => a.kind === 'document')
162
+ .map(att => ({
163
+ type: 'document' as const,
164
+ source: att.sourceType === 'base64'
165
+ ? { type: 'base64' as const, media_type: 'application/pdf' as const, data: att.data }
166
+ : { type: 'text' as const, media_type: 'text/plain' as const, data: att.data },
167
+ title: att.name
168
+ }))
169
+ }
170
+
171
+ return {
172
+ attachments: readonly(attachments),
173
+ addFiles,
174
+ removeAttachment,
175
+ clearAttachments,
176
+ toImageBlocks,
177
+ toDocumentBlocks
178
+ }
179
+ }
180
+
181
+ /** Fallback for files with empty MIME but recognizable extensions */
182
+ function isLikelyTextFile(name: string): boolean {
183
+ const ext = name.split('.').pop()?.toLowerCase()
184
+ if (!ext) return false
185
+ const textExtensions = new Set([
186
+ 'txt', 'md', 'js', 'ts', 'jsx', 'tsx', 'py', 'rb', 'rs', 'go',
187
+ 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'swift', 'kt', 'scala',
188
+ 'json', 'yaml', 'yml', 'toml', 'ini', 'cfg', 'conf',
189
+ 'xml', 'html', 'htm', 'css', 'scss', 'sass', 'less',
190
+ 'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat', 'cmd',
191
+ 'sql', 'graphql', 'gql', 'proto',
192
+ 'csv', 'tsv', 'log', 'env', 'gitignore', 'dockerignore',
193
+ 'vue', 'svelte', 'astro', 'prisma', 'tf', 'r', 'lua', 'dart'
194
+ ])
195
+ return textExtensions.has(ext)
196
+ }
@@ -2,6 +2,8 @@ import type {
2
2
  ChatConversation,
3
3
  ChatMessage,
4
4
  ChatContentBlock,
5
+ ChatImageBlock,
6
+ ChatDocumentBlock,
5
7
  ChatServerMessage,
6
8
  ChatSessionStatus,
7
9
  ChatConnectionStatus
@@ -200,21 +202,31 @@ export function useChat() {
200
202
  }
201
203
  }
202
204
 
203
- function sendMessage(message: string) {
205
+ function sendMessage(message: string, attachments?: ChatImageBlock[], documents?: ChatDocumentBlock[]) {
204
206
  if (!ws.value || ws.value.readyState !== WebSocket.OPEN) return
205
207
 
208
+ // Build content blocks for local display
209
+ const content: ChatContentBlock[] = []
210
+ if (attachments?.length)
211
+ for (const img of attachments) content.push(img)
212
+ if (documents?.length)
213
+ for (const doc of documents) content.push(doc)
214
+ if (message) content.push({ type: 'text', text: message })
215
+
206
216
  // Add user message locally
207
217
  messages.value.push({
208
218
  id: generateId(),
209
219
  conversationId: activeConversationId.value || '',
210
220
  role: 'user',
211
- content: [{ type: 'text', text: message }],
221
+ content,
212
222
  createdAt: new Date()
213
223
  })
214
224
 
215
225
  ws.value.send(JSON.stringify({
216
226
  type: 'chat:send',
217
227
  message,
228
+ attachments: attachments?.length ? attachments : undefined,
229
+ documents: documents?.length ? documents : undefined,
218
230
  conversationId: activeConversationId.value
219
231
  }))
220
232
  }