orcheo-studio 0.21.1

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 (329) hide show
  1. package/.bumpversion.cfg +14 -0
  2. package/.eslintrc +1 -0
  3. package/CHANGELOG.md +32 -0
  4. package/README.md +60 -0
  5. package/bin/orcheo-studio.js +39 -0
  6. package/components.json +21 -0
  7. package/dist/assets/arc-BbeTokFd.js +1 -0
  8. package/dist/assets/architectureDiagram-VXUJARFQ-CN00R9LC.js +36 -0
  9. package/dist/assets/avatar-01-CIAd4XAq.svg +1 -0
  10. package/dist/assets/avatar-02-BkgLJWQZ.svg +1 -0
  11. package/dist/assets/avatar-03-DVVcnD3b.svg +1 -0
  12. package/dist/assets/avatar-04-ydqFTDdh.svg +1 -0
  13. package/dist/assets/avatar-05-CSIhgJow.svg +1 -0
  14. package/dist/assets/avatar-06-B-h1Nz9Y.svg +1 -0
  15. package/dist/assets/avatar-07-BgkDZuBW.svg +1 -0
  16. package/dist/assets/avatar-08-C5KZjVH6.svg +1 -0
  17. package/dist/assets/avatar-09-2EGeSlPh.svg +1 -0
  18. package/dist/assets/avatar-10-DRf4ZGD2.svg +1 -0
  19. package/dist/assets/avatar-11-CEzBCpib.svg +1 -0
  20. package/dist/assets/avatar-12-COVngNEy.svg +1 -0
  21. package/dist/assets/avatar-13-D1i1tZVO.svg +1 -0
  22. package/dist/assets/avatar-14-B68CNAEs.svg +1 -0
  23. package/dist/assets/avatar-15-B9NsRNUX.svg +1 -0
  24. package/dist/assets/avatar-16-DQ_JJRGR.svg +1 -0
  25. package/dist/assets/avatar-17-DfDJkGv5.svg +1 -0
  26. package/dist/assets/avatar-18-BuxiqOaJ.svg +1 -0
  27. package/dist/assets/avatar-19-C2mJfrwW.svg +1 -0
  28. package/dist/assets/avatar-20-DPE_yE1a.svg +1 -0
  29. package/dist/assets/avatar-21-DbB0Lj6t.svg +1 -0
  30. package/dist/assets/blockDiagram-VD42YOAC-BPpieIRz.js +122 -0
  31. package/dist/assets/c4Diagram-YG6GDRKO-DAhuY1fj.js +10 -0
  32. package/dist/assets/channel-DtVQHWfl.js +1 -0
  33. package/dist/assets/chunk-4BX2VUAB-7RJQCroR.js +1 -0
  34. package/dist/assets/chunk-55IACEB6-CEN5yOA_.js +1 -0
  35. package/dist/assets/chunk-B4BG7PRW-vdlAz_3Q.js +165 -0
  36. package/dist/assets/chunk-DI55MBZ5-BTmKjGVg.js +220 -0
  37. package/dist/assets/chunk-FMBD7UC4-CwWvFfrg.js +15 -0
  38. package/dist/assets/chunk-QN33PNHL-XH90lUUN.js +1 -0
  39. package/dist/assets/chunk-QZHKN3VN-DMoMVES0.js +1 -0
  40. package/dist/assets/chunk-TZMSLE5B-Bx2XTXDY.js +1 -0
  41. package/dist/assets/classDiagram-2ON5EDUG-pw6P8q-u.js +1 -0
  42. package/dist/assets/classDiagram-v2-WZHVMYZB-pw6P8q-u.js +1 -0
  43. package/dist/assets/clone-DJq6GzHg.js +1 -0
  44. package/dist/assets/cose-bilkent-S5V4N54A-DS1MNwZB.js +1 -0
  45. package/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  46. package/dist/assets/dagre-6UL2VRFP-DdklXSXB.js +4 -0
  47. package/dist/assets/defaultLocale-C4B-KCzX.js +1 -0
  48. package/dist/assets/diagram-PSM6KHXK-BKBo8pqg.js +24 -0
  49. package/dist/assets/diagram-QEK2KX5R-DaavZhFO.js +43 -0
  50. package/dist/assets/diagram-S2PKOQOG-DkTwknYd.js +24 -0
  51. package/dist/assets/erDiagram-Q2GNP2WA-ClR5qEsI.js +60 -0
  52. package/dist/assets/flowDiagram-NV44I4VS-Cxt_sJHB.js +162 -0
  53. package/dist/assets/ganttDiagram-JELNMOA3-CIbs3JPJ.js +267 -0
  54. package/dist/assets/gitGraphDiagram-V2S2FVAM-DmqLh_uo.js +65 -0
  55. package/dist/assets/graph-zo28rS-a.js +1 -0
  56. package/dist/assets/index-B0_h2wCA.css +1 -0
  57. package/dist/assets/index-Be29_rJf.js +775 -0
  58. package/dist/assets/infoDiagram-HS3SLOUP-D9_rMkAf.js +2 -0
  59. package/dist/assets/init-Gi6I4Gst.js +1 -0
  60. package/dist/assets/isUndefined-C59bc74c.js +1 -0
  61. package/dist/assets/journeyDiagram-XKPGCS4Q-4ZuggLXE.js +139 -0
  62. package/dist/assets/kanban-definition-3W4ZIXB7-BQDNrhHL.js +89 -0
  63. package/dist/assets/katex-CBSAILhF.js +261 -0
  64. package/dist/assets/layout-CXbNm-Yo.js +1 -0
  65. package/dist/assets/linear-DQWATg1F.js +1 -0
  66. package/dist/assets/min-DJjRzMm8.js +1 -0
  67. package/dist/assets/mindmap-definition-VGOIOE7T-DiZQmsHT.js +68 -0
  68. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  69. package/dist/assets/pieDiagram-ADFJNKIX-Bkju7jCD.js +30 -0
  70. package/dist/assets/quadrantDiagram-AYHSOK5B-CIB47UvU.js +7 -0
  71. package/dist/assets/requirementDiagram-UZGBJVZJ-BSB3uACk.js +64 -0
  72. package/dist/assets/sankeyDiagram-TZEHDZUN-rB8wD2kd.js +10 -0
  73. package/dist/assets/sequenceDiagram-WL72ISMW-Cd9s3Q_1.js +145 -0
  74. package/dist/assets/stateDiagram-FKZM4ZOC-zAxtjcM9.js +1 -0
  75. package/dist/assets/stateDiagram-v2-4FDKWEC3-C4DctH65.js +1 -0
  76. package/dist/assets/timeline-definition-IT6M3QCI-Et89F1F3.js +61 -0
  77. package/dist/assets/treemap-GDKQZRPO-6mZVX2Gv.js +162 -0
  78. package/dist/assets/xychartDiagram-PRI3JC2R-CgyY8dNz.js +7 -0
  79. package/dist/favicon.ico +0 -0
  80. package/dist/index.html +19 -0
  81. package/dist/robots.txt +2 -0
  82. package/eslint.config.js +49 -0
  83. package/index.html +18 -0
  84. package/package.json +183 -0
  85. package/postcss.config.js +6 -0
  86. package/public/favicon.ico +0 -0
  87. package/public/robots.txt +2 -0
  88. package/src/App.tsx +115 -0
  89. package/src/assets/avatars/avatar-01.svg +1 -0
  90. package/src/assets/avatars/avatar-02.svg +1 -0
  91. package/src/assets/avatars/avatar-03.svg +1 -0
  92. package/src/assets/avatars/avatar-04.svg +1 -0
  93. package/src/assets/avatars/avatar-05.svg +1 -0
  94. package/src/assets/avatars/avatar-06.svg +1 -0
  95. package/src/assets/avatars/avatar-07.svg +1 -0
  96. package/src/assets/avatars/avatar-08.svg +1 -0
  97. package/src/assets/avatars/avatar-09.svg +1 -0
  98. package/src/assets/avatars/avatar-10.svg +1 -0
  99. package/src/assets/avatars/avatar-11.svg +1 -0
  100. package/src/assets/avatars/avatar-12.svg +1 -0
  101. package/src/assets/avatars/avatar-13.svg +1 -0
  102. package/src/assets/avatars/avatar-14.svg +1 -0
  103. package/src/assets/avatars/avatar-15.svg +1 -0
  104. package/src/assets/avatars/avatar-16.svg +1 -0
  105. package/src/assets/avatars/avatar-17.svg +1 -0
  106. package/src/assets/avatars/avatar-18.svg +1 -0
  107. package/src/assets/avatars/avatar-19.svg +1 -0
  108. package/src/assets/avatars/avatar-20.svg +1 -0
  109. package/src/assets/avatars/avatar-21.svg +1 -0
  110. package/src/assets/avatars/index.ts +99 -0
  111. package/src/design-system/ui/alert-dialog.tsx +141 -0
  112. package/src/design-system/ui/alert.tsx +59 -0
  113. package/src/design-system/ui/avatar.tsx +51 -0
  114. package/src/design-system/ui/badge.tsx +38 -0
  115. package/src/design-system/ui/button.tsx +59 -0
  116. package/src/design-system/ui/card.tsx +83 -0
  117. package/src/design-system/ui/dialog.tsx +122 -0
  118. package/src/design-system/ui/dropdown-menu.tsx +201 -0
  119. package/src/design-system/ui/input.tsx +22 -0
  120. package/src/design-system/ui/label.tsx +26 -0
  121. package/src/design-system/ui/scroll-area.tsx +48 -0
  122. package/src/design-system/ui/select.tsx +159 -0
  123. package/src/design-system/ui/separator.tsx +31 -0
  124. package/src/design-system/ui/sheet.tsx +141 -0
  125. package/src/design-system/ui/skeleton.tsx +15 -0
  126. package/src/design-system/ui/switch.tsx +29 -0
  127. package/src/design-system/ui/table.tsx +120 -0
  128. package/src/design-system/ui/tabs.tsx +55 -0
  129. package/src/design-system/ui/textarea.tsx +22 -0
  130. package/src/design-system/ui/toast.tsx +132 -0
  131. package/src/design-system/ui/toaster.tsx +35 -0
  132. package/src/design-system/ui/toggle-group.tsx +61 -0
  133. package/src/design-system/ui/toggle.tsx +46 -0
  134. package/src/design-system/ui/tooltip.tsx +32 -0
  135. package/src/features/MODULES.md +199 -0
  136. package/src/features/account/components/settings/appearance-settings-tab.tsx +24 -0
  137. package/src/features/account/components/theme-settings.tsx +70 -0
  138. package/src/features/account/components/use-theme-preferences.ts +106 -0
  139. package/src/features/account/pages/profile/components/profile-general-tab.tsx +89 -0
  140. package/src/features/account/pages/profile/types.ts +7 -0
  141. package/src/features/account/pages/profile.tsx +71 -0
  142. package/src/features/account/pages/service-tokens.tsx +522 -0
  143. package/src/features/account/pages/settings.tsx +42 -0
  144. package/src/features/account/pages/workspace-management.tsx +63 -0
  145. package/src/features/account/pages/workspace-members.tsx +335 -0
  146. package/src/features/auth/components/auto-login.tsx +51 -0
  147. package/src/features/auth/components/require-auth.tsx +56 -0
  148. package/src/features/auth/lib/auth-session.ts +448 -0
  149. package/src/features/auth/lib/oidc-client.ts +565 -0
  150. package/src/features/auth/pages/login.tsx +24 -0
  151. package/src/features/auth/pages/oauth-callback.tsx +63 -0
  152. package/src/features/chatkit/components/chatkit-surface.tsx +60 -0
  153. package/src/features/chatkit/components/public-chat-config.ts +70 -0
  154. package/src/features/chatkit/components/public-chat-error-boundary.tsx +74 -0
  155. package/src/features/chatkit/components/public-chat-widget.tsx +150 -0
  156. package/src/features/chatkit/components/studio-chat-bubble.tsx +451 -0
  157. package/src/features/chatkit/lib/chatkit-attachments.ts +31 -0
  158. package/src/features/chatkit/lib/chatkit-client.ts +195 -0
  159. package/src/features/chatkit/lib/chatkit-theme.ts +20 -0
  160. package/src/features/chatkit/lib/telemetry.ts +29 -0
  161. package/src/features/chatkit/lib/workflow-session.ts +70 -0
  162. package/src/features/chatkit/pages/public-chat.tsx +445 -0
  163. package/src/features/shared/components/chat-interface-options.ts +325 -0
  164. package/src/features/shared/components/chat-interface.types.ts +47 -0
  165. package/src/features/shared/components/top-navigation/account-menu.tsx +172 -0
  166. package/src/features/shared/components/top-navigation/active-workspace-indicator.tsx +280 -0
  167. package/src/features/shared/components/top-navigation/studio-brand.tsx +59 -0
  168. package/src/features/shared/components/top-navigation/top-navigation-types.ts +22 -0
  169. package/src/features/shared/components/top-navigation/version-status.tsx +280 -0
  170. package/src/features/shared/components/top-navigation.tsx +42 -0
  171. package/src/features/shared/components/workspace-bootstrap-gate.tsx +235 -0
  172. package/src/features/workflow/components/dialogs/add-credential-dialog.tsx +245 -0
  173. package/src/features/workflow/components/dialogs/confirm-delete-workflow-dialog.tsx +52 -0
  174. package/src/features/workflow/components/dialogs/credential-access-badge.tsx +34 -0
  175. package/src/features/workflow/components/dialogs/credential-status-badge.tsx +45 -0
  176. package/src/features/workflow/components/dialogs/credentials-table.tsx +453 -0
  177. package/src/features/workflow/components/dialogs/credentials-vault.tsx +85 -0
  178. package/src/features/workflow/components/dialogs/edit-credential-dialog.tsx +264 -0
  179. package/src/features/workflow/components/dialogs/update-workflow-dialog.tsx +214 -0
  180. package/src/features/workflow/components/dialogs/upload-workflow-dialog.tsx +288 -0
  181. package/src/features/workflow/components/forms/schema-config-form.tsx +41 -0
  182. package/src/features/workflow/components/layouts/sidebar-layout.tsx +288 -0
  183. package/src/features/workflow/components/layouts/use-sidebar-resize.ts +164 -0
  184. package/src/features/workflow/components/layouts/workflow-page-layout.tsx +69 -0
  185. package/src/features/workflow/components/panels/json-object-field.tsx +138 -0
  186. package/src/features/workflow/components/panels/rjsf-basic-widgets.tsx +21 -0
  187. package/src/features/workflow/components/panels/rjsf-input-widgets.tsx +105 -0
  188. package/src/features/workflow/components/panels/rjsf-templates.tsx +147 -0
  189. package/src/features/workflow/components/panels/rjsf-text-widgets.tsx +139 -0
  190. package/src/features/workflow/components/panels/rjsf-theme.tsx +10 -0
  191. package/src/features/workflow/components/panels/schema-dnd.ts +79 -0
  192. package/src/features/workflow/components/panels/workflow-diff-dialog.tsx +207 -0
  193. package/src/features/workflow/components/panels/workflow-history-filters.tsx +62 -0
  194. package/src/features/workflow/components/panels/workflow-history-footer.tsx +28 -0
  195. package/src/features/workflow/components/panels/workflow-history-header.tsx +47 -0
  196. package/src/features/workflow/components/panels/workflow-history-table.tsx +148 -0
  197. package/src/features/workflow/components/panels/workflow-history.tsx +150 -0
  198. package/src/features/workflow/components/panels/workflow-tabs.tsx +29 -0
  199. package/src/features/workflow/components/trace/agent-prism/Avatar.tsx +146 -0
  200. package/src/features/workflow/components/trace/agent-prism/Badge.tsx +95 -0
  201. package/src/features/workflow/components/trace/agent-prism/BrandLogo.tsx +102 -0
  202. package/src/features/workflow/components/trace/agent-prism/Button.tsx +124 -0
  203. package/src/features/workflow/components/trace/agent-prism/CollapseAndExpandControls.tsx +45 -0
  204. package/src/features/workflow/components/trace/agent-prism/CollapsibleSection.tsx +124 -0
  205. package/src/features/workflow/components/trace/agent-prism/CopyButton.tsx +63 -0
  206. package/src/features/workflow/components/trace/agent-prism/DetailsView/DetailsView.tsx +146 -0
  207. package/src/features/workflow/components/trace/agent-prism/DetailsView/DetailsViewAttributesTab.tsx +125 -0
  208. package/src/features/workflow/components/trace/agent-prism/DetailsView/DetailsViewContentViewer.tsx +51 -0
  209. package/src/features/workflow/components/trace/agent-prism/DetailsView/DetailsViewHeader.tsx +100 -0
  210. package/src/features/workflow/components/trace/agent-prism/DetailsView/DetailsViewInputOutputTab.tsx +401 -0
  211. package/src/features/workflow/components/trace/agent-prism/DetailsView/DetailsViewJsonOutput.tsx +33 -0
  212. package/src/features/workflow/components/trace/agent-prism/DetailsView/DetailsViewRawDataTab.tsx +27 -0
  213. package/src/features/workflow/components/trace/agent-prism/IconButton.tsx +75 -0
  214. package/src/features/workflow/components/trace/agent-prism/PriceBadge.tsx +14 -0
  215. package/src/features/workflow/components/trace/agent-prism/SearchInput.tsx +17 -0
  216. package/src/features/workflow/components/trace/agent-prism/SpanBadge.tsx +54 -0
  217. package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCard.tsx +480 -0
  218. package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCardBadges.tsx +23 -0
  219. package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCardConnector.tsx +36 -0
  220. package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCardTimeline.tsx +60 -0
  221. package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCardToggle.tsx +39 -0
  222. package/src/features/workflow/components/trace/agent-prism/SpanStatus.tsx +80 -0
  223. package/src/features/workflow/components/trace/agent-prism/TabSelector.tsx +35 -0
  224. package/src/features/workflow/components/trace/agent-prism/Tabs.tsx +140 -0
  225. package/src/features/workflow/components/trace/agent-prism/TextInput.tsx +143 -0
  226. package/src/features/workflow/components/trace/agent-prism/TimestampBadge.tsx +22 -0
  227. package/src/features/workflow/components/trace/agent-prism/TokensBadge.tsx +27 -0
  228. package/src/features/workflow/components/trace/agent-prism/TraceList/TraceList.tsx +123 -0
  229. package/src/features/workflow/components/trace/agent-prism/TraceList/TraceListItem.tsx +86 -0
  230. package/src/features/workflow/components/trace/agent-prism/TraceList/TraceListItemHeader.tsx +37 -0
  231. package/src/features/workflow/components/trace/agent-prism/TraceViewer/TraceViewer.tsx +208 -0
  232. package/src/features/workflow/components/trace/agent-prism/TraceViewer/TraceViewerDesktopLayout.tsx +99 -0
  233. package/src/features/workflow/components/trace/agent-prism/TraceViewer/TraceViewerMobileLayout.tsx +103 -0
  234. package/src/features/workflow/components/trace/agent-prism/TraceViewer/TraceViewerPlaceholder.tsx +5 -0
  235. package/src/features/workflow/components/trace/agent-prism/TraceViewer/TraceViewerSearchAndControls.tsx +30 -0
  236. package/src/features/workflow/components/trace/agent-prism/TraceViewer/TraceViewerTreeViewContainer.tsx +76 -0
  237. package/src/features/workflow/components/trace/agent-prism/TraceViewer/useTraceSelection.ts +186 -0
  238. package/src/features/workflow/components/trace/agent-prism/TreeView.tsx +73 -0
  239. package/src/features/workflow/components/trace/agent-prism/index.ts +7 -0
  240. package/src/features/workflow/components/trace/agent-prism/shared.ts +170 -0
  241. package/src/features/workflow/components/trace/agent-prism/theme/index.ts +101 -0
  242. package/src/features/workflow/components/trace/agent-prism/theme/theme.css +247 -0
  243. package/src/features/workflow/data/templates/assets/vibe-agent/config.json +5 -0
  244. package/src/features/workflow/data/templates/candidate-badges.ts +138 -0
  245. package/src/features/workflow/data/templates/index.ts +29 -0
  246. package/src/features/workflow/data/templates/template-definition.ts +77 -0
  247. package/src/features/workflow/data/templates/template-owner.ts +7 -0
  248. package/src/features/workflow/data/workflow-data.ts +8 -0
  249. package/src/features/workflow/data/workflow-types.ts +57 -0
  250. package/src/features/workflow/lib/mermaid-renderer.ts +334 -0
  251. package/src/features/workflow/lib/workflow-diff.ts +191 -0
  252. package/src/features/workflow/lib/workflow-execution-builders.ts +103 -0
  253. package/src/features/workflow/lib/workflow-execution-formatters.ts +145 -0
  254. package/src/features/workflow/lib/workflow-execution-storage.ts +63 -0
  255. package/src/features/workflow/lib/workflow-execution.types.ts +80 -0
  256. package/src/features/workflow/lib/workflow-storage-api.ts +581 -0
  257. package/src/features/workflow/lib/workflow-storage-helpers.ts +373 -0
  258. package/src/features/workflow/lib/workflow-storage-versioning.ts +127 -0
  259. package/src/features/workflow/lib/workflow-storage.constants.ts +21 -0
  260. package/src/features/workflow/lib/workflow-storage.ts +482 -0
  261. package/src/features/workflow/lib/workflow-storage.types.ts +261 -0
  262. package/src/features/workflow/pages/workflow/components/settings-tab-content.tsx +428 -0
  263. package/src/features/workflow/pages/workflow/components/trace-tab-content.tsx +183 -0
  264. package/src/features/workflow/pages/workflow/components/workflow-config-sheet.tsx +207 -0
  265. package/src/features/workflow/pages/workflow/components/workflow-config-sheet.utils.ts +275 -0
  266. package/src/features/workflow/pages/workflow/components/workflow-layout.tsx +143 -0
  267. package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +766 -0
  268. package/src/features/workflow/pages/workflow/handlers/credentials.ts +338 -0
  269. package/src/features/workflow/pages/workflow/helpers/execution.ts +39 -0
  270. package/src/features/workflow/pages/workflow/helpers/trace.ts +838 -0
  271. package/src/features/workflow/pages/workflow/helpers/types.ts +28 -0
  272. package/src/features/workflow/pages/workflow/hooks/controller/build-layout-props.ts +171 -0
  273. package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-controller.ts +19 -0
  274. package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-core.ts +84 -0
  275. package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-execution-controller.ts +167 -0
  276. package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-lifecycle.ts +37 -0
  277. package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-resources.ts +57 -0
  278. package/src/features/workflow/pages/workflow/hooks/execution-log-helpers.ts +75 -0
  279. package/src/features/workflow/pages/workflow/hooks/execution-node-status.ts +46 -0
  280. package/src/features/workflow/pages/workflow/hooks/execution-record-updater.ts +119 -0
  281. package/src/features/workflow/pages/workflow/hooks/execution-record.ts +31 -0
  282. package/src/features/workflow/pages/workflow/hooks/execution-runtime-updates.ts +96 -0
  283. package/src/features/workflow/pages/workflow/hooks/use-execution-trace.ts +512 -0
  284. package/src/features/workflow/pages/workflow/hooks/use-execution-updates.ts +125 -0
  285. package/src/features/workflow/pages/workflow/hooks/use-pause-workflow.ts +70 -0
  286. package/src/features/workflow/pages/workflow/hooks/use-studio-ui-state.ts +10 -0
  287. package/src/features/workflow/pages/workflow/hooks/use-workflow-chat.ts +212 -0
  288. package/src/features/workflow/pages/workflow/hooks/use-workflow-credential-readiness.ts +72 -0
  289. package/src/features/workflow/pages/workflow/hooks/use-workflow-credentials.ts +167 -0
  290. package/src/features/workflow/pages/workflow/hooks/use-workflow-execution-state.ts +20 -0
  291. package/src/features/workflow/pages/workflow/hooks/use-workflow-listeners.ts +182 -0
  292. package/src/features/workflow/pages/workflow/hooks/use-workflow-loader.ts +236 -0
  293. package/src/features/workflow/pages/workflow/hooks/use-workflow-metadata-state.ts +56 -0
  294. package/src/features/workflow/pages/workflow/hooks/use-workflow-saver.ts +191 -0
  295. package/src/features/workflow/pages/workflow/hooks/use-workflow-storage-listener.ts +81 -0
  296. package/src/features/workflow/pages/workflow/hooks/workflow-runner-websocket.ts +150 -0
  297. package/src/features/workflow/pages/workflow-gallery/types.ts +3 -0
  298. package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery-actions.ts +223 -0
  299. package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery-state.ts +206 -0
  300. package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery.ts +14 -0
  301. package/src/features/workflow/pages/workflow-gallery/workflow-card-size.ts +8 -0
  302. package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +327 -0
  303. package/src/features/workflow/pages/workflow-gallery/workflow-gallery-tabs.tsx +173 -0
  304. package/src/features/workflow/pages/workflow-gallery.tsx +96 -0
  305. package/src/features/workflow/pages/workflow.tsx +119 -0
  306. package/src/features/workflow/types/credential-vault.ts +52 -0
  307. package/src/hooks/browser-context-provider.tsx +19 -0
  308. package/src/hooks/use-browser-context.ts +188 -0
  309. package/src/hooks/use-color-scheme.ts +77 -0
  310. package/src/hooks/use-credential-vault.ts +442 -0
  311. package/src/hooks/use-page-context.ts +39 -0
  312. package/src/hooks/use-toast.ts +182 -0
  313. package/src/hooks/use-uploads-allowed.ts +28 -0
  314. package/src/index.css +95 -0
  315. package/src/lib/api.ts +445 -0
  316. package/src/lib/auth-fetch.ts +35 -0
  317. package/src/lib/config.ts +102 -0
  318. package/src/lib/theme.ts +109 -0
  319. package/src/lib/utils.ts +6 -0
  320. package/src/lib/workspace-routing.ts +67 -0
  321. package/src/lib/workspace-session.ts +66 -0
  322. package/src/lib/workspace-slug.ts +10 -0
  323. package/src/main.tsx +28 -0
  324. package/src/vite-env.d.ts +17 -0
  325. package/tailwind.config.js +153 -0
  326. package/tsconfig.app.json +30 -0
  327. package/tsconfig.json +16 -0
  328. package/tsconfig.node.json +21 -0
  329. package/vite.config.ts +122 -0
@@ -0,0 +1,235 @@
1
+ import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
2
+ import { Loader2 } from "lucide-react";
3
+ import { Button } from "@/design-system/ui/button";
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@/design-system/ui/card";
11
+ import { Input } from "@/design-system/ui/input";
12
+ import { Label } from "@/design-system/ui/label";
13
+ import { toast } from "@/hooks/use-toast";
14
+ import {
15
+ createWorkspace,
16
+ getMyWorkspaces,
17
+ type WorkspaceMembershipSummary,
18
+ } from "@/lib/api";
19
+ import { slugifyWorkspace } from "@/lib/workspace-slug";
20
+ import {
21
+ clearSelectedWorkspaceSlug,
22
+ getSelectedWorkspaceSlug,
23
+ setSelectedWorkspaceSlug,
24
+ } from "@/lib/workspace-session";
25
+ import { getWorkspaceGalleryPath } from "@/lib/workspace-routing";
26
+ import { getAuthenticatedUserProfile } from "@features/auth/lib/auth-session";
27
+
28
+ interface WorkspaceBootstrapGateProps {
29
+ children: ReactNode;
30
+ }
31
+
32
+ export function WorkspaceBootstrapGate({
33
+ children,
34
+ }: WorkspaceBootstrapGateProps) {
35
+ const authUser = useMemo(() => getAuthenticatedUserProfile(), []);
36
+ const suggestedWorkspaceName = useMemo(() => {
37
+ if (!authUser?.name) {
38
+ return "My workspace";
39
+ }
40
+ return `${authUser.name}'s workspace`;
41
+ }, [authUser]);
42
+ const [workspaces, setWorkspaces] = useState<WorkspaceMembershipSummary[]>(
43
+ [],
44
+ );
45
+ const [isLoading, setIsLoading] = useState(true);
46
+ const [workspaceName, setWorkspaceName] = useState("");
47
+ const [workspaceSlug, setWorkspaceSlugState] = useState("");
48
+ const [workspaceSlugIsManual, setWorkspaceSlugIsManual] = useState(false);
49
+ const [isCreating, setIsCreating] = useState(false);
50
+ const workspaceNameRef = useRef(workspaceName);
51
+
52
+ useEffect(() => {
53
+ workspaceNameRef.current = workspaceName;
54
+ }, [workspaceName]);
55
+
56
+ useEffect(() => {
57
+ let active = true;
58
+
59
+ const load = async () => {
60
+ try {
61
+ const payload = await getMyWorkspaces();
62
+ if (!active) {
63
+ return;
64
+ }
65
+
66
+ setWorkspaces(payload.memberships);
67
+ const currentSlug = getSelectedWorkspaceSlug();
68
+
69
+ if (payload.memberships.length === 0) {
70
+ const nextWorkspaceName =
71
+ workspaceNameRef.current.trim() || suggestedWorkspaceName;
72
+ setWorkspaceName(nextWorkspaceName);
73
+ setWorkspaceSlugState(slugifyWorkspace(nextWorkspaceName));
74
+ setWorkspaceSlugIsManual(false);
75
+ if (currentSlug) {
76
+ clearSelectedWorkspaceSlug();
77
+ }
78
+ setIsLoading(false);
79
+ return;
80
+ }
81
+
82
+ const selectedWorkspace =
83
+ payload.memberships.find(
84
+ (workspace) => workspace.slug === currentSlug,
85
+ ) ?? payload.memberships[0];
86
+
87
+ if (selectedWorkspace) {
88
+ if (selectedWorkspace.slug !== currentSlug) {
89
+ setSelectedWorkspaceSlug(selectedWorkspace.slug);
90
+ }
91
+ } else if (currentSlug) {
92
+ clearSelectedWorkspaceSlug();
93
+ }
94
+
95
+ setIsLoading(false);
96
+ } catch (error) {
97
+ if (!active) {
98
+ return;
99
+ }
100
+ console.error("Failed to load workspace memberships", error);
101
+ setIsLoading(false);
102
+ }
103
+ };
104
+
105
+ void load();
106
+
107
+ return () => {
108
+ active = false;
109
+ };
110
+ }, [suggestedWorkspaceName]);
111
+
112
+ useEffect(() => {
113
+ if (workspaceSlugIsManual) {
114
+ return;
115
+ }
116
+ setWorkspaceSlugState(slugifyWorkspace(workspaceName));
117
+ }, [workspaceName, workspaceSlugIsManual]);
118
+
119
+ useEffect(() => {
120
+ if (workspaceName.trim()) {
121
+ return;
122
+ }
123
+ setWorkspaceName(suggestedWorkspaceName);
124
+ }, [suggestedWorkspaceName, workspaceName]);
125
+
126
+ const handleCreateWorkspace = async () => {
127
+ const name = workspaceName.trim();
128
+ const slug = workspaceSlug.trim() || slugifyWorkspace(name);
129
+
130
+ if (!name || !slug) {
131
+ toast({
132
+ title: "Workspace details required",
133
+ description: "Provide both a name and a slug for the new workspace.",
134
+ variant: "destructive",
135
+ });
136
+ return;
137
+ }
138
+
139
+ setIsCreating(true);
140
+ try {
141
+ const created = await createWorkspace({ name, slug });
142
+ setSelectedWorkspaceSlug(created.slug);
143
+ window.location.assign(getWorkspaceGalleryPath(created.slug));
144
+ } catch (error) {
145
+ toast({
146
+ title: "Failed to create workspace",
147
+ description:
148
+ error instanceof Error ? error.message : "Unknown error occurred",
149
+ variant: "destructive",
150
+ });
151
+ } finally {
152
+ setIsCreating(false);
153
+ }
154
+ };
155
+
156
+ if (isLoading) {
157
+ return (
158
+ <div className="flex min-h-screen items-center justify-center bg-slate-100 text-foreground dark:bg-slate-950">
159
+ <Card className="w-full max-w-md border-border bg-card/80 backdrop-blur-xl dark:border-primary/25 dark:bg-primary/5">
160
+ <CardContent className="flex items-center gap-3 p-6 text-sm text-muted-foreground">
161
+ <Loader2 className="h-4 w-4 animate-spin" />
162
+ <span>Checking workspace access…</span>
163
+ </CardContent>
164
+ </Card>
165
+ </div>
166
+ );
167
+ }
168
+
169
+ if (workspaces.length > 0) {
170
+ return <>{children}</>;
171
+ }
172
+
173
+ return (
174
+ <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-slate-100 text-foreground dark:bg-slate-950">
175
+ <div
176
+ className="absolute inset-0 bg-gradient-to-br from-slate-100 via-slate-200 to-slate-100 dark:from-slate-950 dark:via-slate-900/80 dark:to-black"
177
+ aria-hidden="true"
178
+ />
179
+ <div
180
+ className="absolute inset-0 opacity-40 mix-blend-soft-light dark:opacity-60"
181
+ style={{
182
+ backgroundImage:
183
+ "radial-gradient(circle at 20% 20%, rgba(148, 163, 184, 0.2), transparent 45%), radial-gradient(circle at 80% 30%, rgba(56, 189, 248, 0.25), transparent 50%), radial-gradient(circle at 50% 80%, rgba(45, 212, 191, 0.2), transparent 55%)",
184
+ }}
185
+ aria-hidden="true"
186
+ />
187
+ <Card className="relative z-10 mx-auto w-full max-w-lg border-border bg-card/85 backdrop-blur-xl dark:border-primary/25 dark:bg-primary/5">
188
+ <CardHeader className="space-y-2">
189
+ <CardTitle className="text-2xl">
190
+ Create your first workspace
191
+ </CardTitle>
192
+ <CardDescription>
193
+ Studio needs a workspace before it can open. Create one to continue
194
+ with your account.
195
+ </CardDescription>
196
+ </CardHeader>
197
+ <CardContent className="grid gap-4">
198
+ <div className="grid gap-2">
199
+ <Label htmlFor="workspace-name">Workspace name</Label>
200
+ <Input
201
+ id="workspace-name"
202
+ value={workspaceName}
203
+ onChange={(event) => setWorkspaceName(event.target.value)}
204
+ placeholder="Acme"
205
+ autoFocus
206
+ />
207
+ </div>
208
+ <div className="grid gap-2">
209
+ <Label htmlFor="workspace-slug">Workspace URL name</Label>
210
+ <Input
211
+ id="workspace-slug"
212
+ value={workspaceSlug}
213
+ onChange={(event) => {
214
+ const nextValue = event.target.value;
215
+ setWorkspaceSlugState(nextValue);
216
+ setWorkspaceSlugIsManual(nextValue.trim().length > 0);
217
+ }}
218
+ placeholder="acme"
219
+ />
220
+ <p className="text-xs text-muted-foreground">
221
+ Used in links to your workspace. You can keep the suggested value
222
+ or choose a short name.
223
+ </p>
224
+ </div>
225
+ <Button onClick={handleCreateWorkspace} disabled={isCreating}>
226
+ {isCreating ? (
227
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
228
+ ) : null}
229
+ {isCreating ? "Creating…" : "Create workspace"}
230
+ </Button>
231
+ </CardContent>
232
+ </Card>
233
+ </div>
234
+ );
235
+ }
@@ -0,0 +1,245 @@
1
+ import React, { useCallback, useMemo, useState } from "react";
2
+ import { Button } from "@/design-system/ui/button";
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ DialogTrigger,
11
+ } from "@/design-system/ui/dialog";
12
+ import { Input } from "@/design-system/ui/input";
13
+ import {
14
+ Select,
15
+ SelectContent,
16
+ SelectItem,
17
+ SelectTrigger,
18
+ SelectValue,
19
+ } from "@/design-system/ui/select";
20
+ import { Eye, EyeOff, Loader2, Plus } from "lucide-react";
21
+ import type {
22
+ CredentialInput,
23
+ CredentialVaultAccessLevel,
24
+ } from "@features/workflow/types/credential-vault";
25
+
26
+ type CredentialAccess = CredentialVaultAccessLevel;
27
+
28
+ interface AddCredentialDialogProps {
29
+ onAddCredential?: (credential: CredentialInput) => Promise<void> | void;
30
+ }
31
+
32
+ const DEFAULT_CREDENTIAL: CredentialInput = {
33
+ name: "",
34
+ provider: "Custom",
35
+ access: "shared",
36
+ secrets: { secret: "" },
37
+ };
38
+
39
+ export function AddCredentialDialog({
40
+ onAddCredential,
41
+ }: AddCredentialDialogProps) {
42
+ const [isOpen, setIsOpen] = useState(false);
43
+ const [pendingCredential, setPendingCredential] =
44
+ useState<CredentialInput>(DEFAULT_CREDENTIAL);
45
+ const [isSaving, setIsSaving] = useState(false);
46
+ const [error, setError] = useState<string | null>(null);
47
+ const [showSecret, setShowSecret] = useState(false);
48
+
49
+ const hasName = pendingCredential.name.trim().length > 0;
50
+ const hasProvider = pendingCredential.provider.trim().length > 0;
51
+
52
+ const resetState = useCallback(() => {
53
+ setPendingCredential(DEFAULT_CREDENTIAL);
54
+ setError(null);
55
+ setIsSaving(false);
56
+ setShowSecret(false);
57
+ }, []);
58
+
59
+ const handleOpenChange = useCallback(
60
+ (open: boolean) => {
61
+ setIsOpen(open);
62
+ if (!open) {
63
+ resetState();
64
+ }
65
+ },
66
+ [resetState],
67
+ );
68
+
69
+ const handleAddCredential = useCallback(async () => {
70
+ if (!onAddCredential || !hasName) {
71
+ return;
72
+ }
73
+
74
+ setError(null);
75
+ setIsSaving(true);
76
+ try {
77
+ await onAddCredential(pendingCredential);
78
+ handleOpenChange(false);
79
+ } catch (err) {
80
+ console.error("Failed to save credential", err);
81
+ const message =
82
+ err instanceof Error
83
+ ? err.message
84
+ : "Unable to save credential. Please try again.";
85
+ setError(message);
86
+ } finally {
87
+ setIsSaving(false);
88
+ }
89
+ }, [handleOpenChange, hasName, onAddCredential, pendingCredential]);
90
+
91
+ const updateCredential = useCallback((partial: Partial<CredentialInput>) => {
92
+ setPendingCredential((prev) => ({
93
+ ...prev,
94
+ ...partial,
95
+ secrets: partial.secrets ?? prev.secrets,
96
+ }));
97
+ }, []);
98
+
99
+ const updateSecret = useCallback(
100
+ (key: string, value: string) => {
101
+ updateCredential({
102
+ secrets: {
103
+ ...pendingCredential.secrets,
104
+ [key]: value,
105
+ },
106
+ });
107
+ },
108
+ [pendingCredential.secrets, updateCredential],
109
+ );
110
+
111
+ const isSaveDisabled = useMemo(
112
+ () => !hasName || !hasProvider || isSaving,
113
+ [hasName, hasProvider, isSaving],
114
+ );
115
+
116
+ return (
117
+ <Dialog open={isOpen} onOpenChange={handleOpenChange}>
118
+ <DialogTrigger asChild>
119
+ <Button>
120
+ <Plus className="h-4 w-4 mr-2" />
121
+ Add Credential
122
+ </Button>
123
+ </DialogTrigger>
124
+ <DialogContent className="sm:max-w-[500px]">
125
+ <DialogHeader>
126
+ <DialogTitle>Add New Credential</DialogTitle>
127
+ <DialogDescription>
128
+ Create a new credential for connecting to external services.
129
+ Credentials are encrypted at rest.
130
+ </DialogDescription>
131
+ </DialogHeader>
132
+ <div className="grid gap-4 py-4">
133
+ <div className="grid grid-cols-4 items-center gap-4">
134
+ <label
135
+ htmlFor="credential-name"
136
+ className="text-right text-sm font-medium"
137
+ >
138
+ Name
139
+ </label>
140
+ <Input
141
+ id="credential-name"
142
+ value={pendingCredential.name}
143
+ onChange={(event) =>
144
+ updateCredential({ name: event.target.value })
145
+ }
146
+ className="col-span-3"
147
+ placeholder="My API Credential"
148
+ />
149
+ </div>
150
+ <div className="grid grid-cols-4 items-center gap-4">
151
+ <label
152
+ htmlFor="credential-provider"
153
+ className="text-right text-sm font-medium"
154
+ >
155
+ Provider
156
+ </label>
157
+ <Input
158
+ id="credential-provider"
159
+ value={pendingCredential.provider}
160
+ onChange={(event) =>
161
+ updateCredential({ provider: event.target.value })
162
+ }
163
+ className="col-span-3"
164
+ placeholder="openai, pinecone, mongodb..."
165
+ />
166
+ </div>
167
+ <div className="grid grid-cols-4 items-center gap-4">
168
+ <label
169
+ htmlFor="credential-access"
170
+ className="text-right text-sm font-medium"
171
+ >
172
+ Access
173
+ </label>
174
+ <Select
175
+ value={pendingCredential.access}
176
+ onValueChange={(value: CredentialAccess) =>
177
+ updateCredential({ access: value })
178
+ }
179
+ >
180
+ <SelectTrigger id="credential-access" className="col-span-3">
181
+ <SelectValue placeholder="Select access level" />
182
+ </SelectTrigger>
183
+ <SelectContent>
184
+ <SelectItem value="scoped">Scoped</SelectItem>
185
+ <SelectItem value="shared">Shared</SelectItem>
186
+ </SelectContent>
187
+ </Select>
188
+ </div>
189
+ <div className="grid grid-cols-4 items-center gap-4">
190
+ <label
191
+ htmlFor="credential-secret"
192
+ className="text-right text-sm font-medium"
193
+ >
194
+ Secret
195
+ </label>
196
+ <div className="relative col-span-3">
197
+ <Input
198
+ id="credential-secret"
199
+ type={showSecret ? "text" : "password"}
200
+ value={pendingCredential.secrets?.secret ?? ""}
201
+ onChange={(event) => updateSecret("secret", event.target.value)}
202
+ className="pr-9"
203
+ placeholder="Enter credential secret"
204
+ />
205
+ <button
206
+ type="button"
207
+ onClick={() => setShowSecret((v) => !v)}
208
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
209
+ aria-label={showSecret ? "Hide secret" : "Show secret"}
210
+ >
211
+ {showSecret ? (
212
+ <EyeOff className="h-4 w-4" />
213
+ ) : (
214
+ <Eye className="h-4 w-4" />
215
+ )}
216
+ </button>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ {error ? (
221
+ <p className="text-sm text-destructive px-1">{error}</p>
222
+ ) : null}
223
+ <DialogFooter>
224
+ <Button
225
+ variant="outline"
226
+ onClick={() => handleOpenChange(false)}
227
+ disabled={isSaving}
228
+ >
229
+ Cancel
230
+ </Button>
231
+ <Button onClick={handleAddCredential} disabled={isSaveDisabled}>
232
+ {isSaving ? (
233
+ <>
234
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
235
+ Saving...
236
+ </>
237
+ ) : (
238
+ "Save Credential"
239
+ )}
240
+ </Button>
241
+ </DialogFooter>
242
+ </DialogContent>
243
+ </Dialog>
244
+ );
245
+ }
@@ -0,0 +1,52 @@
1
+ import {
2
+ AlertDialog,
3
+ AlertDialogAction,
4
+ AlertDialogCancel,
5
+ AlertDialogContent,
6
+ AlertDialogDescription,
7
+ AlertDialogFooter,
8
+ AlertDialogHeader,
9
+ AlertDialogTitle,
10
+ } from "@/design-system/ui/alert-dialog";
11
+
12
+ interface ConfirmDeleteWorkflowDialogProps {
13
+ open: boolean;
14
+ workflowName: string;
15
+ isPending?: boolean;
16
+ onOpenChange: (open: boolean) => void;
17
+ onConfirm: () => Promise<void> | void;
18
+ }
19
+
20
+ export function ConfirmDeleteWorkflowDialog({
21
+ open,
22
+ workflowName,
23
+ isPending = false,
24
+ onOpenChange,
25
+ onConfirm,
26
+ }: ConfirmDeleteWorkflowDialogProps) {
27
+ return (
28
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
29
+ <AlertDialogContent>
30
+ <AlertDialogHeader>
31
+ <AlertDialogTitle>Offboard colleague?</AlertDialogTitle>
32
+ <AlertDialogDescription>
33
+ {`"${workflowName}" will be permanently removed from your workspace. This action cannot be undone.`}
34
+ </AlertDialogDescription>
35
+ </AlertDialogHeader>
36
+ <AlertDialogFooter>
37
+ <AlertDialogCancel disabled={isPending}>Cancel</AlertDialogCancel>
38
+ <AlertDialogAction
39
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
40
+ onClick={(event) => {
41
+ event.preventDefault();
42
+ void onConfirm();
43
+ }}
44
+ disabled={isPending}
45
+ >
46
+ {isPending ? "Offboarding..." : "Offboard"}
47
+ </AlertDialogAction>
48
+ </AlertDialogFooter>
49
+ </AlertDialogContent>
50
+ </AlertDialog>
51
+ );
52
+ }
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import { Badge } from "@/design-system/ui/badge";
3
+ import { Lock, Users } from "lucide-react";
4
+
5
+ interface CredentialAccessBadgeProps {
6
+ access: string;
7
+ }
8
+
9
+ export function CredentialAccessBadge({ access }: CredentialAccessBadgeProps) {
10
+ switch (access) {
11
+ case "scoped":
12
+ return (
13
+ <Badge
14
+ variant="outline"
15
+ className="bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800"
16
+ >
17
+ <Lock className="h-3 w-3 mr-1" />
18
+ Scoped
19
+ </Badge>
20
+ );
21
+ case "shared":
22
+ return (
23
+ <Badge
24
+ variant="outline"
25
+ className="bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-900/30 dark:text-purple-400 dark:border-purple-800"
26
+ >
27
+ <Users className="h-3 w-3 mr-1" />
28
+ Shared
29
+ </Badge>
30
+ );
31
+ default:
32
+ return null;
33
+ }
34
+ }
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import { Badge } from "@/design-system/ui/badge";
3
+ import { AlertTriangle, CheckCircle2, Circle } from "lucide-react";
4
+ import type { CredentialVaultHealthStatus } from "@features/workflow/types/credential-vault";
5
+
6
+ interface CredentialStatusBadgeProps {
7
+ status?: CredentialVaultHealthStatus;
8
+ }
9
+
10
+ export function CredentialStatusBadge({ status }: CredentialStatusBadgeProps) {
11
+ const normalizedStatus = status ?? "unknown";
12
+
13
+ switch (normalizedStatus) {
14
+ case "healthy":
15
+ return (
16
+ <Badge
17
+ variant="outline"
18
+ className="flex items-center gap-1 bg-emerald-100 text-emerald-800 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800"
19
+ >
20
+ <CheckCircle2 className="h-3 w-3" />
21
+ Healthy
22
+ </Badge>
23
+ );
24
+ case "unhealthy":
25
+ return (
26
+ <Badge
27
+ variant="outline"
28
+ className="flex items-center gap-1 bg-red-100 text-red-800 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800"
29
+ >
30
+ <AlertTriangle className="h-3 w-3" />
31
+ Unhealthy
32
+ </Badge>
33
+ );
34
+ default:
35
+ return (
36
+ <Badge
37
+ variant="outline"
38
+ className="flex items-center gap-1 bg-muted text-muted-foreground border-border"
39
+ >
40
+ <Circle className="h-3 w-3" />
41
+ Unknown
42
+ </Badge>
43
+ );
44
+ }
45
+ }