@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,757 @@
1
+ 'use client';
2
+
3
+ import { workspaceUrl, workspaceFetch as fetch } from '../../../host';
4
+ import { useState, useEffect } from 'react';
5
+ import useSWR from 'swr';
6
+ import { Globe2, Loader2, Save, Eye, EyeOff } from 'lucide-react';
7
+ import { Button } from '@larkup/workspace-ui/button';
8
+ import { Input } from '@larkup/workspace-app/components/ui/input';
9
+ import { Label } from '@larkup/workspace-ui/label';
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from '@larkup/workspace-ui/select';
17
+ import { toast } from 'sonner';
18
+ import type { RagConfig } from '@larkup/core/types';
19
+ import {
20
+ Card,
21
+ CardContent,
22
+ CardDescription,
23
+ CardHeader,
24
+ CardTitle,
25
+ CardFooter,
26
+ } from '@larkup/workspace-app/components/ui/card';
27
+ import Image from 'next/image';
28
+ import { Tabs, TabsTrigger, TabsList, TabsContent } from '@larkup/workspace-app/components/ui/tabs';
29
+ import { Switch } from '@larkup/workspace-app/components/ui/switch';
30
+ import {
31
+ Tooltip,
32
+ TooltipContent,
33
+ TooltipProvider,
34
+ TooltipTrigger,
35
+ } from '@larkup/workspace-app/components/ui/tooltip';
36
+
37
+ const fetcher = (url: string) => fetch(url).then((r) => r.json() as Promise<{ config: RagConfig }>);
38
+
39
+ export function SearchIntegrationsSection() {
40
+ const { data, isLoading, mutate } = useSWR(workspaceUrl('/api/config'), fetcher);
41
+ const [form, setForm] = useState<Partial<RagConfig>>({});
42
+ const [saving, setSaving] = useState<string | null>(null);
43
+
44
+ const [showApiKey, setShowApiKey] = useState(false);
45
+ const [verifying, setVerifying] = useState(false);
46
+ const [showProxyPassword, setShowProxyPassword] = useState(false);
47
+ const [verifyStatus, setVerifyStatus] = useState<{
48
+ status: 'success' | 'error' | null;
49
+ message?: string;
50
+ }>({ status: null });
51
+
52
+ const [verifyingCrawler, setVerifyingCrawler] = useState(false);
53
+ const [verifyCrawlerStatus, setVerifyCrawlerStatus] = useState<{
54
+ status: 'success' | 'error' | null;
55
+ message?: string;
56
+ }>({ status: null });
57
+
58
+ useEffect(() => {
59
+ if (data?.config) setForm(data.config);
60
+ }, [data]);
61
+
62
+ const dirtyWebSearch =
63
+ form.serperApiKey !== data?.config?.serperApiKey ||
64
+ form.webSearchProvider !== data?.config?.webSearchProvider ||
65
+ form.tavilyApiKey !== data?.config?.tavilyApiKey ||
66
+ form.googleApiKey !== data?.config?.googleApiKey ||
67
+ form.braveApiKey !== data?.config?.braveApiKey ||
68
+ form.bingApiKey !== data?.config?.bingApiKey ||
69
+ form.exaApiKey !== data?.config?.exaApiKey;
70
+
71
+ const dirtyWebCrawler =
72
+ form.webCrawlerProvider !== data?.config?.webCrawlerProvider ||
73
+ form.firecrawlApiKey !== data?.config?.firecrawlApiKey;
74
+
75
+ const dirtyProxy =
76
+ form.scraperProxyServer !== data?.config?.scraperProxyServer ||
77
+ form.scraperProxyUsername !== data?.config?.scraperProxyUsername ||
78
+ form.scraperProxyPassword !== data?.config?.scraperProxyPassword ||
79
+ form.useScraperProxy !== data?.config?.useScraperProxy;
80
+
81
+ async function handleSave(section: 'webSearch' | 'webCrawler' | 'proxy') {
82
+ setSaving(section);
83
+ try {
84
+ let payload = { ...data?.config };
85
+
86
+ if (section === 'webSearch') {
87
+ payload.serperApiKey = form.serperApiKey;
88
+ payload.webSearchProvider = form.webSearchProvider;
89
+ payload.tavilyApiKey = form.tavilyApiKey;
90
+ payload.googleApiKey = form.googleApiKey;
91
+ payload.braveApiKey = form.braveApiKey;
92
+ payload.bingApiKey = form.bingApiKey;
93
+ payload.exaApiKey = form.exaApiKey;
94
+ } else if (section === 'webCrawler') {
95
+ payload.webCrawlerProvider = form.webCrawlerProvider;
96
+ payload.firecrawlApiKey = form.firecrawlApiKey;
97
+ } else if (section === 'proxy') {
98
+ payload.scraperProxyServer = form.scraperProxyServer;
99
+ payload.scraperProxyUsername = form.scraperProxyUsername;
100
+ payload.scraperProxyPassword = form.scraperProxyPassword;
101
+ payload.useScraperProxy = form.useScraperProxy;
102
+ }
103
+
104
+ const res = await fetch(workspaceUrl('/api/config'), {
105
+ method: 'PUT',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify(payload),
108
+ });
109
+ const json = await res.json();
110
+ if (!res.ok) throw new Error(json.error ?? 'Failed to save');
111
+
112
+ setForm((prev) => ({ ...prev, ...json.config }));
113
+ await mutate(json, { revalidate: false });
114
+ toast.success('Settings saved');
115
+ } catch (err) {
116
+ toast.error(err instanceof Error ? err.message : 'Failed to save');
117
+ } finally {
118
+ setSaving(null);
119
+ }
120
+ }
121
+
122
+ async function setWebSearchEnabled(enabled: boolean) {
123
+ if (!data?.config) return;
124
+ setSaving('webSearchToggle');
125
+ try {
126
+ const response = await fetch(workspaceUrl('/api/config'), {
127
+ method: 'PUT',
128
+ headers: { 'Content-Type': 'application/json' },
129
+ body: JSON.stringify({ ...data.config, webSearchEnabled: enabled }),
130
+ });
131
+ const json = await response.json();
132
+ if (!response.ok) throw new Error(json.error ?? 'Could not update web search.');
133
+ setForm((current) => ({ ...current, ...json.config }));
134
+ await mutate(json, { revalidate: false });
135
+ toast.success(
136
+ enabled ? 'Web search is available to the Agent.' : 'Web search is disabled for the Agent.',
137
+ );
138
+ } catch (error) {
139
+ toast.error(error instanceof Error ? error.message : 'Could not update web search.');
140
+ } finally {
141
+ setSaving(null);
142
+ }
143
+ }
144
+
145
+ async function handleVerify() {
146
+ if (!form.webSearchProvider) return;
147
+ setVerifying(true);
148
+ setVerifyStatus({ status: null });
149
+
150
+ let apiKey = '';
151
+ if (form.webSearchProvider === 'serper' || form.webSearchProvider === 'google')
152
+ apiKey = form.serperApiKey || '';
153
+ if (form.webSearchProvider === 'tavily') apiKey = form.tavilyApiKey || '';
154
+ if (form.webSearchProvider === 'brave') apiKey = form.braveApiKey || '';
155
+ if (form.webSearchProvider === 'bing') apiKey = form.bingApiKey || '';
156
+ if (form.webSearchProvider === 'exa') apiKey = form.exaApiKey || '';
157
+
158
+ try {
159
+ const res = await fetch(workspaceUrl('/api/search/verify'), {
160
+ method: 'POST',
161
+ headers: { 'Content-Type': 'application/json' },
162
+ body: JSON.stringify({ provider: form.webSearchProvider, apiKey }),
163
+ });
164
+ const data = await res.json();
165
+ if (!res.ok) throw new Error(data.error || 'Verification failed');
166
+
167
+ setVerifyStatus({ status: 'success' });
168
+ toast.success('API Key verified successfully!');
169
+ } catch (err) {
170
+ const msg = err instanceof Error ? err.message : 'Verification failed';
171
+ setVerifyStatus({ status: 'error', message: msg });
172
+ toast.error(msg);
173
+ } finally {
174
+ setVerifying(false);
175
+ }
176
+ }
177
+
178
+ async function handleVerifyCrawler() {
179
+ if (form.webCrawlerProvider !== 'cloud') return;
180
+ setVerifyingCrawler(true);
181
+ setVerifyCrawlerStatus({ status: null });
182
+
183
+ try {
184
+ const res = await fetch(workspaceUrl('/api/search/verify'), {
185
+ method: 'POST',
186
+ headers: { 'Content-Type': 'application/json' },
187
+ body: JSON.stringify({ provider: 'firecrawl', apiKey: form.firecrawlApiKey || '' }),
188
+ });
189
+ const data = await res.json();
190
+ if (!res.ok) throw new Error(data.error || 'Verification failed');
191
+
192
+ setVerifyCrawlerStatus({ status: 'success' });
193
+ toast.success('Crawler API Key verified successfully!');
194
+ } catch (err) {
195
+ const msg = err instanceof Error ? err.message : 'Verification failed';
196
+ setVerifyCrawlerStatus({ status: 'error', message: msg });
197
+ toast.error(msg);
198
+ } finally {
199
+ setVerifyingCrawler(false);
200
+ }
201
+ }
202
+
203
+ if (isLoading) {
204
+ return (
205
+ <div className="flex h-40 items-center justify-center">
206
+ <Loader2 className="size-5 animate-spin text-muted-foreground" />
207
+ </div>
208
+ );
209
+ }
210
+
211
+ return (
212
+ <div className="space-y-6">
213
+ <div className="flex items-center justify-between">
214
+ <div>
215
+ <h2 className="text-lg font-semibold tracking-tight">Search & Scraping</h2>
216
+ <p className="text-sm text-muted-foreground">
217
+ Manage your web search and scraping integrations.
218
+ </p>
219
+ </div>
220
+ </div>
221
+
222
+ {/* Web Search */}
223
+ <Card>
224
+ <CardHeader className="pb-3">
225
+ <div className="flex items-start justify-between gap-4">
226
+ <div>
227
+ <CardTitle className="text-sm">Web Search</CardTitle>
228
+ <CardDescription className="mt-1 text-xs">
229
+ Enable AI-powered web search for enriched answers.
230
+ </CardDescription>
231
+ </div>
232
+ <TooltipProvider>
233
+ <Tooltip>
234
+ <TooltipTrigger
235
+ render={
236
+ <span>
237
+ <Switch
238
+ checked={form.webSearchEnabled === true}
239
+ disabled={saving === 'webSearchToggle'}
240
+ aria-label="Enable web search for the Agent"
241
+ onCheckedChange={(enabled) => void setWebSearchEnabled(enabled)}
242
+ />
243
+ </span>
244
+ }
245
+ />
246
+ <TooltipContent>
247
+ {form.webSearchEnabled
248
+ ? 'Web search is enabled for the running Agent.'
249
+ : 'Enable web search for the running Agent.'}
250
+ </TooltipContent>
251
+ </Tooltip>
252
+ </TooltipProvider>
253
+ </div>
254
+ </CardHeader>
255
+ <CardContent className="space-y-4">
256
+ <div className="space-y-1.5">
257
+ <Label className="text-xs">Web Search Provider</Label>
258
+ <Select
259
+ value={form.webSearchProvider || 'tavily'}
260
+ onValueChange={(value) => {
261
+ setForm({ ...form, webSearchProvider: value as any });
262
+ setVerifyStatus({ status: null });
263
+ }}
264
+ >
265
+ <SelectTrigger className=" w-full">
266
+ <div className="flex items-center gap-2">
267
+ {form.webSearchProvider === 'serper' && (
268
+ <Image src="/icons/serper.png" alt="Serper" width={16} height={16} />
269
+ )}
270
+ {form.webSearchProvider === 'tavily' && (
271
+ <Image src="/icons/tavily.png" alt="Tavily" width={16} height={16} />
272
+ )}
273
+ {form.webSearchProvider === 'brave' && (
274
+ <Image src="/icons/brave.png" alt="Brave" width={16} height={16} />
275
+ )}
276
+ {form.webSearchProvider === 'bing' && (
277
+ <Image src="/icons/serpapi.svg" alt="SerpApi" width={16} height={16} />
278
+ )}
279
+ {form.webSearchProvider === 'exa' && (
280
+ <Image src="/icons/exa.png" alt="Exa" width={16} height={16} />
281
+ )}
282
+ {form.webSearchProvider === 'local' && (
283
+ <Globe2 className="size-4" aria-hidden="true" />
284
+ )}
285
+ <SelectValue placeholder="Select provider" />
286
+ </div>
287
+ </SelectTrigger>
288
+ <SelectContent>
289
+ <SelectItem value="serper">
290
+ <div className="flex items-center gap-2">
291
+ <Image src="/icons/serper.png" alt="Serper" width={16} height={16} />
292
+ <span>Serper</span>
293
+ </div>
294
+ </SelectItem>
295
+ <SelectItem value="tavily">
296
+ <div className="flex items-center gap-2">
297
+ <Image src="/icons/tavily.png" alt="Tavily" width={16} height={16} />
298
+ <span>Tavily</span>
299
+ </div>
300
+ </SelectItem>
301
+ <SelectItem value="brave">
302
+ <div className="flex items-center gap-2">
303
+ <Image src="/icons/brave.png" alt="Brave" width={16} height={16} />
304
+ <span>Brave</span>
305
+ </div>
306
+ </SelectItem>
307
+ <SelectItem value="bing">
308
+ <div className="flex items-center gap-2">
309
+ <Image src="/icons/serpapi.svg" alt="SerpApi" width={16} height={16} />
310
+ <span>Bing (via SerpApi)</span>
311
+ </div>
312
+ </SelectItem>
313
+ <SelectItem value="exa">
314
+ <div className="flex items-center gap-2">
315
+ <Image src="/icons/exa.png" alt="Exa" width={16} height={16} />
316
+ <span>Exa</span>
317
+ </div>
318
+ </SelectItem>
319
+ <SelectItem value="local">
320
+ <div className="flex items-center gap-2">
321
+ <Globe2 className="size-4" aria-hidden="true" />
322
+ <span>Built-in web search</span>
323
+ </div>
324
+ </SelectItem>
325
+ </SelectContent>
326
+ </Select>
327
+ </div>
328
+
329
+ {form.webSearchProvider !== 'local' && (
330
+ <div className="space-y-1.5 pt-2">
331
+ <div className="flex items-center justify-between">
332
+ <Label className="text-xs">
333
+ {form.webSearchProvider === 'serper' || form.webSearchProvider === 'google'
334
+ ? 'Serper API Key'
335
+ : form.webSearchProvider === 'brave'
336
+ ? 'Brave API Key'
337
+ : form.webSearchProvider === 'bing'
338
+ ? 'SerpApi API Key'
339
+ : form.webSearchProvider === 'exa'
340
+ ? 'Exa API Key'
341
+ : 'Tavily API Key'}
342
+ </Label>
343
+ </div>
344
+ <div className="flex gap-2">
345
+ <div className="relative flex-1">
346
+ <Input
347
+ type={showApiKey ? 'text' : 'password'}
348
+ className="text-sm pr-10"
349
+ value={
350
+ form.webSearchProvider === 'serper' || form.webSearchProvider === 'google'
351
+ ? form.serperApiKey || ''
352
+ : form.webSearchProvider === 'brave'
353
+ ? form.braveApiKey || ''
354
+ : form.webSearchProvider === 'bing'
355
+ ? form.bingApiKey || ''
356
+ : form.webSearchProvider === 'exa'
357
+ ? form.exaApiKey || ''
358
+ : form.tavilyApiKey || ''
359
+ }
360
+ onChange={(e) => {
361
+ setVerifyStatus({ status: null });
362
+ if (
363
+ form.webSearchProvider === 'serper' ||
364
+ form.webSearchProvider === 'google'
365
+ ) {
366
+ setForm({ ...form, serperApiKey: e.target.value });
367
+ } else if (form.webSearchProvider === 'brave') {
368
+ setForm({ ...form, braveApiKey: e.target.value });
369
+ } else if (form.webSearchProvider === 'bing') {
370
+ setForm({ ...form, bingApiKey: e.target.value });
371
+ } else if (form.webSearchProvider === 'exa') {
372
+ setForm({ ...form, exaApiKey: e.target.value });
373
+ } else {
374
+ setForm({ ...form, tavilyApiKey: e.target.value });
375
+ }
376
+ }}
377
+ placeholder={
378
+ form.webSearchProvider === 'serper' || form.webSearchProvider === 'google'
379
+ ? 'Your Serper API Key'
380
+ : form.webSearchProvider === 'brave'
381
+ ? 'Your Brave API Key'
382
+ : form.webSearchProvider === 'bing'
383
+ ? 'Your SerpApi API Key'
384
+ : form.webSearchProvider === 'exa'
385
+ ? 'Your Exa API Key'
386
+ : 'Your Tavily API Key'
387
+ }
388
+ />
389
+ <Button
390
+ type="button"
391
+ variant="ghost"
392
+ size="sm"
393
+ className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent text-muted-foreground"
394
+ onClick={() => setShowApiKey(!showApiKey)}
395
+ >
396
+ {showApiKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
397
+ </Button>
398
+ </div>
399
+ <Button
400
+ className={'rounded-lg px-5 h-10'}
401
+ variant="outline"
402
+ size="default"
403
+ onClick={handleVerify}
404
+ disabled={verifying}
405
+ >
406
+ {verifying ? <Loader2 className="size-4 animate-spin" /> : 'Verify'}
407
+ </Button>
408
+ </div>
409
+ {verifyStatus.status === 'success' && (
410
+ <p className="text-[11px] text-green-500 font-medium mt-1">✓ API Key Verified</p>
411
+ )}
412
+ {verifyStatus.status === 'error' && (
413
+ <p className="text-[11px] text-red-500 font-medium mt-1">{verifyStatus.message}</p>
414
+ )}
415
+ </div>
416
+ )}
417
+ </CardContent>
418
+ <CardFooter className="flex justify-end pt-4 border-t">
419
+ <Button
420
+ size="lg"
421
+ disabled={saving === 'webSearch' || !dirtyWebSearch}
422
+ onClick={() => handleSave('webSearch')}
423
+ className="gap-1.5 px-4"
424
+ >
425
+ {saving === 'webSearch' ? (
426
+ <Loader2 className="size-3.5 animate-spin" />
427
+ ) : (
428
+ <Save className="size-3.5" />
429
+ )}
430
+ Save
431
+ </Button>
432
+ </CardFooter>
433
+ </Card>
434
+
435
+ {/* Web Scraper Provider */}
436
+ <Card>
437
+ <CardHeader className="pb-3">
438
+ <CardTitle className="text-sm">Web Scraper</CardTitle>
439
+ <CardDescription className="text-xs">
440
+ The built-in crawler works through the Larkup API without Docker or local services.
441
+ </CardDescription>
442
+ </CardHeader>
443
+ <CardContent className="space-y-4">
444
+ <div className="space-y-1.5">
445
+ <Label className="text-xs">Web Scraper Provider</Label>
446
+ <Select
447
+ value={form.webCrawlerProvider || 'local'}
448
+ onValueChange={(val) => {
449
+ setForm({ ...form, webCrawlerProvider: val as 'local' | 'cloud' });
450
+ setVerifyCrawlerStatus({ status: null });
451
+ }}
452
+ >
453
+ <SelectTrigger className="w-full">
454
+ <div className="flex items-center gap-2">
455
+ {form.webCrawlerProvider === 'cloud' ? (
456
+ <Image
457
+ src="/icons/firecrawl.png"
458
+ alt="Cloud web crawler"
459
+ width={16}
460
+ height={16}
461
+ unoptimized
462
+ />
463
+ ) : (
464
+ <Globe2 className="size-4" aria-hidden="true" />
465
+ )}
466
+ <span>
467
+ {form.webCrawlerProvider === 'cloud'
468
+ ? 'Cloud web crawler'
469
+ : 'Built-in crawler (no setup)'}
470
+ </span>
471
+ </div>
472
+ </SelectTrigger>
473
+ <SelectContent>
474
+ <SelectItem value="local">
475
+ <div className="flex items-center gap-2">
476
+ <Globe2 className="size-4" aria-hidden="true" />
477
+ <span>Built-in crawler (no setup)</span>
478
+ </div>
479
+ </SelectItem>
480
+ <SelectItem value="cloud">
481
+ <div className="flex items-center gap-2">
482
+ <Image
483
+ src="/icons/firecrawl.png"
484
+ alt="Cloud web crawler"
485
+ width={16}
486
+ height={16}
487
+ unoptimized
488
+ />
489
+ <span>Cloud web crawler</span>
490
+ </div>
491
+ </SelectItem>
492
+ </SelectContent>
493
+ </Select>
494
+ </div>
495
+
496
+ {form.webCrawlerProvider === 'cloud' && (
497
+ <div className="space-y-1.5 pt-2">
498
+ <div className="flex items-center justify-between">
499
+ <Label className="text-xs">Web crawler API key</Label>
500
+ </div>
501
+ <div className="flex gap-2">
502
+ <div className="relative flex-1">
503
+ <Input
504
+ type={showApiKey ? 'text' : 'password'}
505
+ className="text-sm pr-10"
506
+ value={form.firecrawlApiKey || ''}
507
+ onChange={(e) => {
508
+ setVerifyCrawlerStatus({ status: null });
509
+ setForm({ ...form, firecrawlApiKey: e.target.value });
510
+ }}
511
+ placeholder="fc-..."
512
+ />
513
+ <Button
514
+ type="button"
515
+ variant="ghost"
516
+ size="sm"
517
+ className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent text-muted-foreground"
518
+ onClick={() => setShowApiKey(!showApiKey)}
519
+ >
520
+ {showApiKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
521
+ </Button>
522
+ </div>
523
+ <Button
524
+ className="rounded-lg h-10 px-5"
525
+ variant="outline"
526
+ size="default"
527
+ disabled={verifyingCrawler}
528
+ onClick={handleVerifyCrawler}
529
+ >
530
+ {verifyingCrawler ? <Loader2 className="size-4 animate-spin" /> : 'Verify'}
531
+ </Button>
532
+ </div>
533
+ {verifyCrawlerStatus.status === 'success' && (
534
+ <p className="text-[11px] text-green-500 font-medium mt-1">
535
+ ✓ Crawler API Key Verified
536
+ </p>
537
+ )}
538
+ {verifyCrawlerStatus.status === 'error' && (
539
+ <p className="text-[11px] text-red-500 font-medium mt-1">
540
+ {verifyCrawlerStatus.message}
541
+ </p>
542
+ )}
543
+ </div>
544
+ )}
545
+ </CardContent>
546
+ <CardFooter className="flex justify-end pt-4 border-t">
547
+ <Button
548
+ size="lg"
549
+ disabled={saving === 'webCrawler' || !dirtyWebCrawler}
550
+ onClick={() => handleSave('webCrawler')}
551
+ className="gap-1.5 px-4"
552
+ >
553
+ {saving === 'webCrawler' ? (
554
+ <Loader2 className="size-3.5 animate-spin" />
555
+ ) : (
556
+ <Save className="size-3.5" />
557
+ )}
558
+ Save
559
+ </Button>
560
+ </CardFooter>
561
+ </Card>
562
+
563
+ {/* Proxy Settings */}
564
+ <Card>
565
+ <CardHeader className="pb-3 flex flex-row items-center justify-between space-y-0">
566
+ <div className="space-y-1.5">
567
+ <CardTitle className="text-sm">Local Proxy Settings</CardTitle>
568
+ <CardDescription className="text-xs">
569
+ Configure proxy settings for the local scraper.
570
+ </CardDescription>
571
+ </div>
572
+ <TooltipProvider>
573
+ <Tooltip>
574
+ <TooltipTrigger
575
+ render={
576
+ <div className="flex items-center space-x-2">
577
+ <Switch
578
+ checked={form.useScraperProxy || false}
579
+ onCheckedChange={(checked) => setForm({ ...form, useScraperProxy: checked })}
580
+ />
581
+ </div>
582
+ }
583
+ />
584
+ <TooltipContent>
585
+ <p>Enable proxy for web scraping</p>
586
+ </TooltipContent>
587
+ </Tooltip>
588
+ </TooltipProvider>
589
+ </CardHeader>
590
+ <CardContent className="space-y-4">
591
+ <Tabs defaultValue="one-line" className="w-full ">
592
+ <div className="flex mb-4">
593
+ <TabsList className="grid w-35 grid-cols-2 h-8! bg-muted/10">
594
+ <TabsTrigger value="one-line" className="text-xs h-6">
595
+ One Line
596
+ </TabsTrigger>
597
+ <TabsTrigger value="form" className="text-xs h-6">
598
+ Form
599
+ </TabsTrigger>
600
+ </TabsList>
601
+ </div>
602
+
603
+ <TabsContent value="one-line" className="mt-0 w-full">
604
+ <div className="space-y-1.5">
605
+ <Label className="text-xs">Proxy URL</Label>
606
+ <Input
607
+ className="text-sm"
608
+ value={(() => {
609
+ if (!form.scraperProxyServer) return '';
610
+ try {
611
+ const url = new URL(form.scraperProxyServer);
612
+ if (form.scraperProxyUsername)
613
+ url.username = encodeURIComponent(form.scraperProxyUsername);
614
+ if (form.scraperProxyPassword)
615
+ url.password = encodeURIComponent(form.scraperProxyPassword);
616
+ return url.toString().replace(/\/$/, '');
617
+ } catch {
618
+ return form.scraperProxyServer;
619
+ }
620
+ })()}
621
+ onChange={(e) => {
622
+ const val = e.target.value;
623
+ let server = val;
624
+ let username = '';
625
+ let password = '';
626
+ const trimmed = val.trim();
627
+ if (trimmed) {
628
+ try {
629
+ const url = new URL(trimmed);
630
+ if (url.username || url.password) {
631
+ username = decodeURIComponent(url.username);
632
+ password = decodeURIComponent(url.password);
633
+ url.username = '';
634
+ url.password = '';
635
+ server = url.toString().replace(/\/$/, '');
636
+ }
637
+ } catch {
638
+ const parts = trimmed.split(':');
639
+ if (parts.length === 4 && !trimmed.startsWith('http')) {
640
+ server = `http://${parts[0]}:${parts[1]}`;
641
+ username = parts[2];
642
+ password = parts[3];
643
+ }
644
+ }
645
+ }
646
+ setForm({
647
+ ...form,
648
+ scraperProxyServer: server,
649
+ scraperProxyUsername: username,
650
+ scraperProxyPassword: password,
651
+ });
652
+ }}
653
+ placeholder="http://proxy.example.com:8080 (or paste one-liner)"
654
+ />
655
+ </div>
656
+ </TabsContent>
657
+
658
+ <TabsContent value="form" className="mt-0 space-y-4">
659
+ <div className="space-y-1.5">
660
+ <Label className="text-xs">Proxy Server</Label>
661
+ <Input
662
+ className="text-sm"
663
+ value={form.scraperProxyServer || ''}
664
+ onChange={(e) => setForm({ ...form, scraperProxyServer: e.target.value })}
665
+ placeholder="http://proxy.example.com:8080"
666
+ />
667
+ </div>
668
+ <div className="grid grid-cols-2 gap-3">
669
+ <div className="space-y-1.5">
670
+ <Label className="text-xs">Proxy Username</Label>
671
+ <Input
672
+ className="text-sm"
673
+ value={form.scraperProxyUsername || ''}
674
+ onChange={(e) => setForm({ ...form, scraperProxyUsername: e.target.value })}
675
+ />
676
+ </div>
677
+ <div className="space-y-1.5">
678
+ <Label className="text-xs">Proxy Password</Label>
679
+ <div className="relative">
680
+ <Input
681
+ type={showProxyPassword ? 'text' : 'password'}
682
+ className="text-sm pr-10"
683
+ value={form.scraperProxyPassword || ''}
684
+ onChange={(e) => setForm({ ...form, scraperProxyPassword: e.target.value })}
685
+ />
686
+ <Button
687
+ type="button"
688
+ variant="ghost"
689
+ size="sm"
690
+ className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent text-muted-foreground"
691
+ onClick={() => setShowProxyPassword(!showProxyPassword)}
692
+ >
693
+ {showProxyPassword ? (
694
+ <EyeOff className="size-4" />
695
+ ) : (
696
+ <Eye className="size-4" />
697
+ )}
698
+ </Button>
699
+ </div>
700
+ </div>
701
+ </div>
702
+ </TabsContent>
703
+ </Tabs>
704
+ </CardContent>
705
+ <CardFooter className="flex justify-end gap-2 pt-4 border-t">
706
+ <Button
707
+ className="rounded-lg h-9 gap-2"
708
+ variant="outline"
709
+ size="sm"
710
+ onClick={async () => {
711
+ const promise = (async () => {
712
+ if (!form.scraperProxyServer) {
713
+ throw new Error('Proxy server is required');
714
+ }
715
+ const res = await fetch(workspaceUrl('/api/proxy/verify'), {
716
+ method: 'POST',
717
+ headers: { 'Content-Type': 'application/json' },
718
+ body: JSON.stringify({
719
+ server: form.scraperProxyServer,
720
+ username: form.scraperProxyUsername,
721
+ password: form.scraperProxyPassword,
722
+ }),
723
+ });
724
+ const data = await res.json();
725
+ if (!res.ok) {
726
+ throw new Error(data.error || 'Proxy verification failed');
727
+ }
728
+ return data;
729
+ })();
730
+
731
+ toast.promise(promise, {
732
+ loading: 'Verifying proxy...',
733
+ success: 'Proxy configuration is valid!',
734
+ error: (err) => err.message || 'Proxy verification failed',
735
+ });
736
+ }}
737
+ >
738
+ Verify Proxy
739
+ </Button>
740
+ <Button
741
+ size="lg"
742
+ disabled={saving === 'proxy' || !dirtyProxy}
743
+ onClick={() => handleSave('proxy')}
744
+ className="gap-1.5 px-4"
745
+ >
746
+ {saving === 'proxy' ? (
747
+ <Loader2 className="size-3.5 animate-spin" />
748
+ ) : (
749
+ <Save className="size-3.5" />
750
+ )}
751
+ Save
752
+ </Button>
753
+ </CardFooter>
754
+ </Card>
755
+ </div>
756
+ );
757
+ }