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,124 @@
1
+ import type { ComponentPropsWithRef, ReactElement } from "react";
2
+
3
+ import cn from "classnames";
4
+
5
+ import type { ComponentSize } from "./shared";
6
+
7
+ import { ROUNDED_CLASSES } from "./shared";
8
+
9
+ type ButtonSize = Extract<
10
+ ComponentSize,
11
+ "6" | "7" | "8" | "9" | "10" | "11" | "12" | "16"
12
+ >;
13
+
14
+ type ButtonVariant =
15
+ | "brand"
16
+ | "primary"
17
+ | "outlined"
18
+ | "secondary"
19
+ | "ghost"
20
+ | "destructive"
21
+ | "success";
22
+
23
+ const BASE_CLASSES =
24
+ "inline-flex items-center justify-center font-medium transition-all duration-200";
25
+
26
+ const sizeClasses = {
27
+ "6": "h-6 px-2 gap-1 text-xs",
28
+ "7": "h-7 px-2 gap-1 text-xs",
29
+ "8": "h-8 px-2 gap-1 text-xs",
30
+ "9": "h-9 px-2.5 gap-2 text-sm",
31
+ "10": "h-10 px-4 gap-2 text-sm",
32
+ "11": "h-11 px-5 gap-3 text-base",
33
+ "12": "h-12 px-5 gap-2.5 text-base",
34
+ "16": "h-16 px-7 gap-3 text-lg",
35
+ };
36
+
37
+ const variantClasses: Record<ButtonVariant, string> = {
38
+ brand: "text-agentprism-brand-foreground bg-agentprism-brand",
39
+ primary: "text-agentprism-primary-foreground bg-agentprism-primary",
40
+ outlined:
41
+ "border border bg-transparent text-agentprism-foreground border-agentprism-foreground",
42
+ secondary: "bg-agentprism-secondary text-agentprism-secondary-foreground",
43
+ ghost: "bg-transparent text-agentprism-foreground",
44
+ destructive: "bg-agentprism-error text-agentprism-primary-foreground",
45
+ success: "bg-agentprism-success text-agentprism-primary-foreground",
46
+ };
47
+
48
+ export type ButtonProps = ComponentPropsWithRef<"button"> & {
49
+ /**
50
+ * The size of the button
51
+ * @default "6"
52
+ */
53
+ size?: ButtonSize;
54
+
55
+ /**
56
+ * The border radius of the button
57
+ * @default "md"
58
+ */
59
+ rounded?: "none" | "sm" | "md" | "lg" | "full";
60
+
61
+ /**
62
+ * The visual variant of the button
63
+ * @default "primary"
64
+ */
65
+ variant?: ButtonVariant;
66
+
67
+ /**
68
+ * Makes the button full width
69
+ * @default false
70
+ */
71
+ fullWidth?: boolean;
72
+
73
+ /**
74
+ * Optional icon to display at the start of the button
75
+ */
76
+ iconStart?: ReactElement;
77
+
78
+ /**
79
+ * Optional icon to display at the end of the button
80
+ */
81
+ iconEnd?: ReactElement;
82
+ };
83
+
84
+ export const Button = ({
85
+ children,
86
+ size = "6",
87
+ rounded = "md",
88
+ variant = "primary",
89
+ fullWidth = false,
90
+ disabled = false,
91
+ iconStart,
92
+ iconEnd,
93
+ type = "button",
94
+ onClick,
95
+ className = "",
96
+ ...rest
97
+ }: ButtonProps) => {
98
+ const widthClass = fullWidth ? "w-full" : "";
99
+ const stateClasses = disabled
100
+ ? "cursor-not-allowed opacity-50"
101
+ : "hover:opacity-70";
102
+
103
+ return (
104
+ <button
105
+ type={type}
106
+ onClick={onClick}
107
+ disabled={disabled}
108
+ className={cn(
109
+ BASE_CLASSES,
110
+ sizeClasses[size],
111
+ ROUNDED_CLASSES[rounded],
112
+ variantClasses[variant],
113
+ widthClass,
114
+ stateClasses,
115
+ className,
116
+ )}
117
+ {...rest}
118
+ >
119
+ {iconStart && <span className="mr-1">{iconStart}</span>}
120
+ {children}
121
+ {iconEnd && <span className="ml-1">{iconEnd}</span>}
122
+ </button>
123
+ );
124
+ };
@@ -0,0 +1,45 @@
1
+ import type { ComponentPropsWithRef } from "react";
2
+
3
+ import { ChevronsUpDown, ChevronsDownUp } from "lucide-react";
4
+
5
+ import { IconButton } from "./IconButton";
6
+
7
+ export type SpanCardExpandAllButtonProps = ComponentPropsWithRef<"button"> & {
8
+ onExpandAll: () => void;
9
+ };
10
+
11
+ export type SpanCardCollapseAllButtonProps = ComponentPropsWithRef<"button"> & {
12
+ onCollapseAll: () => void;
13
+ };
14
+
15
+ export const ExpandAllButton = ({
16
+ onExpandAll,
17
+ ...rest
18
+ }: SpanCardExpandAllButtonProps) => {
19
+ return (
20
+ <IconButton
21
+ size="6"
22
+ onClick={onExpandAll}
23
+ aria-label="Expand all"
24
+ {...rest}
25
+ >
26
+ <ChevronsUpDown className="size-3.5" />
27
+ </IconButton>
28
+ );
29
+ };
30
+
31
+ export const CollapseAllButton = ({
32
+ onCollapseAll,
33
+ ...rest
34
+ }: SpanCardCollapseAllButtonProps) => {
35
+ return (
36
+ <IconButton
37
+ size="6"
38
+ onClick={onCollapseAll}
39
+ aria-label="Collapse all"
40
+ {...rest}
41
+ >
42
+ <ChevronsDownUp className="size-3.5" />
43
+ </IconButton>
44
+ );
45
+ };
@@ -0,0 +1,124 @@
1
+ import * as Collapsible from "@radix-ui/react-collapsible";
2
+ import cn from "classnames";
3
+ import { ChevronDown } from "lucide-react";
4
+ import * as React from "react";
5
+
6
+ export interface CollapsibleSectionProps {
7
+ /**
8
+ * The title text displayed in the trigger button
9
+ */
10
+ title: string;
11
+
12
+ /**
13
+ * The content to display on the right side of the title
14
+ */
15
+ rightContent?: React.ReactNode;
16
+
17
+ /**
18
+ * The content to display when the section is expanded
19
+ */
20
+ children: React.ReactNode;
21
+
22
+ /**
23
+ * Whether the section starts in an open state
24
+ * @default false
25
+ */
26
+ defaultOpen?: boolean;
27
+
28
+ /**
29
+ * Optional className for the root container
30
+ */
31
+ className?: string;
32
+
33
+ /**
34
+ * Optional className for the trigger button
35
+ */
36
+ triggerClassName?: string;
37
+
38
+ /**
39
+ * Optional className for the content area
40
+ */
41
+ contentClassName?: string;
42
+
43
+ /**
44
+ * Optional callback fired when the section is expanded or collapsed
45
+ */
46
+ onOpenChange?: (open: boolean) => void;
47
+ }
48
+
49
+ export const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
50
+ title,
51
+ rightContent,
52
+ children,
53
+ defaultOpen = false,
54
+ className = "",
55
+ triggerClassName = "",
56
+ contentClassName = "",
57
+ onOpenChange,
58
+ }) => {
59
+ const [open, setOpen] = React.useState(defaultOpen);
60
+
61
+ const handleOpenChange = React.useCallback(
62
+ (open: boolean): void => {
63
+ setOpen(open);
64
+ onOpenChange?.(open);
65
+ },
66
+ [onOpenChange],
67
+ );
68
+
69
+ const handleKeyDown = React.useCallback(
70
+ (e: React.KeyboardEvent<HTMLDivElement>): void => {
71
+ if (e.key === "Enter" || e.key === " ") {
72
+ e.preventDefault();
73
+ handleOpenChange(!open);
74
+ }
75
+ },
76
+ [handleOpenChange, open],
77
+ );
78
+
79
+ return (
80
+ <Collapsible.Root
81
+ open={open}
82
+ onOpenChange={handleOpenChange}
83
+ className={cn("rounded-lg", className)}
84
+ >
85
+ <Collapsible.Trigger asChild>
86
+ <div
87
+ tabIndex={0}
88
+ role="button"
89
+ className={cn(
90
+ "text-agentprism-muted-foreground mb-2.5 flex w-full items-center justify-between gap-2 rounded-lg px-1 text-left text-sm font-medium",
91
+ triggerClassName,
92
+ )}
93
+ onKeyDown={handleKeyDown}
94
+ aria-expanded={open}
95
+ aria-label={`${open ? "Collapse" : "Expand"} content of "${title}" section`}
96
+ >
97
+ <div className="text-agentprism-muted-foreground flex min-w-0 flex-1 items-center gap-2">
98
+ <ChevronDown
99
+ className={cn("h-3 w-3 shrink-0 -rotate-90", open && "rotate-0")}
100
+ />
101
+ <span
102
+ className="min-w-0 truncate text-sm font-medium"
103
+ title={title}
104
+ >
105
+ {title}
106
+ </span>
107
+ </div>
108
+
109
+ <div className="shrink-0">{rightContent}</div>
110
+ </div>
111
+ </Collapsible.Trigger>
112
+
113
+ <Collapsible.Content
114
+ className={cn(
115
+ "data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown",
116
+ "text-agentprism-muted-foreground",
117
+ contentClassName,
118
+ )}
119
+ >
120
+ {children}
121
+ </Collapsible.Content>
122
+ </Collapsible.Root>
123
+ );
124
+ };
@@ -0,0 +1,63 @@
1
+ import { Check, Copy, X } from "lucide-react";
2
+ import { useState } from "react";
3
+
4
+ import { IconButton } from "./IconButton";
5
+
6
+ type CopyButtonProps = {
7
+ label: string;
8
+ content: string;
9
+ };
10
+
11
+ type CopyState = "idle" | "success" | "error";
12
+
13
+ export const CopyButton = ({ label, content }: CopyButtonProps) => {
14
+ const [copyState, setCopyState] = useState<CopyState>("idle");
15
+
16
+ const onClick = async () => {
17
+ try {
18
+ if (!navigator.clipboard) {
19
+ throw new Error("Clipboard API not supported");
20
+ }
21
+
22
+ await navigator.clipboard.writeText(content);
23
+ setCopyState("success");
24
+ setTimeout(() => setCopyState("idle"), 2000);
25
+ } catch {
26
+ setCopyState("error");
27
+ setTimeout(() => setCopyState("idle"), 2000);
28
+ }
29
+ };
30
+
31
+ const getIcon = () => {
32
+ switch (copyState) {
33
+ case "success":
34
+ return <Check className="size-3" />;
35
+ case "error":
36
+ return <X className="size-3" />;
37
+ default:
38
+ return <Copy className="size-3" />;
39
+ }
40
+ };
41
+
42
+ const getAriaLabel = () => {
43
+ switch (copyState) {
44
+ case "success":
45
+ return `${label} Copied`;
46
+ case "error":
47
+ return `Failed to copy ${label}`;
48
+ default:
49
+ return `Copy ${label}`;
50
+ }
51
+ };
52
+
53
+ return (
54
+ <IconButton
55
+ onClick={onClick}
56
+ aria-label={getAriaLabel()}
57
+ variant="ghost"
58
+ disabled={copyState !== "idle"}
59
+ >
60
+ {getIcon()}
61
+ </IconButton>
62
+ );
63
+ };
@@ -0,0 +1,146 @@
1
+ import type { TraceSpan } from "@evilmartians/agent-prism-types";
2
+ import type { ReactElement, ReactNode } from "react";
3
+
4
+ import cn from "classnames";
5
+ import { SquareTerminal, Tags, ArrowRightLeft } from "lucide-react";
6
+ import { useState } from "react";
7
+
8
+ import type { AvatarProps } from "../Avatar";
9
+ import type { TabItem } from "../Tabs";
10
+
11
+ import { TabSelector } from "../TabSelector";
12
+ import { DetailsViewAttributesTab } from "./DetailsViewAttributesTab";
13
+ import { DetailsViewHeader } from "./DetailsViewHeader";
14
+ import { DetailsViewInputOutputTab } from "./DetailsViewInputOutputTab";
15
+ import { DetailsViewRawDataTab } from "./DetailsViewRawDataTab";
16
+
17
+ type DetailsViewTab = "input-output" | "attributes" | "raw";
18
+
19
+ export interface DetailsViewProps {
20
+ /**
21
+ * The span data to display in the details view
22
+ */
23
+ data: TraceSpan;
24
+
25
+ /**
26
+ * Optional avatar configuration for the header
27
+ */
28
+ avatar?: AvatarProps;
29
+
30
+ /**
31
+ * The initially selected tab
32
+ */
33
+ defaultTab?: DetailsViewTab;
34
+
35
+ /**
36
+ * Optional className for the root container
37
+ */
38
+ className?: string;
39
+
40
+ /**
41
+ * Configuration for the copy button functionality
42
+ */
43
+ copyButton?: {
44
+ isEnabled?: boolean;
45
+ onCopy?: (data: TraceSpan) => void;
46
+ };
47
+
48
+ /**
49
+ * Custom header actions to render
50
+ * Can be a ReactNode or a render function that receives the data
51
+ */
52
+ headerActions?: ReactNode | ((data: TraceSpan) => ReactNode);
53
+
54
+ /**
55
+ * Optional custom header component to replace the default
56
+ */
57
+ customHeader?: ReactNode | ((props: { data: TraceSpan }) => ReactNode);
58
+
59
+ /**
60
+ * Callback fired when the active tab changes
61
+ */
62
+ onTabChange?: (tabValue: DetailsViewTab) => void;
63
+ }
64
+
65
+ const TAB_ITEMS: TabItem<DetailsViewTab>[] = [
66
+ {
67
+ value: "input-output",
68
+ label: "In/Out",
69
+ icon: <ArrowRightLeft className="size-4" />,
70
+ },
71
+ {
72
+ value: "attributes",
73
+ label: "Attributes",
74
+ icon: <Tags className="size-4" />,
75
+ },
76
+ {
77
+ value: "raw",
78
+ label: "RAW",
79
+ icon: <SquareTerminal className="size-4" />,
80
+ },
81
+ ];
82
+
83
+ export const DetailsView = ({
84
+ data,
85
+ avatar,
86
+ defaultTab = "input-output",
87
+ className,
88
+ copyButton,
89
+ headerActions,
90
+ customHeader,
91
+ onTabChange,
92
+ }: DetailsViewProps): ReactElement => {
93
+ const [tab, setTab] = useState<DetailsViewTab>(defaultTab);
94
+
95
+ const handleTabChange = (tabValue: DetailsViewTab) => {
96
+ setTab(tabValue);
97
+ onTabChange?.(tabValue);
98
+ };
99
+
100
+ const resolvedHeaderActions =
101
+ typeof headerActions === "function" ? headerActions(data) : headerActions;
102
+
103
+ const headerContent = customHeader ? (
104
+ typeof customHeader === "function" ? (
105
+ customHeader({ data })
106
+ ) : (
107
+ customHeader
108
+ )
109
+ ) : (
110
+ <DetailsViewHeader
111
+ data={data}
112
+ avatar={avatar}
113
+ copyButton={copyButton}
114
+ actions={resolvedHeaderActions}
115
+ />
116
+ );
117
+
118
+ return (
119
+ <div
120
+ className={cn(
121
+ "border-agentprism-border bg-agentprism-background flex h-full min-h-0 min-w-0 flex-col rounded-md border p-4",
122
+ className,
123
+ )}
124
+ >
125
+ <div className="mb-4 shrink-0">{headerContent}</div>
126
+ <div className="shrink-0">
127
+ <TabSelector
128
+ items={TAB_ITEMS}
129
+ value={tab}
130
+ onValueChange={handleTabChange}
131
+ theme="underline"
132
+ defaultValue={defaultTab}
133
+ />
134
+ </div>
135
+
136
+ <div
137
+ key={tab}
138
+ className="min-h-0 min-w-0 flex-1 overflow-x-auto overflow-y-auto py-4"
139
+ >
140
+ {tab === "input-output" && <DetailsViewInputOutputTab data={data} />}
141
+ {tab === "attributes" && <DetailsViewAttributesTab data={data} />}
142
+ {tab === "raw" && <DetailsViewRawDataTab data={data} />}
143
+ </div>
144
+ </div>
145
+ );
146
+ };
@@ -0,0 +1,125 @@
1
+ import { type TraceSpan } from "@evilmartians/agent-prism-types";
2
+ import { type ReactElement, useState } from "react";
3
+
4
+ import type { TabItem } from "../Tabs";
5
+
6
+ import { CollapsibleSection } from "../CollapsibleSection";
7
+ import { TabSelector } from "../TabSelector";
8
+ import {
9
+ DetailsViewContentViewer,
10
+ type DetailsViewContentViewMode,
11
+ } from "./DetailsViewContentViewer";
12
+
13
+ interface AttributesTabProps {
14
+ data: TraceSpan;
15
+ }
16
+
17
+ const TAB_ITEMS: TabItem<DetailsViewContentViewMode>[] = [
18
+ { value: "json", label: "JSON" },
19
+ { value: "plain", label: "Plain" },
20
+ ];
21
+
22
+ export const DetailsViewAttributesTab = ({
23
+ data,
24
+ }: AttributesTabProps): ReactElement => {
25
+ if (!data.attributes || data.attributes.length === 0) {
26
+ return (
27
+ <div className="p-6 text-center">
28
+ <p className="text-agentprism-muted-foreground">
29
+ No attributes available for this span.
30
+ </p>
31
+ </div>
32
+ );
33
+ }
34
+
35
+ return (
36
+ <div className="space-y-4">
37
+ {data.attributes.map((attribute, index) => {
38
+ const stringValue = attribute.value.stringValue;
39
+ const simpleValue =
40
+ stringValue ||
41
+ attribute.value.intValue?.toString() ||
42
+ attribute.value.boolValue?.toString() ||
43
+ "N/A";
44
+
45
+ let parsedJson: string | null = null;
46
+ if (typeof stringValue === "string") {
47
+ try {
48
+ parsedJson = JSON.parse(stringValue);
49
+ } catch {
50
+ parsedJson = null;
51
+ }
52
+ }
53
+
54
+ const isComplex = parsedJson !== null;
55
+
56
+ if (isComplex && parsedJson && stringValue) {
57
+ return (
58
+ <AttributeSection
59
+ key={`${attribute.key}-${index}`}
60
+ attributeKey={attribute.key}
61
+ content={stringValue}
62
+ parsedContent={parsedJson}
63
+ id={`${data.id}-${attribute.key}-${index}`}
64
+ />
65
+ );
66
+ }
67
+
68
+ return (
69
+ <div
70
+ key={`${attribute.key}-${index}`}
71
+ className="border-agentprism-border rounded-md border p-4"
72
+ >
73
+ <dt className="text-agentprism-muted-foreground mb-1 text-sm">
74
+ {attribute.key}
75
+ </dt>
76
+ <dd className="text-agentprism-foreground break-words text-sm">
77
+ {simpleValue}
78
+ </dd>
79
+ </div>
80
+ );
81
+ })}
82
+ </div>
83
+ );
84
+ };
85
+
86
+ interface AttributeSectionProps {
87
+ attributeKey: string;
88
+ content: string;
89
+ parsedContent: string;
90
+ id: string;
91
+ }
92
+
93
+ const AttributeSection = ({
94
+ attributeKey,
95
+ content,
96
+ parsedContent,
97
+ id,
98
+ }: AttributeSectionProps): ReactElement => {
99
+ const [tab, setTab] = useState<DetailsViewContentViewMode>("json");
100
+
101
+ return (
102
+ <CollapsibleSection
103
+ title={attributeKey}
104
+ defaultOpen
105
+ rightContent={
106
+ <TabSelector<DetailsViewContentViewMode>
107
+ items={TAB_ITEMS}
108
+ defaultValue="json"
109
+ value={tab}
110
+ onValueChange={setTab}
111
+ theme="pill"
112
+ onClick={(event) => event.stopPropagation()}
113
+ />
114
+ }
115
+ >
116
+ <DetailsViewContentViewer
117
+ content={content}
118
+ parsedContent={parsedContent}
119
+ mode={tab}
120
+ label={attributeKey}
121
+ id={id}
122
+ />
123
+ </CollapsibleSection>
124
+ );
125
+ };
@@ -0,0 +1,51 @@
1
+ import { type ReactElement } from "react";
2
+
3
+ import { CopyButton } from "../CopyButton";
4
+ import { DetailsViewJsonOutput } from "./DetailsViewJsonOutput";
5
+
6
+ export type DetailsViewContentViewMode = "json" | "plain";
7
+
8
+ export interface DetailsViewContentViewerProps {
9
+ content: string;
10
+ parsedContent: string | null;
11
+ mode: DetailsViewContentViewMode;
12
+ label: string;
13
+ id: string;
14
+ className?: string;
15
+ }
16
+
17
+ export const DetailsViewContentViewer = ({
18
+ content,
19
+ parsedContent,
20
+ mode,
21
+ label,
22
+ id,
23
+ className = "",
24
+ }: DetailsViewContentViewerProps): ReactElement => {
25
+ if (!content) {
26
+ return (
27
+ <p className="text-agentprism-muted-foreground p-3 text-sm italic">
28
+ No data available
29
+ </p>
30
+ );
31
+ }
32
+
33
+ return (
34
+ <div
35
+ className={`border-agentprism-border relative min-w-0 rounded-lg border ${className}`}
36
+ >
37
+ <div className="absolute right-1.5 top-1.5 z-10">
38
+ <CopyButton label={label} content={content} />
39
+ </div>
40
+ {mode === "json" && parsedContent ? (
41
+ <DetailsViewJsonOutput content={parsedContent} id={id} />
42
+ ) : (
43
+ <div className="bg-agentprism-background rounded-lg p-4">
44
+ <pre className="text-agentprism-foreground overflow-x-auto whitespace-pre-wrap text-left font-mono text-sm">
45
+ {content}
46
+ </pre>
47
+ </div>
48
+ )}
49
+ </div>
50
+ );
51
+ };