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,522 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Copy, KeyRound, RotateCw, Trash2 } from "lucide-react";
3
+ import { Button } from "@/design-system/ui/button";
4
+ import { Input } from "@/design-system/ui/input";
5
+ import { Label } from "@/design-system/ui/label";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from "@/design-system/ui/select";
13
+ import {
14
+ Table,
15
+ TableBody,
16
+ TableCell,
17
+ TableHead,
18
+ TableHeader,
19
+ TableRow,
20
+ } from "@/design-system/ui/table";
21
+ import { Badge } from "@/design-system/ui/badge";
22
+ import {
23
+ AlertDialog,
24
+ AlertDialogAction,
25
+ AlertDialogCancel,
26
+ AlertDialogContent,
27
+ AlertDialogDescription,
28
+ AlertDialogFooter,
29
+ AlertDialogHeader,
30
+ AlertDialogTitle,
31
+ } from "@/design-system/ui/alert-dialog";
32
+ import {
33
+ Dialog,
34
+ DialogContent,
35
+ DialogDescription,
36
+ DialogFooter,
37
+ DialogHeader,
38
+ DialogTitle,
39
+ } from "@/design-system/ui/dialog";
40
+ import { toast } from "@/hooks/use-toast";
41
+ import {
42
+ createServiceToken,
43
+ getActiveWorkspace,
44
+ listServiceTokens,
45
+ revokeServiceToken,
46
+ rotateServiceToken,
47
+ type ServiceToken,
48
+ } from "@/lib/api";
49
+
50
+ const AVAILABLE_SCOPES = [
51
+ "workflows:read",
52
+ "workflows:write",
53
+ "workflows:execute",
54
+ "vault:read",
55
+ "vault:write",
56
+ ] as const;
57
+
58
+ const EXPIRY_OPTIONS = [
59
+ { value: "never", label: "Never", seconds: null },
60
+ { value: "1h", label: "1 hour", seconds: 3600 },
61
+ { value: "1d", label: "1 day", seconds: 86400 },
62
+ { value: "7d", label: "7 days", seconds: 604800 },
63
+ { value: "30d", label: "30 days", seconds: 2592000 },
64
+ ] as const;
65
+
66
+ const ROTATION_OVERLAP_SECONDS = 300;
67
+
68
+ const formatDate = (value: string | null | undefined): string =>
69
+ value ? new Date(value).toLocaleString() : "—";
70
+
71
+ const tokenStatus = (token: ServiceToken): "Revoked" | "Rotated" | "Active" => {
72
+ if (token.revoked_at) {
73
+ return "Revoked";
74
+ }
75
+ if (token.rotated_to) {
76
+ return "Rotated";
77
+ }
78
+ return "Active";
79
+ };
80
+
81
+ interface RevealedSecret {
82
+ title: string;
83
+ identifier: string;
84
+ secret: string;
85
+ }
86
+
87
+ export default function ServiceTokens() {
88
+ const [tokens, setTokens] = useState<ServiceToken[]>([]);
89
+ const [isLoading, setIsLoading] = useState(true);
90
+ const [workspaceName, setWorkspaceName] = useState<string | null>(null);
91
+
92
+ const [createOpen, setCreateOpen] = useState(false);
93
+ const [identifier, setIdentifier] = useState("");
94
+ const [selectedScopes, setSelectedScopes] = useState<Set<string>>(new Set());
95
+ const [expiry, setExpiry] = useState<string>("never");
96
+ const [isMinting, setIsMinting] = useState(false);
97
+
98
+ const [revealed, setRevealed] = useState<RevealedSecret | null>(null);
99
+ const [rotatingId, setRotatingId] = useState<string | null>(null);
100
+ const [revokingId, setRevokingId] = useState<string | null>(null);
101
+ const [revokeTarget, setRevokeTarget] = useState<string | null>(null);
102
+
103
+ useEffect(() => {
104
+ let active = true;
105
+
106
+ const loadData = async () => {
107
+ try {
108
+ const [workspace, tokenList] = await Promise.all([
109
+ getActiveWorkspace(),
110
+ listServiceTokens(),
111
+ ]);
112
+ if (!active) {
113
+ return;
114
+ }
115
+ setWorkspaceName(workspace.name);
116
+ setTokens(tokenList.tokens);
117
+ } catch (err) {
118
+ if (active) {
119
+ toast({
120
+ title: "Failed to load API keys",
121
+ description: err instanceof Error ? err.message : undefined,
122
+ variant: "destructive",
123
+ });
124
+ }
125
+ } finally {
126
+ if (active) {
127
+ setIsLoading(false);
128
+ }
129
+ }
130
+ };
131
+
132
+ void loadData();
133
+
134
+ return () => {
135
+ active = false;
136
+ };
137
+ }, []);
138
+
139
+ const resetCreateForm = () => {
140
+ setIdentifier("");
141
+ setSelectedScopes(new Set());
142
+ setExpiry("never");
143
+ };
144
+
145
+ const toggleScope = (scope: string) => {
146
+ setSelectedScopes((prev) => {
147
+ const next = new Set(prev);
148
+ if (next.has(scope)) {
149
+ next.delete(scope);
150
+ } else {
151
+ next.add(scope);
152
+ }
153
+ return next;
154
+ });
155
+ };
156
+
157
+ const handleMint = async () => {
158
+ setIsMinting(true);
159
+ try {
160
+ const expiryOption = EXPIRY_OPTIONS.find(
161
+ (option) => option.value === expiry,
162
+ );
163
+ const token = await createServiceToken({
164
+ identifier: identifier.trim() || undefined,
165
+ scopes: [...selectedScopes],
166
+ expires_in_seconds: expiryOption?.seconds ?? undefined,
167
+ });
168
+ setTokens((prev) => [token, ...prev]);
169
+ setRevealed({
170
+ title: "API key created",
171
+ identifier: token.identifier,
172
+ secret: token.secret ?? "",
173
+ });
174
+ resetCreateForm();
175
+ setCreateOpen(false);
176
+ toast({ title: "API key created" });
177
+ } catch (err) {
178
+ toast({
179
+ title: "Failed to create API key",
180
+ description: err instanceof Error ? err.message : undefined,
181
+ variant: "destructive",
182
+ });
183
+ } finally {
184
+ setIsMinting(false);
185
+ }
186
+ };
187
+
188
+ const handleRotate = async (tokenId: string) => {
189
+ setRotatingId(tokenId);
190
+ try {
191
+ const rotated = await rotateServiceToken(
192
+ tokenId,
193
+ ROTATION_OVERLAP_SECONDS,
194
+ );
195
+ const tokenList = await listServiceTokens();
196
+ setTokens(tokenList.tokens);
197
+ setRevealed({
198
+ title: "API key rotated",
199
+ identifier: rotated.identifier,
200
+ secret: rotated.secret ?? "",
201
+ });
202
+ toast({ title: "API key rotated" });
203
+ } catch (err) {
204
+ toast({
205
+ title: "Failed to rotate API key",
206
+ description: err instanceof Error ? err.message : undefined,
207
+ variant: "destructive",
208
+ });
209
+ } finally {
210
+ setRotatingId(null);
211
+ }
212
+ };
213
+
214
+ const handleRevoke = async () => {
215
+ const tokenId = revokeTarget;
216
+ if (!tokenId) {
217
+ return;
218
+ }
219
+ setRevokeTarget(null);
220
+ setRevokingId(tokenId);
221
+ try {
222
+ await revokeServiceToken(tokenId, "Revoked via Studio");
223
+ setTokens((prev) => prev.filter((token) => token.identifier !== tokenId));
224
+ toast({ title: "API key revoked" });
225
+ } catch (err) {
226
+ toast({
227
+ title: "Failed to revoke API key",
228
+ description: err instanceof Error ? err.message : undefined,
229
+ variant: "destructive",
230
+ });
231
+ } finally {
232
+ setRevokingId(null);
233
+ }
234
+ };
235
+
236
+ const handleCopySecret = async (secret: string) => {
237
+ try {
238
+ await navigator.clipboard.writeText(secret);
239
+ toast({ title: "Secret copied to clipboard" });
240
+ } catch {
241
+ toast({
242
+ title: "Could not copy secret",
243
+ description: "Copy it manually from the field above.",
244
+ variant: "destructive",
245
+ });
246
+ }
247
+ };
248
+
249
+ return (
250
+ <div className="flex flex-col space-y-6">
251
+ <div className="flex items-start justify-between gap-4">
252
+ <div>
253
+ <h2 className="text-xl font-bold">API Keys</h2>
254
+ <p className="mt-1 text-sm text-muted-foreground">
255
+ API keys authenticate the Orcheo SDK and CLI when accessing{" "}
256
+ <span className="font-medium text-foreground">
257
+ {workspaceName ?? "this workspace"}
258
+ </span>{" "}
259
+ programmatically. They are scoped to this workspace and cannot
260
+ access any other workspace.
261
+ </p>
262
+ </div>
263
+ <Button onClick={() => setCreateOpen(true)}>
264
+ <KeyRound className="mr-2 h-4 w-4" />
265
+ Create a new key
266
+ </Button>
267
+ </div>
268
+
269
+ {isLoading ? (
270
+ <p className="text-sm text-muted-foreground">Loading API keys...</p>
271
+ ) : tokens.length === 0 ? (
272
+ <p className="text-sm text-muted-foreground">
273
+ No API keys for this workspace yet.
274
+ </p>
275
+ ) : (
276
+ <div className="rounded-lg border">
277
+ <Table>
278
+ <TableHeader>
279
+ <TableRow>
280
+ <TableHead>Identifier</TableHead>
281
+ <TableHead>Key</TableHead>
282
+ <TableHead>Scopes</TableHead>
283
+ <TableHead>Issued</TableHead>
284
+ <TableHead>Expires</TableHead>
285
+ <TableHead>Last used</TableHead>
286
+ <TableHead>Status</TableHead>
287
+ <TableHead className="text-right">Actions</TableHead>
288
+ </TableRow>
289
+ </TableHeader>
290
+ <TableBody>
291
+ {tokens.map((token) => {
292
+ const status = tokenStatus(token);
293
+ const isActive = status === "Active";
294
+ return (
295
+ <TableRow key={token.identifier}>
296
+ <TableCell className="font-mono text-sm">
297
+ {token.identifier}
298
+ </TableCell>
299
+ <TableCell className="font-mono text-sm text-muted-foreground">
300
+ {token.secret_preview
301
+ ? `••••••••${token.secret_preview}`
302
+ : "—"}
303
+ </TableCell>
304
+ <TableCell className="text-sm">
305
+ {token.scopes.length > 0 ? (
306
+ <div className="flex flex-wrap gap-1">
307
+ {token.scopes.map((scope) => (
308
+ <Badge key={scope} variant="secondary">
309
+ {scope}
310
+ </Badge>
311
+ ))}
312
+ </div>
313
+ ) : (
314
+ <span className="text-muted-foreground">—</span>
315
+ )}
316
+ </TableCell>
317
+ <TableCell className="text-sm text-muted-foreground">
318
+ {formatDate(token.issued_at)}
319
+ </TableCell>
320
+ <TableCell className="text-sm text-muted-foreground">
321
+ {token.expires_at
322
+ ? formatDate(token.expires_at)
323
+ : "Never"}
324
+ </TableCell>
325
+ <TableCell className="text-sm text-muted-foreground">
326
+ {formatDate(token.last_used_at)}
327
+ </TableCell>
328
+ <TableCell>
329
+ <Badge variant={isActive ? "default" : "outline"}>
330
+ {status}
331
+ </Badge>
332
+ </TableCell>
333
+ <TableCell className="text-right">
334
+ <div className="flex justify-end gap-1">
335
+ <Button
336
+ variant="ghost"
337
+ size="sm"
338
+ onClick={() => void handleRotate(token.identifier)}
339
+ disabled={
340
+ !isActive ||
341
+ rotatingId === token.identifier ||
342
+ revokingId === token.identifier
343
+ }
344
+ >
345
+ <RotateCw className="mr-2 h-4 w-4" />
346
+ {rotatingId === token.identifier
347
+ ? "Rotating..."
348
+ : "Rotate"}
349
+ </Button>
350
+ <Button
351
+ variant="ghost"
352
+ size="sm"
353
+ className="text-destructive hover:text-destructive"
354
+ onClick={() => setRevokeTarget(token.identifier)}
355
+ disabled={
356
+ !isActive ||
357
+ rotatingId === token.identifier ||
358
+ revokingId === token.identifier
359
+ }
360
+ >
361
+ <Trash2 className="mr-2 h-4 w-4" />
362
+ {revokingId === token.identifier
363
+ ? "Revoking..."
364
+ : "Revoke"}
365
+ </Button>
366
+ </div>
367
+ </TableCell>
368
+ </TableRow>
369
+ );
370
+ })}
371
+ </TableBody>
372
+ </Table>
373
+ </div>
374
+ )}
375
+
376
+ <Dialog
377
+ open={createOpen}
378
+ onOpenChange={(open) => {
379
+ if (!isMinting) {
380
+ setCreateOpen(open);
381
+ }
382
+ }}
383
+ >
384
+ <DialogContent>
385
+ <DialogHeader>
386
+ <DialogTitle>Create API key</DialogTitle>
387
+ <DialogDescription>
388
+ The key is scoped to this workspace. Its secret is shown once
389
+ after creation.
390
+ </DialogDescription>
391
+ </DialogHeader>
392
+ <div className="space-y-4">
393
+ <div className="space-y-1.5">
394
+ <Label htmlFor="token-identifier">Identifier (optional)</Label>
395
+ <Input
396
+ id="token-identifier"
397
+ placeholder="Auto-generated if left blank"
398
+ value={identifier}
399
+ onChange={(event) => setIdentifier(event.target.value)}
400
+ disabled={isMinting}
401
+ />
402
+ </div>
403
+
404
+ <div className="space-y-1.5">
405
+ <Label>Scopes</Label>
406
+ <div className="flex flex-wrap gap-x-4 gap-y-2">
407
+ {AVAILABLE_SCOPES.map((scope) => (
408
+ <label
409
+ key={scope}
410
+ className="flex items-center gap-2 text-sm"
411
+ >
412
+ <input
413
+ type="checkbox"
414
+ className="h-4 w-4 rounded border-border"
415
+ checked={selectedScopes.has(scope)}
416
+ onChange={() => toggleScope(scope)}
417
+ disabled={isMinting}
418
+ />
419
+ <span className="font-mono">{scope}</span>
420
+ </label>
421
+ ))}
422
+ </div>
423
+ </div>
424
+
425
+ <div className="w-44 space-y-1.5">
426
+ <Label htmlFor="token-expiry">Expires</Label>
427
+ <Select
428
+ value={expiry}
429
+ onValueChange={setExpiry}
430
+ disabled={isMinting}
431
+ >
432
+ <SelectTrigger id="token-expiry">
433
+ <SelectValue />
434
+ </SelectTrigger>
435
+ <SelectContent>
436
+ {EXPIRY_OPTIONS.map((option) => (
437
+ <SelectItem key={option.value} value={option.value}>
438
+ {option.label}
439
+ </SelectItem>
440
+ ))}
441
+ </SelectContent>
442
+ </Select>
443
+ </div>
444
+ </div>
445
+ <DialogFooter>
446
+ <Button onClick={() => void handleMint()} disabled={isMinting}>
447
+ {isMinting ? "Creating..." : "Create a new key"}
448
+ </Button>
449
+ </DialogFooter>
450
+ </DialogContent>
451
+ </Dialog>
452
+
453
+ {revealed && (
454
+ <Dialog
455
+ open
456
+ onOpenChange={(open) => {
457
+ if (!open) {
458
+ setRevealed(null);
459
+ }
460
+ }}
461
+ >
462
+ <DialogContent>
463
+ <DialogHeader>
464
+ <DialogTitle>{revealed.title}</DialogTitle>
465
+ <DialogDescription>
466
+ Copy this secret now — it will not be shown again.
467
+ </DialogDescription>
468
+ </DialogHeader>
469
+ <div className="space-y-2">
470
+ <p className="text-sm">
471
+ <span className="text-muted-foreground">ID: </span>
472
+ <span className="font-mono">{revealed.identifier}</span>
473
+ </p>
474
+ <p className="break-all rounded border bg-muted p-3 font-mono text-sm">
475
+ {revealed.secret}
476
+ </p>
477
+ </div>
478
+ <DialogFooter>
479
+ <Button
480
+ variant="outline"
481
+ onClick={() => void handleCopySecret(revealed.secret)}
482
+ >
483
+ <Copy className="mr-2 h-4 w-4" />
484
+ Copy secret
485
+ </Button>
486
+ <Button onClick={() => setRevealed(null)}>Done</Button>
487
+ </DialogFooter>
488
+ </DialogContent>
489
+ </Dialog>
490
+ )}
491
+
492
+ <AlertDialog
493
+ open={revokeTarget !== null}
494
+ onOpenChange={(open) => {
495
+ if (!open) {
496
+ setRevokeTarget(null);
497
+ }
498
+ }}
499
+ >
500
+ <AlertDialogContent>
501
+ <AlertDialogHeader>
502
+ <AlertDialogTitle>Revoke API key</AlertDialogTitle>
503
+ <AlertDialogDescription>
504
+ Revoking <span className="font-mono">{revokeTarget}</span>{" "}
505
+ immediately disables it. Any client using this key will lose
506
+ access. This cannot be undone.
507
+ </AlertDialogDescription>
508
+ </AlertDialogHeader>
509
+ <AlertDialogFooter>
510
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
511
+ <AlertDialogAction
512
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
513
+ onClick={() => void handleRevoke()}
514
+ >
515
+ Revoke
516
+ </AlertDialogAction>
517
+ </AlertDialogFooter>
518
+ </AlertDialogContent>
519
+ </AlertDialog>
520
+ </div>
521
+ );
522
+ }
@@ -0,0 +1,42 @@
1
+ import { useEffect } from "react";
2
+ import useCredentialVault from "@/hooks/use-credential-vault";
3
+ import { usePageContext } from "@/hooks/use-page-context";
4
+ import AppearanceSettingsTab from "@features/account/components/settings/appearance-settings-tab";
5
+ import TopNavigation from "@features/shared/components/top-navigation";
6
+
7
+ export default function Settings() {
8
+ const { setPageContext } = usePageContext();
9
+ useEffect(() => {
10
+ setPageContext({ page: "settings" });
11
+ }, [setPageContext]);
12
+ const {
13
+ credentials,
14
+ isLoading: isCredentialsLoading,
15
+ onAddCredential,
16
+ onUpdateCredential,
17
+ onDeleteCredential,
18
+ onRevealCredentialSecret,
19
+ } = useCredentialVault();
20
+
21
+ return (
22
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
23
+ <TopNavigation
24
+ credentials={credentials}
25
+ isCredentialsLoading={isCredentialsLoading}
26
+ onAddCredential={onAddCredential}
27
+ onUpdateCredential={onUpdateCredential}
28
+ onDeleteCredential={onDeleteCredential}
29
+ onRevealCredentialSecret={onRevealCredentialSecret}
30
+ />
31
+
32
+ <main className="flex-1 min-h-0 overflow-auto">
33
+ <div className="mx-auto flex w-full max-w-7xl flex-col space-y-4 p-8 pt-6">
34
+ <div className="flex items-center justify-between space-y-2">
35
+ <h2 className="text-3xl font-bold tracking-tight">Settings</h2>
36
+ </div>
37
+ <AppearanceSettingsTab />
38
+ </div>
39
+ </main>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,63 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ Tabs,
4
+ TabsContent,
5
+ TabsList,
6
+ TabsTrigger,
7
+ } from "@/design-system/ui/tabs";
8
+ import useCredentialVault from "@/hooks/use-credential-vault";
9
+ import { usePageContext } from "@/hooks/use-page-context";
10
+ import ServiceTokens from "@features/account/pages/service-tokens";
11
+ import WorkspaceMembers from "@features/account/pages/workspace-members";
12
+ import TopNavigation from "@features/shared/components/top-navigation";
13
+
14
+ export default function WorkspaceManagement() {
15
+ const { setPageContext } = usePageContext();
16
+ useEffect(() => {
17
+ setPageContext({ page: "workspace" });
18
+ }, [setPageContext]);
19
+
20
+ const {
21
+ credentials,
22
+ isLoading: isCredentialsLoading,
23
+ onAddCredential,
24
+ onUpdateCredential,
25
+ onDeleteCredential,
26
+ onRevealCredentialSecret,
27
+ } = useCredentialVault();
28
+
29
+ return (
30
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
31
+ <TopNavigation
32
+ credentials={credentials}
33
+ isCredentialsLoading={isCredentialsLoading}
34
+ onAddCredential={onAddCredential}
35
+ onUpdateCredential={onUpdateCredential}
36
+ onDeleteCredential={onDeleteCredential}
37
+ onRevealCredentialSecret={onRevealCredentialSecret}
38
+ />
39
+
40
+ <main className="flex-1 min-h-0 overflow-auto">
41
+ <div className="mx-auto flex w-full max-w-7xl flex-col space-y-6 p-8 pt-6">
42
+ <h1 className="text-3xl font-bold tracking-tight">
43
+ Workspace Management
44
+ </h1>
45
+
46
+ <Tabs defaultValue="members" className="flex flex-col gap-4">
47
+ <TabsList className="self-start">
48
+ <TabsTrigger value="members">Workspace Members</TabsTrigger>
49
+ <TabsTrigger value="api-keys">API Keys</TabsTrigger>
50
+ </TabsList>
51
+
52
+ <TabsContent value="members">
53
+ <WorkspaceMembers />
54
+ </TabsContent>
55
+ <TabsContent value="api-keys">
56
+ <ServiceTokens />
57
+ </TabsContent>
58
+ </Tabs>
59
+ </div>
60
+ </main>
61
+ </div>
62
+ );
63
+ }