@larkup/workspace-app 0.0.0-stage → 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 (440) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +4 -2
  3. package/assets/github.svg +3 -0
  4. package/assets/icons/Confluence.png +0 -0
  5. package/assets/icons/Instagram.png +0 -0
  6. package/assets/icons/Northflank.png +0 -0
  7. package/assets/icons/Salesforce.png +0 -0
  8. package/assets/icons/agentskills.png +0 -0
  9. package/assets/icons/airtable.png +0 -0
  10. package/assets/icons/asana.png +0 -0
  11. package/assets/icons/ashby.png +0 -0
  12. package/assets/icons/audio.png +0 -0
  13. package/assets/icons/aws-kendra.png +0 -0
  14. package/assets/icons/aws.svg +38 -0
  15. package/assets/icons/azure-ai-search.png +0 -0
  16. package/assets/icons/azure.svg +1 -0
  17. package/assets/icons/bigquery.png +0 -0
  18. package/assets/icons/bing.png +0 -0
  19. package/assets/icons/bitbucket.png +0 -0
  20. package/assets/icons/brave.png +0 -0
  21. package/assets/icons/browserbase.png +0 -0
  22. package/assets/icons/calendly.png +0 -0
  23. package/assets/icons/chroma.png +0 -0
  24. package/assets/icons/cloudflare.png +0 -0
  25. package/assets/icons/cohere.svg +30 -0
  26. package/assets/icons/databricks.png +0 -0
  27. package/assets/icons/daytonaio.png +0 -0
  28. package/assets/icons/daytonaio2.png +0 -0
  29. package/assets/icons/deepgram.png +0 -0
  30. package/assets/icons/deepl.png +0 -0
  31. package/assets/icons/digital-ocean.webp +0 -0
  32. package/assets/icons/discord.png +0 -0
  33. package/assets/icons/e2b-2.png +0 -0
  34. package/assets/icons/e2b.png +0 -0
  35. package/assets/icons/elevenlabs.png +0 -0
  36. package/assets/icons/email.png +0 -0
  37. package/assets/icons/exa.png +0 -0
  38. package/assets/icons/excel.png +0 -0
  39. package/assets/icons/fal.png +0 -0
  40. package/assets/icons/firecrawl.png +0 -0
  41. package/assets/icons/firecrawl2.png +0 -0
  42. package/assets/icons/flyio.jpeg +0 -0
  43. package/assets/icons/gcp.svg +1 -0
  44. package/assets/icons/gemini.svg +1 -0
  45. package/assets/icons/gmail.png +0 -0
  46. package/assets/icons/google-analytics.png +0 -0
  47. package/assets/icons/google-calender.png +0 -0
  48. package/assets/icons/google-docs.png +0 -0
  49. package/assets/icons/google-drive.png +0 -0
  50. package/assets/icons/google-maps.png +0 -0
  51. package/assets/icons/google-meet.png +0 -0
  52. package/assets/icons/google-sheets.png +0 -0
  53. package/assets/icons/google-slides.png +0 -0
  54. package/assets/icons/google-tasks.png +0 -0
  55. package/assets/icons/google.png +0 -0
  56. package/assets/icons/grok.png +0 -0
  57. package/assets/icons/hubspot.png +0 -0
  58. package/assets/icons/image.png +0 -0
  59. package/assets/icons/jina.svg +1 -0
  60. package/assets/icons/jira.png +0 -0
  61. package/assets/icons/lancedb2.png +0 -0
  62. package/assets/icons/linear.png +0 -0
  63. package/assets/icons/linux.png +0 -0
  64. package/assets/icons/looker.png +0 -0
  65. package/assets/icons/luma.png +0 -0
  66. package/assets/icons/mac.png +0 -0
  67. package/assets/icons/markdown.png +0 -0
  68. package/assets/icons/mcp.png +0 -0
  69. package/assets/icons/mcp.svg +1 -0
  70. package/assets/icons/messenger.svg +1 -0
  71. package/assets/icons/microsoft-intra.png +0 -0
  72. package/assets/icons/microsoft-onenote.png +0 -0
  73. package/assets/icons/microsoft-planner.png +0 -0
  74. package/assets/icons/microsoft-teams.png +0 -0
  75. package/assets/icons/miro.png +0 -0
  76. package/assets/icons/mistral.svg +469 -0
  77. package/assets/icons/modal.png +0 -0
  78. package/assets/icons/ngrok.png +0 -0
  79. package/assets/icons/nomic.png +0 -0
  80. package/assets/icons/onedrive.png +0 -0
  81. package/assets/icons/openai.svg +1 -0
  82. package/assets/icons/outlook-calendar.png +0 -0
  83. package/assets/icons/outlook-email.png +0 -0
  84. package/assets/icons/outlook.png +0 -0
  85. package/assets/icons/pdf.png +0 -0
  86. package/assets/icons/personio.png +0 -0
  87. package/assets/icons/pgvector2.png +0 -0
  88. package/assets/icons/phone.png +0 -0
  89. package/assets/icons/pinecone.png +0 -0
  90. package/assets/icons/pipedrive.png +0 -0
  91. package/assets/icons/power-pi.png +0 -0
  92. package/assets/icons/pylon.png +0 -0
  93. package/assets/icons/python.png +0 -0
  94. package/assets/icons/qdrant.svg +35 -0
  95. package/assets/icons/revai.png +0 -0
  96. package/assets/icons/revai.svg +1 -0
  97. package/assets/icons/sap.png +0 -0
  98. package/assets/icons/serpapi.svg +34 -0
  99. package/assets/icons/serper.png +0 -0
  100. package/assets/icons/servicenow.png +0 -0
  101. package/assets/icons/sharepoint.png +0 -0
  102. package/assets/icons/slack.png +0 -0
  103. package/assets/icons/snowflake.png +0 -0
  104. package/assets/icons/statistia.png +0 -0
  105. package/assets/icons/stripe.png +0 -0
  106. package/assets/icons/supabase.png +0 -0
  107. package/assets/icons/tableau.png +0 -0
  108. package/assets/icons/tavily.png +0 -0
  109. package/assets/icons/telegram.png +0 -0
  110. package/assets/icons/typescript.png +0 -0
  111. package/assets/icons/vercel copy.svg +1 -0
  112. package/assets/icons/vercel.svg +1 -0
  113. package/assets/icons/vertex-ai.png +0 -0
  114. package/assets/icons/video.png +0 -0
  115. package/assets/icons/voyage-light.png +0 -0
  116. package/assets/icons/weaviate.webp +0 -0
  117. package/assets/icons/webcontainers.svg +11 -0
  118. package/assets/icons/webhook.png +0 -0
  119. package/assets/icons/whatsapp copy.png +0 -0
  120. package/assets/icons/whatsapp.png +0 -0
  121. package/assets/icons/windows.png +0 -0
  122. package/assets/icons/word.png +0 -0
  123. package/assets/icons/zendesk.png +0 -0
  124. package/assets/icons/zoom.png +0 -0
  125. package/assets/notion-white.png +0 -0
  126. package/assets/notion.png +0 -0
  127. package/dist-types/asset-server.d.ts +3 -0
  128. package/dist-types/asset-server.d.ts.map +1 -0
  129. package/dist-types/components/alerts/generic-alert.d.ts +15 -0
  130. package/dist-types/components/alerts/generic-alert.d.ts.map +1 -0
  131. package/dist-types/components/chat/canvas/doc-editor-provider.d.ts +62 -0
  132. package/dist-types/components/chat/canvas/doc-editor-provider.d.ts.map +1 -0
  133. package/dist-types/components/chat/canvas/document-canvas.d.ts +7 -0
  134. package/dist-types/components/chat/canvas/document-canvas.d.ts.map +1 -0
  135. package/dist-types/components/chat/canvas/document-field.d.ts +13 -0
  136. package/dist-types/components/chat/canvas/document-field.d.ts.map +1 -0
  137. package/dist-types/components/chat/canvas/document-viewer.d.ts +2 -0
  138. package/dist-types/components/chat/canvas/document-viewer.d.ts.map +1 -0
  139. package/dist-types/components/chat/canvas/file-attachment-button.d.ts +9 -0
  140. package/dist-types/components/chat/canvas/file-attachment-button.d.ts.map +1 -0
  141. package/dist-types/components/chat/chat-workspace.d.ts +4 -0
  142. package/dist-types/components/chat/chat-workspace.d.ts.map +1 -0
  143. package/dist-types/components/chat/message-actions.d.ts +11 -0
  144. package/dist-types/components/chat/message-actions.d.ts.map +1 -0
  145. package/dist-types/components/chat/message-item.d.ts +20 -0
  146. package/dist-types/components/chat/message-item.d.ts.map +1 -0
  147. package/dist-types/components/chat/message-queue.d.ts +15 -0
  148. package/dist-types/components/chat/message-queue.d.ts.map +1 -0
  149. package/dist-types/components/chat/tools/chat-chart.d.ts +6 -0
  150. package/dist-types/components/chat/tools/chat-chart.d.ts.map +1 -0
  151. package/dist-types/components/chat/tools/chat-citations.d.ts +32 -0
  152. package/dist-types/components/chat/tools/chat-citations.d.ts.map +1 -0
  153. package/dist-types/components/chat/tools/chat-data-table.d.ts +14 -0
  154. package/dist-types/components/chat/tools/chat-data-table.d.ts.map +1 -0
  155. package/dist-types/components/chat/tools/chat-file-export.d.ts +5 -0
  156. package/dist-types/components/chat/tools/chat-file-export.d.ts.map +1 -0
  157. package/dist-types/components/chat/tools/chat-media-preview.d.ts +35 -0
  158. package/dist-types/components/chat/tools/chat-media-preview.d.ts.map +1 -0
  159. package/dist-types/components/chat/tools/chat-sandbox-result.d.ts +17 -0
  160. package/dist-types/components/chat/tools/chat-sandbox-result.d.ts.map +1 -0
  161. package/dist-types/components/chat/tools/chat-signature-request.d.ts +13 -0
  162. package/dist-types/components/chat/tools/chat-signature-request.d.ts.map +1 -0
  163. package/dist-types/components/chat/tools/chat-tabs.d.ts +17 -0
  164. package/dist-types/components/chat/tools/chat-tabs.d.ts.map +1 -0
  165. package/dist-types/components/chat/tools/corpus-data-result.d.ts +36 -0
  166. package/dist-types/components/chat/tools/corpus-data-result.d.ts.map +1 -0
  167. package/dist-types/components/chat/tools/knowledge-base-result.d.ts +14 -0
  168. package/dist-types/components/chat/tools/knowledge-base-result.d.ts.map +1 -0
  169. package/dist-types/components/chat/tools/visual-placement-modal.d.ts +23 -0
  170. package/dist-types/components/chat/tools/visual-placement-modal.d.ts.map +1 -0
  171. package/dist-types/components/configure/custom-model-modal.d.ts +13 -0
  172. package/dist-types/components/configure/custom-model-modal.d.ts.map +1 -0
  173. package/dist-types/components/configure/store-fields.d.ts +11 -0
  174. package/dist-types/components/configure/store-fields.d.ts.map +1 -0
  175. package/dist-types/components/data/corpus-panel.d.ts +12 -0
  176. package/dist-types/components/data/corpus-panel.d.ts.map +1 -0
  177. package/dist-types/components/data/data-entry-mode-switch.d.ts +13 -0
  178. package/dist-types/components/data/data-entry-mode-switch.d.ts.map +1 -0
  179. package/dist-types/components/data/data-primary-action.d.ts +7 -0
  180. package/dist-types/components/data/data-primary-action.d.ts.map +1 -0
  181. package/dist-types/components/data/data-workspace.d.ts +6 -0
  182. package/dist-types/components/data/data-workspace.d.ts.map +1 -0
  183. package/dist-types/components/data/groups-panel.d.ts +5 -0
  184. package/dist-types/components/data/groups-panel.d.ts.map +1 -0
  185. package/dist-types/components/data/integration-resources-panel.d.ts +9 -0
  186. package/dist-types/components/data/integration-resources-panel.d.ts.map +1 -0
  187. package/dist-types/components/data/integrations-panel.d.ts +5 -0
  188. package/dist-types/components/data/integrations-panel.d.ts.map +1 -0
  189. package/dist-types/components/data/jobs-panel.d.ts +6 -0
  190. package/dist-types/components/data/jobs-panel.d.ts.map +1 -0
  191. package/dist-types/components/data/jobs-tracking-button.d.ts +2 -0
  192. package/dist-types/components/data/jobs-tracking-button.d.ts.map +1 -0
  193. package/dist-types/components/data/live-media-progress.d.ts +9 -0
  194. package/dist-types/components/data/live-media-progress.d.ts.map +1 -0
  195. package/dist-types/components/data/media-indexing-jobs-panel.d.ts +34 -0
  196. package/dist-types/components/data/media-indexing-jobs-panel.d.ts.map +1 -0
  197. package/dist-types/components/data/media-panel.d.ts +58 -0
  198. package/dist-types/components/data/media-panel.d.ts.map +1 -0
  199. package/dist-types/components/data/notion-panel.d.ts +6 -0
  200. package/dist-types/components/data/notion-panel.d.ts.map +1 -0
  201. package/dist-types/components/data/paste-panel.d.ts +7 -0
  202. package/dist-types/components/data/paste-panel.d.ts.map +1 -0
  203. package/dist-types/components/data/project-data-page.d.ts +3 -0
  204. package/dist-types/components/data/project-data-page.d.ts.map +1 -0
  205. package/dist-types/components/data/scrape-panel.d.ts +9 -0
  206. package/dist-types/components/data/scrape-panel.d.ts.map +1 -0
  207. package/dist-types/components/data/tabular-preview.d.ts +10 -0
  208. package/dist-types/components/data/tabular-preview.d.ts.map +1 -0
  209. package/dist-types/components/data/tool-indexing-dialog.d.ts +54 -0
  210. package/dist-types/components/data/tool-indexing-dialog.d.ts.map +1 -0
  211. package/dist-types/components/data/upload-panel.d.ts +7 -0
  212. package/dist-types/components/data/upload-panel.d.ts.map +1 -0
  213. package/dist-types/components/data/web-crawler-status.d.ts +3 -0
  214. package/dist-types/components/data/web-crawler-status.d.ts.map +1 -0
  215. package/dist-types/components/projects/project-form-dialog.d.ts +10 -0
  216. package/dist-types/components/projects/project-form-dialog.d.ts.map +1 -0
  217. package/dist-types/components/projects/project-provider.d.ts +34 -0
  218. package/dist-types/components/projects/project-provider.d.ts.map +1 -0
  219. package/dist-types/components/settings/general/agent-customization-section.d.ts +2 -0
  220. package/dist-types/components/settings/general/agent-customization-section.d.ts.map +1 -0
  221. package/dist-types/components/settings/general/mcp-connections-section.d.ts +2 -0
  222. package/dist-types/components/settings/general/mcp-connections-section.d.ts.map +1 -0
  223. package/dist-types/components/settings/general/model-combobox.d.ts +24 -0
  224. package/dist-types/components/settings/general/model-combobox.d.ts.map +1 -0
  225. package/dist-types/components/settings/general/models-section.d.ts +2 -0
  226. package/dist-types/components/settings/general/models-section.d.ts.map +1 -0
  227. package/dist-types/components/settings/general/sandbox-section.d.ts +7 -0
  228. package/dist-types/components/settings/general/sandbox-section.d.ts.map +1 -0
  229. package/dist-types/components/settings/general/search-scraping-section.d.ts +2 -0
  230. package/dist-types/components/settings/general/search-scraping-section.d.ts.map +1 -0
  231. package/dist-types/components/settings/general/storage-indexing-section.d.ts +2 -0
  232. package/dist-types/components/settings/general/storage-indexing-section.d.ts.map +1 -0
  233. package/dist-types/components/ui/alert-dialog.d.ts +19 -0
  234. package/dist-types/components/ui/alert-dialog.d.ts.map +1 -0
  235. package/dist-types/components/ui/alert.d.ts +11 -0
  236. package/dist-types/components/ui/alert.d.ts.map +1 -0
  237. package/dist-types/components/ui/badge.d.ts +8 -0
  238. package/dist-types/components/ui/badge.d.ts.map +1 -0
  239. package/dist-types/components/ui/card.d.ts +2 -0
  240. package/dist-types/components/ui/card.d.ts.map +1 -0
  241. package/dist-types/components/ui/checkbox.d.ts +4 -0
  242. package/dist-types/components/ui/checkbox.d.ts.map +1 -0
  243. package/dist-types/components/ui/collapsible.d.ts +6 -0
  244. package/dist-types/components/ui/collapsible.d.ts.map +1 -0
  245. package/dist-types/components/ui/command.d.ts +20 -0
  246. package/dist-types/components/ui/command.d.ts.map +1 -0
  247. package/dist-types/components/ui/dialog.d.ts +18 -0
  248. package/dist-types/components/ui/dialog.d.ts.map +1 -0
  249. package/dist-types/components/ui/dropdown-menu.d.ts +30 -0
  250. package/dist-types/components/ui/dropdown-menu.d.ts.map +1 -0
  251. package/dist-types/components/ui/input-group.d.ts +19 -0
  252. package/dist-types/components/ui/input-group.d.ts.map +1 -0
  253. package/dist-types/components/ui/input.d.ts +4 -0
  254. package/dist-types/components/ui/input.d.ts.map +1 -0
  255. package/dist-types/components/ui/line-tabs.d.ts +15 -0
  256. package/dist-types/components/ui/line-tabs.d.ts.map +1 -0
  257. package/dist-types/components/ui/popover.d.ts +10 -0
  258. package/dist-types/components/ui/popover.d.ts.map +1 -0
  259. package/dist-types/components/ui/progress.d.ts +8 -0
  260. package/dist-types/components/ui/progress.d.ts.map +1 -0
  261. package/dist-types/components/ui/scroll-area.d.ts +6 -0
  262. package/dist-types/components/ui/scroll-area.d.ts.map +1 -0
  263. package/dist-types/components/ui/sheet.d.ts +15 -0
  264. package/dist-types/components/ui/sheet.d.ts.map +1 -0
  265. package/dist-types/components/ui/slider.d.ts +4 -0
  266. package/dist-types/components/ui/slider.d.ts.map +1 -0
  267. package/dist-types/components/ui/switch.d.ts +6 -0
  268. package/dist-types/components/ui/switch.d.ts.map +1 -0
  269. package/dist-types/components/ui/table.d.ts +2 -0
  270. package/dist-types/components/ui/table.d.ts.map +1 -0
  271. package/dist-types/components/ui/tabs.d.ts +11 -0
  272. package/dist-types/components/ui/tabs.d.ts.map +1 -0
  273. package/dist-types/components/ui/textarea.d.ts +2 -0
  274. package/dist-types/components/ui/textarea.d.ts.map +1 -0
  275. package/dist-types/components/ui/tooltip.d.ts +7 -0
  276. package/dist-types/components/ui/tooltip.d.ts.map +1 -0
  277. package/dist-types/hooks/use-copy-to-clipboard.d.ts +5 -0
  278. package/dist-types/hooks/use-copy-to-clipboard.d.ts.map +1 -0
  279. package/dist-types/hooks/use-integration-auth.d.ts +10 -0
  280. package/dist-types/hooks/use-integration-auth.d.ts.map +1 -0
  281. package/dist-types/hooks/use-live-media-progress.d.ts +14 -0
  282. package/dist-types/hooks/use-live-media-progress.d.ts.map +1 -0
  283. package/dist-types/hooks/use-notion-auth.d.ts +9 -0
  284. package/dist-types/hooks/use-notion-auth.d.ts.map +1 -0
  285. package/dist-types/host.d.ts +20 -0
  286. package/dist-types/host.d.ts.map +1 -0
  287. package/dist-types/lib/browser-cache.d.ts +7 -0
  288. package/dist-types/lib/browser-cache.d.ts.map +1 -0
  289. package/dist-types/lib/chat/chart-config.d.ts +2 -0
  290. package/dist-types/lib/chat/chart-config.d.ts.map +1 -0
  291. package/dist-types/lib/chat/data-export.d.ts +2 -0
  292. package/dist-types/lib/chat/data-export.d.ts.map +1 -0
  293. package/dist-types/lib/chat/live-tool-progress.d.ts +44 -0
  294. package/dist-types/lib/chat/live-tool-progress.d.ts.map +1 -0
  295. package/dist-types/lib/chat/message-feedback.d.ts +2 -0
  296. package/dist-types/lib/chat/message-feedback.d.ts.map +1 -0
  297. package/dist-types/lib/chat/table-presentation.d.ts +2 -0
  298. package/dist-types/lib/chat/table-presentation.d.ts.map +1 -0
  299. package/dist-types/lib/chat/tool-output.d.ts +3 -0
  300. package/dist-types/lib/chat/tool-output.d.ts.map +1 -0
  301. package/dist-types/lib/chat-supporting-clip.d.ts +11 -0
  302. package/dist-types/lib/chat-supporting-clip.d.ts.map +1 -0
  303. package/dist-types/lib/constants/stores.d.ts +6 -0
  304. package/dist-types/lib/constants/stores.d.ts.map +1 -0
  305. package/dist-types/lib/constants/tools.d.ts +27 -0
  306. package/dist-types/lib/constants/tools.d.ts.map +1 -0
  307. package/dist-types/lib/error-formatter.d.ts +2 -0
  308. package/dist-types/lib/error-formatter.d.ts.map +1 -0
  309. package/dist-types/lib/jobs-client.d.ts +7 -0
  310. package/dist-types/lib/jobs-client.d.ts.map +1 -0
  311. package/dist-types/lib/media/pdf.d.ts +6 -0
  312. package/dist-types/lib/media/pdf.d.ts.map +1 -0
  313. package/dist-types/lib/media/progress.d.ts +61 -0
  314. package/dist-types/lib/media/progress.d.ts.map +1 -0
  315. package/dist-types/lib/media/source-selection.d.ts +10 -0
  316. package/dist-types/lib/media/source-selection.d.ts.map +1 -0
  317. package/dist-types/lib/media/tool-indexing-estimate.d.ts +13 -0
  318. package/dist-types/lib/media/tool-indexing-estimate.d.ts.map +1 -0
  319. package/dist-types/lib/media/video-intelligence-capacity.d.ts +2 -0
  320. package/dist-types/lib/media/video-intelligence-capacity.d.ts.map +1 -0
  321. package/dist-types/lib/media/video-runtime-scope.d.ts +2 -0
  322. package/dist-types/lib/media/video-runtime-scope.d.ts.map +1 -0
  323. package/dist-types/lib/shared/error-formatter.d.ts +2 -0
  324. package/dist-types/lib/shared/error-formatter.d.ts.map +1 -0
  325. package/dist-types/lib/utils.d.ts +3 -0
  326. package/dist-types/lib/utils.d.ts.map +1 -0
  327. package/dist-types/storage.d.ts +5 -0
  328. package/dist-types/storage.d.ts.map +1 -0
  329. package/dist-types/store/chat-store.d.ts +19 -0
  330. package/dist-types/store/chat-store.d.ts.map +1 -0
  331. package/dist-types/store/scrape-store.d.ts +42 -0
  332. package/dist-types/store/scrape-store.d.ts.map +1 -0
  333. package/package.json +579 -3
  334. package/scripts/build-assets.mjs +12 -0
  335. package/src/asset-data.json +1 -0
  336. package/src/asset-server.ts +19 -0
  337. package/src/components/alerts/generic-alert.tsx +76 -0
  338. package/src/components/chat/canvas/doc-editor-provider.tsx +338 -0
  339. package/src/components/chat/canvas/document-canvas.tsx +98 -0
  340. package/src/components/chat/canvas/document-field.tsx +167 -0
  341. package/src/components/chat/canvas/document-viewer.tsx +446 -0
  342. package/src/components/chat/canvas/file-attachment-button.tsx +88 -0
  343. package/src/components/chat/chat-workspace.tsx +1552 -0
  344. package/src/components/chat/message-actions.tsx +96 -0
  345. package/src/components/chat/message-item.tsx +1398 -0
  346. package/src/components/chat/message-queue.tsx +86 -0
  347. package/src/components/chat/tools/chat-chart.tsx +558 -0
  348. package/src/components/chat/tools/chat-citations.tsx +152 -0
  349. package/src/components/chat/tools/chat-data-table.tsx +373 -0
  350. package/src/components/chat/tools/chat-file-export.tsx +49 -0
  351. package/src/components/chat/tools/chat-media-preview.tsx +466 -0
  352. package/src/components/chat/tools/chat-sandbox-result.tsx +130 -0
  353. package/src/components/chat/tools/chat-signature-request.tsx +810 -0
  354. package/src/components/chat/tools/chat-tabs.tsx +59 -0
  355. package/src/components/chat/tools/corpus-data-result.tsx +194 -0
  356. package/src/components/chat/tools/knowledge-base-result.tsx +345 -0
  357. package/src/components/chat/tools/visual-placement-modal.tsx +308 -0
  358. package/src/components/configure/custom-model-modal.tsx +209 -0
  359. package/src/components/configure/store-fields.tsx +129 -0
  360. package/src/components/data/corpus-panel.tsx +1223 -0
  361. package/src/components/data/data-entry-mode-switch.tsx +53 -0
  362. package/src/components/data/data-primary-action.ts +6 -0
  363. package/src/components/data/data-workspace.tsx +623 -0
  364. package/src/components/data/groups-panel.tsx +530 -0
  365. package/src/components/data/integration-resources-panel.tsx +282 -0
  366. package/src/components/data/integrations-panel.tsx +392 -0
  367. package/src/components/data/jobs-panel.tsx +490 -0
  368. package/src/components/data/jobs-tracking-button.tsx +224 -0
  369. package/src/components/data/live-media-progress.tsx +79 -0
  370. package/src/components/data/media-indexing-jobs-panel.tsx +190 -0
  371. package/src/components/data/media-panel.tsx +2905 -0
  372. package/src/components/data/notion-panel.tsx +408 -0
  373. package/src/components/data/paste-panel.tsx +82 -0
  374. package/src/components/data/project-data-page.tsx +55 -0
  375. package/src/components/data/scrape-panel.tsx +1290 -0
  376. package/src/components/data/tabular-preview.tsx +205 -0
  377. package/src/components/data/tool-indexing-dialog.tsx +210 -0
  378. package/src/components/data/upload-panel.tsx +1313 -0
  379. package/src/components/data/web-crawler-status.tsx +132 -0
  380. package/src/components/projects/project-form-dialog.tsx +97 -0
  381. package/src/components/projects/project-provider.tsx +143 -0
  382. package/src/components/settings/general/agent-customization-section.tsx +620 -0
  383. package/src/components/settings/general/mcp-connections-section.tsx +907 -0
  384. package/src/components/settings/general/model-combobox.tsx +235 -0
  385. package/src/components/settings/general/models-section.tsx +909 -0
  386. package/src/components/settings/general/sandbox-section.tsx +394 -0
  387. package/src/components/settings/general/search-scraping-section.tsx +757 -0
  388. package/src/components/settings/general/storage-indexing-section.tsx +394 -0
  389. package/src/components/ui/alert-dialog.tsx +162 -0
  390. package/src/components/ui/alert.tsx +69 -0
  391. package/src/components/ui/badge.tsx +49 -0
  392. package/src/components/ui/card.tsx +9 -0
  393. package/src/components/ui/checkbox.tsx +28 -0
  394. package/src/components/ui/collapsible.tsx +17 -0
  395. package/src/components/ui/command.tsx +175 -0
  396. package/src/components/ui/dialog.tsx +138 -0
  397. package/src/components/ui/dropdown-menu.tsx +267 -0
  398. package/src/components/ui/input-group.tsx +146 -0
  399. package/src/components/ui/input.tsx +19 -0
  400. package/src/components/ui/line-tabs.tsx +49 -0
  401. package/src/components/ui/popover.tsx +77 -0
  402. package/src/components/ui/progress.tsx +66 -0
  403. package/src/components/ui/scroll-area.tsx +51 -0
  404. package/src/components/ui/sheet.tsx +125 -0
  405. package/src/components/ui/slider.tsx +52 -0
  406. package/src/components/ui/switch.tsx +32 -0
  407. package/src/components/ui/table.tsx +10 -0
  408. package/src/components/ui/tabs.tsx +77 -0
  409. package/src/components/ui/textarea.tsx +1 -0
  410. package/src/components/ui/tooltip.tsx +54 -0
  411. package/src/hooks/use-copy-to-clipboard.ts +25 -0
  412. package/src/hooks/use-integration-auth.ts +39 -0
  413. package/src/hooks/use-live-media-progress.ts +71 -0
  414. package/src/hooks/use-notion-auth.ts +66 -0
  415. package/src/host.ts +55 -0
  416. package/src/lib/browser-cache.ts +17 -0
  417. package/src/lib/chat/chart-config.ts +1 -0
  418. package/src/lib/chat/data-export.ts +1 -0
  419. package/src/lib/chat/live-tool-progress.ts +69 -0
  420. package/src/lib/chat/message-feedback.ts +1 -0
  421. package/src/lib/chat/table-presentation.ts +1 -0
  422. package/src/lib/chat/tool-output.ts +14 -0
  423. package/src/lib/chat-supporting-clip.ts +16 -0
  424. package/src/lib/constants/stores.ts +12 -0
  425. package/src/lib/constants/tools.ts +178 -0
  426. package/src/lib/error-formatter.ts +30 -0
  427. package/src/lib/jobs-client.ts +27 -0
  428. package/src/lib/media/pdf.ts +57 -0
  429. package/src/lib/media/progress.ts +209 -0
  430. package/src/lib/media/source-selection.ts +47 -0
  431. package/src/lib/media/tool-indexing-estimate.ts +39 -0
  432. package/src/lib/media/video-intelligence-capacity.ts +1 -0
  433. package/src/lib/media/video-runtime-scope.ts +1 -0
  434. package/src/lib/shared/error-formatter.ts +1 -0
  435. package/src/lib/utils.ts +6 -0
  436. package/src/source.css +1 -0
  437. package/src/storage.ts +16 -0
  438. package/src/store/chat-store.ts +19 -0
  439. package/src/store/scrape-store.ts +76 -0
  440. package/src/styles.d.ts +1 -0
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { cn } from '../../../lib/utils';
5
+
6
+ export interface ChatTabItem {
7
+ label: string;
8
+ icon?: React.ReactNode;
9
+ content: React.ReactNode;
10
+ }
11
+
12
+ export interface ChatTabsConfig {
13
+ tabs: ChatTabItem[];
14
+ defaultIndex?: number;
15
+ }
16
+
17
+ /**
18
+ * Premium tabbed interface for chat tool responses.
19
+ * Supports optional icons per tab with a smooth animated underline.
20
+ */
21
+ export function ChatTabs({ config }: { config: ChatTabsConfig }) {
22
+ const { tabs, defaultIndex = 0 } = config;
23
+ const [active, setActive] = useState(defaultIndex);
24
+
25
+ if (!tabs || tabs.length === 0) return null;
26
+ if (tabs.length === 1) return <>{tabs[0].content}</>;
27
+
28
+ return (
29
+ <div className="w-full my-4 animate-in fade-in slide-in-from-bottom-2 duration-500">
30
+ {/* Tab bar */}
31
+ <div className="flex items-center gap-0.5 border-b border-border/30 px-1">
32
+ {tabs.map((tab, i) => (
33
+ <button
34
+ key={i}
35
+ type="button"
36
+ onClick={() => setActive(i)}
37
+ className={cn(
38
+ 'relative flex items-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors',
39
+ 'focus:outline-none focus-visible:outline-none',
40
+ active === i ? 'text-foreground' : 'text-muted-foreground hover:text-foreground/70',
41
+ )}
42
+ >
43
+ {tab.icon && <span className="shrink-0 [&_svg]:size-3.5">{tab.icon}</span>}
44
+ <span>{tab.label}</span>
45
+ {/* Animated underline */}
46
+ {active === i && (
47
+ <span className="absolute bottom-0 left-2 right-2 h-0.5 rounded-full bg-foreground animate-in fade-in zoom-in-75 duration-300" />
48
+ )}
49
+ </button>
50
+ ))}
51
+ </div>
52
+
53
+ {/* Tab content */}
54
+ <div key={active} className="pt-3 animate-in fade-in slide-in-from-bottom-1 duration-400">
55
+ {tabs[active]?.content}
56
+ </div>
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,194 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { ChevronDown, Database, FileText, Globe, Upload, Table2, Image } from 'lucide-react';
5
+ import { ChatDataTable, type DataTableConfig } from './chat-data-table';
6
+ import { Card } from '../../ui/card';
7
+
8
+ interface CorpusDocumentSummary {
9
+ id: string;
10
+ title: string;
11
+ source: string;
12
+ url?: string;
13
+ charCount: number;
14
+ status?: string;
15
+ metadata?: Record<string, any>;
16
+ createdAt: string;
17
+ content?: string;
18
+ }
19
+
20
+ interface CorpusSummary {
21
+ totalDocuments: number;
22
+ bySource: Record<string, number>;
23
+ byStatus: Record<string, number>;
24
+ topMetadataKeys: { key: string; count: number }[];
25
+ totalCharacters: number;
26
+ dateRange: { earliest: string; latest: string } | null;
27
+ }
28
+
29
+ export interface CorpusDataConfig {
30
+ documents: CorpusDocumentSummary[];
31
+ total: number;
32
+ summary: CorpusSummary;
33
+ error?: string;
34
+ }
35
+
36
+ const SOURCE_ICONS: Record<string, React.ReactNode> = {
37
+ paste: <FileText className="size-3" />,
38
+ upload: <Upload className="size-3" />,
39
+ scrape: <Globe className="size-3" />,
40
+ tabular: <Table2 className="size-3" />,
41
+ media: <Image className="size-3" />,
42
+ };
43
+
44
+ const SOURCE_COLORS: Record<string, string> = {
45
+ paste: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
46
+ upload: 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400',
47
+ scrape: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400',
48
+ tabular: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400',
49
+ media: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-400',
50
+ };
51
+
52
+ function formatChars(chars: number): string {
53
+ if (chars >= 1_000_000) return `${(chars / 1_000_000).toFixed(1)}M`;
54
+ if (chars >= 1_000) return `${(chars / 1_000).toFixed(1)}K`;
55
+ return String(chars);
56
+ }
57
+
58
+ export function CorpusDataResult({ config }: { config: CorpusDataConfig }) {
59
+ const [showTable, setShowTable] = useState(false);
60
+ const { documents, total, summary, error } = config;
61
+
62
+ if (error) {
63
+ return (
64
+ <Card className="overflow-hidden animate-in fade-in slide-in-from-bottom-2 duration-500">
65
+ <div className="flex items-center gap-2 px-4 py-3 text-sm text-red-600 dark:text-red-400">
66
+ <Database className="size-4" />
67
+ <span>Failed to fetch corpus data: {error}</span>
68
+ </div>
69
+ </Card>
70
+ );
71
+ }
72
+
73
+ if (summary.totalDocuments === 0) {
74
+ return (
75
+ <div className="flex items-center gap-2 px-2 py-1 text-xs text-muted-foreground animate-in fade-in slide-in-from-bottom-2 duration-500">
76
+ <Database className="size-3" />
77
+ <span>No documents found in the corpus.</span>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ // Build source badges
83
+ const sourceEntries = Object.entries(summary.bySource).sort((a, b) => b[1] - a[1]);
84
+ const statusEntries = Object.entries(summary.byStatus).sort((a, b) => b[1] - a[1]);
85
+
86
+ // Build table config from documents
87
+ const tableConfig: DataTableConfig | null =
88
+ documents.length > 0
89
+ ? {
90
+ columns: ['title', 'source', 'status', 'charCount', 'createdAt'],
91
+ rows: documents.map((d) => ({
92
+ title: d.title,
93
+ source: d.source,
94
+ status: d.status ?? 'unindexed',
95
+ charCount: d.charCount,
96
+ createdAt: d.createdAt.split('T')[0],
97
+ ...(d.url ? { url: d.url } : {}),
98
+ })),
99
+ totalRows: total,
100
+ }
101
+ : null;
102
+
103
+ return (
104
+ <Card className="overflow-hidden animate-in fade-in slide-in-from-bottom-2 duration-500">
105
+ {/* Summary header */}
106
+ <div className="border-b border-border/40 px-4 py-3">
107
+ <div className="flex items-center justify-between">
108
+ <div className="flex items-center gap-2">
109
+ <div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary/10 text-primary">
110
+ <Database className="size-3" />
111
+ </div>
112
+ <span className="text-sm font-medium text-foreground">Corpus Overview</span>
113
+ <span className="rounded-full bg-secondary px-2 py-0.5 text-[11px] tabular-nums text-muted-foreground">
114
+ {total.toLocaleString()} {total === 1 ? 'document' : 'documents'}
115
+ </span>
116
+ </div>
117
+ <span className="text-[11px] tabular-nums text-muted-foreground">
118
+ {formatChars(summary.totalCharacters)} chars
119
+ </span>
120
+ </div>
121
+
122
+ {/* Source & status badges */}
123
+ <div className="mt-2.5 flex flex-wrap gap-1.5">
124
+ {sourceEntries.map(([source, count]) => (
125
+ <span
126
+ key={source}
127
+ className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-medium ${
128
+ SOURCE_COLORS[source] ?? 'bg-secondary text-muted-foreground'
129
+ }`}
130
+ >
131
+ {SOURCE_ICONS[source]}
132
+ {source}
133
+ <span className="tabular-nums font-normal opacity-70">{count}</span>
134
+ </span>
135
+ ))}
136
+ {statusEntries.map(([status, count]) => (
137
+ <span
138
+ key={status}
139
+ className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-medium ${
140
+ status === 'indexed'
141
+ ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
142
+ : 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
143
+ }`}
144
+ >
145
+ {status}
146
+ <span className="tabular-nums font-normal opacity-70">{count}</span>
147
+ </span>
148
+ ))}
149
+ </div>
150
+
151
+ {/* Metadata keys */}
152
+ {summary.topMetadataKeys.length > 0 && (
153
+ <div className="mt-2 flex flex-wrap gap-1">
154
+ <span className="text-[10px] text-muted-foreground/60 self-center mr-0.5">
155
+ metadata:
156
+ </span>
157
+ {summary.topMetadataKeys.slice(0, 8).map(({ key, count }) => (
158
+ <span
159
+ key={key}
160
+ className="rounded bg-secondary/60 px-1.5 py-0.5 text-[10px] text-muted-foreground"
161
+ >
162
+ {key} <span className="tabular-nums opacity-60">({count})</span>
163
+ </span>
164
+ ))}
165
+ </div>
166
+ )}
167
+ </div>
168
+
169
+ {/* Expandable document table */}
170
+ {tableConfig && (
171
+ <>
172
+ <button
173
+ type="button"
174
+ onClick={() => setShowTable((o) => !o)}
175
+ className="flex w-full items-center justify-between px-4 py-2 text-xs text-muted-foreground hover:bg-secondary/30 transition-colors"
176
+ >
177
+ <span>
178
+ {showTable ? 'Hide' : 'Show'} documents ({documents.length}
179
+ {total > documents.length ? ` of ${total}` : ''})
180
+ </span>
181
+ <ChevronDown
182
+ className={`size-3.5 transition-transform ${showTable ? 'rotate-180' : ''}`}
183
+ />
184
+ </button>
185
+ {showTable && (
186
+ <div className="border-t border-border/40 px-3 py-3">
187
+ <ChatDataTable config={tableConfig} />
188
+ </div>
189
+ )}
190
+ </>
191
+ )}
192
+ </Card>
193
+ );
194
+ }
@@ -0,0 +1,345 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import {
5
+ ChevronDown,
6
+ Database,
7
+ ExternalLink,
8
+ FileText,
9
+ Image as ImageIcon,
10
+ Loader2,
11
+ } from 'lucide-react';
12
+ import { isIndeterminateProgress } from '../../../lib/chat/live-tool-progress';
13
+
14
+ function Typewriter({ text, animate }: { text: string; animate: boolean }) {
15
+ const [length, setLength] = useState(animate ? 0 : text.length);
16
+
17
+ useEffect(() => {
18
+ if (!animate) {
19
+ setLength(text.length);
20
+ return;
21
+ }
22
+ setLength((prev) => Math.min(prev, text.length));
23
+ const interval = setInterval(() => {
24
+ setLength((prev) => {
25
+ if (prev >= text.length) {
26
+ clearInterval(interval);
27
+ return prev;
28
+ }
29
+ return prev + 1;
30
+ });
31
+ }, 20);
32
+ return () => clearInterval(interval);
33
+ }, [text, animate]);
34
+
35
+ return <>{text.substring(0, length)}</>;
36
+ }
37
+
38
+ interface KBHit {
39
+ title: string;
40
+ url: string;
41
+ score: number;
42
+ text: string;
43
+ context?: string;
44
+ timelineContext?: Array<{
45
+ role: 'before' | 'matched' | 'after';
46
+ text: string;
47
+ startSecs?: number;
48
+ endSecs?: number;
49
+ }>;
50
+ images?: { imageUrl: string; index: number; pageNumber?: number }[];
51
+ metadata?: {
52
+ mediaAssetId?: string;
53
+ mediaType?: 'image' | 'video' | 'audio';
54
+ fileName?: string;
55
+ startSecs?: number;
56
+ endSecs?: number;
57
+ images?: { imageUrl: string; index: number }[];
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Renders knowledge base retrieval results from tool-searchKnowledgeBase parts.
63
+ */
64
+ export function KnowledgeBaseResult({
65
+ parts,
66
+ isShimmering,
67
+ activity,
68
+ }: {
69
+ parts: any[];
70
+ isShimmering?: boolean;
71
+ activity?: {
72
+ percent: number;
73
+ label: string;
74
+ message: string;
75
+ phase?: 'waking-up' | 'analyzing';
76
+ } | null;
77
+ }) {
78
+ const [open, setOpen] = useState(false);
79
+ const isRunning = parts.some((part: any) => {
80
+ // New format
81
+ if (part.type === 'tool-invocation') {
82
+ const state = part.toolInvocation?.state;
83
+ return state === 'partial-call' || state === 'call';
84
+ }
85
+ // Legacy format
86
+ return part.state === 'input-streaming' || part.state === 'input-available';
87
+ });
88
+
89
+ // Track if this component mounted while running (so we only animate new searches)
90
+ const [isNew] = useState(isRunning);
91
+
92
+ const query = useMemo(() => {
93
+ for (const part of parts) {
94
+ const rawArgs =
95
+ part.type === 'tool-invocation' ? part.toolInvocation?.args : (part.input ?? part.args);
96
+ if (rawArgs?.query) {
97
+ return rawArgs.query;
98
+ }
99
+ }
100
+ return '';
101
+ }, [parts]);
102
+ const hits = useMemo<KBHit[]>(
103
+ () =>
104
+ parts.flatMap((part: any) => {
105
+ const rawResult =
106
+ part.type === 'tool-invocation'
107
+ ? part.toolInvocation?.result
108
+ : (part.output ?? part.result);
109
+ const result = parseToolResult(rawResult);
110
+ return Array.isArray(result?.hits) ? result.hits : [];
111
+ }),
112
+ [parts],
113
+ );
114
+ useEffect(() => {
115
+ setOpen(isRunning);
116
+ }, [isRunning]);
117
+
118
+ return (
119
+ <div className="mb-0 w-full p" data-testid="chat-citations">
120
+ <div className="relative inline-flex items-center">
121
+ {isShimmering && (
122
+ <div className="pointer-events-none absolute inset-0 z-10 animate-pulse bg-linear-to-r from-transparent via-foreground/5 to-transparent" />
123
+ )}
124
+ <button
125
+ type="button"
126
+ onClick={() => hits.length > 0 && setOpen((o) => !o)}
127
+ className={`flex items-center gap-2 pr-2 py-1.5 text-xs transition-colors duration-500 rounded-md ${
128
+ hits.length > 0 ? 'hover:bg-muted/50 cursor-pointer' : 'cursor-default'
129
+ } ${isRunning ? 'text-foreground' : 'text-muted-foreground'}`}
130
+ >
131
+ {isRunning ? (
132
+ <Loader2 className="h-3.5 w-3.5 shrink-0 animate-spin" />
133
+ ) : (
134
+ <Database className="h-3.5 w-3.5 shrink-0" />
135
+ )}
136
+ <span className="truncate max-w-62.5 sm:max-w-100">
137
+ {query ? (
138
+ <>
139
+ <span className="mr-1">{isRunning ? 'Searching' : 'Searched'}</span>
140
+ <span className="font-medium">
141
+ &ldquo;
142
+ <Typewriter text={query} animate={isNew} />
143
+ &rdquo;
144
+ </span>
145
+ </>
146
+ ) : isRunning ? (
147
+ 'Finding relevant sources…'
148
+ ) : hits.length > 0 ? (
149
+ 'Sources'
150
+ ) : (
151
+ 'No matching sources'
152
+ )}
153
+ </span>
154
+ {!isRunning && hits.length > 0 ? (
155
+ <span className="shrink-0 text-[10px] bg-secondary text-foreground px-1.5 py-0.5 rounded-full font-medium ml-1">
156
+ {hits.length} source{hits.length === 1 ? '' : 's'}
157
+ </span>
158
+ ) : null}
159
+ {!isRunning && hits.length > 0 ? (
160
+ <ChevronDown
161
+ className={`h-3 w-3 shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}
162
+ />
163
+ ) : null}
164
+ </button>
165
+ </div>
166
+
167
+ {isRunning && activity ? (
168
+ <div
169
+ className="mt-2.5 max-w-2xl rounded-xl border border-emerald-500/70 bg-background px-3 py-2.5"
170
+ aria-live="polite"
171
+ data-testid="video-analysis-progress"
172
+ >
173
+ <div className="flex items-center justify-between gap-3 text-xs">
174
+ <span className="larkup-shimmer-text font-medium">{activity.message}</span>
175
+ {isIndeterminateProgress(activity.percent) ? null : (
176
+ <span className="shrink-0 tabular-nums text-muted-foreground">
177
+ {Math.max(1, Math.round(activity.percent))}%
178
+ </span>
179
+ )}
180
+ </div>
181
+ <div
182
+ className="mt-2 h-1.5 overflow-hidden rounded-full bg-emerald-950/10 dark:bg-emerald-100/10"
183
+ role="progressbar"
184
+ aria-label={activity.message}
185
+ aria-valuemin={0}
186
+ aria-valuemax={100}
187
+ {...(isIndeterminateProgress(activity.percent)
188
+ ? {}
189
+ : { 'aria-valuenow': Math.max(1, Math.round(activity.percent)) })}
190
+ >
191
+ {isIndeterminateProgress(activity.percent) ? (
192
+ <div className="larkup-progress-shuttle h-full rounded-full bg-emerald-500" />
193
+ ) : (
194
+ <div
195
+ className="h-full rounded-full bg-emerald-500 transition-[width] duration-700 ease-out"
196
+ style={{ width: `${Math.max(6, Math.min(100, activity.percent))}%` }}
197
+ />
198
+ )}
199
+ </div>
200
+ </div>
201
+ ) : null}
202
+
203
+ {open && hits.length > 0 ? (
204
+ <div
205
+ className="mt-2 overflow-hidden rounded-xl border border-border bg-card"
206
+ data-testid="citation-list"
207
+ >
208
+ {isRunning && hits.length === 0 ? (
209
+ <div className="flex items-center gap-2 px-3.5 py-2.5">
210
+ <Loader2 className="size-3 animate-spin text-muted-foreground" />
211
+ </div>
212
+ ) : null}
213
+ {hits.slice(0, 4).map((h, i) => (
214
+ <div
215
+ key={`${h.title}-${i}`}
216
+ className="flex gap-3 border-b border-border/60 px-3.5 py-3 last:border-0"
217
+ data-testid="citation-source"
218
+ >
219
+ <SourceThumbnail hit={h} />
220
+ <div className="min-w-0 flex-1">
221
+ <div className="flex items-start justify-between gap-3">
222
+ <SourceTitle hit={h} />
223
+ {isSafeSourceUrl(h.url) ? (
224
+ <a
225
+ href={h.url}
226
+ target="_blank"
227
+ rel="noreferrer"
228
+ aria-label={`Open ${h.title}`}
229
+ className="mt-0.5 shrink-0 text-muted-foreground transition-colors hover:text-foreground"
230
+ >
231
+ <ExternalLink className="size-3.5" />
232
+ </a>
233
+ ) : null}
234
+ </div>
235
+ {h.metadata?.mediaType ? (
236
+ <div className="mt-1.5 flex items-center gap-2 text-[11px] text-muted-foreground">
237
+ <span className="capitalize">{h.metadata.mediaType} source</span>
238
+ {Number.isFinite(h.metadata.startSecs) ? (
239
+ <span className="rounded-md bg-muted px-1.5 py-0.5 font-mono tabular-nums">
240
+ {formatTimestamp(h.metadata.startSecs!)}
241
+ {Number.isFinite(h.metadata.endSecs)
242
+ ? `–${formatTimestamp(h.metadata.endSecs!)}`
243
+ : ''}
244
+ </span>
245
+ ) : null}
246
+ </div>
247
+ ) : null}
248
+ {contextForHit(h) ? (
249
+ <div className="mt-2 rounded-lg bg-muted/55 px-2.5 py-2">
250
+ <div className="mb-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
251
+ Indexed context
252
+ </div>
253
+ <p
254
+ className="line-clamp-4 text-xs leading-relaxed text-foreground/80"
255
+ dir="auto"
256
+ >
257
+ {contextForHit(h)}
258
+ </p>
259
+ </div>
260
+ ) : null}
261
+ </div>
262
+ </div>
263
+ ))}
264
+ </div>
265
+ ) : null}
266
+ </div>
267
+ );
268
+ }
269
+
270
+ function contextForHit(hit: KBHit) {
271
+ const direct = hit.context?.trim();
272
+ if (direct) return direct;
273
+ const matched = hit.timelineContext?.find((item) => item.role === 'matched')?.text?.trim();
274
+ if (matched) return matched;
275
+ const text = hit.text?.trim();
276
+ return text && !text.startsWith('Verified media evidence is available') ? text : '';
277
+ }
278
+
279
+ function formatTimestamp(seconds: number) {
280
+ const total = Math.max(0, Math.floor(seconds));
281
+ const hours = Math.floor(total / 3_600);
282
+ const minutes = Math.floor((total % 3_600) / 60);
283
+ const rest = total % 60;
284
+ return hours > 0
285
+ ? `${hours}:${String(minutes).padStart(2, '0')}:${String(rest).padStart(2, '0')}`
286
+ : `${minutes}:${String(rest).padStart(2, '0')}`;
287
+ }
288
+
289
+ function parseToolResult(value: unknown): { hits?: KBHit[] } | null {
290
+ if (typeof value === 'string') {
291
+ try {
292
+ return parseToolResult(JSON.parse(value));
293
+ } catch {
294
+ return null;
295
+ }
296
+ }
297
+ return typeof value === 'object' && value !== null ? (value as { hits?: KBHit[] }) : null;
298
+ }
299
+
300
+ function isSafeSourceUrl(url: string | undefined) {
301
+ return Boolean(url && (url.startsWith('/') || /^https?:\/\//i.test(url)));
302
+ }
303
+
304
+ function SourceTitle({ hit }: { hit: KBHit }) {
305
+ const title = hit.title || 'Untitled source';
306
+ const className =
307
+ 'line-clamp-1 text-xs font-medium text-foreground transition-colors hover:text-primary hover:underline';
308
+ return isSafeSourceUrl(hit.url) ? (
309
+ <a href={hit.url} target="_blank" rel="noreferrer" className={className}>
310
+ {title}
311
+ </a>
312
+ ) : (
313
+ <span className="line-clamp-1 text-xs font-medium text-foreground">{title}</span>
314
+ );
315
+ }
316
+
317
+ function SourceThumbnail({ hit }: { hit: KBHit }) {
318
+ const [imageUnavailable, setImageUnavailable] = useState(false);
319
+ const image = (hit.images ?? hit.metadata?.images)?.find((candidate) =>
320
+ isSafeSourceUrl(candidate.imageUrl),
321
+ );
322
+ if (image && !imageUnavailable) {
323
+ return (
324
+ // Retrieval supplies this URL; it is never a model-suggested placeholder.
325
+ // eslint-disable-next-line @next/next/no-img-element
326
+ <img
327
+ src={image.imageUrl}
328
+ alt=""
329
+ className="size-11 shrink-0 rounded-md border border-border object-cover"
330
+ loading="lazy"
331
+ onError={() => setImageUnavailable(true)}
332
+ />
333
+ );
334
+ }
335
+
336
+ return (
337
+ <div className="flex size-11 shrink-0 items-center justify-center rounded-md border border-border bg-muted/40 text-muted-foreground">
338
+ {hit.metadata?.mediaType === 'image' ? (
339
+ <ImageIcon className="size-4" />
340
+ ) : (
341
+ <FileText className="size-4" />
342
+ )}
343
+ </div>
344
+ );
345
+ }