@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,282 @@
1
+ 'use client';
2
+
3
+ import { workspaceUrl, workspaceFetch as fetch } from '../../host';
4
+ import { useMemo, useState } from 'react';
5
+ import useSWR from 'swr';
6
+ import { CheckCircle2, Loader2, RefreshCw, Search, Unplug } from 'lucide-react';
7
+ import { Button } from '@larkup/workspace-ui/button';
8
+ import { Checkbox } from '../ui/checkbox';
9
+ import { DialogFooter } from '../ui/dialog';
10
+ import { Input } from '../ui/input';
11
+ import { useIntegrationAuth } from '../../hooks/use-integration-auth';
12
+ import { toast } from 'sonner';
13
+
14
+ interface Resource {
15
+ id: string;
16
+ title: string;
17
+ url?: string;
18
+ kind?: string;
19
+ }
20
+ interface Status {
21
+ connected: boolean;
22
+ resources: Resource[];
23
+ error?: string;
24
+ }
25
+
26
+ export function IntegrationResourcesPanel({
27
+ integration,
28
+ name,
29
+ icon,
30
+ onAdded,
31
+ onClose,
32
+ groupId,
33
+ }: {
34
+ integration: string;
35
+ name: string;
36
+ icon: string;
37
+ onAdded: () => void;
38
+ onClose: () => void;
39
+ groupId: string;
40
+ }) {
41
+ const endpoint = workspaceUrl(`/api/integrations/${integration}`);
42
+ const { data, isLoading, mutate } = useSWR<Status>(endpoint, (url: string) =>
43
+ fetch(url).then(async (response) => {
44
+ const payload = await response.json();
45
+ if (!response.ok && !payload.connected) return payload;
46
+ if (!response.ok) throw new Error(payload.error);
47
+ return payload;
48
+ }),
49
+ );
50
+ const [selected, setSelected] = useState<Set<string>>(new Set());
51
+ const [importing, setImporting] = useState(false);
52
+ const [disconnecting, setDisconnecting] = useState(false);
53
+
54
+ const [search, setSearch] = useState('');
55
+ const [activeTab, setActiveTab] = useState('all');
56
+
57
+ const resources = data?.resources ?? [];
58
+
59
+ const kinds = useMemo(() => {
60
+ const set = new Set<string>();
61
+ for (const r of resources) {
62
+ if (r.kind) set.add(r.kind);
63
+ }
64
+ return ['all', ...Array.from(set).sort()];
65
+ }, [resources]);
66
+
67
+ const filteredResources = useMemo(() => {
68
+ return resources.filter((r) => {
69
+ if (activeTab !== 'all' && r.kind !== activeTab) return false;
70
+ if (search && !r.title.toLowerCase().includes(search.toLowerCase())) return false;
71
+ return true;
72
+ });
73
+ }, [resources, search, activeTab]);
74
+
75
+ const { connect } = useIntegrationAuth({
76
+ onSuccess: () => {
77
+ toast.success(`${name} connected`);
78
+ mutate();
79
+ },
80
+ onError: (error) => toast.error(error),
81
+ });
82
+ const allSelected = useMemo(
83
+ () =>
84
+ filteredResources.length > 0 &&
85
+ filteredResources.every((resource) => selected.has(resource.id)),
86
+ [filteredResources, selected],
87
+ );
88
+
89
+ async function disconnect() {
90
+ setDisconnecting(true);
91
+ try {
92
+ const response = await fetch(endpoint, { method: 'DELETE' });
93
+ const payload = await response.json();
94
+ if (!response.ok) throw new Error(payload.error ?? 'Unable to disconnect');
95
+ toast.success(`${name} disconnected`);
96
+ setSelected(new Set());
97
+ onClose();
98
+ } catch (error) {
99
+ toast.error(error instanceof Error ? error.message : `Unable to disconnect ${name}`);
100
+ } finally {
101
+ setDisconnecting(false);
102
+ }
103
+ }
104
+
105
+ async function importSelected() {
106
+ if (!selected.size) return toast.error('Select at least one item to import.');
107
+ setImporting(true);
108
+ try {
109
+ const response = await fetch(endpoint, {
110
+ method: 'POST',
111
+ headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify({ resourceIds: [...selected], groupId }),
113
+ });
114
+ const payload = await response.json();
115
+ if (!response.ok) throw new Error(payload.error);
116
+ toast.success(`${payload.imported} of ${payload.total} items imported`);
117
+ setSelected(new Set());
118
+ onAdded();
119
+ } catch (error) {
120
+ toast.error(error instanceof Error ? error.message : 'Import failed');
121
+ } finally {
122
+ setImporting(false);
123
+ }
124
+ }
125
+
126
+ if (isLoading)
127
+ return (
128
+ <div className="flex justify-center gap-2 p-4 py-16 text-sm text-muted-foreground">
129
+ <Loader2 className="size-4 animate-spin" />
130
+ Checking connection…
131
+ </div>
132
+ );
133
+ if (!data?.connected)
134
+ return (
135
+ <div className="flex w-full min-w-0 flex-col">
136
+ <div className="flex flex-1 flex-col items-center justify-center gap-4 p-4 py-12 text-center">
137
+ <img src={icon} alt="" className="size-9 object-contain" />
138
+ <div>
139
+ <h3 className="text-lg font-semibold">Connect {name}</h3>
140
+ <p className="mt-1 text-sm text-muted-foreground">
141
+ Read and import selected knowledge into your corpus.
142
+ </p>
143
+ {/* {data?.error && <p className="mt-2 text-xs text-destructive">{data.error}</p>} */}
144
+ </div>
145
+ <Button onClick={() => connect(integration)}>Connect {name}</Button>
146
+ </div>
147
+ {data?.error && (
148
+ <DialogFooter className="m-0! flex flex-row items-center justify-between space-x-0 gap-2 border-t border-border/70 bg-muted/50 p-4 sm:justify-between">
149
+ <Button variant="destructive" onClick={disconnect} disabled={disconnecting}>
150
+ {disconnecting ? (
151
+ <Loader2 className="size-4 mr-2 animate-spin" />
152
+ ) : (
153
+ <Unplug className="size-4 mr-2" />
154
+ )}
155
+ Disconnect
156
+ </Button>
157
+ <Button variant="outline" onClick={onClose}>
158
+ Cancel
159
+ </Button>
160
+ </DialogFooter>
161
+ )}
162
+ </div>
163
+ );
164
+ return (
165
+ <div className="flex flex-col">
166
+ <div className="space-y-4 p-4">
167
+ <div className="flex items-center justify-between gap-3">
168
+ <div className="flex items-center gap-2 text-sm font-medium">
169
+ <CheckCircle2 className="size-4 text-green-600" />
170
+ {name} connected{' '}
171
+ <span className="text-muted-foreground">· {resources.length} items</span>
172
+ <Button
173
+ variant="ghost"
174
+ size="icon"
175
+ onClick={() => mutate()}
176
+ className="h-7 shrink-0 gap-1.5 text-xs"
177
+ >
178
+ <RefreshCw className="size-3" />
179
+ </Button>
180
+ </div>
181
+ </div>
182
+ <div className="flex flex-col gap-3">
183
+ <div className="relative">
184
+ <Search className="absolute left-2.5 top-2.5 size-4 text-muted-foreground" />
185
+ <Input
186
+ placeholder={`Search ${resources.length} items...`}
187
+ className="h-9 pl-9"
188
+ value={search}
189
+ onChange={(event) => setSearch(event.target.value)}
190
+ />
191
+ </div>
192
+ {kinds.length > 1 && (
193
+ <div className="flex flex-wrap items-center gap-2">
194
+ {kinds.map((kind) => {
195
+ const isActive = activeTab === kind;
196
+ return (
197
+ <button
198
+ key={kind}
199
+ type="button"
200
+ onClick={() => setActiveTab(kind)}
201
+ className={`rounded-full px-3 py-1 text-xs font-medium capitalize transition-colors ${
202
+ isActive
203
+ ? 'bg-muted text-foreground'
204
+ : 'border border-border bg-transparent text-muted-foreground hover:bg-muted/50'
205
+ }`}
206
+ >
207
+ {kind}
208
+ </button>
209
+ );
210
+ })}
211
+ </div>
212
+ )}
213
+ </div>
214
+ <div className="max-h-80 overflow-y-auto rounded-lg border">
215
+ <label className="sticky top-0 z-10 flex items-center gap-3 border-b bg-background px-3 py-2 text-sm font-medium">
216
+ <Checkbox
217
+ checked={allSelected}
218
+ onCheckedChange={() =>
219
+ setSelected((current) => {
220
+ const next = new Set(current);
221
+ if (allSelected) {
222
+ filteredResources.forEach((r) => next.delete(r.id));
223
+ } else {
224
+ filteredResources.forEach((r) => next.add(r.id));
225
+ }
226
+ return next;
227
+ })
228
+ }
229
+ />
230
+ Select all
231
+ </label>
232
+ {filteredResources.length === 0 ? (
233
+ <div className="p-8 text-center text-sm text-muted-foreground">
234
+ No items match your filters.
235
+ </div>
236
+ ) : (
237
+ filteredResources.map((resource) => (
238
+ <label
239
+ key={resource.id}
240
+ className="flex cursor-pointer items-center gap-3 border-b px-3 py-2.5 last:border-0 hover:bg-muted/30"
241
+ >
242
+ <Checkbox
243
+ checked={selected.has(resource.id)}
244
+ onCheckedChange={() =>
245
+ setSelected((current) => {
246
+ const next = new Set(current);
247
+ next.has(resource.id) ? next.delete(resource.id) : next.add(resource.id);
248
+ return next;
249
+ })
250
+ }
251
+ />
252
+ <span className="min-w-0 flex-1 truncate text-sm">{resource.title}</span>
253
+ {resource.kind && (
254
+ <span className="text-[10px] text-muted-foreground">{resource.kind}</span>
255
+ )}
256
+ </label>
257
+ ))
258
+ )}
259
+ </div>
260
+ </div>
261
+ <DialogFooter className="m-0! flex flex-row items-center justify-between space-x-0 gap-2 border-t border-border/70 bg-muted/50 p-4 sm:justify-between">
262
+ <Button variant="destructive" onClick={disconnect} disabled={importing || disconnecting}>
263
+ {disconnecting ? (
264
+ <Loader2 className="size-4 mr-2 animate-spin" />
265
+ ) : (
266
+ <Unplug className="size-4 mr-2" />
267
+ )}
268
+ Disconnect
269
+ </Button>
270
+ <div className="flex items-center space-x-2">
271
+ <Button variant="outline" onClick={onClose} disabled={importing || disconnecting}>
272
+ Cancel
273
+ </Button>
274
+ <Button disabled={!selected.size || importing || disconnecting} onClick={importSelected}>
275
+ {importing && <Loader2 className="mr-2 size-4 animate-spin" />}
276
+ Import {selected.size || ''} selected
277
+ </Button>
278
+ </div>
279
+ </DialogFooter>
280
+ </div>
281
+ );
282
+ }
@@ -0,0 +1,392 @@
1
+ 'use client';
2
+
3
+ import { workspaceUrl, workspaceFetch as fetch } from '../../host';
4
+ import { useMemo, useState } from 'react';
5
+ import useSWR from 'swr';
6
+ import { NotionPanel } from './notion-panel';
7
+ import { IntegrationResourcesPanel } from './integration-resources-panel';
8
+ import { useNotionAuth } from '../../hooks/use-notion-auth';
9
+ import { useIntegrationAuth } from '../../hooks/use-integration-auth';
10
+ import { integrations } from '@larkup/integrations';
11
+ import { cn } from '../../lib/utils';
12
+ import { CableIcon, Search } from 'lucide-react';
13
+ import { Button } from '@larkup/workspace-ui/button';
14
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '../ui/dialog';
15
+ import { Input } from '../ui/input';
16
+ import { Textarea } from '../ui/textarea';
17
+ import { Label } from '@larkup/workspace-ui/label';
18
+ import { Loader2, Sparkles } from 'lucide-react';
19
+ import { toast } from 'sonner';
20
+
21
+ const googleIntegrationsAwaitingVerification = new Set([
22
+ 'google-analytics',
23
+ 'google-calendar',
24
+ 'google-docs',
25
+ 'google-drive',
26
+ 'google-maps',
27
+ 'google-meet',
28
+ 'google-sheets',
29
+ 'google-slides',
30
+ ]);
31
+
32
+ const hiddenIntegrationIds = new Set(['jira-data-center']);
33
+ const releasedIntegrationIds = new Set(['jira', 'linear']);
34
+
35
+ const panelIntegrations = integrations
36
+ .filter((integration) => !hiddenIntegrationIds.has(integration.id))
37
+ .map((integration) => {
38
+ if (googleIntegrationsAwaitingVerification.has(integration.id)) {
39
+ return { ...integration, status: 'coming-soon' as const };
40
+ }
41
+
42
+ if (releasedIntegrationIds.has(integration.id)) {
43
+ return { ...integration, status: 'ready' as const };
44
+ }
45
+
46
+ return integration;
47
+ });
48
+
49
+ export function IntegrationsPanel({ onAdded, groupId }: { onAdded: () => void; groupId: string }) {
50
+ const [activeIntegration, setActiveIntegration] = useState<string | null>(null);
51
+ const [search, setSearch] = useState('');
52
+
53
+ const {
54
+ data: connectionData,
55
+ isLoading: isLoadingStatus,
56
+ mutate: mutateConnections,
57
+ } = useSWR(workspaceUrl('/api/integrations'), (url) => fetch(url).then((res) => res.json()));
58
+ const connectedStatus = Object.fromEntries(
59
+ (connectionData?.integrations ?? []).map((integration: { id: string; connected: boolean }) => [
60
+ integration.id,
61
+ integration.connected,
62
+ ]),
63
+ ) as Record<string, boolean>;
64
+ const visibleIntegrations = useMemo(() => {
65
+ const query = search.trim().toLowerCase();
66
+ const priority = (integration: (typeof panelIntegrations)[number]) => {
67
+ if (integration.status === 'ready' && connectedStatus[integration.id]) return 0;
68
+ return integration.status === 'ready' ? 1 : 2;
69
+ };
70
+
71
+ return panelIntegrations
72
+ .filter(
73
+ (integration) =>
74
+ !query || `${integration.name} ${integration.category}`.toLowerCase().includes(query),
75
+ )
76
+ .sort((left, right) => priority(left) - priority(right));
77
+ }, [connectedStatus, search]);
78
+
79
+ const [isRequestModalOpen, setIsRequestModalOpen] = useState(false);
80
+ const [requestName, setRequestName] = useState('');
81
+ const [requestCompany, setRequestCompany] = useState('');
82
+ const [requestEmail, setRequestEmail] = useState('');
83
+ const [requestMessage, setRequestMessage] = useState('');
84
+ const [isRequesting, setIsRequesting] = useState(false);
85
+ const [requestSuccess, setRequestSuccess] = useState(false);
86
+
87
+ const handleRequestIntegration = async (e: React.FormEvent) => {
88
+ e.preventDefault();
89
+ if (!requestEmail) return;
90
+
91
+ setIsRequesting(true);
92
+ try {
93
+ const res = await fetch(
94
+ process.env.NEXT_PUBLIC_CONNECT_API_URL || 'https://www.larkup.de/api/connect',
95
+ {
96
+ method: 'POST',
97
+ headers: {
98
+ 'Content-Type': 'application/json',
99
+ },
100
+ body: JSON.stringify({
101
+ name: requestName,
102
+ company: requestCompany,
103
+ email: requestEmail,
104
+ message: requestMessage,
105
+ }),
106
+ },
107
+ );
108
+
109
+ if (!res.ok) {
110
+ throw new Error('Failed to send request');
111
+ }
112
+
113
+ setRequestSuccess(true);
114
+ toast.success('Request sent successfully!');
115
+
116
+ setTimeout(() => {
117
+ setIsRequestModalOpen(false);
118
+ setRequestSuccess(false);
119
+ setRequestName('');
120
+ setRequestCompany('');
121
+ setRequestEmail('');
122
+ setRequestMessage('');
123
+ }, 2000);
124
+ } catch (error) {
125
+ toast.error('Failed to send request. Please try again later.');
126
+ } finally {
127
+ setIsRequesting(false);
128
+ }
129
+ };
130
+
131
+ const fetchStatuses = () => {
132
+ mutateConnections();
133
+ };
134
+
135
+ const { connectToNotion: handleConnectNotion } = useNotionAuth({
136
+ onSuccess: () => {
137
+ fetchStatuses();
138
+ toast.success('Successfully connected to Notion');
139
+ },
140
+ onError: (error) => {
141
+ toast.error(error);
142
+ },
143
+ });
144
+ const { connect: connectIntegration } = useIntegrationAuth({
145
+ onSuccess: (integration) => {
146
+ fetchStatuses();
147
+ const name = panelIntegrations.find((item) => item.id === integration)?.name ?? 'integration';
148
+ toast.success(`Successfully connected to ${name}`);
149
+ },
150
+ onError: (error) => toast.error(error),
151
+ });
152
+
153
+ return (
154
+ <div className="space-y-6 pb-9">
155
+ <div>
156
+ <div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
157
+ <div className="relative w-full sm:max-w-sm">
158
+ <Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
159
+ <Input
160
+ aria-label="Search integrations"
161
+ placeholder="Search integrations..."
162
+ value={search}
163
+ onChange={(event) => setSearch(event.target.value)}
164
+ className="h-9 pl-9 bg-white"
165
+ />
166
+ </div>
167
+ <Button
168
+ variant="outline"
169
+ size="sm"
170
+ className="h-8 gap-2 bg-white/80 hover:bg-white"
171
+ onClick={() => setIsRequestModalOpen(true)}
172
+ >
173
+ <CableIcon className="size-3.5" />
174
+ Ask for integration
175
+ </Button>
176
+ </div>
177
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
178
+ {visibleIntegrations.map((integration) => {
179
+ const isConnected = connectedStatus[integration.id];
180
+
181
+ return (
182
+ <div
183
+ key={integration.id}
184
+ data-testid="integration-card"
185
+ data-integration-id={integration.id}
186
+ className={cn(
187
+ 'group flex items-center justify-between gap-3.5 rounded-xl border px-4 py-3.5 text-left transition-all',
188
+ integration.status === 'ready'
189
+ ? 'border-border bg-white/80 hover:border-primary/20 hover:bg-white'
190
+ : 'border-border/60 bg-muted/15',
191
+ )}
192
+ >
193
+ <div className="flex items-center gap-3.5 min-w-0">
194
+ <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-muted/30">
195
+ <img
196
+ src={integration.icon}
197
+ alt={integration.name}
198
+ className="size-6 object-contain"
199
+ />
200
+ </div>
201
+ <div className="flex-1 min-w-0">
202
+ <div className="flex items-center gap-2">
203
+ <span className="text-sm font-medium text-foreground">
204
+ {integration.name}
205
+ </span>
206
+ <span className="text-[10px] font-medium text-muted-foreground/70">
207
+ {integration.category}
208
+ </span>
209
+ </div>
210
+ <p className="text-xs text-muted-foreground mt-0.5 truncate max-w-50">
211
+ {integration.description}
212
+ </p>
213
+ </div>
214
+ </div>
215
+
216
+ <div className="shrink-0">
217
+ {integration.status === 'coming-soon' ? (
218
+ <span className="rounded-md border border-border/70 bg-background px-2 py-1 text-[10px] font-medium text-muted-foreground">
219
+ Coming soon
220
+ </span>
221
+ ) : isLoadingStatus ? (
222
+ <Button variant="outline" size="sm" className="h-8 w-21" disabled>
223
+ <Loader2 className="size-3.5 animate-spin" />
224
+ </Button>
225
+ ) : isConnected ? (
226
+ <Button
227
+ variant="default"
228
+ size="sm"
229
+ className="h-8 w-21"
230
+ onClick={() => setActiveIntegration(integration.id)}
231
+ >
232
+ Configure
233
+ </Button>
234
+ ) : (
235
+ <Button
236
+ variant="outline"
237
+ size="sm"
238
+ className="h-8 w-21 bg-muted/50 hover:bg-muted/80 "
239
+ onClick={() =>
240
+ integration.id === 'notion'
241
+ ? handleConnectNotion()
242
+ : connectIntegration(integration.id)
243
+ }
244
+ >
245
+ Connect
246
+ </Button>
247
+ )}
248
+ </div>
249
+ </div>
250
+ );
251
+ })}
252
+ </div>
253
+ {search.trim() &&
254
+ !panelIntegrations.some((integration) =>
255
+ `${integration.name} ${integration.category}`
256
+ .toLowerCase()
257
+ .includes(search.trim().toLowerCase()),
258
+ ) && (
259
+ <p className="py-10 text-center text-sm text-muted-foreground">
260
+ No integrations match “{search.trim()}”.
261
+ </p>
262
+ )}
263
+ </div>
264
+
265
+ <Dialog
266
+ open={!!activeIntegration}
267
+ onOpenChange={(open) => {
268
+ if (!open) {
269
+ setActiveIntegration(null);
270
+ // Re-fetch status when closing modal to update UI
271
+ if (activeIntegration === 'notion') {
272
+ mutateConnections();
273
+ }
274
+ }
275
+ }}
276
+ >
277
+ <DialogContent className="flex flex-col gap-0 overflow-hidden p-0 sm:max-w-3xl">
278
+ <DialogHeader className="sr-only">
279
+ <DialogTitle>Integration Panel</DialogTitle>
280
+ </DialogHeader>
281
+ {activeIntegration === 'notion' && (
282
+ <NotionPanel
283
+ onAdded={onAdded}
284
+ onClose={() => setActiveIntegration(null)}
285
+ groupId={groupId}
286
+ />
287
+ )}
288
+ {activeIntegration &&
289
+ activeIntegration !== 'notion' &&
290
+ (() => {
291
+ const integration = panelIntegrations.find(
292
+ (item) => item.id === activeIntegration && item.status === 'ready',
293
+ );
294
+ return integration ? (
295
+ <IntegrationResourcesPanel
296
+ integration={integration.id}
297
+ name={integration.name}
298
+ icon={integration.icon}
299
+ onAdded={onAdded}
300
+ groupId={groupId}
301
+ onClose={() => {
302
+ setActiveIntegration(null);
303
+ mutateConnections();
304
+ }}
305
+ />
306
+ ) : null;
307
+ })()}
308
+ </DialogContent>
309
+ </Dialog>
310
+
311
+ <Dialog open={isRequestModalOpen} onOpenChange={setIsRequestModalOpen}>
312
+ <DialogContent className="sm:max-w-3xl">
313
+ <DialogHeader>
314
+ <DialogTitle>Ask for Integration</DialogTitle>
315
+ </DialogHeader>
316
+ {requestSuccess ? (
317
+ <div className="flex flex-col items-center justify-center py-8 text-center space-y-4">
318
+ <div className="h-12 w-12 rounded-full bg-primary/10 flex items-center justify-center text-primary">
319
+ <Sparkles className="h-6 w-6" />
320
+ </div>
321
+ <p className="text-sm text-muted-foreground">
322
+ Thank you! We've received your request and will get back to you soon.
323
+ </p>
324
+ </div>
325
+ ) : (
326
+ <form onSubmit={handleRequestIntegration} className="space-y-4 mt-4">
327
+ <div className="grid grid-cols-2 gap-4">
328
+ <div className="space-y-2">
329
+ <Label htmlFor="name">Name</Label>
330
+ <Input
331
+ id="name"
332
+ type="text"
333
+ placeholder="John Doe"
334
+ value={requestName}
335
+ onChange={(e) => setRequestName(e.target.value)}
336
+ />
337
+ </div>
338
+ <div className="space-y-2">
339
+ <Label htmlFor="company">Company</Label>
340
+ <Input
341
+ id="company"
342
+ type="text"
343
+ placeholder="Acme Inc."
344
+ value={requestCompany}
345
+ onChange={(e) => setRequestCompany(e.target.value)}
346
+ />
347
+ </div>
348
+ </div>
349
+ <div className="space-y-2">
350
+ <Label htmlFor="email">
351
+ Your Email <span className="text-red-500">*</span>
352
+ </Label>
353
+ <Input
354
+ id="email"
355
+ type="email"
356
+ required
357
+ placeholder="name@company.com"
358
+ value={requestEmail}
359
+ onChange={(e) => setRequestEmail(e.target.value)}
360
+ />
361
+ </div>
362
+ <div className="space-y-2">
363
+ <Label htmlFor="message">Which integration do you need?</Label>
364
+ <Textarea
365
+ id="message"
366
+ placeholder="Tell us which tool you want to connect and how you plan to use it..."
367
+ className="resize-none min-h-30 max-h-67.5!"
368
+ rows={5}
369
+ value={requestMessage}
370
+ onChange={(e) => setRequestMessage(e.target.value)}
371
+ />
372
+ </div>
373
+ <DialogFooter className="bg-muted/50 border-border/70 mt-6">
374
+ <Button
375
+ type="button"
376
+ variant="outline"
377
+ onClick={() => setIsRequestModalOpen(false)}
378
+ >
379
+ Cancel
380
+ </Button>
381
+ <Button type="submit" disabled={isRequesting}>
382
+ {isRequesting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
383
+ Submit Request
384
+ </Button>
385
+ </DialogFooter>
386
+ </form>
387
+ )}
388
+ </DialogContent>
389
+ </Dialog>
390
+ </div>
391
+ );
392
+ }