@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,224 @@
1
+ 'use client';
2
+
3
+ import { workspaceUrl, workspaceFetch as fetch } from '../../host';
4
+ import { useCallback, useState } from 'react';
5
+ import type { IndexRun } from '@larkup/core/types';
6
+ import { BriefcaseBusiness, ListTodo, Loader2, TriangleAlert } from 'lucide-react';
7
+ import useSWR from 'swr';
8
+ import { useProject } from '../projects/project-provider';
9
+ import { JobsPanel } from './jobs-panel';
10
+ import {
11
+ isActiveMediaIndexing,
12
+ MediaIndexingJobsPanel,
13
+ type MediaIndexingResponse,
14
+ } from './media-indexing-jobs-panel';
15
+ import {
16
+ Sheet,
17
+ SheetContent,
18
+ SheetDescription,
19
+ SheetHeader,
20
+ SheetTitle,
21
+ SheetTrigger,
22
+ } from '../ui/sheet';
23
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip';
24
+ import { fetchJobsWithSync } from '../../lib/jobs-client';
25
+ import { cn } from '../../lib/utils';
26
+
27
+ const fetcher = async <T,>(url: string): Promise<T> => {
28
+ const response = await fetch(url);
29
+ if (!response.ok) throw new Error('Could not load job progress.');
30
+ return response.json() as Promise<T>;
31
+ };
32
+
33
+ interface IndexStatus {
34
+ unindexedCount: number;
35
+ running: boolean;
36
+ run: IndexRun | null;
37
+ blockers: string[];
38
+ }
39
+
40
+ export function JobsTrackingButton() {
41
+ const [open, setOpen] = useState(false);
42
+ const [filter, setFilter] = useState<'running' | 'all'>('running');
43
+ const { activeProject } = useProject();
44
+ const serverId = activeProject?.id;
45
+
46
+ const {
47
+ data: jobsData,
48
+ error: jobsError,
49
+ mutate: mutateJobs,
50
+ } = useSWR(workspaceUrl('/api/jobs'), fetchJobsWithSync, {
51
+ refreshInterval: (data) =>
52
+ data?.jobs?.some((job) => job.status === 'running' || job.status === 'queued') ? 4_000 : 0,
53
+ revalidateOnFocus: true,
54
+ });
55
+ const {
56
+ data: indexData,
57
+ error: indexError,
58
+ mutate: mutateIndex,
59
+ } = useSWR<IndexStatus>(workspaceUrl('/api/index'), fetcher, {
60
+ refreshInterval: (data) => (data?.running ? 2_000 : 0),
61
+ revalidateOnFocus: true,
62
+ });
63
+ const mediaUrl = workspaceUrl(
64
+ `/api/media?type=all${serverId ? `&serverId=${encodeURIComponent(serverId)}` : ''}`,
65
+ );
66
+ const {
67
+ data: mediaData,
68
+ error: mediaError,
69
+ mutate: mutateMedia,
70
+ } = useSWR<MediaIndexingResponse>(mediaUrl, fetcher, {
71
+ refreshInterval: (data) => (data?.assets?.some(isActiveMediaIndexing) ? 2_000 : 0),
72
+ revalidateOnFocus: true,
73
+ });
74
+
75
+ const jobs = jobsData?.jobs ?? [];
76
+ const activeCrawlJobs = jobs.filter((job) => job.status === 'running' || job.status === 'queued');
77
+ const activeMediaAssets = (mediaData?.assets ?? []).filter(isActiveMediaIndexing);
78
+ const indexRunning = indexData?.running ?? false;
79
+ const activeJobCount = activeCrawlJobs.length + activeMediaAssets.length + (indexRunning ? 1 : 0);
80
+ const hasActiveJobs = activeJobCount > 0;
81
+ const hasAnyJobs = jobs.length > 0 || activeMediaAssets.length > 0 || indexRunning;
82
+ const loadError = jobsError || indexError || mediaError;
83
+ const visibleJobs = filter === 'running' ? activeCrawlJobs : jobs;
84
+
85
+ const refreshAll = useCallback(() => {
86
+ void Promise.all([mutateJobs(), mutateIndex(), mutateMedia()]);
87
+ }, [mutateIndex, mutateJobs, mutateMedia]);
88
+
89
+ return (
90
+ <Sheet
91
+ open={open}
92
+ onOpenChange={(nextOpen) => {
93
+ setOpen(nextOpen);
94
+ if (nextOpen) refreshAll();
95
+ }}
96
+ >
97
+ <TooltipProvider delay={50}>
98
+ <Tooltip>
99
+ <TooltipTrigger
100
+ render={
101
+ <SheetTrigger
102
+ render={
103
+ <button
104
+ type="button"
105
+ aria-label={
106
+ hasActiveJobs ? `Jobs tracking, ${activeJobCount} active` : 'Jobs tracking'
107
+ }
108
+ className={cn(
109
+ 'relative flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground',
110
+ hasActiveJobs &&
111
+ 'bg-emerald-500/10 text-emerald-700 hover:bg-emerald-500/15 hover:text-emerald-700 dark:text-emerald-400',
112
+ )}
113
+ >
114
+ <ListTodo className="size-4" />
115
+ {hasActiveJobs && (
116
+ <span
117
+ aria-hidden="true"
118
+ className="absolute -right-1 -top-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-emerald-500 px-0.5 text-[9px] font-semibold leading-none text-white ring-2 ring-background motion-safe:animate-pulse"
119
+ >
120
+ {activeJobCount > 9 ? '9+' : activeJobCount}
121
+ </span>
122
+ )}
123
+ </button>
124
+ }
125
+ />
126
+ }
127
+ />
128
+ <TooltipContent>{hasActiveJobs ? 'Active jobs' : 'Jobs'}</TooltipContent>
129
+ </Tooltip>
130
+ </TooltipProvider>
131
+
132
+ <SheetContent side="right" className="w-[calc(100vw-1rem)]! max-w-2xl! gap-0 p-0 sm:w-2xl!">
133
+ <SheetHeader className="border-b border-border px-5 py-4 pr-14">
134
+ <div className="flex items-center gap-2.5">
135
+ <div
136
+ className={cn(
137
+ 'flex size-8 items-center justify-center rounded-lg bg-muted text-muted-foreground',
138
+ hasActiveJobs && 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400',
139
+ )}
140
+ >
141
+ <ListTodo className="size-4" />
142
+ </div>
143
+ <div className="min-w-0">
144
+ <SheetTitle className="text-sm font-semibold">Jobs</SheetTitle>
145
+ <SheetDescription className="text-xs">
146
+ {hasActiveJobs
147
+ ? `${activeJobCount} background ${activeJobCount === 1 ? 'job' : 'jobs'} active`
148
+ : 'Track indexing and website jobs'}
149
+ </SheetDescription>
150
+ </div>
151
+ </div>
152
+ </SheetHeader>
153
+
154
+ <div className="min-h-0 flex-1 overflow-y-auto p-5">
155
+ {loadError && (
156
+ <div className="mb-4 flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/5 px-3 py-2.5 text-xs text-amber-800 dark:text-amber-300">
157
+ <TriangleAlert className="mt-0.5 size-3.5 shrink-0" />
158
+ Some job progress could not be refreshed. The available results are shown below.
159
+ </div>
160
+ )}
161
+
162
+ {!hasAnyJobs ? (
163
+ <div className="flex min-h-64 flex-col items-center justify-center text-center">
164
+ <div className="mb-3 flex size-10 items-center justify-center rounded-xl border border-border bg-muted/40 text-muted-foreground">
165
+ <BriefcaseBusiness className="size-4.5" />
166
+ </div>
167
+ <p className="text-sm font-medium text-foreground">No jobs yet</p>
168
+ <p className="mt-1 max-w-xs text-xs leading-relaxed text-muted-foreground">
169
+ File indexing, media processing, and website jobs will appear here while they run.
170
+ </p>
171
+ </div>
172
+ ) : (
173
+ <div className="space-y-5">
174
+ <MediaIndexingJobsPanel assets={activeMediaAssets} />
175
+
176
+ {indexRunning && (
177
+ <div className="flex items-center gap-3 rounded-lg border border-border bg-background px-3 py-2.5">
178
+ <Loader2 className="size-4 shrink-0 animate-spin text-emerald-600" />
179
+ <div className="min-w-0">
180
+ <p className="text-xs font-medium text-foreground">Indexing files</p>
181
+ <p className="text-[11px] text-muted-foreground">
182
+ Running in the background — new files will join the queue.
183
+ </p>
184
+ </div>
185
+ </div>
186
+ )}
187
+
188
+ {jobs.length > 0 && (
189
+ <section
190
+ className={cn(
191
+ (activeMediaAssets.length > 0 || indexRunning) && 'border-t border-border pt-4',
192
+ )}
193
+ >
194
+ <div className="mb-3 flex items-center gap-2">
195
+ {[
196
+ { id: 'running', label: 'Running' },
197
+ { id: 'all', label: 'All jobs' },
198
+ ].map((tab) => (
199
+ <button
200
+ key={tab.id}
201
+ type="button"
202
+ aria-pressed={filter === tab.id}
203
+ onClick={() => setFilter(tab.id as 'running' | 'all')}
204
+ className={cn(
205
+ 'rounded-full px-3 py-1 text-xs font-medium transition-colors',
206
+ filter === tab.id
207
+ ? 'bg-muted text-foreground'
208
+ : 'border border-border text-muted-foreground hover:bg-muted/50 hover:text-foreground',
209
+ )}
210
+ >
211
+ {tab.label}
212
+ </button>
213
+ ))}
214
+ </div>
215
+ <JobsPanel jobs={visibleJobs} onChanged={refreshAll} />
216
+ </section>
217
+ )}
218
+ </div>
219
+ )}
220
+ </div>
221
+ </SheetContent>
222
+ </Sheet>
223
+ );
224
+ }
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { motion } from 'framer-motion';
5
+ import {
6
+ formatMediaProgressPercent,
7
+ isMediaStepTelemetryStale,
8
+ type MediaProgressStep,
9
+ } from '../../lib/media/progress';
10
+ import { cn } from '../../lib/utils';
11
+
12
+ export function LiveMediaProgress({
13
+ value,
14
+ step,
15
+ paused = false,
16
+ label = 'Overall indexing progress',
17
+ className,
18
+ }: {
19
+ value: number;
20
+ step?: MediaProgressStep;
21
+ paused?: boolean;
22
+ label?: string;
23
+ className?: string;
24
+ }) {
25
+ const [now, setNow] = useState(() => Date.now());
26
+ useEffect(() => {
27
+ if (paused) return;
28
+ const interval = window.setInterval(() => setNow(Date.now()), 1_000);
29
+ return () => window.clearInterval(interval);
30
+ }, [paused]);
31
+
32
+ const bounded = Math.max(0, Math.min(100, Number.isFinite(value) ? value : 0));
33
+ const stale = isMediaStepTelemetryStale(step, now);
34
+ const live = !paused && !stale && bounded < 100;
35
+ const formatted = formatMediaProgressPercent(bounded);
36
+ const liveTrackStart = Math.min(96, Math.max(0, bounded));
37
+ const status = paused
38
+ ? `${formatted}% progress, paused`
39
+ : stale
40
+ ? `${formatted}% progress, waiting for worker update`
41
+ : `${formatted}% progress, worker activity`;
42
+
43
+ return (
44
+ <div
45
+ role="progressbar"
46
+ aria-label={label}
47
+ aria-valuemin={0}
48
+ aria-valuemax={100}
49
+ aria-valuenow={Math.round(bounded * 10) / 10}
50
+ aria-valuetext={status}
51
+ data-live-progress={live ? 'active' : stale ? 'stale' : paused ? 'paused' : 'idle'}
52
+ className={cn(
53
+ 'relative h-1 w-full overflow-hidden rounded-full bg-muted',
54
+ stale && 'opacity-60',
55
+ className,
56
+ )}
57
+ >
58
+ <motion.div
59
+ className="absolute inset-y-0 left-0 rounded-full bg-emerald-500 dark:bg-emerald-400"
60
+ animate={{ width: `${bounded}%` }}
61
+ transition={{ duration: 0.55, ease: 'easeOut' }}
62
+ />
63
+ {live ? (
64
+ <div
65
+ data-testid="live-indexing-progress-pulse"
66
+ className="absolute inset-y-0 right-0 overflow-hidden"
67
+ style={{ left: `${liveTrackStart}%` }}
68
+ >
69
+ <motion.span
70
+ className="absolute inset-y-0 w-1/4 rounded-full bg-linear-to-r from-transparent via-emerald-400/75 to-transparent dark:via-emerald-300/75"
71
+ initial={{ x: '-120%' }}
72
+ animate={{ x: ['-120%', '420%'] }}
73
+ transition={{ duration: 1.35, ease: 'linear', repeat: Infinity }}
74
+ />
75
+ </div>
76
+ ) : null}
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,190 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { motion } from 'framer-motion';
5
+ import { AudioLines, Clock3, Image as ImageIcon, Loader2, Video } from 'lucide-react';
6
+ import { useLiveMediaProgress } from '../../hooks/use-live-media-progress';
7
+ import { LiveMediaProgress } from './live-media-progress';
8
+ import {
9
+ describeActiveMediaStep,
10
+ estimateMediaStepRemainingSeconds,
11
+ formatMediaProgressPercent,
12
+ isFinalizingMediaStep,
13
+ mediaProcessingStartedAt,
14
+ primaryRunningMediaStep,
15
+ } from '../../lib/media/progress';
16
+
17
+ export interface MediaIndexingAsset {
18
+ id: string;
19
+ type: 'image' | 'video' | 'audio';
20
+ fileName: string;
21
+ processingStatus: 'pending' | 'processing' | 'completed' | 'failed';
22
+ processingMessage?: string;
23
+ processingProgress?: number;
24
+ processingPaused?: boolean;
25
+ processingStartedAt?: string;
26
+ durationSecs?: number;
27
+ createdAt: string;
28
+ processingSteps?: Array<{
29
+ stage: string;
30
+ status: 'waiting' | 'running' | 'completed' | 'skipped' | 'failed';
31
+ percent?: number;
32
+ current?: number;
33
+ total?: number;
34
+ unit?: string;
35
+ estimatedRemainingSeconds?: number;
36
+ elapsedSeconds?: number;
37
+ sequence?: number;
38
+ message?: string;
39
+ startedAt?: string;
40
+ updatedAt?: string;
41
+ }>;
42
+ }
43
+
44
+ export interface MediaIndexingResponse {
45
+ assets: MediaIndexingAsset[];
46
+ }
47
+
48
+ export function isActiveMediaIndexing(asset: MediaIndexingAsset): boolean {
49
+ return (
50
+ asset.processingStatus === 'processing' ||
51
+ (asset.processingStatus === 'pending' && Boolean(asset.processingMessage))
52
+ );
53
+ }
54
+
55
+ export function MediaIndexingJobsPanel({ assets }: { assets: MediaIndexingAsset[] }) {
56
+ if (assets.length === 0) return null;
57
+
58
+ return (
59
+ <section aria-label="Media indexing progress" className="space-y-2">
60
+ <div className="flex items-center gap-2 text-xs font-medium text-foreground">
61
+ <Video className="size-3.5 text-emerald-600" />
62
+ Media indexing
63
+ <span className="text-muted-foreground">{assets.length} active</span>
64
+ </div>
65
+ <div className="space-y-2">
66
+ {assets.map((asset) => (
67
+ <MediaIndexingJob key={asset.id} asset={asset} />
68
+ ))}
69
+ </div>
70
+ </section>
71
+ );
72
+ }
73
+
74
+ function MediaIndexingJob({ asset }: { asset: MediaIndexingAsset }) {
75
+ const Icon = asset.type === 'video' ? Video : asset.type === 'audio' ? AudioLines : ImageIcon;
76
+ const progress = useLiveMediaProgress(asset);
77
+ const activeStep = primaryRunningMediaStep(asset.processingSteps);
78
+ const stepDescription = describeActiveMediaStep(activeStep);
79
+ const isFinalizing = isFinalizingMediaStep(activeStep);
80
+ const isColdStart = !asset.processingPaused && progress === 0;
81
+
82
+ return (
83
+ <article className="rounded-md border border-border/70 bg-background/70 px-3 py-2.5">
84
+ <div className="flex min-w-0 items-start gap-2.5">
85
+ <div className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md bg-emerald-500/10 text-emerald-700 dark:text-emerald-400">
86
+ <Icon className="size-3.5" />
87
+ </div>
88
+ <div className="min-w-0 flex-1 space-y-1.5">
89
+ <div className="flex min-w-0 items-center gap-2">
90
+ <p className="truncate text-xs font-medium text-foreground">{asset.fileName}</p>
91
+ <ElapsedTime startTime={mediaProcessingStartedAt(asset)} />
92
+ </div>
93
+ <div className="flex min-w-0 items-center gap-1.5" aria-live="polite">
94
+ {asset.processingPaused ? (
95
+ <Clock3 className="size-3 shrink-0 text-amber-600" />
96
+ ) : (
97
+ <Loader2 className="size-3 shrink-0 animate-spin text-emerald-600" />
98
+ )}
99
+ <p className="truncate text-[11px] text-muted-foreground">
100
+ {asset.processingPaused
101
+ ? 'Paused — resume when you are ready.'
102
+ : asset.processingMessage || 'Preparing media indexing…'}
103
+ </p>
104
+ </div>
105
+ {isFinalizing ? (
106
+ <div
107
+ role="progressbar"
108
+ aria-label={`Finalizing ${asset.fileName}`}
109
+ aria-valuetext="Finalizing verified results"
110
+ className="relative h-2 min-h-2 w-full overflow-hidden rounded-full bg-emerald-100 dark:bg-emerald-950/60"
111
+ >
112
+ <motion.div
113
+ className="absolute inset-y-0 left-0 w-2/5 rounded-full bg-emerald-500 dark:bg-emerald-400"
114
+ initial={{ x: '-120%' }}
115
+ animate={{ x: ['-120%', '270%'] }}
116
+ transition={{ duration: 1.15, ease: 'easeInOut', repeat: Infinity }}
117
+ />
118
+ </div>
119
+ ) : (
120
+ <LiveMediaProgress
121
+ value={progress}
122
+ step={activeStep}
123
+ paused={asset.processingPaused}
124
+ label={`Overall indexing progress for ${asset.fileName}`}
125
+ className="h-1.5"
126
+ />
127
+ )}
128
+ <div className="flex min-w-0 items-center justify-between gap-2 text-[10px] text-muted-foreground">
129
+ <span className="truncate">
130
+ {stepDescription || (isColdStart ? 'Starting GPU worker…' : 'Processing…')}
131
+ </span>
132
+ <div className="flex shrink-0 items-center gap-1.5 tabular-nums">
133
+ <EstimatedStepTime step={activeStep} />
134
+ <span>
135
+ {isFinalizing ? 'Finalizing' : `${formatMediaProgressPercent(progress)}%`}
136
+ </span>
137
+ </div>
138
+ </div>
139
+ {isColdStart && (
140
+ <p className="text-[10px] text-muted-foreground/80">
141
+ The first GPU job can take a minute or two to start.
142
+ </p>
143
+ )}
144
+ </div>
145
+ </div>
146
+ </article>
147
+ );
148
+ }
149
+
150
+ function EstimatedStepTime({
151
+ step,
152
+ }: {
153
+ step: NonNullable<MediaIndexingAsset['processingSteps']>[number] | undefined;
154
+ }) {
155
+ const [now, setNow] = useState(() => Date.now());
156
+ useEffect(() => {
157
+ const interval = setInterval(() => setNow(Date.now()), 15_000);
158
+ return () => clearInterval(interval);
159
+ }, []);
160
+ const remainingSeconds = estimateMediaStepRemainingSeconds(step, now);
161
+ if (remainingSeconds === null) return null;
162
+ const minutes = Math.round(remainingSeconds / 60);
163
+ const label =
164
+ minutes < 1
165
+ ? '<1m'
166
+ : minutes < 60
167
+ ? `~${minutes}m`
168
+ : `~${Math.floor(minutes / 60)}h ${minutes % 60}m`;
169
+ return <span title="Estimated time remaining from completed remote work">{label} left</span>;
170
+ }
171
+
172
+ function ElapsedTime({ startTime }: { startTime: string }) {
173
+ const [elapsed, setElapsed] = useState(0);
174
+
175
+ useEffect(() => {
176
+ const start = new Date(startTime).getTime();
177
+ const update = () => setElapsed(Math.max(0, Math.floor((Date.now() - start) / 1000)));
178
+ update();
179
+ const interval = setInterval(update, 1_000);
180
+ return () => clearInterval(interval);
181
+ }, [startTime]);
182
+
183
+ const minutes = Math.floor(elapsed / 60);
184
+ const seconds = elapsed % 60;
185
+ return (
186
+ <span className="shrink-0 font-mono tabular-nums text-[10px] text-muted-foreground/70">
187
+ {minutes}:{seconds.toString().padStart(2, '0')}
188
+ </span>
189
+ );
190
+ }