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,565 @@
1
+ import {
2
+ clearAuthSession,
3
+ getAuthTokens,
4
+ setAuthTokens,
5
+ } from "@features/auth/lib/auth-session";
6
+
7
+ type AuthProvider = "google" | "github" | "email";
8
+
9
+ interface OidcInviteContext {
10
+ invitation?: string;
11
+ organization?: string;
12
+ organizationName?: string;
13
+ loginHint?: string;
14
+ screenHint?: string;
15
+ }
16
+
17
+ interface OidcDiscovery {
18
+ authorization_endpoint: string;
19
+ token_endpoint: string;
20
+ end_session_endpoint?: string;
21
+ }
22
+
23
+ interface OidcAuthConfig {
24
+ issuer: string;
25
+ clientId: string;
26
+ redirectUri: string;
27
+ scopes: string;
28
+ audience?: string;
29
+ organization?: string;
30
+ providerParam?: string;
31
+ signupProvider?: string;
32
+ providerValues: Partial<Record<AuthProvider, string>>;
33
+ }
34
+
35
+ const AUTH_STATE_KEY = "orcheo_studio_oidc_state";
36
+ const AUTH_VERIFIER_KEY = "orcheo_studio_oidc_verifier";
37
+ const AUTH_REDIRECT_KEY = "orcheo_studio_oidc_redirect";
38
+ const AUTH_STATE_ISSUED_AT_KEY = "orcheo_studio_oidc_state_issued_at";
39
+
40
+ const DEFAULT_STATE_BYTES = 32;
41
+ const DEFAULT_VERIFIER_BYTES = 64;
42
+ const MIN_STATE_BYTES = 16;
43
+ const MAX_STATE_BYTES = 96;
44
+ const MIN_VERIFIER_BYTES = 32;
45
+ const MAX_VERIFIER_BYTES = 96;
46
+ const AUTH_STATE_TTL_MS = 10 * 60 * 1000;
47
+
48
+ const BASE64_URL_PATTERN = /^[A-Za-z0-9_-]+$/;
49
+
50
+ const readEnv = (key: string): string | undefined => {
51
+ const value = (import.meta.env?.[key] ?? "") as string;
52
+ if (typeof value === "string" && value.trim()) {
53
+ return value.trim();
54
+ }
55
+ return undefined;
56
+ };
57
+
58
+ const readEnvInt = (key: string): number | undefined => {
59
+ const value = readEnv(key);
60
+ if (!value) {
61
+ return undefined;
62
+ }
63
+ const parsed = Number.parseInt(value, 10);
64
+ if (!Number.isFinite(parsed)) {
65
+ return undefined;
66
+ }
67
+ return parsed;
68
+ };
69
+
70
+ const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, "");
71
+
72
+ const isLocalhostHost = (hostname: string): boolean =>
73
+ hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
74
+
75
+ const parseUrl = (value: string, label: string): URL => {
76
+ try {
77
+ return new URL(value);
78
+ } catch {
79
+ throw new Error(`${label} must be a valid URL.`);
80
+ }
81
+ };
82
+
83
+ const validateUrl = (
84
+ value: string,
85
+ label: string,
86
+ { allowHttpLocalhost }: { allowHttpLocalhost: boolean },
87
+ ): string => {
88
+ const parsed = parseUrl(value, label);
89
+ const isHttp = parsed.protocol === "http:";
90
+ const isHttps = parsed.protocol === "https:";
91
+ if (
92
+ !isHttps &&
93
+ !(allowHttpLocalhost && isHttp && isLocalhostHost(parsed.hostname))
94
+ ) {
95
+ throw new Error(`${label} must use https (or http for localhost).`);
96
+ }
97
+ return value.trim();
98
+ };
99
+
100
+ const readEnvUrl = (
101
+ key: string,
102
+ label: string,
103
+ { allowHttpLocalhost }: { allowHttpLocalhost: boolean },
104
+ ): string | undefined => {
105
+ const value = readEnv(key);
106
+ if (!value) {
107
+ return undefined;
108
+ }
109
+ return validateUrl(value, label, { allowHttpLocalhost });
110
+ };
111
+
112
+ const resolveRandomByteLength = (
113
+ key: string,
114
+ fallback: number,
115
+ min: number,
116
+ max: number,
117
+ ): number => {
118
+ const configured = readEnvInt(key);
119
+ if (configured === undefined) {
120
+ return fallback;
121
+ }
122
+ if (configured < min || configured > max) {
123
+ console.warn(
124
+ `${key} must be between ${min} and ${max}. Falling back to ${fallback}.`,
125
+ );
126
+ return fallback;
127
+ }
128
+ return configured;
129
+ };
130
+
131
+ const base64UrlLength = (bytes: number): number => Math.ceil((bytes * 4) / 3);
132
+
133
+ const STATE_BYTES = resolveRandomByteLength(
134
+ "VITE_ORCHEO_AUTH_STATE_BYTES",
135
+ DEFAULT_STATE_BYTES,
136
+ MIN_STATE_BYTES,
137
+ MAX_STATE_BYTES,
138
+ );
139
+ const VERIFIER_BYTES = resolveRandomByteLength(
140
+ "VITE_ORCHEO_AUTH_VERIFIER_BYTES",
141
+ DEFAULT_VERIFIER_BYTES,
142
+ MIN_VERIFIER_BYTES,
143
+ MAX_VERIFIER_BYTES,
144
+ );
145
+ const MIN_STATE_LENGTH = base64UrlLength(STATE_BYTES);
146
+ const MIN_VERIFIER_LENGTH = base64UrlLength(VERIFIER_BYTES);
147
+
148
+ const assertValidBase64Url = (
149
+ value: string,
150
+ label: string,
151
+ minLength: number,
152
+ ): void => {
153
+ if (!BASE64_URL_PATTERN.test(value) || value.length < minLength) {
154
+ throw new Error(`${label} is invalid.`);
155
+ }
156
+ };
157
+
158
+ const resolveRedirectUri = (): string => {
159
+ const fromEnv = readEnvUrl("VITE_ORCHEO_AUTH_REDIRECT_URI", "Redirect URI", {
160
+ allowHttpLocalhost: true,
161
+ });
162
+ if (fromEnv) {
163
+ return fromEnv;
164
+ }
165
+ if (typeof window !== "undefined") {
166
+ return `${window.location.origin}/auth/callback`;
167
+ }
168
+ return "http://localhost:2026/auth/callback";
169
+ };
170
+
171
+ const getAuthConfig = (): OidcAuthConfig => {
172
+ const issuer = readEnvUrl("VITE_ORCHEO_AUTH_ISSUER", "Issuer", {
173
+ allowHttpLocalhost: true,
174
+ });
175
+ const clientId = readEnv("VITE_ORCHEO_AUTH_CLIENT_ID");
176
+ if (!issuer || !clientId) {
177
+ throw new Error(
178
+ "OAuth is not configured. Set VITE_ORCHEO_AUTH_ISSUER and VITE_ORCHEO_AUTH_CLIENT_ID.",
179
+ );
180
+ }
181
+ const scopes = readEnv("VITE_ORCHEO_AUTH_SCOPES") ?? "openid profile email";
182
+ const audience = readEnv("VITE_ORCHEO_AUTH_AUDIENCE");
183
+ const organization = readEnv("VITE_ORCHEO_AUTH_ORGANIZATION");
184
+ const providerParam = readEnv("VITE_ORCHEO_AUTH_PROVIDER_PARAM");
185
+ const signupProvider = readEnv("VITE_ORCHEO_AUTH_PROVIDER_SIGNUP");
186
+ const providerValues: Partial<Record<AuthProvider, string>> = {
187
+ google: readEnv("VITE_ORCHEO_AUTH_PROVIDER_GOOGLE"),
188
+ github: readEnv("VITE_ORCHEO_AUTH_PROVIDER_GITHUB"),
189
+ email:
190
+ readEnv("VITE_ORCHEO_AUTH_PROVIDER_EMAIL") ??
191
+ "Username-Password-Authentication",
192
+ };
193
+
194
+ return {
195
+ issuer: trimTrailingSlash(issuer),
196
+ clientId,
197
+ redirectUri: resolveRedirectUri(),
198
+ scopes,
199
+ audience,
200
+ organization,
201
+ providerParam,
202
+ signupProvider,
203
+ providerValues,
204
+ };
205
+ };
206
+
207
+ const loadDiscovery = async (issuer: string): Promise<OidcDiscovery> => {
208
+ const url = `${trimTrailingSlash(issuer)}/.well-known/openid-configuration`;
209
+ const response = await fetch(url);
210
+ if (!response.ok) {
211
+ throw new Error("Failed to load OAuth discovery metadata.");
212
+ }
213
+ return (await response.json()) as OidcDiscovery;
214
+ };
215
+
216
+ const base64UrlEncode = (input: ArrayBuffer): string => {
217
+ const bytes = new Uint8Array(input);
218
+ let binary = "";
219
+ bytes.forEach((value) => {
220
+ binary += String.fromCharCode(value);
221
+ });
222
+ return btoa(binary)
223
+ .replace(/\+/g, "-")
224
+ .replace(/\//g, "_")
225
+ .replace(/=+$/, "");
226
+ };
227
+
228
+ const createRandomString = (length = 32): string => {
229
+ const bytes = new Uint8Array(length);
230
+ crypto.getRandomValues(bytes);
231
+ return base64UrlEncode(bytes.buffer);
232
+ };
233
+
234
+ const sha256 = async (value: string): Promise<ArrayBuffer> => {
235
+ const encoder = new TextEncoder();
236
+ const data = encoder.encode(value);
237
+ return crypto.subtle.digest("SHA-256", data);
238
+ };
239
+
240
+ interface JwtPayloadWithExpiry {
241
+ exp?: unknown;
242
+ }
243
+
244
+ const isJwtPayloadWithExpiry = (
245
+ value: unknown,
246
+ ): value is JwtPayloadWithExpiry => typeof value === "object" && value !== null;
247
+
248
+ const parseJwtExpiry = (token?: string): number | undefined => {
249
+ if (!token) {
250
+ return undefined;
251
+ }
252
+ const parts = token.split(".");
253
+ if (parts.length < 2) {
254
+ return undefined;
255
+ }
256
+ try {
257
+ const payload = JSON.parse(
258
+ atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")),
259
+ ) as unknown;
260
+ if (!isJwtPayloadWithExpiry(payload)) {
261
+ return undefined;
262
+ }
263
+ const exp = payload.exp;
264
+ if (typeof exp !== "number" || !Number.isFinite(exp)) {
265
+ return undefined;
266
+ }
267
+ return exp * 1000;
268
+ } catch {
269
+ return undefined;
270
+ }
271
+ };
272
+
273
+ const storeOidcState = (state: string, verifier: string): void => {
274
+ if (typeof window === "undefined") {
275
+ return;
276
+ }
277
+ window.sessionStorage.setItem(AUTH_STATE_KEY, state);
278
+ window.sessionStorage.setItem(AUTH_VERIFIER_KEY, verifier);
279
+ window.sessionStorage.setItem(
280
+ AUTH_STATE_ISSUED_AT_KEY,
281
+ Date.now().toString(),
282
+ );
283
+ };
284
+
285
+ const storePostLoginRedirect = (redirectTo?: string): void => {
286
+ if (typeof window === "undefined" || !redirectTo) {
287
+ return;
288
+ }
289
+ window.sessionStorage.setItem(AUTH_REDIRECT_KEY, redirectTo);
290
+ };
291
+
292
+ export const consumePostLoginRedirect = (): string | null => {
293
+ if (typeof window === "undefined") {
294
+ return null;
295
+ }
296
+ const redirectTo = window.sessionStorage.getItem(AUTH_REDIRECT_KEY);
297
+ if (redirectTo) {
298
+ window.sessionStorage.removeItem(AUTH_REDIRECT_KEY);
299
+ }
300
+ return redirectTo;
301
+ };
302
+
303
+ const readOidcState = (): { state: string; verifier: string } => {
304
+ if (typeof window === "undefined") {
305
+ throw new Error("OAuth state missing.");
306
+ }
307
+ const state = window.sessionStorage.getItem(AUTH_STATE_KEY);
308
+ const verifier = window.sessionStorage.getItem(AUTH_VERIFIER_KEY);
309
+ const issuedAtRaw = window.sessionStorage.getItem(AUTH_STATE_ISSUED_AT_KEY);
310
+ const issuedAt = issuedAtRaw ? Number.parseInt(issuedAtRaw, 10) : NaN;
311
+ if (
312
+ !state ||
313
+ !verifier ||
314
+ !Number.isFinite(issuedAt) ||
315
+ Date.now() - issuedAt > AUTH_STATE_TTL_MS
316
+ ) {
317
+ clearOidcState();
318
+ throw new Error("OAuth state missing.");
319
+ }
320
+ assertValidBase64Url(state, "OAuth state", MIN_STATE_LENGTH);
321
+ assertValidBase64Url(verifier, "OAuth verifier", MIN_VERIFIER_LENGTH);
322
+ return { state, verifier };
323
+ };
324
+
325
+ const clearOidcState = (): void => {
326
+ if (typeof window === "undefined") {
327
+ return;
328
+ }
329
+ window.sessionStorage.removeItem(AUTH_STATE_KEY);
330
+ window.sessionStorage.removeItem(AUTH_VERIFIER_KEY);
331
+ window.sessionStorage.removeItem(AUTH_STATE_ISSUED_AT_KEY);
332
+ };
333
+
334
+ const normalizeOptionalParam = (value?: string): string | undefined => {
335
+ const normalized = value?.trim();
336
+ return normalized || undefined;
337
+ };
338
+
339
+ export const startOidcLogin = async ({
340
+ provider,
341
+ redirectTo,
342
+ invitation,
343
+ organization,
344
+ organizationName,
345
+ loginHint,
346
+ screenHint,
347
+ signup = false,
348
+ prompt,
349
+ }: {
350
+ provider?: AuthProvider;
351
+ redirectTo?: string;
352
+ signup?: boolean;
353
+ prompt?: string;
354
+ } & OidcInviteContext): Promise<void> => {
355
+ const normalizedInvitation = normalizeOptionalParam(invitation);
356
+ const normalizedOrganization = normalizeOptionalParam(organization);
357
+ const normalizedOrganizationName = normalizeOptionalParam(organizationName);
358
+ const normalizedLoginHint = normalizeOptionalParam(loginHint);
359
+ const normalizedScreenHint = normalizeOptionalParam(screenHint);
360
+
361
+ const config = getAuthConfig();
362
+ // Configured workspace restriction must remain authoritative over URL params.
363
+ const effectiveOrganization =
364
+ (config.organization ?? normalizedOrganization) || undefined;
365
+ const effectiveOrganizationName = config.organization
366
+ ? undefined
367
+ : normalizedOrganizationName || undefined;
368
+ const discovery = await loadDiscovery(config.issuer);
369
+ const state = createRandomString(STATE_BYTES);
370
+ const verifier = createRandomString(VERIFIER_BYTES);
371
+ const challenge = base64UrlEncode(await sha256(verifier));
372
+ storeOidcState(state, verifier);
373
+ storePostLoginRedirect(redirectTo);
374
+
375
+ const url = new URL(discovery.authorization_endpoint);
376
+ url.searchParams.set("response_type", "code");
377
+ url.searchParams.set("client_id", config.clientId);
378
+ url.searchParams.set("redirect_uri", config.redirectUri);
379
+ url.searchParams.set("scope", config.scopes);
380
+ url.searchParams.set("state", state);
381
+ url.searchParams.set("code_challenge", challenge);
382
+ url.searchParams.set("code_challenge_method", "S256");
383
+ if (effectiveOrganization) {
384
+ url.searchParams.set("organization", effectiveOrganization);
385
+ }
386
+ if (effectiveOrganizationName) {
387
+ url.searchParams.set("organization_name", effectiveOrganizationName);
388
+ }
389
+ if (normalizedInvitation) {
390
+ url.searchParams.set("invitation", normalizedInvitation);
391
+ }
392
+ if (normalizedLoginHint) {
393
+ url.searchParams.set("login_hint", normalizedLoginHint);
394
+ }
395
+ if (normalizedScreenHint) {
396
+ url.searchParams.set("screen_hint", normalizedScreenHint);
397
+ }
398
+ if (prompt) {
399
+ url.searchParams.set("prompt", prompt);
400
+ }
401
+ if (config.audience) {
402
+ url.searchParams.set("audience", config.audience);
403
+ }
404
+ const effectiveProvider = signup
405
+ ? (config.signupProvider ?? provider)
406
+ : provider;
407
+ if (effectiveProvider && config.providerParam) {
408
+ const providerValue =
409
+ provider && provider in config.providerValues
410
+ ? (config.providerValues[provider] ?? provider)
411
+ : effectiveProvider;
412
+ url.searchParams.set(config.providerParam, providerValue);
413
+ }
414
+
415
+ window.location.assign(url.toString());
416
+ };
417
+
418
+ export const completeOidcLogin = async ({
419
+ code,
420
+ state,
421
+ }: {
422
+ code: string;
423
+ state: string;
424
+ }): Promise<void> => {
425
+ const config = getAuthConfig();
426
+ const discovery = await loadDiscovery(config.issuer);
427
+ assertValidBase64Url(state, "OAuth state", MIN_STATE_LENGTH);
428
+ const stored = readOidcState();
429
+ if (stored.state !== state) {
430
+ clearOidcState();
431
+ throw new Error("OAuth state mismatch.");
432
+ }
433
+
434
+ const body = new URLSearchParams();
435
+ body.set("grant_type", "authorization_code");
436
+ body.set("client_id", config.clientId);
437
+ body.set("code", code);
438
+ body.set("redirect_uri", config.redirectUri);
439
+ body.set("code_verifier", stored.verifier);
440
+
441
+ const response = await fetch(discovery.token_endpoint, {
442
+ method: "POST",
443
+ headers: {
444
+ "Content-Type": "application/x-www-form-urlencoded",
445
+ },
446
+ body: body.toString(),
447
+ });
448
+
449
+ clearOidcState();
450
+
451
+ if (!response.ok) {
452
+ const errorText = await response.text().catch(() => "");
453
+ throw new Error(
454
+ errorText || "OAuth token exchange failed. Check your IdP settings.",
455
+ );
456
+ }
457
+
458
+ const payload = (await response.json()) as {
459
+ access_token?: string;
460
+ id_token?: string;
461
+ refresh_token?: string;
462
+ token_type?: string;
463
+ expires_in?: number;
464
+ };
465
+
466
+ if (!payload.access_token) {
467
+ throw new Error("OAuth token response missing access token.");
468
+ }
469
+
470
+ const expiresIn = payload.expires_in;
471
+ const expiryFromToken =
472
+ parseJwtExpiry(payload.access_token) ?? parseJwtExpiry(payload.id_token);
473
+ const expiresAt =
474
+ typeof expiresIn === "number"
475
+ ? Date.now() + expiresIn * 1000
476
+ : expiryFromToken;
477
+
478
+ setAuthTokens({
479
+ accessToken: payload.access_token,
480
+ idToken: payload.id_token,
481
+ refreshToken: payload.refresh_token,
482
+ tokenType: payload.token_type,
483
+ expiresAt,
484
+ });
485
+ };
486
+
487
+ export const tryRefreshTokens = async (): Promise<boolean> => {
488
+ const tokens = getAuthTokens();
489
+ if (!tokens?.refreshToken) {
490
+ return false;
491
+ }
492
+
493
+ let config: OidcAuthConfig;
494
+ try {
495
+ config = getAuthConfig();
496
+ } catch {
497
+ return false;
498
+ }
499
+
500
+ let discovery: OidcDiscovery;
501
+ try {
502
+ discovery = await loadDiscovery(config.issuer);
503
+ } catch {
504
+ return false;
505
+ }
506
+
507
+ const body = new URLSearchParams();
508
+ body.set("grant_type", "refresh_token");
509
+ body.set("client_id", config.clientId);
510
+ body.set("refresh_token", tokens.refreshToken);
511
+
512
+ let response: Response;
513
+ try {
514
+ response = await fetch(discovery.token_endpoint, {
515
+ method: "POST",
516
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
517
+ body: body.toString(),
518
+ });
519
+ } catch {
520
+ return false;
521
+ }
522
+
523
+ if (!response.ok) {
524
+ clearAuthSession();
525
+ return false;
526
+ }
527
+
528
+ let payload: {
529
+ access_token?: string;
530
+ id_token?: string;
531
+ refresh_token?: string;
532
+ token_type?: string;
533
+ expires_in?: number;
534
+ };
535
+ try {
536
+ payload = (await response.json()) as typeof payload;
537
+ } catch {
538
+ clearAuthSession();
539
+ return false;
540
+ }
541
+
542
+ if (!payload.access_token) {
543
+ clearAuthSession();
544
+ return false;
545
+ }
546
+
547
+ const expiresIn = payload.expires_in;
548
+ const expiryFromToken =
549
+ parseJwtExpiry(payload.access_token) ?? parseJwtExpiry(payload.id_token);
550
+ const expiresAt =
551
+ typeof expiresIn === "number"
552
+ ? Date.now() + expiresIn * 1000
553
+ : expiryFromToken;
554
+
555
+ setAuthTokens({
556
+ accessToken: payload.access_token,
557
+ idToken: payload.id_token,
558
+ // keep the old refresh token if the server doesn't issue a new one
559
+ refreshToken: payload.refresh_token ?? tokens.refreshToken,
560
+ tokenType: payload.token_type,
561
+ expiresAt,
562
+ });
563
+
564
+ return true;
565
+ };
@@ -0,0 +1,24 @@
1
+ import { useMemo } from "react";
2
+ import { useLocation } from "react-router-dom";
3
+ import AutoLogin from "@features/auth/components/auto-login";
4
+
5
+ const parseInviteContext = (search: string) => {
6
+ const params = new URLSearchParams(search);
7
+ const normalize = (v: string | null) => v?.trim() || undefined;
8
+ return {
9
+ invitation: normalize(params.get("invitation")),
10
+ organization: normalize(params.get("organization")),
11
+ organizationName: normalize(params.get("organization_name")),
12
+ loginHint: normalize(params.get("login_hint")),
13
+ screenHint: normalize(params.get("screen_hint")),
14
+ };
15
+ };
16
+
17
+ export default function Login() {
18
+ const location = useLocation();
19
+ const inviteContext = useMemo(
20
+ () => parseInviteContext(location.search),
21
+ [location.search],
22
+ );
23
+ return <AutoLogin {...inviteContext} />;
24
+ }
@@ -0,0 +1,63 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useNavigate } from "react-router-dom";
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardHeader,
7
+ CardTitle,
8
+ } from "@/design-system/ui/card";
9
+ import { Loader2 } from "lucide-react";
10
+ import {
11
+ completeOidcLogin,
12
+ consumePostLoginRedirect,
13
+ } from "@features/auth/lib/oidc-client";
14
+
15
+ export default function OAuthCallback() {
16
+ const navigate = useNavigate();
17
+ const [message, setMessage] = useState("Completing sign-in…");
18
+
19
+ useEffect(() => {
20
+ const handleCallback = async () => {
21
+ const url = new URL(window.location.href);
22
+ const error = url.searchParams.get("error");
23
+ const errorDescription = url.searchParams.get("error_description");
24
+ if (error) {
25
+ console.error("OAuth login failed.", { error, errorDescription });
26
+ setMessage("OAuth login failed. Please try again.");
27
+ return;
28
+ }
29
+
30
+ const code = url.searchParams.get("code");
31
+ const state = url.searchParams.get("state");
32
+ if (!code || !state) {
33
+ setMessage("Missing OAuth response details.");
34
+ return;
35
+ }
36
+
37
+ try {
38
+ await completeOidcLogin({ code, state });
39
+ const redirectTo = consumePostLoginRedirect() ?? "/";
40
+ navigate(redirectTo, { replace: true });
41
+ } catch (err) {
42
+ console.error("Unable to complete login.", err);
43
+ setMessage("Unable to complete login.");
44
+ }
45
+ };
46
+
47
+ void handleCallback();
48
+ }, [navigate]);
49
+
50
+ return (
51
+ <div className="flex min-h-screen items-center justify-center bg-slate-950 text-foreground">
52
+ <Card className="max-w-md border-primary/25 bg-primary/5">
53
+ <CardHeader>
54
+ <CardTitle>Signing in</CardTitle>
55
+ </CardHeader>
56
+ <CardContent className="flex items-center gap-3 text-sm text-muted-foreground">
57
+ <Loader2 className="h-4 w-4 animate-spin" />
58
+ <span>{message}</span>
59
+ </CardContent>
60
+ </Card>
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,60 @@
1
+ import { useMemo, useRef } from "react";
2
+ import {
3
+ ChatKit,
4
+ useChatKit,
5
+ type UseChatKitOptions,
6
+ type UseChatKitReturn,
7
+ } from "@openai/chatkit-react";
8
+ import { cn } from "@/lib/utils";
9
+ import { toast } from "@/hooks/use-toast";
10
+
11
+ interface ChatKitSurfaceProps {
12
+ options: UseChatKitOptions;
13
+ className?: string;
14
+ }
15
+
16
+ export function ChatKitSurface({ options, className }: ChatKitSurfaceProps) {
17
+ const sendActionRef = useRef<UseChatKitReturn["sendCustomAction"] | null>(
18
+ null,
19
+ );
20
+ const optionsWithWidgetActions = useMemo<UseChatKitOptions>(() => {
21
+ const originalOnAction = options.widgets?.onAction;
22
+ return {
23
+ ...options,
24
+ widgets: {
25
+ ...options.widgets,
26
+ onAction: async (action, widgetItem) => {
27
+ try {
28
+ if (originalOnAction) {
29
+ await originalOnAction(action, widgetItem);
30
+ }
31
+ const sendAction = sendActionRef.current;
32
+ if (!sendAction) {
33
+ throw new Error(
34
+ "Chat session is not ready to send widget actions",
35
+ );
36
+ }
37
+ await sendAction(action, widgetItem.id);
38
+ } catch (error) {
39
+ console.error("Failed to dispatch widget action", error);
40
+ toast({
41
+ title: "Widget action failed",
42
+ description:
43
+ "We could not send that widget action. Please try again.",
44
+ variant: "destructive",
45
+ });
46
+ }
47
+ },
48
+ },
49
+ };
50
+ }, [options]);
51
+
52
+ const { control, sendCustomAction } = useChatKit(optionsWithWidgetActions);
53
+ sendActionRef.current = sendCustomAction;
54
+
55
+ return (
56
+ <div className={cn("flex h-full w-full flex-col", className)}>
57
+ <ChatKit control={control} className="flex h-full w-full flex-col" />
58
+ </div>
59
+ );
60
+ }