@larkup/workspace-app 0.0.0-stage → 0.1.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,909 @@
1
+ 'use client';
2
+
3
+ import { workspaceUrl, workspaceFetch as fetch } from '../../../host';
4
+ import { SettingsCardHeader } from '@larkup/workspace-ui';
5
+
6
+ import { useState, useEffect, useMemo } from 'react';
7
+ import useSWR from 'swr';
8
+ import { Eye, EyeOff, Loader2, Save, Sparkles, MessageCircle, Plus, ScanEye } from 'lucide-react';
9
+ import { Button } from '@larkup/workspace-ui/button';
10
+ import { Input } from '../../ui/input';
11
+ import { Label } from '@larkup/workspace-ui/label';
12
+ import { cn } from '../../../lib/utils';
13
+ import { Select, SelectContent, SelectItem, SelectTrigger } from '@larkup/workspace-ui/select';
14
+ import {
15
+ Card,
16
+ CardContent,
17
+ CardDescription,
18
+ CardHeader,
19
+ CardTitle,
20
+ CardFooter,
21
+ } from '../../ui/card';
22
+ import { toast } from 'sonner';
23
+ import { useProject } from '../../projects/project-provider';
24
+ import { PROVIDER_META, ProviderIcon } from '@larkup/workspace-ui/provider-icon';
25
+ import { ProjectFormDialog } from '../../projects/project-form-dialog';
26
+
27
+ import { CustomModelModal } from '../../configure/custom-model-modal';
28
+ import { ModelCombobox } from './model-combobox';
29
+ import type { RagConfig, CustomModelConfig, EmbeddingModelDescriptor } from '@larkup/core/types';
30
+ import { EMBEDDING_DIMENSIONS } from '@larkup/core/embeddings/registry';
31
+
32
+ const fetcher = (url: string) => fetch(url).then((r) => r.json() as Promise<{ config: RagConfig }>);
33
+
34
+ const EMBEDDING_PROVIDER_LIST = [
35
+ 'vercel_ai_gateway',
36
+ 'openai',
37
+ 'google',
38
+ 'deepseek',
39
+ 'mistral',
40
+ 'cohere',
41
+ ] as const;
42
+
43
+ const CHAT_PROVIDER_LIST = [
44
+ 'vercel_ai_gateway',
45
+ 'openai',
46
+ 'anthropic',
47
+ 'google',
48
+ 'deepseek',
49
+ 'mistral',
50
+ 'cohere',
51
+ ] as const;
52
+
53
+ const VISION_PROVIDER_LIST = new Set([
54
+ 'vercel_ai_gateway',
55
+ 'openai',
56
+ 'anthropic',
57
+ 'google',
58
+ 'mistral',
59
+ 'cohere',
60
+ 'deepseek',
61
+ ]);
62
+
63
+ export function ModelsSection() {
64
+ const { activeProject } = useProject();
65
+ const serverId = activeProject?.id;
66
+
67
+ const configUrl = serverId
68
+ ? workspaceUrl(`/api/config?serverId=${encodeURIComponent(serverId)}`)
69
+ : workspaceUrl('/api/config');
70
+ const { data, isLoading, mutate } = useSWR(configUrl, fetcher);
71
+
72
+ const { data: toolsData } = useSWR(workspaceUrl('/api/marketplace'), (url: string) =>
73
+ fetch(url).then((r) => r.json()),
74
+ );
75
+
76
+ const [form, setForm] = useState<Partial<RagConfig>>({});
77
+ const [saving, setSaving] = useState<string | null>(null);
78
+ const [showEmbeddingKey, setShowEmbeddingKey] = useState(false);
79
+ const [showChatKey, setShowChatKey] = useState(false);
80
+ const [showVisionKey, setShowVisionKey] = useState(false);
81
+ const [showToolKeys, setShowToolKeys] = useState<Record<string, boolean>>({});
82
+
83
+ const currentChatProvider = form.chatProvider || form.embeddingProvider || 'openai';
84
+ const statusKey = serverId
85
+ ? workspaceUrl(
86
+ `/api/chat/status?serverId=${encodeURIComponent(serverId)}&provider=${currentChatProvider}`,
87
+ )
88
+ : workspaceUrl(`/api/chat/status?provider=${currentChatProvider}`);
89
+ const { data: chatStatus } = useSWR(statusKey, (url: string) => fetch(url).then((r) => r.json()));
90
+
91
+ const embeddingModels: EmbeddingModelDescriptor[] = chatStatus?.availableEmbeddingModels ?? [];
92
+ const visionModels: Array<{
93
+ id: string;
94
+ label: string;
95
+ provider: string;
96
+ availableFrom?: string[];
97
+ }> = chatStatus?.availableVisionModels ?? [];
98
+ const visionProviders = useMemo(() => {
99
+ const providers = new Set(
100
+ visionModels.flatMap((model) => model.availableFrom ?? [model.provider]),
101
+ );
102
+ return [...providers].filter(
103
+ (provider) =>
104
+ VISION_PROVIDER_LIST.has(provider) && PROVIDER_META[provider as keyof typeof PROVIDER_META],
105
+ );
106
+ }, [visionModels]);
107
+ const EMBEDDING_BY_PROVIDER = useMemo(() => {
108
+ return embeddingModels.reduce<Record<string, EmbeddingModelDescriptor[]>>((acc, m) => {
109
+ (acc[m.provider] ??= []).push(m);
110
+ return acc;
111
+ }, {});
112
+ }, [embeddingModels]);
113
+
114
+ const { data: indexData } = useSWR(workspaceUrl('/api/index'), (url: string) =>
115
+ fetch(url).then((r) => r.json()),
116
+ );
117
+ const indexedRun = indexData?.run?.status === 'completed' ? indexData.run : null;
118
+
119
+ const [newServerModalOpen, setNewServerModalOpen] = useState(false);
120
+ const [errors, setErrors] = useState<Record<string, string>>({});
121
+ const [customEmbeddingModalOpen, setCustomEmbeddingModalOpen] = useState(false);
122
+ const [customChatModalOpen, setCustomChatModalOpen] = useState(false);
123
+ const [customVisionModalOpen, setCustomVisionModalOpen] = useState(false);
124
+ const [isOtherEmbedding, setIsOtherEmbedding] = useState(false);
125
+ const [isOtherChat, setIsOtherChat] = useState(false);
126
+ const [isOtherVision, setIsOtherVision] = useState(false);
127
+
128
+ useEffect(() => {
129
+ if (data?.config) {
130
+ setForm({
131
+ ...data.config,
132
+ chatProvider: data.config.chatProvider || data.config.embeddingProvider,
133
+ chatApiKey: data.config.chatApiKey || data.config.embeddingApiKey,
134
+ visionProvider:
135
+ data.config.visionProvider || data.config.chatProvider || data.config.embeddingProvider,
136
+ visionApiKey:
137
+ data.config.visionApiKey || data.config.chatApiKey || data.config.embeddingApiKey,
138
+ toolConfigs: data.config.toolConfigs || {},
139
+ });
140
+ }
141
+ }, [data]);
142
+
143
+ const dirtyEmbedding =
144
+ form.embeddingProvider !== data?.config?.embeddingProvider ||
145
+ form.embeddingModelId !== data?.config?.embeddingModelId ||
146
+ form.embeddingApiKey !== data?.config?.embeddingApiKey ||
147
+ JSON.stringify(form.customEmbeddings) !== JSON.stringify(data?.config?.customEmbeddings);
148
+
149
+ const dirtyChat =
150
+ form.chatProvider !== (data?.config?.chatProvider || data?.config?.embeddingProvider) ||
151
+ form.chatModelId !== data?.config?.chatModelId ||
152
+ form.chatApiKey !== (data?.config?.chatApiKey || data?.config?.embeddingApiKey) ||
153
+ JSON.stringify(form.customChatModels) !== JSON.stringify(data?.config?.customChatModels);
154
+ const savedVisionProvider =
155
+ data?.config?.visionProvider || data?.config?.chatProvider || data?.config?.embeddingProvider;
156
+ const savedVisionApiKey =
157
+ data?.config?.visionApiKey || data?.config?.chatApiKey || data?.config?.embeddingApiKey;
158
+ const dirtyVision =
159
+ form.visionProvider !== savedVisionProvider ||
160
+ form.visionModelId !== (data?.config?.visionModelId || '') ||
161
+ form.visionApiKey !== savedVisionApiKey ||
162
+ JSON.stringify(form.customVisionModels) !== JSON.stringify(data?.config?.customVisionModels);
163
+
164
+ const clearError = (key: string) => {
165
+ if (errors[key]) {
166
+ setErrors((prev) => {
167
+ const next = { ...prev };
168
+ delete next[key];
169
+ return next;
170
+ });
171
+ }
172
+ };
173
+
174
+ function getEmbeddingDimensions(modelId: string | undefined): number | undefined {
175
+ if (!modelId) return undefined;
176
+ return (
177
+ embeddingModels.find((model) => model.id === modelId)?.dimensions ??
178
+ form.customEmbeddings?.find((model) => `custom:${model.modelName}` === modelId)?.dimensions ??
179
+ EMBEDDING_DIMENSIONS[modelId]?.dimensions
180
+ );
181
+ }
182
+
183
+ function canUseEmbeddingModel(modelId: string | undefined, knownDimensions?: number): boolean {
184
+ if (!indexedRun) return true;
185
+ const dimensions = knownDimensions ?? getEmbeddingDimensions(modelId);
186
+ if (dimensions === indexedRun.dimensions) return true;
187
+
188
+ toast.error('This embedding model is not compatible with the current index', {
189
+ description:
190
+ dimensions === undefined
191
+ ? `The current index uses ${indexedRun.dimensions} dimensions. Choose a model with the same dimension or create a new project.`
192
+ : `The current index uses ${indexedRun.dimensions} dimensions, but this model uses ${dimensions}. Create a new project to use it.`,
193
+ duration: Number.POSITIVE_INFINITY,
194
+ action: {
195
+ label: 'New Project',
196
+ onClick: () => setNewServerModalOpen(true),
197
+ },
198
+ });
199
+ return false;
200
+ }
201
+
202
+ async function handleSave(section: string) {
203
+ if (!data?.config) return;
204
+
205
+ let hasError = false;
206
+ const newErrors: Record<string, string> = {};
207
+
208
+ if (section === 'embedding') {
209
+ if (!form.embeddingProvider) {
210
+ newErrors.embeddingProvider = 'Required';
211
+ hasError = true;
212
+ }
213
+ if (!form.embeddingApiKey) {
214
+ newErrors.embeddingApiKey = 'Required';
215
+ hasError = true;
216
+ }
217
+ if (!form.embeddingModelId) {
218
+ newErrors.embeddingModelId = 'Required';
219
+ hasError = true;
220
+ } else if (!canUseEmbeddingModel(form.embeddingModelId)) {
221
+ return;
222
+ }
223
+ } else if (section === 'chat') {
224
+ if (!form.chatProvider) {
225
+ newErrors.chatProvider = 'Required';
226
+ hasError = true;
227
+ }
228
+ if (!form.chatApiKey) {
229
+ newErrors.chatApiKey = 'Required';
230
+ hasError = true;
231
+ }
232
+ } else if (section === 'vision') {
233
+ if (!form.visionProvider) {
234
+ newErrors.visionProvider = 'Required';
235
+ hasError = true;
236
+ }
237
+ if (!form.visionApiKey) {
238
+ newErrors.visionApiKey = 'Required';
239
+ hasError = true;
240
+ }
241
+ } else if (section.startsWith('tool:')) {
242
+ const toolId = section.split(':')[1];
243
+ const tool = toolsData?.tools?.find((t: any) => t.id === toolId);
244
+ if (tool?.configSchema) {
245
+ for (const schema of tool.configSchema) {
246
+ if (
247
+ !form.toolConfigs?.[toolId]?.[schema.key] &&
248
+ schema.type !== 'password' &&
249
+ schema.type !== 'text'
250
+ ) {
251
+ // For passwords we allow empty since it could be optional or fallback
252
+ // You can add stricter validation if schema.required was added to manifest.
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ setErrors(newErrors);
259
+ if (hasError) {
260
+ toast.error('Please fill in all required fields', {
261
+ duration: Number.POSITIVE_INFINITY,
262
+ });
263
+ return;
264
+ }
265
+
266
+ setSaving(section);
267
+ try {
268
+ const payload = { ...data.config };
269
+
270
+ if (section === 'embedding') {
271
+ if (form.embeddingProvider !== undefined)
272
+ payload.embeddingProvider = form.embeddingProvider;
273
+ if (form.embeddingModelId !== undefined) payload.embeddingModelId = form.embeddingModelId;
274
+ if (form.embeddingApiKey !== undefined) payload.embeddingApiKey = form.embeddingApiKey;
275
+ if (form.customEmbeddings !== undefined) payload.customEmbeddings = form.customEmbeddings;
276
+ } else if (section === 'chat') {
277
+ if (form.chatProvider !== undefined) payload.chatProvider = form.chatProvider;
278
+ if (form.chatModelId !== undefined) payload.chatModelId = form.chatModelId;
279
+ if (form.chatApiKey !== undefined) payload.chatApiKey = form.chatApiKey;
280
+ if (form.customChatModels !== undefined) payload.customChatModels = form.customChatModels;
281
+ } else if (section === 'vision') {
282
+ if (form.visionProvider !== undefined) payload.visionProvider = form.visionProvider;
283
+ if (form.visionModelId !== undefined) payload.visionModelId = form.visionModelId;
284
+ if (form.visionApiKey !== undefined) payload.visionApiKey = form.visionApiKey;
285
+ if (form.customVisionModels !== undefined)
286
+ payload.customVisionModels = form.customVisionModels;
287
+ } else if (section.startsWith('tool:')) {
288
+ payload.toolConfigs = form.toolConfigs;
289
+ }
290
+
291
+ const verifyPayload: any = {};
292
+ if (section === 'embedding') {
293
+ verifyPayload.embeddingProvider = payload.embeddingProvider;
294
+ verifyPayload.embeddingApiKey = payload.embeddingApiKey;
295
+ verifyPayload.embeddingModelId = payload.embeddingModelId;
296
+ verifyPayload.customEmbeddings = payload.customEmbeddings;
297
+ } else if (section === 'chat') {
298
+ verifyPayload.chatProvider = payload.chatProvider;
299
+ verifyPayload.chatApiKey = payload.chatApiKey;
300
+ verifyPayload.chatModelId = payload.chatModelId;
301
+ verifyPayload.customChatModels = payload.customChatModels;
302
+ } else if (section === 'vision') {
303
+ // Vision API compatibility is checked from the catalog on save. A
304
+ // frame request is deliberately not sent just to save preferences.
305
+ } else if (section.startsWith('tool:')) {
306
+ // Skip verification for arbitrary tool configs right now
307
+ // We'll trust the payload
308
+ }
309
+
310
+ if (section !== 'vision' && !section.startsWith('tool:')) {
311
+ const verifyRes = await fetch(workspaceUrl('/api/config/verify'), {
312
+ method: 'POST',
313
+ headers: { 'Content-Type': 'application/json' },
314
+ body: JSON.stringify(verifyPayload),
315
+ });
316
+
317
+ if (!verifyRes.ok) {
318
+ const err = await verifyRes.json();
319
+ throw new Error(err.error || 'Verification failed');
320
+ }
321
+ }
322
+
323
+ const res = await fetch(configUrl, {
324
+ method: 'PUT',
325
+ headers: { 'Content-Type': 'application/json' },
326
+ body: JSON.stringify(payload),
327
+ });
328
+ const json = await res.json();
329
+ if (!res.ok) throw new Error(json.error ?? 'Failed to save');
330
+
331
+ setForm((prev) => ({ ...prev, ...json.config }));
332
+ await mutate(json, { revalidate: false });
333
+ toast.success('AI model settings saved', {
334
+ duration: Number.POSITIVE_INFINITY,
335
+ });
336
+ } catch (err) {
337
+ toast.error(err instanceof Error ? err.message : 'Failed to save', {
338
+ duration: Number.POSITIVE_INFINITY,
339
+ });
340
+ } finally {
341
+ setSaving(null);
342
+ }
343
+ }
344
+
345
+ if (isLoading) {
346
+ return (
347
+ <div className="flex h-40 items-center justify-center">
348
+ <Loader2 className="size-5 animate-spin text-muted-foreground" />
349
+ </div>
350
+ );
351
+ }
352
+
353
+ return (
354
+ <div className="space-y-6">
355
+ <div className="flex items-center justify-between">
356
+ <div>
357
+ <h2 className="text-lg font-semibold tracking-tight">AI Models</h2>
358
+ <p className="text-sm text-muted-foreground">
359
+ Configure the AI models that power your knowledge base.
360
+ </p>
361
+ </div>
362
+ </div>
363
+
364
+ <ProjectFormDialog
365
+ mode="create"
366
+ open={newServerModalOpen}
367
+ onOpenChange={setNewServerModalOpen}
368
+ />
369
+ <CustomModelModal
370
+ type="embedding"
371
+ open={customEmbeddingModalOpen}
372
+ onOpenChange={setCustomEmbeddingModalOpen}
373
+ onSave={(cfg) => {
374
+ const modelId = `custom:${cfg.modelName}`;
375
+ if (!canUseEmbeddingModel(modelId, cfg.dimensions)) return;
376
+ setForm({
377
+ ...form,
378
+ embeddingProvider: 'custom',
379
+ embeddingModelId: modelId,
380
+ customEmbeddings: [cfg],
381
+ });
382
+ clearError('embeddingProvider');
383
+ }}
384
+ />
385
+ <CustomModelModal
386
+ type="chat"
387
+ open={customChatModalOpen}
388
+ onOpenChange={setCustomChatModalOpen}
389
+ onSave={(cfg) => {
390
+ setForm({
391
+ ...form,
392
+ chatProvider: 'custom',
393
+ chatModelId: `custom:${cfg.modelName}`,
394
+ customChatModels: [cfg],
395
+ });
396
+ clearError('chatProvider');
397
+ }}
398
+ />
399
+ <CustomModelModal
400
+ type="vision"
401
+ open={customVisionModalOpen}
402
+ onOpenChange={setCustomVisionModalOpen}
403
+ onSave={(cfg) => {
404
+ setForm({
405
+ ...form,
406
+ visionProvider: 'custom',
407
+ visionModelId: `custom:${cfg.modelName}`,
408
+ customVisionModels: [cfg],
409
+ });
410
+ clearError('visionProvider');
411
+ }}
412
+ />
413
+
414
+ {/* Embedding Provider */}
415
+ <Card>
416
+ <SettingsCardHeader
417
+ title="Embedding Model"
418
+ description="Transforms your documents into searchable representations."
419
+ icon={<Sparkles className="size-3.5 text-primary" />}
420
+ />
421
+ <CardContent className="space-y-4">
422
+ <div className="space-y-1.5">
423
+ <Label className="text-xs">Provider</Label>
424
+ <div className="flex gap-2">
425
+ <Select
426
+ value={form.embeddingProvider ?? 'openai'}
427
+ onValueChange={(v: any) => {
428
+ setForm({
429
+ ...form,
430
+ embeddingProvider: v,
431
+ embeddingModelId: '',
432
+ });
433
+ clearError('embeddingProvider');
434
+ }}
435
+ >
436
+ <SelectTrigger
437
+ className={cn('w-full', errors.embeddingProvider && 'border-destructive')}
438
+ >
439
+ <span className="flex items-center gap-2">
440
+ {PROVIDER_META[
441
+ (form.embeddingProvider ?? 'openai') as keyof typeof PROVIDER_META
442
+ ] ? (
443
+ <>
444
+ <ProviderIcon
445
+ src={
446
+ PROVIDER_META[form.embeddingProvider as keyof typeof PROVIDER_META]
447
+ ?.iconSrc ?? ''
448
+ }
449
+ alt={
450
+ PROVIDER_META[form.embeddingProvider as keyof typeof PROVIDER_META]
451
+ ?.label ?? ''
452
+ }
453
+ pillBg={
454
+ PROVIDER_META[form.embeddingProvider as keyof typeof PROVIDER_META]
455
+ ?.pillBg ?? undefined
456
+ }
457
+ size={16}
458
+ />
459
+ {PROVIDER_META[form.embeddingProvider as keyof typeof PROVIDER_META]
460
+ ?.label ?? form.embeddingProvider}
461
+ </>
462
+ ) : (
463
+ form.embeddingProvider || 'Select provider'
464
+ )}
465
+ </span>
466
+ </SelectTrigger>
467
+ <SelectContent>
468
+ {EMBEDDING_PROVIDER_LIST.filter(
469
+ (key) =>
470
+ (EMBEDDING_BY_PROVIDER[key] && EMBEDDING_BY_PROVIDER[key].length > 0) ||
471
+ key === 'vercel_ai_gateway',
472
+ ).map((key) => {
473
+ const meta = PROVIDER_META[key as keyof typeof PROVIDER_META];
474
+ if (!meta) return null;
475
+ return (
476
+ <SelectItem key={key} value={key}>
477
+ <div className="flex items-center gap-2">
478
+ <ProviderIcon
479
+ src={meta.iconSrc}
480
+ alt={meta.label}
481
+ pillBg={meta.pillBg ?? undefined}
482
+ size={16}
483
+ />
484
+ <span>
485
+ {meta.label}
486
+ {key === 'vercel_ai_gateway' && ' (Recommended)'}
487
+ </span>
488
+ </div>
489
+ </SelectItem>
490
+ );
491
+ })}
492
+ {form.customEmbeddings?.map((cfg) => (
493
+ <SelectItem key={`custom:${cfg.modelName}`} value="custom">
494
+ <div className="flex items-center gap-2">
495
+ <ProviderIcon
496
+ src={PROVIDER_META.custom.iconSrc}
497
+ alt="Custom"
498
+ pillBg={PROVIDER_META.custom.pillBg}
499
+ size={16}
500
+ />
501
+ <span>{cfg.modelName} (Custom)</span>
502
+ </div>
503
+ </SelectItem>
504
+ ))}
505
+ </SelectContent>
506
+ </Select>
507
+ <Button
508
+ variant="outline"
509
+ size="icon"
510
+ type="button"
511
+ onClick={() => {
512
+ setCustomEmbeddingModalOpen(true);
513
+ }}
514
+ className="shrink-0 size-10 bg-background"
515
+ >
516
+ <Plus className="size-4" />
517
+ </Button>
518
+ </div>
519
+ </div>
520
+
521
+ <div className="space-y-1.5">
522
+ <Label className="text-xs">Model</Label>
523
+ <ModelCombobox
524
+ value={form.embeddingModelId || ''}
525
+ onValueChange={(v) => {
526
+ if (!canUseEmbeddingModel(v)) return;
527
+ setForm({ ...form, embeddingModelId: v });
528
+ clearError('embeddingModelId');
529
+ }}
530
+ groups={Object.fromEntries(
531
+ Object.entries(EMBEDDING_BY_PROVIDER).filter(
532
+ ([provider]) =>
533
+ form.embeddingProvider === 'vercel_ai_gateway' ||
534
+ provider === form.embeddingProvider ||
535
+ provider === 'deepseek',
536
+ ),
537
+ )}
538
+ customModels={form.customEmbeddings}
539
+ isOther={isOtherEmbedding}
540
+ onOtherChange={(val) => setIsOtherEmbedding(val)}
541
+ placeholder="Default"
542
+ />
543
+ {isOtherEmbedding && (
544
+ <Input
545
+ placeholder="Enter custom model ID"
546
+ value={form.embeddingModelId || ''}
547
+ onChange={(e) => setForm({ ...form, embeddingModelId: e.target.value })}
548
+ className="mt-2"
549
+ />
550
+ )}
551
+ </div>
552
+
553
+ <div className="space-y-1.5">
554
+ <Label className="text-xs">API Key</Label>
555
+ <div className="relative">
556
+ <Input
557
+ type={showEmbeddingKey ? 'text' : 'password'}
558
+ value={form.embeddingApiKey || ''}
559
+ onChange={(e) => {
560
+ setForm({
561
+ ...form,
562
+ embeddingApiKey: e.target.value,
563
+ });
564
+ clearError('embeddingApiKey');
565
+ }}
566
+ placeholder="sk-..."
567
+ className={cn('pr-10', errors.embeddingApiKey && 'border-destructive')}
568
+ />
569
+ <button
570
+ type="button"
571
+ onClick={() => setShowEmbeddingKey(!showEmbeddingKey)}
572
+ className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground"
573
+ >
574
+ {showEmbeddingKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
575
+ </button>
576
+ </div>
577
+ </div>
578
+ </CardContent>
579
+ <CardFooter className="flex justify-end pt-4 border-t">
580
+ <Button
581
+ size="lg"
582
+ disabled={saving === 'embedding' || !dirtyEmbedding}
583
+ onClick={() => handleSave('embedding')}
584
+ className="gap-1.5 px-4"
585
+ >
586
+ {saving === 'embedding' ? (
587
+ <Loader2 className="size-3.5 animate-spin" />
588
+ ) : (
589
+ <Save className="size-3.5" />
590
+ )}
591
+ Save
592
+ </Button>
593
+ </CardFooter>
594
+ </Card>
595
+
596
+ {/* Vision-language model — shared by image and media tools. */}
597
+ <Card>
598
+ <SettingsCardHeader
599
+ title="Vision Model"
600
+ description="Used by video, image, OCR, and future tools that need to understand pixels. Only providers with vision-capable models are shown."
601
+ icon={<ScanEye className="size-3.5 text-primary" />}
602
+ />
603
+ <CardContent className="space-y-4">
604
+ <div className="space-y-1.5">
605
+ <Label className="text-xs">Provider</Label>
606
+ <div className="flex gap-2">
607
+ <Select
608
+ value={form.visionProvider || ''}
609
+ onValueChange={(value: string | null) => {
610
+ if (!value) return;
611
+ setForm({ ...form, visionProvider: value, visionModelId: '' });
612
+ clearError('visionProvider');
613
+ }}
614
+ >
615
+ <SelectTrigger
616
+ className={cn('w-full', errors.visionProvider && 'border-destructive')}
617
+ >
618
+ <span className="flex items-center gap-2">
619
+ {(() => {
620
+ const provider = form.visionProvider || '';
621
+ const meta = PROVIDER_META[provider as keyof typeof PROVIDER_META];
622
+ return meta ? (
623
+ <>
624
+ <ProviderIcon
625
+ src={meta.iconSrc}
626
+ alt={meta.label}
627
+ pillBg={meta.pillBg}
628
+ size={16}
629
+ />
630
+ {meta.label}
631
+ </>
632
+ ) : (
633
+ provider || 'Select a vision provider'
634
+ );
635
+ })()}
636
+ </span>
637
+ </SelectTrigger>
638
+ <SelectContent>
639
+ {visionProviders.map((provider) => {
640
+ const meta = PROVIDER_META[provider as keyof typeof PROVIDER_META];
641
+ if (!meta) return null;
642
+ return (
643
+ <SelectItem key={provider} value={provider}>
644
+ <div className="flex items-center gap-2">
645
+ <ProviderIcon
646
+ src={meta.iconSrc}
647
+ alt={meta.label}
648
+ pillBg={meta.pillBg}
649
+ size={16}
650
+ />
651
+ <span>{meta.label}</span>
652
+ </div>
653
+ </SelectItem>
654
+ );
655
+ })}
656
+ {form.customVisionModels?.map((cfg) => (
657
+ <SelectItem key={`custom:${cfg.modelName}`} value="custom">
658
+ <div className="flex items-center gap-2">
659
+ <ProviderIcon
660
+ src={PROVIDER_META.custom.iconSrc}
661
+ alt="Custom"
662
+ pillBg={PROVIDER_META.custom.pillBg}
663
+ size={16}
664
+ />
665
+ <span>{cfg.modelName} (Custom)</span>
666
+ </div>
667
+ </SelectItem>
668
+ ))}
669
+ </SelectContent>
670
+ </Select>
671
+ <Button
672
+ variant="outline"
673
+ size="icon"
674
+ type="button"
675
+ onClick={() => {
676
+ setCustomVisionModalOpen(true);
677
+ }}
678
+ className="shrink-0 size-10 bg-background"
679
+ >
680
+ <Plus className="size-4" />
681
+ </Button>
682
+ </div>
683
+ {!visionProviders.includes(form.visionProvider || '') ? (
684
+ <p className="text-[11px] text-amber-700 dark:text-amber-400">
685
+ This provider has no compatible vision model. Choose a provider from the list for
686
+ video and image tasks.
687
+ </p>
688
+ ) : null}
689
+ </div>
690
+
691
+ <div className="space-y-1.5">
692
+ <Label className="text-xs">Model</Label>
693
+ <ModelCombobox
694
+ value={form.visionModelId || 'default'}
695
+ onValueChange={(v) => {
696
+ setForm({ ...form, visionModelId: v === 'default' ? '' : v });
697
+ }}
698
+ items={[
699
+ { id: 'default', label: 'Default vision model' },
700
+ ...visionModels.filter((model) =>
701
+ (model.availableFrom ?? [model.provider]).includes(form.visionProvider || ''),
702
+ ),
703
+ ]}
704
+ customModels={form.customVisionModels}
705
+ isOther={isOtherVision}
706
+ onOtherChange={(val) => setIsOtherVision(val)}
707
+ placeholder="Default vision model"
708
+ />
709
+ {isOtherVision && (
710
+ <Input
711
+ placeholder="Enter custom model ID"
712
+ value={form.visionModelId || ''}
713
+ onChange={(e) => setForm({ ...form, visionModelId: e.target.value })}
714
+ className="mt-2"
715
+ />
716
+ )}
717
+ </div>
718
+
719
+ <div className="space-y-1.5">
720
+ <Label className="text-xs">API Key</Label>
721
+ <div className="relative">
722
+ <Input
723
+ type={showVisionKey ? 'text' : 'password'}
724
+ value={form.visionApiKey || ''}
725
+ onChange={(event) => {
726
+ setForm({ ...form, visionApiKey: event.target.value });
727
+ clearError('visionApiKey');
728
+ }}
729
+ placeholder="sk-..."
730
+ className={cn('pr-10', errors.visionApiKey && 'border-destructive')}
731
+ />
732
+ <button
733
+ type="button"
734
+ onClick={() => setShowVisionKey(!showVisionKey)}
735
+ className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground"
736
+ >
737
+ {showVisionKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
738
+ </button>
739
+ </div>
740
+ </div>
741
+ </CardContent>
742
+ <CardFooter className="flex justify-end border-t pt-4">
743
+ <Button
744
+ size="lg"
745
+ disabled={saving === 'vision' || !dirtyVision}
746
+ onClick={() => handleSave('vision')}
747
+ className="gap-1.5 px-4"
748
+ >
749
+ {saving === 'vision' ? (
750
+ <Loader2 className="size-3.5 animate-spin" />
751
+ ) : (
752
+ <Save className="size-3.5" />
753
+ )}
754
+ Save
755
+ </Button>
756
+ </CardFooter>
757
+ </Card>
758
+
759
+ {/* Chat (LLM) Provider */}
760
+ <Card>
761
+ <SettingsCardHeader
762
+ title="Chat Model"
763
+ description="Powers the AI assistant that answers questions from your knowledge base. Leave blank to use the same provider as embedding."
764
+ icon={<MessageCircle className="size-3.5 text-primary" />}
765
+ />
766
+ <CardContent className="space-y-4">
767
+ <div className="space-y-1.5">
768
+ <Label className="text-xs">Provider</Label>
769
+ <div className="flex gap-2">
770
+ <Select
771
+ value={form.chatProvider || form.embeddingProvider || 'openai'}
772
+ onValueChange={(v: any) => {
773
+ setForm({ ...form, chatProvider: v, chatModelId: '' });
774
+ clearError('chatProvider');
775
+ }}
776
+ >
777
+ <SelectTrigger
778
+ className={cn('w-full', errors.chatProvider && 'border-destructive')}
779
+ >
780
+ <span className="flex items-center gap-2">
781
+ {(() => {
782
+ const pKey = form.chatProvider || form.embeddingProvider || 'openai';
783
+ const meta = PROVIDER_META[pKey as keyof typeof PROVIDER_META];
784
+ return meta ? (
785
+ <>
786
+ <ProviderIcon
787
+ src={meta.iconSrc}
788
+ alt={meta.label}
789
+ pillBg={meta.pillBg ?? undefined}
790
+ size={16}
791
+ />
792
+ {meta.label}
793
+ </>
794
+ ) : (
795
+ pKey
796
+ );
797
+ })()}
798
+ </span>
799
+ </SelectTrigger>
800
+ <SelectContent>
801
+ {CHAT_PROVIDER_LIST.map((key) => {
802
+ const meta = PROVIDER_META[key as keyof typeof PROVIDER_META];
803
+ if (!meta) return null;
804
+ return (
805
+ <SelectItem key={key} value={key}>
806
+ <div className="flex items-center gap-2">
807
+ <ProviderIcon
808
+ src={meta.iconSrc}
809
+ alt={meta.label}
810
+ pillBg={meta.pillBg}
811
+ size={16}
812
+ />
813
+ <span>{meta.label}</span>
814
+ </div>
815
+ </SelectItem>
816
+ );
817
+ })}
818
+ {form.customChatModels?.map((cfg) => (
819
+ <SelectItem key={`custom:${cfg.modelName}`} value="custom">
820
+ <div className="flex items-center gap-2">
821
+ <ProviderIcon
822
+ src={PROVIDER_META.custom.iconSrc}
823
+ alt="Custom"
824
+ pillBg={PROVIDER_META.custom.pillBg}
825
+ size={16}
826
+ />
827
+ <span>{cfg.modelName} (Custom)</span>
828
+ </div>
829
+ </SelectItem>
830
+ ))}
831
+ </SelectContent>
832
+ </Select>
833
+ <Button
834
+ variant="outline"
835
+ size="icon"
836
+ type="button"
837
+ onClick={() => setCustomChatModalOpen(true)}
838
+ className="shrink-0 h-9 w-9"
839
+ >
840
+ <Plus className="size-4" />
841
+ </Button>
842
+ </div>
843
+ </div>
844
+
845
+ <div className="space-y-1.5">
846
+ <Label className="text-xs">Model</Label>
847
+ <ModelCombobox
848
+ value={form.chatModelId || ''}
849
+ onValueChange={(v) => {
850
+ setForm({ ...form, chatModelId: v });
851
+ }}
852
+ items={chatStatus?.availableModels || []}
853
+ customModels={form.customChatModels}
854
+ isOther={isOtherChat}
855
+ onOtherChange={setIsOtherChat}
856
+ placeholder="Default"
857
+ />
858
+ {isOtherChat && (
859
+ <Input
860
+ placeholder="Enter custom model ID"
861
+ value={form.chatModelId || ''}
862
+ onChange={(e) => setForm({ ...form, chatModelId: e.target.value })}
863
+ className="mt-2"
864
+ />
865
+ )}
866
+ </div>
867
+
868
+ <div className="space-y-1.5">
869
+ <Label className="text-xs">API Key</Label>
870
+ <div className="relative">
871
+ <Input
872
+ type={showChatKey ? 'text' : 'password'}
873
+ value={form.chatApiKey || ''}
874
+ onChange={(e) => {
875
+ setForm({ ...form, chatApiKey: e.target.value });
876
+ clearError('chatApiKey');
877
+ }}
878
+ placeholder="sk-..."
879
+ className={cn('pr-10', errors.chatApiKey && 'border-destructive')}
880
+ />
881
+ <button
882
+ type="button"
883
+ onClick={() => setShowChatKey(!showChatKey)}
884
+ className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground"
885
+ >
886
+ {showChatKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
887
+ </button>
888
+ </div>
889
+ </div>
890
+ </CardContent>
891
+ <CardFooter className="flex justify-end pt-4 border-t">
892
+ <Button
893
+ size="lg"
894
+ disabled={saving === 'chat' || !dirtyChat}
895
+ onClick={() => handleSave('chat')}
896
+ className="gap-1.5 px-4"
897
+ >
898
+ {saving === 'chat' ? (
899
+ <Loader2 className="size-3.5 animate-spin" />
900
+ ) : (
901
+ <Save className="size-3.5" />
902
+ )}
903
+ Save
904
+ </Button>
905
+ </CardFooter>
906
+ </Card>
907
+ </div>
908
+ );
909
+ }