@octaviaflow/core 3.1.0-beta.113 → 3.1.0-beta.115

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 (256) hide show
  1. package/dist/chunk-75JAQ4ZG.js +2694 -0
  2. package/dist/chunk-75JAQ4ZG.js.map +1 -0
  3. package/dist/chunk-LQVPNIO5.js +6789 -0
  4. package/dist/chunk-LQVPNIO5.js.map +1 -0
  5. package/dist/chunk-P46JGJC2.js +5619 -0
  6. package/dist/chunk-P46JGJC2.js.map +1 -0
  7. package/dist/chunk-ZALBYWCU.js +1547 -0
  8. package/dist/chunk-ZALBYWCU.js.map +1 -0
  9. package/dist/components/Accordion/Accordion.d.ts.map +1 -1
  10. package/dist/components/AgentAppMark/AgentAppMark.d.ts +28 -0
  11. package/dist/components/AgentAppMark/AgentAppMark.d.ts.map +1 -0
  12. package/dist/components/AgentAppMark/index.d.ts +3 -0
  13. package/dist/components/AgentAppMark/index.d.ts.map +1 -0
  14. package/dist/components/AgentCard/AgentCard.d.ts.map +1 -1
  15. package/dist/components/AlertCard/AlertCard.d.ts.map +1 -1
  16. package/dist/components/AuthCard/AuthCard.d.ts.map +1 -1
  17. package/dist/components/AuthScreen/AuthScreen.d.ts.map +1 -1
  18. package/dist/components/Badge/Badge.d.ts.map +1 -1
  19. package/dist/components/Banner/Banner.d.ts.map +1 -1
  20. package/dist/components/BarChart/BarChart.d.ts.map +1 -1
  21. package/dist/components/Calendar/Calendar.d.ts.map +1 -1
  22. package/dist/components/CalendarHeatmap/CalendarHeatmap.d.ts.map +1 -1
  23. package/dist/components/CalendarHeatmap/index.d.ts +1 -1
  24. package/dist/components/CalendarHeatmap/index.d.ts.map +1 -1
  25. package/dist/components/Callout/Callout.d.ts.map +1 -1
  26. package/dist/components/Card/Card.d.ts.map +1 -1
  27. package/dist/components/Catalog/Catalog.d.ts.map +1 -1
  28. package/dist/components/Catalog/index.d.ts +1 -1
  29. package/dist/components/Catalog/index.d.ts.map +1 -1
  30. package/dist/components/ChatAttachment/ChatAttachment.d.ts.map +1 -1
  31. package/dist/components/ChatAttachment/index.d.ts +1 -1
  32. package/dist/components/ChatAttachment/index.d.ts.map +1 -1
  33. package/dist/components/ChatBubble/ChatBubble.d.ts.map +1 -1
  34. package/dist/components/ChatCodeBlock/ChatCodeBlock.d.ts.map +1 -1
  35. package/dist/components/ChatDayDivider/ChatDayDivider.d.ts.map +1 -1
  36. package/dist/components/ChatFeedback/ChatFeedback.d.ts.map +1 -1
  37. package/dist/components/ChatPanel/ChatPanel.d.ts +1 -1
  38. package/dist/components/ChatPanel/ChatPanel.d.ts.map +1 -1
  39. package/dist/components/ChatScrollToBottom/ChatScrollToBottom.d.ts.map +1 -1
  40. package/dist/components/ChatSources/ChatSources.d.ts.map +1 -1
  41. package/dist/components/ChatSources/index.d.ts +1 -1
  42. package/dist/components/ChatSources/index.d.ts.map +1 -1
  43. package/dist/components/ChatSuggestions/ChatSuggestions.d.ts.map +1 -1
  44. package/dist/components/ChatSuggestions/index.d.ts +1 -1
  45. package/dist/components/ChatSuggestions/index.d.ts.map +1 -1
  46. package/dist/components/ChatThinking/ChatThinking.d.ts.map +1 -1
  47. package/dist/components/ChatThinking/index.d.ts +1 -1
  48. package/dist/components/ChatThinking/index.d.ts.map +1 -1
  49. package/dist/components/ChatThread/ChatThread.d.ts.map +1 -1
  50. package/dist/components/ChatThread/index.d.ts +1 -1
  51. package/dist/components/ChatThread/index.d.ts.map +1 -1
  52. package/dist/components/ChatToolTrace/ChatToolTrace.d.ts.map +1 -1
  53. package/dist/components/ChatToolTrace/index.d.ts +1 -1
  54. package/dist/components/ChatToolTrace/index.d.ts.map +1 -1
  55. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
  56. package/dist/components/Chip/Chip.d.ts.map +1 -1
  57. package/dist/components/ChoiceCard/ChoiceCard.d.ts.map +1 -1
  58. package/dist/components/Coachmark/Coachmark.d.ts.map +1 -1
  59. package/dist/components/CodeEditor/CodeEditor.d.ts +15 -0
  60. package/dist/components/CodeEditor/CodeEditor.d.ts.map +1 -1
  61. package/dist/components/ColorPicker/ColorPicker.d.ts.map +1 -1
  62. package/dist/components/Combobox/Combobox.d.ts.map +1 -1
  63. package/dist/components/Combobox/index.d.ts +1 -1
  64. package/dist/components/Combobox/index.d.ts.map +1 -1
  65. package/dist/components/CommandPalette/CommandPalette.d.ts.map +1 -1
  66. package/dist/components/ConditionBuilder/ConditionBuilder.d.ts.map +1 -1
  67. package/dist/components/ConfigPanel/ConfigPanel.d.ts.map +1 -1
  68. package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -1
  69. package/dist/components/ConnectorCard/ConnectorCard.d.ts.map +1 -1
  70. package/dist/components/ConnectorCard/index.d.ts +1 -1
  71. package/dist/components/ConnectorCard/index.d.ts.map +1 -1
  72. package/dist/components/ContextMenu/ContextMenu.d.ts.map +1 -1
  73. package/dist/components/CountUp/CountUp.d.ts.map +1 -1
  74. package/dist/components/CsvViewer/CsvViewer.d.ts.map +1 -1
  75. package/dist/components/DataMapper/DataMapper.d.ts.map +1 -1
  76. package/dist/components/DataTable/DataTable.d.ts.map +1 -1
  77. package/dist/components/DatasetCard/DatasetCard.d.ts.map +1 -1
  78. package/dist/components/DatePicker/DatePicker.d.ts.map +1 -1
  79. package/dist/components/DescriptionList/DescriptionList.d.ts.map +1 -1
  80. package/dist/components/Dialog/Dialog.d.ts +7 -0
  81. package/dist/components/Dialog/Dialog.d.ts.map +1 -1
  82. package/dist/components/Divider/Divider.d.ts.map +1 -1
  83. package/dist/components/DonutChart/DonutChart.d.ts.map +1 -1
  84. package/dist/components/Drawer/Drawer.d.ts.map +1 -1
  85. package/dist/components/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  86. package/dist/components/EmptyState/EmptyState.d.ts.map +1 -1
  87. package/dist/components/ExecutionConsole/ExecutionConsole.d.ts.map +1 -1
  88. package/dist/components/ExpressionField/ExpressionField.d.ts +21 -3
  89. package/dist/components/ExpressionField/ExpressionField.d.ts.map +1 -1
  90. package/dist/components/ExpressionField/dataRefTokens.d.ts +52 -0
  91. package/dist/components/ExpressionField/dataRefTokens.d.ts.map +1 -0
  92. package/dist/components/ExpressionField/index.d.ts +3 -2
  93. package/dist/components/ExpressionField/index.d.ts.map +1 -1
  94. package/dist/components/FeatureCard/FeatureCard.d.ts.map +1 -1
  95. package/dist/components/FieldPicker/FieldPicker.d.ts.map +1 -1
  96. package/dist/components/FieldPicker/index.d.ts +1 -1
  97. package/dist/components/FieldPicker/index.d.ts.map +1 -1
  98. package/dist/components/FileBrowser/FileBrowser.d.ts.map +1 -1
  99. package/dist/components/FileDropzone/FileDropzone.d.ts.map +1 -1
  100. package/dist/components/FileList/FileList.d.ts.map +1 -1
  101. package/dist/components/FilePicker/FilePicker.d.ts +6 -1
  102. package/dist/components/FilePicker/FilePicker.d.ts.map +1 -1
  103. package/dist/components/FileViewer/FileViewer.d.ts.map +1 -1
  104. package/dist/components/FlowMinimap/FlowMinimap.d.ts.map +1 -1
  105. package/dist/components/Gauge/Gauge.d.ts.map +1 -1
  106. package/dist/components/Grid/Grid.d.ts.map +1 -1
  107. package/dist/components/GroupByBuilder/index.d.ts +1 -1
  108. package/dist/components/GroupByBuilder/index.d.ts.map +1 -1
  109. package/dist/components/Heatmap/Heatmap.d.ts.map +1 -1
  110. package/dist/components/IconCard/IconCard.d.ts.map +1 -1
  111. package/dist/components/InlineMessage/InlineMessage.d.ts.map +1 -1
  112. package/dist/components/IntegrationCard/IntegrationCard.d.ts.map +1 -1
  113. package/dist/components/ItemDetail/ItemDetail.d.ts.map +1 -1
  114. package/dist/components/ItemDetail/index.d.ts +1 -1
  115. package/dist/components/ItemDetail/index.d.ts.map +1 -1
  116. package/dist/components/JsonViewer/JsonViewer.d.ts.map +1 -1
  117. package/dist/components/Kbd/Kbd.d.ts.map +1 -1
  118. package/dist/components/KeyValueEditor/KeyValueEditor.d.ts +22 -1
  119. package/dist/components/KeyValueEditor/KeyValueEditor.d.ts.map +1 -1
  120. package/dist/components/KeyValueEditor/index.d.ts +1 -1
  121. package/dist/components/KeyValueEditor/index.d.ts.map +1 -1
  122. package/dist/components/LinkButton/LinkButton.d.ts.map +1 -1
  123. package/dist/components/MetricCard/MetricCard.d.ts.map +1 -1
  124. package/dist/components/MonacoEditor/MonacoDiffEditor.d.ts.map +1 -1
  125. package/dist/components/MonacoEditor/MonacoEditor.d.ts.map +1 -1
  126. package/dist/components/MultiSelect/MultiSelect.d.ts.map +1 -1
  127. package/dist/components/NumberInput/NumberInput.d.ts.map +1 -1
  128. package/dist/components/OnboardingFlow/OnboardingFlow.d.ts.map +1 -1
  129. package/dist/components/OrgSwitcher/OrgSwitcher.d.ts.map +1 -1
  130. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  131. package/dist/components/Pagination/Pagination.d.ts.map +1 -1
  132. package/dist/components/PasswordInput/PasswordInput.d.ts.map +1 -1
  133. package/dist/components/PhoneInput/PhoneInput.d.ts.map +1 -1
  134. package/dist/components/PhoneInput/countries.d.ts.map +1 -1
  135. package/dist/components/PhoneInput/index.d.ts +1 -1
  136. package/dist/components/PhoneInput/index.d.ts.map +1 -1
  137. package/dist/components/PipelineCard/PipelineCard.d.ts +1 -1
  138. package/dist/components/PipelineCard/PipelineCard.d.ts.map +1 -1
  139. package/dist/components/PlanCard/PlanCard.d.ts.map +1 -1
  140. package/dist/components/PlanPicker/PlanPicker.d.ts.map +1 -1
  141. package/dist/components/PlanPicker/context.d.ts.map +1 -1
  142. package/dist/components/PresenceBar/PresenceBar.d.ts +11 -0
  143. package/dist/components/PresenceBar/PresenceBar.d.ts.map +1 -1
  144. package/dist/components/PricingCard/PricingCard.d.ts.map +1 -1
  145. package/dist/components/ProductCard/ProductCard.d.ts.map +1 -1
  146. package/dist/components/ProgressBar/ProgressBar.d.ts.map +1 -1
  147. package/dist/components/ProgressRing/ProgressRing.d.ts.map +1 -1
  148. package/dist/components/PromptInput/PromptInput.d.ts.map +1 -1
  149. package/dist/components/Quote/Quote.d.ts.map +1 -1
  150. package/dist/components/Radio/Radio.d.ts.map +1 -1
  151. package/dist/components/Radio/RadioGroup.d.ts.map +1 -1
  152. package/dist/components/RangeSlider/RangeSlider.d.ts.map +1 -1
  153. package/dist/components/Rating/BinaryRating.d.ts.map +1 -1
  154. package/dist/components/Rating/Rating.d.ts.map +1 -1
  155. package/dist/components/Rating/index.d.ts +1 -1
  156. package/dist/components/Rating/index.d.ts.map +1 -1
  157. package/dist/components/Resizable/Resizable.d.ts.map +1 -1
  158. package/dist/components/Ribbon/Ribbon.d.ts.map +1 -1
  159. package/dist/components/Sankey/Sankey.d.ts.map +1 -1
  160. package/dist/components/ScheduleBuilder/ScheduleBuilder.d.ts.map +1 -1
  161. package/dist/components/ScheduleBuilder/index.d.ts +1 -1
  162. package/dist/components/ScheduleBuilder/index.d.ts.map +1 -1
  163. package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  164. package/dist/components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  165. package/dist/components/Select/Select.d.ts.map +1 -1
  166. package/dist/components/Sheet/Sheet.d.ts +9 -1
  167. package/dist/components/Sheet/Sheet.d.ts.map +1 -1
  168. package/dist/components/Show/Show.d.ts.map +1 -1
  169. package/dist/components/Skeleton/Skeleton.d.ts.map +1 -1
  170. package/dist/components/SlideoutPanel/SlideoutPanel.d.ts.map +1 -1
  171. package/dist/components/Slider/Slider.d.ts.map +1 -1
  172. package/dist/components/SocialButton/SocialButton.d.ts.map +1 -1
  173. package/dist/components/SortBuilder/index.d.ts +1 -1
  174. package/dist/components/SortBuilder/index.d.ts.map +1 -1
  175. package/dist/components/Sortable/Sortable.d.ts.map +1 -1
  176. package/dist/components/Sparkline/Sparkline.d.ts.map +1 -1
  177. package/dist/components/Spinner/Spinner.d.ts.map +1 -1
  178. package/dist/components/Spotlight/Spotlight.d.ts.map +1 -1
  179. package/dist/components/Stack/Stack.d.ts.map +1 -1
  180. package/dist/components/Stat/Stat.d.ts.map +1 -1
  181. package/dist/components/StatusList/StatusList.d.ts +1 -1
  182. package/dist/components/StatusList/StatusList.d.ts.map +1 -1
  183. package/dist/components/StatusList/index.d.ts +1 -1
  184. package/dist/components/StatusList/index.d.ts.map +1 -1
  185. package/dist/components/StatusTiles/StatusTiles.d.ts.map +1 -1
  186. package/dist/components/StatusTiles/index.d.ts +1 -1
  187. package/dist/components/StatusTiles/index.d.ts.map +1 -1
  188. package/dist/components/Stepper/Stepper.d.ts.map +1 -1
  189. package/dist/components/Switch/Switch.d.ts.map +1 -1
  190. package/dist/components/Table/Table.d.ts.map +1 -1
  191. package/dist/components/TagsInput/TagsInput.d.ts.map +1 -1
  192. package/dist/components/TemplateDetail/TemplateDetail.d.ts +92 -0
  193. package/dist/components/TemplateDetail/TemplateDetail.d.ts.map +1 -0
  194. package/dist/components/TemplateDetail/index.d.ts +2 -0
  195. package/dist/components/TemplateDetail/index.d.ts.map +1 -0
  196. package/dist/components/Textarea/Textarea.d.ts.map +1 -1
  197. package/dist/components/TimePicker/TimePicker.d.ts.map +1 -1
  198. package/dist/components/Timeline/Timeline.d.ts.map +1 -1
  199. package/dist/components/TimezonePicker/TimezonePicker.d.ts.map +1 -1
  200. package/dist/components/Toggle/Toggle.d.ts.map +1 -1
  201. package/dist/components/ToolCard/ToolCard.d.ts.map +1 -1
  202. package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
  203. package/dist/components/TraceStep/TraceStep.d.ts.map +1 -1
  204. package/dist/components/TreeView/TreeView.d.ts.map +1 -1
  205. package/dist/components/TreeView/index.d.ts +1 -1
  206. package/dist/components/TreeView/index.d.ts.map +1 -1
  207. package/dist/components/UsageMeter/UsageMeter.d.ts.map +1 -1
  208. package/dist/components/VersionHistory/VersionHistory.d.ts.map +1 -1
  209. package/dist/components/VersionHistory/index.d.ts +1 -1
  210. package/dist/components/VersionHistory/index.d.ts.map +1 -1
  211. package/dist/components/XmlViewer/XmlViewer.d.ts.map +1 -1
  212. package/dist/components/YamlViewer/YamlViewer.d.ts.map +1 -1
  213. package/dist/email/emailPalette.d.ts.map +1 -1
  214. package/dist/email/emailSystem.d.ts +3 -3
  215. package/dist/email/emailSystem.d.ts.map +1 -1
  216. package/dist/email/emailTemplates.d.ts +1 -1
  217. package/dist/email/emailTemplates.d.ts.map +1 -1
  218. package/dist/hooks/useRating.d.ts.map +1 -1
  219. package/dist/hooks/useTextareaCommands.d.ts.map +1 -1
  220. package/dist/hooks/useTextareaSelection.d.ts.map +1 -1
  221. package/dist/hooks/useTextareaTools.d.ts.map +1 -1
  222. package/dist/hooks/useTraceTimeline.d.ts.map +1 -1
  223. package/dist/index.cjs +17176 -18061
  224. package/dist/index.cjs.map +1 -1
  225. package/dist/index.d.ts +6 -4
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +13603 -15501
  228. package/dist/index.js.map +1 -1
  229. package/dist/marketing.cjs +580 -726
  230. package/dist/marketing.cjs.map +1 -1
  231. package/dist/marketing.js +2 -2
  232. package/dist/monaco.cjs +364 -389
  233. package/dist/monaco.cjs.map +1 -1
  234. package/dist/monaco.js +381 -410
  235. package/dist/monaco.js.map +1 -1
  236. package/dist/notifications/components/NotificationBell/NotificationBell.d.ts.map +1 -1
  237. package/dist/styles.css +1 -1
  238. package/dist/utils/Slot.d.ts.map +1 -1
  239. package/dist/utils/gradients.d.ts.map +1 -1
  240. package/dist/utils/useAnchoredPopover.d.ts +8 -1
  241. package/dist/utils/useAnchoredPopover.d.ts.map +1 -1
  242. package/dist/workflow/components/ConfigPanel/ConfigPanel.d.ts.map +1 -1
  243. package/dist/workflow/components/FlowNode/FlowNode.d.ts.map +1 -1
  244. package/dist/workflow/components/FxPanel/DataRefButton.d.ts +1 -1
  245. package/dist/workflow/components/FxPanel/DataRefButton.d.ts.map +1 -1
  246. package/dist/workflow/components/FxPanel/FxPanel.d.ts.map +1 -1
  247. package/dist/workflow/editor.d.ts +22 -0
  248. package/dist/workflow/editor.d.ts.map +1 -1
  249. package/dist/workflow/index.d.ts +6 -6
  250. package/dist/workflow/index.d.ts.map +1 -1
  251. package/dist/workflow/types.d.ts.map +1 -1
  252. package/dist/workflow.cjs +10310 -9355
  253. package/dist/workflow.cjs.map +1 -1
  254. package/dist/workflow.js +1932 -1963
  255. package/dist/workflow.js.map +1 -1
  256. package/package.json +2 -2
package/dist/workflow.js CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  useSelection,
63
63
  useViewport,
64
64
  useViewportOrNull
65
- } from "./chunk-NFNETL4U.js";
65
+ } from "./chunk-LQVPNIO5.js";
66
66
  import {
67
67
  Accordion,
68
68
  Button,
@@ -70,7 +70,7 @@ import {
70
70
  Select,
71
71
  Spinner,
72
72
  Tooltip
73
- } from "./chunk-2VYRM5DQ.js";
73
+ } from "./chunk-75JAQ4ZG.js";
74
74
  import {
75
75
  cn
76
76
  } from "./chunk-ZAUUGK2Y.js";
@@ -583,15 +583,7 @@ function ConfigPanel({
583
583
  t.id
584
584
  )) }),
585
585
  banner && /* @__PURE__ */ jsx("div", { className: "ods-flow-config-panel__banner", children: banner }),
586
- /* @__PURE__ */ jsx(
587
- "div",
588
- {
589
- className: "ods-flow-config-panel__body",
590
- role: "tabpanel",
591
- style: { padding },
592
- children: tabs ? activeTab?.content : children
593
- }
594
- ),
586
+ /* @__PURE__ */ jsx("div", { className: "ods-flow-config-panel__body", role: "tabpanel", style: { padding }, children: tabs ? activeTab?.content : children }),
595
587
  /* @__PURE__ */ jsxs("footer", { className: "ods-flow-config-panel__footer", children: [
596
588
  saveState && /* @__PURE__ */ jsxs(
597
589
  "span",
@@ -640,2031 +632,2008 @@ function ConfigPanel({
640
632
  );
641
633
  }
642
634
 
643
- // src/workflow/components/HttpActionPanel/HttpActionPanel.tsx
635
+ // src/workflow/components/FxPanel/DataRefButton.tsx
636
+ import { DataReferenceIcon } from "@octaviaflow/icons";
637
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
638
+ function DataRefButton({
639
+ active = false,
640
+ label = "Data ref",
641
+ className,
642
+ type = "button",
643
+ ...rest
644
+ }) {
645
+ return /* @__PURE__ */ jsxs2(
646
+ "button",
647
+ {
648
+ ...rest,
649
+ type,
650
+ className: cn(
651
+ "ods-flow-dataref-button",
652
+ active && "ods-flow-dataref-button--active",
653
+ label == null && "ods-flow-dataref-button--icon-only",
654
+ className
655
+ ),
656
+ "aria-pressed": active,
657
+ title: active ? "Hide data references" : "Show data references",
658
+ children: [
659
+ /* @__PURE__ */ jsx2(DataReferenceIcon, { size: "sm", className: "ods-flow-dataref-button__icon" }),
660
+ label != null && /* @__PURE__ */ jsx2("span", { className: "ods-flow-dataref-button__label", children: label })
661
+ ]
662
+ }
663
+ );
664
+ }
665
+
666
+ // src/workflow/components/FxPanel/FxPanel.tsx
644
667
  import {
645
- CheckmarkIcon,
646
- CodeIcon,
647
- CopyIcon,
648
- DataViewAltIcon,
649
- GlobeIcon,
650
- ListIcon,
651
- LockedIcon,
652
- PlayIcon,
653
- RenewIcon,
654
- TagIcon
668
+ ChevronDownIcon,
669
+ ChevronRightIcon,
670
+ CloseIcon,
671
+ CollapseAllIcon,
672
+ DraggableIcon,
673
+ ExpandAllIcon,
674
+ FunctionMathIcon,
675
+ SearchIcon,
676
+ ValueVariableIcon
655
677
  } from "@octaviaflow/icons";
656
- import { useCallback as useCallback4, useId, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
657
-
658
- // src/workflow/components/HttpActionPanel/httpActionTypes.ts
659
- var HTTP_METHODS = [
660
- "GET",
661
- "POST",
662
- "PUT",
663
- "PATCH",
664
- "DELETE",
665
- "HEAD",
666
- "OPTIONS"
667
- ];
668
- var HTTP_ACTION_DEFAULTS = {
669
- apiType: "rest",
670
- method: "GET",
671
- url: "",
672
- queryParams: [],
673
- headers: [],
674
- bodyType: "json",
675
- formData: [],
676
- graphqlQuery: "",
677
- graphqlVariables: {},
678
- auth: { type: "none" },
679
- timeout: 3e4,
680
- failOnError: true,
681
- followRedirects: true,
682
- retry: {
683
- maxAttempts: 1,
684
- retryOn: [429, 500, 502, 503, 504],
685
- initialDelayMs: 1e3,
686
- backoffFactor: 2,
687
- maxDelayMs: 3e4
688
- },
689
- pagination: { type: "none" },
690
- extract: {},
691
- responseType: "json",
692
- expectedContentTypes: []
678
+ import {
679
+ useMemo as useMemo2,
680
+ useRef as useRef4,
681
+ useState as useState4
682
+ } from "react";
683
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
684
+ var KIND_ICON = {
685
+ function: FunctionMathIcon,
686
+ variable: ValueVariableIcon
693
687
  };
694
- function normalizeKeyValues(raw) {
695
- if (Array.isArray(raw)) {
696
- return raw.map((r) => ({
697
- key: String(r.key ?? ""),
698
- value: String(r.value ?? ""),
699
- description: r.description,
700
- enabled: r.enabled !== false
701
- }));
702
- }
703
- if (raw && typeof raw === "object") {
704
- return Object.entries(raw).map(([key, value]) => ({
705
- key,
706
- value: String(value),
707
- enabled: true
708
- }));
688
+ function countLeaves(items) {
689
+ let n = 0;
690
+ for (const it of items) {
691
+ if (it.children && it.children.length > 0) n += countLeaves(it.children);
692
+ else n += 1;
709
693
  }
710
- return [];
711
- }
712
- function normalizeAuth(raw) {
713
- const r = raw ?? {};
714
- const rawType = r.type ?? "none";
715
- const type = rawType === "oauth2" ? "bearer" : rawType;
716
- return {
717
- type: ["none", "basic", "bearer", "apikey"].includes(type) ? type : "none",
718
- username: r.username ?? "",
719
- password: r.password ?? "",
720
- token: r.token ?? r.accessToken ?? "",
721
- apiKeyName: r.apiKeyName ?? "X-API-Key",
722
- apiKeyValue: r.apiKeyValue ?? "",
723
- apiKeyLocation: r.apiKeyLocation === "query" ? "query" : "header"
724
- };
725
- }
726
- function effectiveUrl(baseUrl, url) {
727
- const base = (baseUrl ?? "").trim();
728
- if (!base) return url;
729
- return `${base.replace(/\/$/, "")}/${url.replace(/^\//, "")}`;
730
- }
731
- function serializeKeyValues(rows) {
732
- return rows.filter((r) => r.key || r.value).map((r) => ({
733
- key: r.key,
734
- value: r.value,
735
- enabled: r.enabled !== false,
736
- ...r.description ? { description: r.description } : {}
737
- }));
694
+ return n;
738
695
  }
739
- function validateHttpConfig(config) {
740
- const errors = [];
741
- const apiType = config.apiType ?? "rest";
742
- if (apiType === "rest" && !config.method) errors.push("Method is required");
743
- if (apiType === "graphql" && !String(config.graphqlQuery ?? "").trim()) {
744
- errors.push("GraphQL query is required");
745
- }
746
- const hasConnection = Boolean(config.connectionId);
747
- if (!config.url && !hasConnection) {
748
- errors.push("URL is required");
749
- } else if (config.url && !hasConnection && !config.baseUrl && !config.url.includes("{{")) {
750
- try {
751
- new URL(config.url);
752
- } catch {
753
- errors.push("Invalid URL format");
696
+ function collectBranchIds(items, out) {
697
+ for (const it of items) {
698
+ if (it.children && it.children.length > 0) {
699
+ out.add(it.id);
700
+ collectBranchIds(it.children, out);
754
701
  }
755
702
  }
756
- if (typeof config.timeout === "number" && config.timeout < 1e3) {
757
- errors.push("Timeout must be at least 1000ms");
758
- }
759
- return { valid: errors.length === 0, errors };
760
- }
761
- function countGraphqlVariables(query) {
762
- return new Set(query.match(/\$[A-Za-z_][A-Za-z0-9_]*/g) ?? []).size;
763
703
  }
764
- function shellTokens(input) {
765
- const tokens = [];
766
- const flat = input.replace(/\\\r?\n/g, " ");
767
- const re = /'([^']*)'|"((?:[^"\\]|\\.)*)"|(\S+)/g;
768
- let m = re.exec(flat);
769
- while (m !== null) {
770
- if (m[1] !== void 0) tokens.push(m[1]);
771
- else if (m[2] !== void 0) tokens.push(m[2].replace(/\\(["\\])/g, "$1"));
772
- else tokens.push(m[3]);
773
- m = re.exec(flat);
704
+ function flattenTree(items, depth, expandedItems, out) {
705
+ for (const it of items) {
706
+ const hasChildren = !!it.children && it.children.length > 0;
707
+ const expanded = hasChildren && expandedItems.has(it.id);
708
+ out.push({ item: it, depth, hasChildren, expanded });
709
+ if (expanded) flattenTree(it.children, depth + 1, expandedItems, out);
774
710
  }
775
- return tokens;
776
711
  }
777
- function parseCurl(input) {
778
- const tokens = shellTokens(input.trim());
779
- if (!tokens.length || !/^curl$/i.test(tokens[0])) return null;
780
- let method;
781
- let url = "";
782
- const headers = [];
783
- const formData = [];
784
- let bodyRaw;
785
- let basicUser;
786
- let followRedirects;
787
- for (let i = 1; i < tokens.length; i++) {
788
- const t = tokens[i];
789
- const next = () => tokens[++i] ?? "";
790
- if (t === "-X" || t === "--request") {
791
- const m = next().toUpperCase();
792
- if (HTTP_METHODS.includes(m)) method = m;
793
- } else if (t === "-H" || t === "--header") {
794
- const raw = next();
795
- const idx = raw.indexOf(":");
796
- if (idx > 0) {
797
- headers.push({
798
- key: raw.slice(0, idx).trim(),
799
- value: raw.slice(idx + 1).trim(),
800
- enabled: true
801
- });
802
- }
803
- } else if (t === "-d" || t === "--data" || t === "--data-raw" || t === "--data-binary" || t === "--data-urlencode") {
804
- bodyRaw = next();
805
- } else if (t === "-F" || t === "--form") {
806
- const raw = next();
807
- const idx = raw.indexOf("=");
808
- if (idx > 0) {
809
- formData.push({ key: raw.slice(0, idx), value: raw.slice(idx + 1), enabled: true });
810
- }
811
- } else if (t === "-u" || t === "--user") {
812
- basicUser = next();
813
- } else if (t === "--url") {
814
- url = next();
815
- } else if (t === "-L" || t === "--location") {
816
- followRedirects = true;
817
- } else if (t.startsWith("-")) {
818
- if ([
819
- "-o",
820
- "--output",
821
- "-A",
822
- "--user-agent",
823
- "-e",
824
- "--referer",
825
- "-b",
826
- "--cookie",
827
- "--connect-timeout",
828
- "-m",
829
- "--max-time"
830
- ].includes(t)) {
831
- next();
832
- }
833
- } else if (!url) {
834
- url = t;
712
+ function collectHits(items, ancestors, q, out) {
713
+ for (const it of items) {
714
+ if (it.children && it.children.length > 0) {
715
+ collectHits(it.children, [...ancestors, it.label], q, out);
716
+ } else {
717
+ const path = [...ancestors, it.label].join(" / ");
718
+ const hay = `${it.label} ${it.description ?? ""} ${path} ${it.insertValue ?? ""}`.toLowerCase();
719
+ if (hay.includes(q)) out.push({ item: it, ancestors });
835
720
  }
836
721
  }
837
- if (!url) return null;
838
- const patch = { url, apiType: "rest" };
839
- const qIdx = url.indexOf("?");
840
- if (qIdx >= 0) {
841
- const query = url.slice(qIdx + 1);
842
- patch.url = url.slice(0, qIdx);
843
- patch.queryParams = query.split("&").filter(Boolean).map((pair) => {
844
- const eq = pair.indexOf("=");
845
- const rawKey = eq >= 0 ? pair.slice(0, eq) : pair;
846
- const rawValue = eq >= 0 ? pair.slice(eq + 1) : "";
847
- const decode = (s) => {
848
- try {
849
- return decodeURIComponent(s.replace(/\+/g, "%20"));
850
- } catch {
851
- return s;
852
- }
853
- };
854
- return { key: decode(rawKey), value: decode(rawValue), enabled: true };
855
- });
856
- }
857
- const authHeaderIdx = headers.findIndex((h) => h.key.toLowerCase() === "authorization");
858
- if (authHeaderIdx >= 0 && /^bearer\s+/i.test(headers[authHeaderIdx].value)) {
859
- patch.auth = {
860
- type: "bearer",
861
- token: headers[authHeaderIdx].value.replace(/^bearer\s+/i, "")
862
- };
863
- headers.splice(authHeaderIdx, 1);
864
- } else if (basicUser) {
865
- const sep = basicUser.indexOf(":");
866
- patch.auth = {
867
- type: "basic",
868
- username: sep >= 0 ? basicUser.slice(0, sep) : basicUser,
869
- password: sep >= 0 ? basicUser.slice(sep + 1) : ""
870
- };
871
- }
872
- if (headers.length) patch.headers = headers;
873
- if (followRedirects !== void 0) patch.followRedirects = followRedirects;
874
- if (formData.length) {
875
- patch.bodyType = "form-data";
876
- patch.formData = formData;
877
- patch.method = method ?? "POST";
878
- } else if (bodyRaw !== void 0) {
879
- try {
880
- patch.body = JSON.parse(bodyRaw);
881
- patch.bodyType = "json";
882
- } catch {
883
- patch.body = bodyRaw;
884
- patch.bodyType = "raw";
885
- }
886
- patch.method = method ?? "POST";
887
- } else {
888
- patch.method = method ?? "GET";
889
- patch.bodyType = "none";
890
- }
891
- return patch;
892
722
  }
893
- function shellQuote(value) {
894
- return `'${value.replace(/'/g, `'\\''`)}'`;
895
- }
896
- function buildCurl(config) {
897
- const parts = ["curl"];
898
- const method = config.apiType === "graphql" ? "POST" : config.method ?? "GET";
899
- if (method !== "GET") parts.push(`-X ${method}`);
900
- let url = effectiveUrl(config.baseUrl, config.url);
901
- const params = normalizeKeyValues(config.queryParams).filter((p) => p.enabled !== false && p.key);
902
- if (params.length) {
903
- const qs = params.map((p) => `${encodeURIComponent(p.key)}=${encodeURIComponent(p.value)}`).join("&");
904
- url += (url.includes("?") ? "&" : "?") + qs;
905
- }
906
- parts.push(shellQuote(url));
907
- for (const h of normalizeKeyValues(config.headers).filter((h2) => h2.enabled !== false && h2.key)) {
908
- parts.push(`-H ${shellQuote(`${h.key}: ${h.value}`)}`);
909
- }
910
- const auth = config.auth;
911
- if (auth?.type === "bearer" && auth.token) {
912
- parts.push(`-H ${shellQuote(`Authorization: Bearer ${auth.token}`)}`);
913
- } else if (auth?.type === "basic" && auth.username) {
914
- parts.push(`-u ${shellQuote(`${auth.username}:${auth.password ?? ""}`)}`);
915
- } else if (auth?.type === "apikey" && auth.apiKeyName && auth.apiKeyValue) {
916
- if ((auth.apiKeyLocation ?? "header") === "header") {
917
- parts.push(`-H ${shellQuote(`${auth.apiKeyName}: ${auth.apiKeyValue}`)}`);
723
+ function FxPanel({
724
+ categories,
725
+ title = "Data References",
726
+ hint = "Drag onto a field, or click an item to insert it.",
727
+ search: controlledSearch,
728
+ defaultSearch = "",
729
+ onSearchChange,
730
+ expandedCategory: controlledExpanded,
731
+ defaultExpandedCategory,
732
+ onExpandedCategoryChange,
733
+ onClose,
734
+ onItemDragStart,
735
+ onItemSelect,
736
+ width = 300,
737
+ emptyLabel = "No matches",
738
+ searchPlaceholder = "Search fields, functions & variables\u2026",
739
+ className,
740
+ style
741
+ }) {
742
+ const [internalSearch, setInternalSearch] = useState4(defaultSearch);
743
+ const search = controlledSearch ?? internalSearch;
744
+ const query = search.trim().toLowerCase();
745
+ const setSearch = (next) => {
746
+ if (controlledSearch === void 0) setInternalSearch(next);
747
+ onSearchChange?.(next);
748
+ };
749
+ const visibleCategories = useMemo2(
750
+ () => categories.filter((c) => c.items.length > 0),
751
+ [categories]
752
+ );
753
+ const [internalCat, setInternalCat] = useState4(
754
+ defaultExpandedCategory !== void 0 ? defaultExpandedCategory : visibleCategories[0]?.id ?? null
755
+ );
756
+ const openCategoryId = controlledExpanded ?? internalCat;
757
+ const setOpenCategory = (id) => {
758
+ if (controlledExpanded === void 0) setInternalCat(id);
759
+ onExpandedCategoryChange?.(id);
760
+ };
761
+ const [expandedItems, setExpandedItems] = useState4(() => {
762
+ const ids = /* @__PURE__ */ new Set();
763
+ for (const cat of categories) {
764
+ for (const item of cat.items) {
765
+ if (item.children && item.children.length > 0) ids.add(item.id);
766
+ }
918
767
  }
919
- }
920
- if (config.apiType === "graphql") {
921
- const payload = JSON.stringify({
922
- query: config.graphqlQuery ?? "",
923
- variables: typeof config.graphqlVariables === "string" ? config.graphqlVariables : config.graphqlVariables ?? {}
768
+ return ids;
769
+ });
770
+ const toggleItem = (id) => {
771
+ setExpandedItems((prev) => {
772
+ const next = new Set(prev);
773
+ if (next.has(id)) next.delete(id);
774
+ else next.add(id);
775
+ return next;
924
776
  });
925
- parts.push(`-H ${shellQuote("Content-Type: application/json")}`);
926
- parts.push(`-d ${shellQuote(payload)}`);
927
- } else if (config.bodyType === "form-data") {
928
- for (const f of normalizeKeyValues(config.formData).filter(
929
- (f2) => f2.enabled !== false && f2.key
930
- )) {
931
- parts.push(`-F ${shellQuote(`${f.key}=${f.value}`)}`);
932
- }
933
- } else if (config.bodyType && config.bodyType !== "none" && config.body != null) {
934
- const raw = typeof config.body === "string" ? config.body : JSON.stringify(config.body);
935
- if (raw) {
936
- if (config.bodyType === "json") {
937
- parts.push(`-H ${shellQuote("Content-Type: application/json")}`);
777
+ };
778
+ const setCategoryExpansion = (cat, expand) => {
779
+ const branchIds = /* @__PURE__ */ new Set();
780
+ collectBranchIds(cat.items, branchIds);
781
+ setExpandedItems((prev) => {
782
+ const next = new Set(prev);
783
+ for (const id of branchIds) {
784
+ if (expand) next.add(id);
785
+ else next.delete(id);
938
786
  }
939
- parts.push(`-d ${shellQuote(raw)}`);
787
+ return next;
788
+ });
789
+ };
790
+ const [draggingId, setDraggingId] = useState4(null);
791
+ const [focusedId, setFocusedId] = useState4(null);
792
+ const hits = useMemo2(() => {
793
+ if (!query) return null;
794
+ const all = [];
795
+ for (const cat of visibleCategories) {
796
+ const out = [];
797
+ collectHits(cat.items, [], query, out);
798
+ for (const hit of out) all.push({ category: cat, hit });
940
799
  }
941
- }
942
- if (config.followRedirects !== false) parts.push("-L");
943
- return parts.join(" ");
944
- }
945
- function inferJsonSchema(value, depth = 0) {
946
- if (value === null || value === void 0) return { type: "null" };
947
- if (Array.isArray(value)) {
948
- if (!value.length || depth > 6) return { type: "array" };
949
- return { type: "array", items: inferJsonSchema(value[0], depth + 1) };
950
- }
951
- if (typeof value === "object") {
952
- if (depth > 6) return { type: "object" };
953
- const properties = {};
954
- for (const [k, v] of Object.entries(value)) {
955
- properties[k] = inferJsonSchema(v, depth + 1);
800
+ return all;
801
+ }, [query, visibleCategories]);
802
+ const handleDragStart = (item, category) => (e) => {
803
+ setDraggingId(item.id);
804
+ if (onItemDragStart) {
805
+ onItemDragStart(item, category, e);
806
+ } else if (item.insertValue) {
807
+ e.dataTransfer.setData("text/plain", item.insertValue);
808
+ e.dataTransfer.setData("application/x-fx-insert", item.insertValue);
809
+ e.dataTransfer.setData("application/x-fx-type", category.kind ?? "function");
810
+ e.dataTransfer.effectAllowed = "copy";
956
811
  }
957
- return { type: "object", properties };
958
- }
959
- return { type: typeof value };
960
- }
961
- function countSchemaPaths(schema, depth = 0) {
962
- if (!schema || typeof schema !== "object" || depth > 8) return 0;
963
- const s = schema;
964
- let count = 0;
965
- if (s.type === "object" && s.properties) {
966
- for (const child of Object.values(s.properties)) {
967
- count += 1 + countSchemaPaths(child, depth + 1);
968
- }
969
- } else if (s.type === "array" && s.items) {
970
- count += countSchemaPaths(s.items, depth + 1);
971
- }
972
- return count;
973
- }
974
- function statusTone(status) {
975
- if (status < 300) return "success";
976
- if (status < 400) return "info";
977
- if (status < 500) return "warning";
978
- return "error";
979
- }
980
- function formatBytes(size) {
981
- return size < 1024 ? `${size} B` : `${(size / 1024).toFixed(1)} KB`;
982
- }
983
-
984
- // src/workflow/components/HttpActionPanel/HttpActionPanel.tsx
985
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
986
- function toPairs(rows) {
987
- return rows.map((r) => ({
988
- key: r.key,
989
- value: r.value,
990
- enabled: r.enabled !== false,
991
- description: r.description
992
- }));
993
- }
994
- function textOf(raw) {
995
- if (typeof raw === "string") return raw;
996
- if (raw == null) return "";
997
- try {
998
- return JSON.stringify(raw, null, 2);
999
- } catch {
1000
- return "";
1001
- }
1002
- }
1003
- function HttpActionPanel({
1004
- value,
1005
- onChange,
1006
- disabled = false,
1007
- connections,
1008
- connectionsLoading = false,
1009
- onSendPreview,
1010
- className
1011
- }) {
1012
- const uid = useId();
1013
- const cfgRef = useRef4({
1014
- ...HTTP_ACTION_DEFAULTS,
1015
- ...value,
1016
- queryParams: normalizeKeyValues(value?.queryParams),
1017
- headers: normalizeKeyValues(value?.headers),
1018
- formData: normalizeKeyValues(value?.formData),
1019
- auth: normalizeAuth(value?.auth),
1020
- retry: {
1021
- ...HTTP_ACTION_DEFAULTS.retry,
1022
- ...value?.retry
1023
- },
1024
- pagination: {
1025
- ...HTTP_ACTION_DEFAULTS.pagination,
1026
- ...value?.pagination
1027
- }
1028
- });
1029
- const [, force] = useState4(0);
1030
- const emit = useCallback4(
1031
- (patch) => {
1032
- cfgRef.current = { ...cfgRef.current, ...patch };
1033
- force((n) => n + 1);
1034
- onChange?.(cfgRef.current);
1035
- },
1036
- [onChange]
1037
- );
1038
- const cfg = cfgRef.current;
1039
- const connectionId = cfg.connectionId ?? "";
1040
- const selectedConnection = connections?.find((c) => c.id === connectionId) ?? null;
1041
- const endpoints = selectedConnection?.endpoints ?? [];
1042
- const method = cfg.method ?? "GET";
1043
- const url = cfg.url ?? "";
1044
- const baseUrl = cfg.baseUrl ?? "";
1045
- const fullUrl = connectionId ? url : effectiveUrl(baseUrl, url);
1046
- const queryParams = normalizeKeyValues(cfg.queryParams);
1047
- const headers = normalizeKeyValues(cfg.headers);
1048
- const formData = normalizeKeyValues(cfg.formData);
1049
- const auth = normalizeAuth(cfg.auth);
1050
- const retry = cfg.retry ?? HTTP_ACTION_DEFAULTS.retry;
1051
- const pagination = cfg.pagination ?? { type: "none" };
1052
- const responseType = cfg.responseType ?? "json";
1053
- const [bodyText, setBodyText] = useState4(() => textOf(cfg.body));
1054
- const [bodyError, setBodyError] = useState4();
1055
- const [gqlVarsText, setGqlVarsText] = useState4(() => textOf(cfg.graphqlVariables));
1056
- const [gqlVarsError, setGqlVarsError] = useState4();
1057
- const [retryOnText, setRetryOnText] = useState4(() => (retry?.retryOn ?? []).join(", "));
1058
- const [expectedCtText, setExpectedCtText] = useState4(
1059
- () => (cfg.expectedContentTypes ?? []).join(", ")
1060
- );
1061
- const [urlError, setUrlError] = useState4();
1062
- const [extractRows, setExtractRows] = useState4(
1063
- () => Object.entries(cfg.extract ?? {}).map(([key, v]) => ({ key, value: v, enabled: true }))
1064
- );
1065
- const [curlOpen, setCurlOpen] = useState4(false);
1066
- const [curlText, setCurlText] = useState4("");
1067
- const [curlError, setCurlError] = useState4();
1068
- const [copiedCurl, setCopiedCurl] = useState4(false);
1069
- const [copiedResult, setCopiedResult] = useState4(false);
1070
- const [copiedSection, setCopiedSection] = useState4(null);
1071
- const applyCurl = () => {
1072
- const patch = parseCurl(curlText);
1073
- if (!patch) {
1074
- setCurlError(
1075
- "That doesn't look like a cURL command \u2014 it should start with `curl` and include a URL."
1076
- );
1077
- return;
1078
- }
1079
- setCurlError(void 0);
1080
- setCurlText("");
1081
- setCurlOpen(false);
1082
- setBodyText(textOf(patch.body));
1083
- setBodyError(void 0);
1084
- emit({ ...patch, connectionId: void 0, baseUrl: void 0 });
1085
- };
1086
- const copyCurl = async () => {
1087
- try {
1088
- await navigator.clipboard?.writeText(buildCurl(cfg));
1089
- setCopiedCurl(true);
1090
- setTimeout(() => setCopiedCurl(false), 1600);
1091
- } catch {
812
+ };
813
+ const listRef = useRef4(null);
814
+ const focusRow = (id) => {
815
+ setFocusedId(id);
816
+ requestAnimationFrame(() => {
817
+ listRef.current?.querySelector(`[data-fx-row="${CSS.escape(id)}"]`)?.focus();
818
+ });
819
+ };
820
+ const onRowKeyDown = (e, rows, row, category) => {
821
+ const idx = rows.findIndex((r) => r.item.id === row.item.id);
822
+ if (idx < 0) return;
823
+ switch (e.key) {
824
+ case "ArrowDown":
825
+ e.preventDefault();
826
+ if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
827
+ break;
828
+ case "ArrowUp":
829
+ e.preventDefault();
830
+ if (idx > 0) focusRow(rows[idx - 1].item.id);
831
+ break;
832
+ case "ArrowRight":
833
+ if (row.hasChildren) {
834
+ e.preventDefault();
835
+ if (!row.expanded) toggleItem(row.item.id);
836
+ else if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
837
+ }
838
+ break;
839
+ case "ArrowLeft":
840
+ if (row.hasChildren && row.expanded) {
841
+ e.preventDefault();
842
+ toggleItem(row.item.id);
843
+ } else {
844
+ for (let i = idx - 1; i >= 0; i--) {
845
+ if (rows[i].depth < row.depth) {
846
+ e.preventDefault();
847
+ focusRow(rows[i].item.id);
848
+ break;
849
+ }
850
+ }
851
+ }
852
+ break;
853
+ case "Home":
854
+ e.preventDefault();
855
+ if (rows.length) focusRow(rows[0].item.id);
856
+ break;
857
+ case "End":
858
+ e.preventDefault();
859
+ if (rows.length) focusRow(rows[rows.length - 1].item.id);
860
+ break;
861
+ case "Enter":
862
+ case " ":
863
+ e.preventDefault();
864
+ if (row.hasChildren) toggleItem(row.item.id);
865
+ else onItemSelect?.(row.item, category);
866
+ break;
867
+ default:
868
+ break;
1092
869
  }
1093
870
  };
1094
- const copyResult = async () => {
1095
- if (preview.phase !== "success") return;
1096
- const r = preview.response;
1097
- const bundle = {
1098
- request: {
1099
- method: cfg.apiType === "graphql" ? "POST" : method,
1100
- url: connectionId ? `${selectedConnection?.name ?? connectionId} \xB7 ${url}` : fullUrl
871
+ const searchRows = useMemo2(
872
+ () => hits ? hits.map(({ hit }) => ({
873
+ item: hit.item,
874
+ depth: 0,
875
+ hasChildren: false,
876
+ expanded: false
877
+ })) : [],
878
+ [hits]
879
+ );
880
+ const renderRow = (row, category, rows, crumb) => {
881
+ const { item, depth, hasChildren, expanded } = row;
882
+ const mono = category.kind === "function";
883
+ const draggable = !!item.insertValue;
884
+ const isFocused = focusedId === item.id;
885
+ const showsReference = !!item.insertValue && !item.insertValue.trim().startsWith("ods://");
886
+ const tip = /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__tip", children: [
887
+ /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__tip-head", children: [
888
+ /* @__PURE__ */ jsx3(
889
+ "span",
890
+ {
891
+ className: cn(
892
+ "ods-flow-fx-panel__tip-label",
893
+ mono && "ods-flow-fx-panel__tip-label--mono"
894
+ ),
895
+ children: item.label
896
+ }
897
+ ),
898
+ item.valueType && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__tip-type", children: item.valueType })
899
+ ] }),
900
+ item.description && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__tip-desc", children: item.description }),
901
+ showsReference && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__tip-ref", children: item.insertValue })
902
+ ] });
903
+ return /* @__PURE__ */ jsx3(
904
+ Tooltip,
905
+ {
906
+ content: tip,
907
+ position: "left",
908
+ disabled: !item.description && !showsReference,
909
+ children: /* @__PURE__ */ jsxs3(
910
+ "div",
911
+ {
912
+ "data-fx-row": item.id,
913
+ className: cn(
914
+ "ods-flow-fx-panel__row",
915
+ hasChildren ? "ods-flow-fx-panel__row--branch" : "ods-flow-fx-panel__row--leaf",
916
+ draggingId === item.id && "ods-flow-fx-panel__row--dragging",
917
+ isFocused && "ods-flow-fx-panel__row--focused"
918
+ ),
919
+ role: "treeitem",
920
+ "aria-expanded": hasChildren ? expanded : void 0,
921
+ "aria-level": depth + 1,
922
+ tabIndex: isFocused || focusedId === null && rows[0]?.item.id === item.id ? 0 : -1,
923
+ draggable,
924
+ onDragStart: draggable ? handleDragStart(item, category) : void 0,
925
+ onDragEnd: () => setDraggingId(null),
926
+ onFocus: () => setFocusedId(item.id),
927
+ onKeyDown: (e) => onRowKeyDown(e, rows, row, category),
928
+ onClick: () => {
929
+ if (hasChildren) toggleItem(item.id);
930
+ else onItemSelect?.(item, category);
931
+ },
932
+ children: [
933
+ Array.from({ length: depth }, (_, i) => /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__indent", "aria-hidden": "true" }, i)),
934
+ hasChildren ? /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__twisty", "aria-hidden": "true", children: expanded ? /* @__PURE__ */ jsx3(ChevronDownIcon, { size: "sm" }) : /* @__PURE__ */ jsx3(ChevronRightIcon, { size: "sm" }) }) : /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__grip", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(DraggableIcon, { size: "sm" }) }),
935
+ /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-main", children: [
936
+ /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-line", children: [
937
+ mono ? /* @__PURE__ */ jsx3("code", { className: "ods-flow-fx-panel__row-label ods-flow-fx-panel__row-label--mono", children: item.label }) : /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__row-label", children: item.label }),
938
+ item.valueType && /* @__PURE__ */ jsx3(
939
+ "span",
940
+ {
941
+ className: cn(
942
+ "ods-flow-fx-panel__type",
943
+ `ods-flow-fx-panel__type--${item.valueType}`
944
+ ),
945
+ children: item.valueType
946
+ }
947
+ )
948
+ ] }),
949
+ (item.description || crumb) && /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-sub", children: [
950
+ crumb && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__crumb", children: crumb }),
951
+ item.description
952
+ ] })
953
+ ] })
954
+ ]
955
+ }
956
+ )
1101
957
  },
1102
- response: {
1103
- status: r.status,
1104
- statusText: r.statusText,
1105
- ok: r.ok,
1106
- durationMs: r.duration,
1107
- sizeBytes: r.size,
1108
- headers: r.headers,
1109
- body: r.body
1110
- }
1111
- };
1112
- try {
1113
- await navigator.clipboard?.writeText(JSON.stringify(bundle, null, 2));
1114
- setCopiedResult(true);
1115
- setTimeout(() => setCopiedResult(false), 1600);
1116
- } catch {
1117
- }
1118
- };
1119
- const copySection = async (text, key) => {
1120
- try {
1121
- await navigator.clipboard?.writeText(text);
1122
- setCopiedSection(key);
1123
- setTimeout(() => setCopiedSection(null), 1600);
1124
- } catch {
1125
- }
958
+ item.id
959
+ );
1126
960
  };
1127
- const sectionCopyButton = (text, key, label) => /* @__PURE__ */ jsx2(
1128
- Button,
961
+ return /* @__PURE__ */ jsxs3(
962
+ "aside",
1129
963
  {
1130
- variant: "ghost",
1131
- size: "sm",
1132
- "aria-label": copiedSection === key ? `${label} copied` : `Copy ${label}`,
1133
- leftIcon: copiedSection === key ? /* @__PURE__ */ jsx2(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx2(CopyIcon, { size: "sm" }),
1134
- className: cn(copiedSection === key && "ods-http-panel__copy-done"),
1135
- onClick: () => {
1136
- void copySection(text, key);
1137
- }
1138
- }
1139
- );
1140
- const validateUrl = (nextUrl, nextBase, hasConnection) => {
1141
- if (!nextUrl && !nextBase && !hasConnection) {
1142
- setUrlError("URL is required");
1143
- return;
1144
- }
1145
- if (!nextUrl || hasConnection || nextBase || nextUrl.includes("{{")) {
1146
- setUrlError(void 0);
1147
- return;
1148
- }
1149
- try {
1150
- new URL(nextUrl);
1151
- setUrlError(void 0);
1152
- } catch {
1153
- setUrlError("URL does not look valid");
1154
- }
1155
- };
1156
- const bodyMode = cfg.apiType === "graphql" ? "graphql" : cfg.bodyType ?? "json";
1157
- const gqlVarCount = useMemo2(
1158
- () => countGraphqlVariables(cfg.graphqlQuery ?? ""),
1159
- [cfg.graphqlQuery]
1160
- );
1161
- const handleBodyMode = (next) => {
1162
- if (next === "graphql") {
1163
- emit(method === "GET" ? { apiType: "graphql", method: "POST" } : { apiType: "graphql" });
1164
- return;
964
+ className: cn("ods-flow-fx-panel", className),
965
+ style: { width, ...style },
966
+ "aria-label": typeof title === "string" ? title : "Data References",
967
+ children: [
968
+ /* @__PURE__ */ jsxs3("header", { className: "ods-flow-fx-panel__header", children: [
969
+ /* @__PURE__ */ jsx3("h3", { className: "ods-flow-fx-panel__title", children: title }),
970
+ onClose && /* @__PURE__ */ jsx3(
971
+ "button",
972
+ {
973
+ type: "button",
974
+ className: "ods-flow-fx-panel__close",
975
+ onClick: onClose,
976
+ "aria-label": "Close data references",
977
+ children: /* @__PURE__ */ jsx3(CloseIcon, { size: "sm" })
978
+ }
979
+ )
980
+ ] }),
981
+ /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__search", children: [
982
+ /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__search-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(SearchIcon, { size: "sm" }) }),
983
+ /* @__PURE__ */ jsx3(
984
+ "input",
985
+ {
986
+ type: "text",
987
+ className: "ods-flow-fx-panel__search-input",
988
+ placeholder: searchPlaceholder,
989
+ "aria-label": "Search fields, functions and variables",
990
+ value: search,
991
+ onChange: (e) => setSearch(e.target.value)
992
+ }
993
+ ),
994
+ search && /* @__PURE__ */ jsx3(
995
+ "button",
996
+ {
997
+ type: "button",
998
+ className: "ods-flow-fx-panel__search-clear",
999
+ onClick: () => setSearch(""),
1000
+ "aria-label": "Clear search",
1001
+ children: /* @__PURE__ */ jsx3(CloseIcon, { size: "sm" })
1002
+ }
1003
+ )
1004
+ ] }),
1005
+ hint && !query && /* @__PURE__ */ jsx3("p", { className: "ods-flow-fx-panel__hint", children: hint }),
1006
+ /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__list", ref: listRef, role: "tree", children: [
1007
+ query && hits && (hits.length > 0 ? /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__results", children: [
1008
+ /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__results-count", children: [
1009
+ hits.length,
1010
+ " ",
1011
+ hits.length === 1 ? "match" : "matches"
1012
+ ] }),
1013
+ hits.map(
1014
+ ({ category, hit }, i) => renderRow(
1015
+ searchRows[i],
1016
+ category,
1017
+ searchRows,
1018
+ hit.ancestors.length ? hit.ancestors.join(" / ") : category.label
1019
+ )
1020
+ )
1021
+ ] }) : /* @__PURE__ */ jsx3("div", { className: "ods-flow-fx-panel__empty", children: emptyLabel })),
1022
+ !query && visibleCategories.map((cat) => {
1023
+ const isOpen = openCategoryId === cat.id;
1024
+ const kind = cat.kind ?? "function";
1025
+ const KindIcon = KIND_ICON[kind];
1026
+ const branchIds = /* @__PURE__ */ new Set();
1027
+ collectBranchIds(cat.items, branchIds);
1028
+ const hasTree = branchIds.size > 0;
1029
+ const rows = [];
1030
+ if (isOpen) flattenTree(cat.items, 0, expandedItems, rows);
1031
+ const allExpanded = hasTree && [...branchIds].every((id) => expandedItems.has(id));
1032
+ return /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__category", children: [
1033
+ /* @__PURE__ */ jsxs3(
1034
+ "div",
1035
+ {
1036
+ className: cn(
1037
+ "ods-flow-fx-panel__cat-header",
1038
+ isOpen && "ods-flow-fx-panel__cat-header--open"
1039
+ ),
1040
+ children: [
1041
+ /* @__PURE__ */ jsxs3(
1042
+ "button",
1043
+ {
1044
+ type: "button",
1045
+ className: "ods-flow-fx-panel__cat-toggle",
1046
+ "aria-expanded": isOpen,
1047
+ onClick: () => setOpenCategory(isOpen ? null : cat.id),
1048
+ children: [
1049
+ /* @__PURE__ */ jsx3(
1050
+ ChevronRightIcon,
1051
+ {
1052
+ size: "sm",
1053
+ className: cn(
1054
+ "ods-flow-fx-panel__chevron",
1055
+ isOpen && "ods-flow-fx-panel__chevron--open"
1056
+ )
1057
+ }
1058
+ ),
1059
+ /* @__PURE__ */ jsxs3(
1060
+ "span",
1061
+ {
1062
+ className: cn(
1063
+ "ods-flow-fx-panel__badge",
1064
+ `ods-flow-fx-panel__badge--${kind}`
1065
+ ),
1066
+ children: [
1067
+ /* @__PURE__ */ jsx3(KindIcon, { size: "sm", className: "ods-flow-fx-panel__badge-icon" }),
1068
+ cat.label
1069
+ ]
1070
+ }
1071
+ ),
1072
+ /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__count", children: countLeaves(cat.items) })
1073
+ ]
1074
+ }
1075
+ ),
1076
+ isOpen && hasTree && /* @__PURE__ */ jsx3(
1077
+ "button",
1078
+ {
1079
+ type: "button",
1080
+ className: "ods-flow-fx-panel__cat-action",
1081
+ "aria-label": allExpanded ? "Collapse all" : "Expand all",
1082
+ title: allExpanded ? "Collapse all" : "Expand all",
1083
+ onClick: () => setCategoryExpansion(cat, !allExpanded),
1084
+ children: allExpanded ? /* @__PURE__ */ jsx3(CollapseAllIcon, { size: "sm" }) : /* @__PURE__ */ jsx3(ExpandAllIcon, { size: "sm" })
1085
+ }
1086
+ )
1087
+ ]
1088
+ }
1089
+ ),
1090
+ isOpen && /* @__PURE__ */ jsx3("div", { className: "ods-flow-fx-panel__rows", role: "group", "aria-label": cat.label, children: rows.map((row) => renderRow(row, cat, rows)) })
1091
+ ] }, cat.id);
1092
+ })
1093
+ ] })
1094
+ ]
1095
+ }
1096
+ );
1097
+ }
1098
+
1099
+ // src/workflow/components/HttpActionPanel/HttpActionPanel.tsx
1100
+ import {
1101
+ CheckmarkIcon,
1102
+ CodeIcon,
1103
+ CopyIcon,
1104
+ DataViewAltIcon,
1105
+ GlobeIcon,
1106
+ ListIcon,
1107
+ LockedIcon,
1108
+ PlayIcon,
1109
+ RenewIcon,
1110
+ TagIcon
1111
+ } from "@octaviaflow/icons";
1112
+ import { useCallback as useCallback4, useId, useMemo as useMemo3, useRef as useRef5, useState as useState5 } from "react";
1113
+
1114
+ // src/workflow/components/HttpActionPanel/httpActionTypes.ts
1115
+ var HTTP_METHODS = [
1116
+ "GET",
1117
+ "POST",
1118
+ "PUT",
1119
+ "PATCH",
1120
+ "DELETE",
1121
+ "HEAD",
1122
+ "OPTIONS"
1123
+ ];
1124
+ var HTTP_ACTION_DEFAULTS = {
1125
+ apiType: "rest",
1126
+ method: "GET",
1127
+ url: "",
1128
+ queryParams: [],
1129
+ headers: [],
1130
+ bodyType: "json",
1131
+ formData: [],
1132
+ graphqlQuery: "",
1133
+ graphqlVariables: {},
1134
+ auth: { type: "none" },
1135
+ timeout: 3e4,
1136
+ failOnError: true,
1137
+ followRedirects: true,
1138
+ retry: {
1139
+ maxAttempts: 1,
1140
+ retryOn: [429, 500, 502, 503, 504],
1141
+ initialDelayMs: 1e3,
1142
+ backoffFactor: 2,
1143
+ maxDelayMs: 3e4
1144
+ },
1145
+ pagination: { type: "none" },
1146
+ extract: {},
1147
+ responseType: "json",
1148
+ expectedContentTypes: []
1149
+ };
1150
+ function normalizeKeyValues(raw) {
1151
+ if (Array.isArray(raw)) {
1152
+ return raw.map((r) => ({
1153
+ key: String(r.key ?? ""),
1154
+ value: String(r.value ?? ""),
1155
+ description: r.description,
1156
+ enabled: r.enabled !== false
1157
+ }));
1158
+ }
1159
+ if (raw && typeof raw === "object") {
1160
+ return Object.entries(raw).map(([key, value]) => ({
1161
+ key,
1162
+ value: String(value),
1163
+ enabled: true
1164
+ }));
1165
+ }
1166
+ return [];
1167
+ }
1168
+ function normalizeAuth(raw) {
1169
+ const r = raw ?? {};
1170
+ const rawType = r.type ?? "none";
1171
+ const type = rawType === "oauth2" ? "bearer" : rawType;
1172
+ return {
1173
+ type: ["none", "basic", "bearer", "apikey"].includes(type) ? type : "none",
1174
+ username: r.username ?? "",
1175
+ password: r.password ?? "",
1176
+ token: r.token ?? r.accessToken ?? "",
1177
+ apiKeyName: r.apiKeyName ?? "X-API-Key",
1178
+ apiKeyValue: r.apiKeyValue ?? "",
1179
+ apiKeyLocation: r.apiKeyLocation === "query" ? "query" : "header"
1180
+ };
1181
+ }
1182
+ function effectiveUrl(baseUrl, url) {
1183
+ const base = (baseUrl ?? "").trim();
1184
+ if (!base) return url;
1185
+ return `${base.replace(/\/$/, "")}/${url.replace(/^\//, "")}`;
1186
+ }
1187
+ function serializeKeyValues(rows) {
1188
+ return rows.filter((r) => r.key || r.value).map((r) => ({
1189
+ key: r.key,
1190
+ value: r.value,
1191
+ enabled: r.enabled !== false,
1192
+ ...r.description ? { description: r.description } : {}
1193
+ }));
1194
+ }
1195
+ function validateHttpConfig(config) {
1196
+ const errors = [];
1197
+ const apiType = config.apiType ?? "rest";
1198
+ if (apiType === "rest" && !config.method) errors.push("Method is required");
1199
+ if (apiType === "graphql" && !String(config.graphqlQuery ?? "").trim()) {
1200
+ errors.push("GraphQL query is required");
1201
+ }
1202
+ const hasConnection = Boolean(config.connectionId);
1203
+ if (!config.url && !hasConnection) {
1204
+ errors.push("URL is required");
1205
+ } else if (config.url && !hasConnection && !config.baseUrl && !config.url.includes("{{")) {
1206
+ try {
1207
+ new URL(config.url);
1208
+ } catch {
1209
+ errors.push("Invalid URL format");
1210
+ }
1211
+ }
1212
+ if (typeof config.timeout === "number" && config.timeout < 1e3) {
1213
+ errors.push("Timeout must be at least 1000ms");
1214
+ }
1215
+ return { valid: errors.length === 0, errors };
1216
+ }
1217
+ function countGraphqlVariables(query) {
1218
+ return new Set(query.match(/\$[A-Za-z_][A-Za-z0-9_]*/g) ?? []).size;
1219
+ }
1220
+ function shellTokens(input) {
1221
+ const tokens = [];
1222
+ const flat = input.replace(/\\\r?\n/g, " ");
1223
+ const re = /'([^']*)'|"((?:[^"\\]|\\.)*)"|(\S+)/g;
1224
+ let m = re.exec(flat);
1225
+ while (m !== null) {
1226
+ if (m[1] !== void 0) tokens.push(m[1]);
1227
+ else if (m[2] !== void 0) tokens.push(m[2].replace(/\\(["\\])/g, "$1"));
1228
+ else tokens.push(m[3]);
1229
+ m = re.exec(flat);
1230
+ }
1231
+ return tokens;
1232
+ }
1233
+ function parseCurl(input) {
1234
+ const tokens = shellTokens(input.trim());
1235
+ if (!tokens.length || !/^curl$/i.test(tokens[0])) return null;
1236
+ let method;
1237
+ let url = "";
1238
+ const headers = [];
1239
+ const formData = [];
1240
+ let bodyRaw;
1241
+ let basicUser;
1242
+ let followRedirects;
1243
+ for (let i = 1; i < tokens.length; i++) {
1244
+ const t = tokens[i];
1245
+ const next = () => tokens[++i] ?? "";
1246
+ if (t === "-X" || t === "--request") {
1247
+ const m = next().toUpperCase();
1248
+ if (HTTP_METHODS.includes(m)) method = m;
1249
+ } else if (t === "-H" || t === "--header") {
1250
+ const raw = next();
1251
+ const idx = raw.indexOf(":");
1252
+ if (idx > 0) {
1253
+ headers.push({
1254
+ key: raw.slice(0, idx).trim(),
1255
+ value: raw.slice(idx + 1).trim(),
1256
+ enabled: true
1257
+ });
1258
+ }
1259
+ } else if (t === "-d" || t === "--data" || t === "--data-raw" || t === "--data-binary" || t === "--data-urlencode") {
1260
+ bodyRaw = next();
1261
+ } else if (t === "-F" || t === "--form") {
1262
+ const raw = next();
1263
+ const idx = raw.indexOf("=");
1264
+ if (idx > 0) {
1265
+ formData.push({ key: raw.slice(0, idx), value: raw.slice(idx + 1), enabled: true });
1266
+ }
1267
+ } else if (t === "-u" || t === "--user") {
1268
+ basicUser = next();
1269
+ } else if (t === "--url") {
1270
+ url = next();
1271
+ } else if (t === "-L" || t === "--location") {
1272
+ followRedirects = true;
1273
+ } else if (t.startsWith("-")) {
1274
+ if ([
1275
+ "-o",
1276
+ "--output",
1277
+ "-A",
1278
+ "--user-agent",
1279
+ "-e",
1280
+ "--referer",
1281
+ "-b",
1282
+ "--cookie",
1283
+ "--connect-timeout",
1284
+ "-m",
1285
+ "--max-time"
1286
+ ].includes(t)) {
1287
+ next();
1288
+ }
1289
+ } else if (!url) {
1290
+ url = t;
1291
+ }
1292
+ }
1293
+ if (!url) return null;
1294
+ const patch = { url, apiType: "rest" };
1295
+ const qIdx = url.indexOf("?");
1296
+ if (qIdx >= 0) {
1297
+ const query = url.slice(qIdx + 1);
1298
+ patch.url = url.slice(0, qIdx);
1299
+ patch.queryParams = query.split("&").filter(Boolean).map((pair) => {
1300
+ const eq = pair.indexOf("=");
1301
+ const rawKey = eq >= 0 ? pair.slice(0, eq) : pair;
1302
+ const rawValue = eq >= 0 ? pair.slice(eq + 1) : "";
1303
+ const decode = (s) => {
1304
+ try {
1305
+ return decodeURIComponent(s.replace(/\+/g, "%20"));
1306
+ } catch {
1307
+ return s;
1308
+ }
1309
+ };
1310
+ return { key: decode(rawKey), value: decode(rawValue), enabled: true };
1311
+ });
1312
+ }
1313
+ const authHeaderIdx = headers.findIndex((h) => h.key.toLowerCase() === "authorization");
1314
+ if (authHeaderIdx >= 0 && /^bearer\s+/i.test(headers[authHeaderIdx].value)) {
1315
+ patch.auth = {
1316
+ type: "bearer",
1317
+ token: headers[authHeaderIdx].value.replace(/^bearer\s+/i, "")
1318
+ };
1319
+ headers.splice(authHeaderIdx, 1);
1320
+ } else if (basicUser) {
1321
+ const sep = basicUser.indexOf(":");
1322
+ patch.auth = {
1323
+ type: "basic",
1324
+ username: sep >= 0 ? basicUser.slice(0, sep) : basicUser,
1325
+ password: sep >= 0 ? basicUser.slice(sep + 1) : ""
1326
+ };
1327
+ }
1328
+ if (headers.length) patch.headers = headers;
1329
+ if (followRedirects !== void 0) patch.followRedirects = followRedirects;
1330
+ if (formData.length) {
1331
+ patch.bodyType = "form-data";
1332
+ patch.formData = formData;
1333
+ patch.method = method ?? "POST";
1334
+ } else if (bodyRaw !== void 0) {
1335
+ try {
1336
+ patch.body = JSON.parse(bodyRaw);
1337
+ patch.bodyType = "json";
1338
+ } catch {
1339
+ patch.body = bodyRaw;
1340
+ patch.bodyType = "raw";
1341
+ }
1342
+ patch.method = method ?? "POST";
1343
+ } else {
1344
+ patch.method = method ?? "GET";
1345
+ patch.bodyType = "none";
1346
+ }
1347
+ return patch;
1348
+ }
1349
+ function shellQuote(value) {
1350
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1351
+ }
1352
+ function buildCurl(config) {
1353
+ const parts = ["curl"];
1354
+ const method = config.apiType === "graphql" ? "POST" : config.method ?? "GET";
1355
+ if (method !== "GET") parts.push(`-X ${method}`);
1356
+ let url = effectiveUrl(config.baseUrl, config.url);
1357
+ const params = normalizeKeyValues(config.queryParams).filter((p) => p.enabled !== false && p.key);
1358
+ if (params.length) {
1359
+ const qs = params.map((p) => `${encodeURIComponent(p.key)}=${encodeURIComponent(p.value)}`).join("&");
1360
+ url += (url.includes("?") ? "&" : "?") + qs;
1361
+ }
1362
+ parts.push(shellQuote(url));
1363
+ for (const h of normalizeKeyValues(config.headers).filter((h2) => h2.enabled !== false && h2.key)) {
1364
+ parts.push(`-H ${shellQuote(`${h.key}: ${h.value}`)}`);
1365
+ }
1366
+ const auth = config.auth;
1367
+ if (auth?.type === "bearer" && auth.token) {
1368
+ parts.push(`-H ${shellQuote(`Authorization: Bearer ${auth.token}`)}`);
1369
+ } else if (auth?.type === "basic" && auth.username) {
1370
+ parts.push(`-u ${shellQuote(`${auth.username}:${auth.password ?? ""}`)}`);
1371
+ } else if (auth?.type === "apikey" && auth.apiKeyName && auth.apiKeyValue) {
1372
+ if ((auth.apiKeyLocation ?? "header") === "header") {
1373
+ parts.push(`-H ${shellQuote(`${auth.apiKeyName}: ${auth.apiKeyValue}`)}`);
1374
+ }
1375
+ }
1376
+ if (config.apiType === "graphql") {
1377
+ const payload = JSON.stringify({
1378
+ query: config.graphqlQuery ?? "",
1379
+ variables: typeof config.graphqlVariables === "string" ? config.graphqlVariables : config.graphqlVariables ?? {}
1380
+ });
1381
+ parts.push(`-H ${shellQuote("Content-Type: application/json")}`);
1382
+ parts.push(`-d ${shellQuote(payload)}`);
1383
+ } else if (config.bodyType === "form-data") {
1384
+ for (const f of normalizeKeyValues(config.formData).filter(
1385
+ (f2) => f2.enabled !== false && f2.key
1386
+ )) {
1387
+ parts.push(`-F ${shellQuote(`${f.key}=${f.value}`)}`);
1165
1388
  }
1166
- emit({ apiType: "rest", bodyType: next });
1167
- };
1168
- const [preview, setPreview] = useState4({ phase: "idle" });
1169
- const [schemaInfo, setSchemaInfo] = useState4(
1170
- null
1171
- );
1172
- const lastSchemaKey = useRef4("");
1173
- const inferAndEmitSchema = useCallback4(
1174
- (response, forceRefresh = false) => {
1175
- if (responseType === "binary") return;
1176
- const snapshot = {
1177
- status: response.status,
1178
- statusText: response.statusText,
1179
- ok: response.ok,
1180
- headers: response.headers,
1181
- body: response.body,
1182
- duration: response.duration,
1183
- attempts: 1,
1184
- extracted: {}
1185
- };
1186
- const inferred = inferJsonSchema(snapshot);
1187
- const key = JSON.stringify(inferred);
1188
- if (!forceRefresh && key === lastSchemaKey.current) return;
1189
- lastSchemaKey.current = key;
1190
- emit({ _outputSchema: inferred });
1191
- const bodySchema = inferred.properties?.body;
1192
- const topKeys = Object.keys(bodySchema?.properties ?? inferred.properties ?? {});
1193
- setSchemaInfo({ pathCount: countSchemaPaths(inferred), topKeys });
1389
+ } else if (config.bodyType && config.bodyType !== "none" && config.body != null) {
1390
+ const raw = typeof config.body === "string" ? config.body : JSON.stringify(config.body);
1391
+ if (raw) {
1392
+ if (config.bodyType === "json") {
1393
+ parts.push(`-H ${shellQuote("Content-Type: application/json")}`);
1394
+ }
1395
+ parts.push(`-d ${shellQuote(raw)}`);
1396
+ }
1397
+ }
1398
+ if (config.followRedirects !== false) parts.push("-L");
1399
+ return parts.join(" ");
1400
+ }
1401
+ function inferJsonSchema(value, depth = 0) {
1402
+ if (value === null || value === void 0) return { type: "null" };
1403
+ if (Array.isArray(value)) {
1404
+ if (!value.length || depth > 6) return { type: "array" };
1405
+ return { type: "array", items: inferJsonSchema(value[0], depth + 1) };
1406
+ }
1407
+ if (typeof value === "object") {
1408
+ if (depth > 6) return { type: "object" };
1409
+ const properties = {};
1410
+ for (const [k, v] of Object.entries(value)) {
1411
+ properties[k] = inferJsonSchema(v, depth + 1);
1412
+ }
1413
+ return { type: "object", properties };
1414
+ }
1415
+ return { type: typeof value };
1416
+ }
1417
+ function countSchemaPaths(schema, depth = 0) {
1418
+ if (!schema || typeof schema !== "object" || depth > 8) return 0;
1419
+ const s = schema;
1420
+ let count = 0;
1421
+ if (s.type === "object" && s.properties) {
1422
+ for (const child of Object.values(s.properties)) {
1423
+ count += 1 + countSchemaPaths(child, depth + 1);
1424
+ }
1425
+ } else if (s.type === "array" && s.items) {
1426
+ count += countSchemaPaths(s.items, depth + 1);
1427
+ }
1428
+ return count;
1429
+ }
1430
+ function statusTone(status) {
1431
+ if (status < 300) return "success";
1432
+ if (status < 400) return "info";
1433
+ if (status < 500) return "warning";
1434
+ return "error";
1435
+ }
1436
+ function formatBytes(size) {
1437
+ return size < 1024 ? `${size} B` : `${(size / 1024).toFixed(1)} KB`;
1438
+ }
1439
+
1440
+ // src/workflow/components/HttpActionPanel/HttpActionPanel.tsx
1441
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1442
+ function toPairs(rows) {
1443
+ return rows.map((r) => ({
1444
+ key: r.key,
1445
+ value: r.value,
1446
+ enabled: r.enabled !== false,
1447
+ description: r.description
1448
+ }));
1449
+ }
1450
+ function textOf(raw) {
1451
+ if (typeof raw === "string") return raw;
1452
+ if (raw == null) return "";
1453
+ try {
1454
+ return JSON.stringify(raw, null, 2);
1455
+ } catch {
1456
+ return "";
1457
+ }
1458
+ }
1459
+ function HttpActionPanel({
1460
+ value,
1461
+ onChange,
1462
+ disabled = false,
1463
+ connections,
1464
+ connectionsLoading = false,
1465
+ onSendPreview,
1466
+ className
1467
+ }) {
1468
+ const uid = useId();
1469
+ const cfgRef = useRef5({
1470
+ ...HTTP_ACTION_DEFAULTS,
1471
+ ...value,
1472
+ queryParams: normalizeKeyValues(value?.queryParams),
1473
+ headers: normalizeKeyValues(value?.headers),
1474
+ formData: normalizeKeyValues(value?.formData),
1475
+ auth: normalizeAuth(value?.auth),
1476
+ retry: {
1477
+ ...HTTP_ACTION_DEFAULTS.retry,
1478
+ ...value?.retry
1194
1479
  },
1195
- [emit, responseType]
1480
+ pagination: {
1481
+ ...HTTP_ACTION_DEFAULTS.pagination,
1482
+ ...value?.pagination
1483
+ }
1484
+ });
1485
+ const [, force] = useState5(0);
1486
+ const emit = useCallback4(
1487
+ (patch) => {
1488
+ cfgRef.current = { ...cfgRef.current, ...patch };
1489
+ force((n) => n + 1);
1490
+ onChange?.(cfgRef.current);
1491
+ },
1492
+ [onChange]
1196
1493
  );
1197
- const sendPreview = async () => {
1198
- if (!onSendPreview) return;
1199
- setPreview({ phase: "loading" });
1494
+ const cfg = cfgRef.current;
1495
+ const connectionId = cfg.connectionId ?? "";
1496
+ const selectedConnection = connections?.find((c) => c.id === connectionId) ?? null;
1497
+ const endpoints = selectedConnection?.endpoints ?? [];
1498
+ const method = cfg.method ?? "GET";
1499
+ const url = cfg.url ?? "";
1500
+ const baseUrl = cfg.baseUrl ?? "";
1501
+ const fullUrl = connectionId ? url : effectiveUrl(baseUrl, url);
1502
+ const queryParams = normalizeKeyValues(cfg.queryParams);
1503
+ const headers = normalizeKeyValues(cfg.headers);
1504
+ const formData = normalizeKeyValues(cfg.formData);
1505
+ const auth = normalizeAuth(cfg.auth);
1506
+ const retry = cfg.retry ?? HTTP_ACTION_DEFAULTS.retry;
1507
+ const pagination = cfg.pagination ?? { type: "none" };
1508
+ const responseType = cfg.responseType ?? "json";
1509
+ const [bodyText, setBodyText] = useState5(() => textOf(cfg.body));
1510
+ const [bodyError, setBodyError] = useState5();
1511
+ const [gqlVarsText, setGqlVarsText] = useState5(() => textOf(cfg.graphqlVariables));
1512
+ const [gqlVarsError, setGqlVarsError] = useState5();
1513
+ const [retryOnText, setRetryOnText] = useState5(() => (retry?.retryOn ?? []).join(", "));
1514
+ const [expectedCtText, setExpectedCtText] = useState5(
1515
+ () => (cfg.expectedContentTypes ?? []).join(", ")
1516
+ );
1517
+ const [urlError, setUrlError] = useState5();
1518
+ const [extractRows, setExtractRows] = useState5(
1519
+ () => Object.entries(cfg.extract ?? {}).map(([key, v]) => ({ key, value: v, enabled: true }))
1520
+ );
1521
+ const [curlOpen, setCurlOpen] = useState5(false);
1522
+ const [curlText, setCurlText] = useState5("");
1523
+ const [curlError, setCurlError] = useState5();
1524
+ const [copiedCurl, setCopiedCurl] = useState5(false);
1525
+ const [copiedResult, setCopiedResult] = useState5(false);
1526
+ const [copiedSection, setCopiedSection] = useState5(null);
1527
+ const applyCurl = () => {
1528
+ const patch = parseCurl(curlText);
1529
+ if (!patch) {
1530
+ setCurlError(
1531
+ "That doesn't look like a cURL command \u2014 it should start with `curl` and include a URL."
1532
+ );
1533
+ return;
1534
+ }
1535
+ setCurlError(void 0);
1536
+ setCurlText("");
1537
+ setCurlOpen(false);
1538
+ setBodyText(textOf(patch.body));
1539
+ setBodyError(void 0);
1540
+ emit({ ...patch, connectionId: void 0, baseUrl: void 0 });
1541
+ };
1542
+ const copyCurl = async () => {
1200
1543
  try {
1201
- const response = await onSendPreview({
1544
+ await navigator.clipboard?.writeText(buildCurl(cfg));
1545
+ setCopiedCurl(true);
1546
+ setTimeout(() => setCopiedCurl(false), 1600);
1547
+ } catch {
1548
+ }
1549
+ };
1550
+ const copyResult = async () => {
1551
+ if (preview.phase !== "success") return;
1552
+ const r = preview.response;
1553
+ const bundle = {
1554
+ request: {
1202
1555
  method: cfg.apiType === "graphql" ? "POST" : method,
1203
- url: connectionId ? url : fullUrl,
1204
- connectionId: connectionId || void 0,
1205
- headers,
1206
- queryParams,
1207
- bodyType: cfg.bodyType ?? "json",
1208
- body: bodyText,
1209
- formData,
1210
- graphqlQuery: cfg.apiType === "graphql" ? cfg.graphqlQuery ?? "" : void 0,
1211
- graphqlVariables: cfg.apiType === "graphql" ? gqlVarsText : void 0,
1212
- auth,
1213
- timeout: cfg.timeout ?? 3e4
1214
- });
1215
- setPreview({ phase: "success", response });
1216
- inferAndEmitSchema(response);
1217
- } catch (err) {
1218
- setPreview({
1219
- phase: "error",
1220
- message: err instanceof Error ? err.message : "Request failed"
1221
- });
1556
+ url: connectionId ? `${selectedConnection?.name ?? connectionId} \xB7 ${url}` : fullUrl
1557
+ },
1558
+ response: {
1559
+ status: r.status,
1560
+ statusText: r.statusText,
1561
+ ok: r.ok,
1562
+ durationMs: r.duration,
1563
+ sizeBytes: r.size,
1564
+ headers: r.headers,
1565
+ body: r.body
1566
+ }
1567
+ };
1568
+ try {
1569
+ await navigator.clipboard?.writeText(JSON.stringify(bundle, null, 2));
1570
+ setCopiedResult(true);
1571
+ setTimeout(() => setCopiedResult(false), 1600);
1572
+ } catch {
1222
1573
  }
1223
1574
  };
1224
- const previewUrlOk = Boolean(fullUrl) && !fullUrl.includes("{{");
1225
- const canSend = Boolean(onSendPreview) && previewUrlOk && preview.phase !== "loading" && !disabled;
1226
- const paramCount = queryParams.filter((r) => r.enabled !== false && r.key).length;
1227
- const headerCount = headers.filter((r) => r.enabled !== false && r.key).length;
1228
- const extractCount = extractRows.filter((r) => r.key).length;
1229
- const advancedSummary = [];
1230
- if ((cfg.timeout ?? 3e4) !== 3e4) advancedSummary.push(`${(cfg.timeout ?? 0) / 1e3}s`);
1231
- if ((retry?.maxAttempts ?? 1) > 1) advancedSummary.push(`retry \xD7${retry?.maxAttempts}`);
1232
- if (pagination.type !== "none") advancedSummary.push(pagination.type.replace("_", " "));
1233
- if (extractCount > 0) advancedSummary.push(`${extractCount} extracted`);
1234
- const kvTab = (rows, key, desc, keyPlaceholder) => /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1235
- /* @__PURE__ */ jsx2("p", { className: "ods-http-panel__desc", children: desc }),
1236
- /* @__PURE__ */ jsx2(
1237
- KeyValueEditor,
1238
- {
1239
- pairs: toPairs(rows),
1240
- onChange: (pairs) => emit({ [key]: pairs }),
1241
- rowToggle: true,
1242
- showDescription: true,
1243
- keyPlaceholder,
1244
- valuePlaceholder: "value or {{var}}",
1245
- emptyLabel: "No entries yet \u2014 click Add to begin.",
1246
- addLabel: "Add",
1247
- size: "sm",
1248
- disabled
1575
+ const copySection = async (text, key) => {
1576
+ try {
1577
+ await navigator.clipboard?.writeText(text);
1578
+ setCopiedSection(key);
1579
+ setTimeout(() => setCopiedSection(null), 1600);
1580
+ } catch {
1581
+ }
1582
+ };
1583
+ const sectionCopyButton = (text, key, label) => /* @__PURE__ */ jsx4(
1584
+ Button,
1585
+ {
1586
+ variant: "ghost",
1587
+ size: "sm",
1588
+ "aria-label": copiedSection === key ? `${label} copied` : `Copy ${label}`,
1589
+ leftIcon: copiedSection === key ? /* @__PURE__ */ jsx4(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx4(CopyIcon, { size: "sm" }),
1590
+ className: cn(copiedSection === key && "ods-http-panel__copy-done"),
1591
+ onClick: () => {
1592
+ void copySection(text, key);
1249
1593
  }
1250
- )
1251
- ] });
1252
- const paramsTab = kvTab(
1253
- queryParams,
1254
- "queryParams",
1255
- /* @__PURE__ */ jsxs2(Fragment2, { children: [
1256
- "Query parameters appended to the URL. Supports ",
1257
- /* @__PURE__ */ jsx2("code", { children: "{{variable}}" }),
1258
- " interpolation."
1259
- ] }),
1260
- "param name"
1594
+ }
1261
1595
  );
1262
- const headersTab = kvTab(
1263
- headers,
1264
- "headers",
1265
- /* @__PURE__ */ jsxs2(Fragment2, { children: [
1266
- "Request headers. Auth headers are injected automatically from the Auth tab. Supports",
1267
- " ",
1268
- /* @__PURE__ */ jsx2("code", { children: "{{variable}}" }),
1269
- " interpolation."
1270
- ] }),
1271
- "Header-Name"
1596
+ const validateUrl = (nextUrl, nextBase, hasConnection) => {
1597
+ if (!nextUrl && !nextBase && !hasConnection) {
1598
+ setUrlError("URL is required");
1599
+ return;
1600
+ }
1601
+ if (!nextUrl || hasConnection || nextBase || nextUrl.includes("{{")) {
1602
+ setUrlError(void 0);
1603
+ return;
1604
+ }
1605
+ try {
1606
+ new URL(nextUrl);
1607
+ setUrlError(void 0);
1608
+ } catch {
1609
+ setUrlError("URL does not look valid");
1610
+ }
1611
+ };
1612
+ const bodyMode = cfg.apiType === "graphql" ? "graphql" : cfg.bodyType ?? "json";
1613
+ const gqlVarCount = useMemo3(
1614
+ () => countGraphqlVariables(cfg.graphqlQuery ?? ""),
1615
+ [cfg.graphqlQuery]
1272
1616
  );
1273
- const bodyTab = /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1274
- /* @__PURE__ */ jsx2(
1275
- SegmentedControl,
1276
- {
1277
- size: "sm",
1278
- "aria-label": "Body type",
1279
- options: [
1280
- { value: "none", label: "None" },
1281
- { value: "json", label: "JSON" },
1282
- { value: "graphql", label: "GraphQL" },
1283
- { value: "raw", label: "Raw" },
1284
- { value: "form-data", label: "Form" },
1285
- { value: "xml", label: "XML" }
1286
- ],
1287
- value: bodyMode,
1288
- onChange: handleBodyMode
1289
- }
1290
- ),
1291
- bodyMode === "none" && /* @__PURE__ */ jsx2("p", { className: "ods-http-panel__empty", children: "No request body will be sent." }),
1292
- (bodyMode === "json" || bodyMode === "raw" || bodyMode === "xml") && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1293
- /* @__PURE__ */ jsx2(
1294
- CodeEditor,
1295
- {
1296
- value: bodyText,
1297
- language: bodyMode === "json" ? "json" : "plain",
1298
- onChange: (next) => {
1299
- setBodyText(next);
1300
- if (bodyMode === "json" && next.trim()) {
1301
- try {
1302
- emit({ body: JSON.parse(next) });
1303
- setBodyError(void 0);
1304
- } catch {
1305
- setBodyError("Invalid JSON");
1306
- emit({ body: next });
1307
- }
1308
- } else {
1309
- setBodyError(void 0);
1310
- emit({ body: next || void 0 });
1311
- }
1312
- },
1313
- readOnly: disabled,
1314
- minHeight: 180,
1315
- ariaLabel: "Request body"
1316
- }
1317
- ),
1318
- bodyError && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__error", children: [
1319
- "\u26A0 ",
1320
- bodyError
1321
- ] }),
1322
- /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1323
- "Supports ",
1324
- /* @__PURE__ */ jsx2("code", { children: "{{variable}}" }),
1325
- " interpolation."
1326
- ] })
1327
- ] }),
1328
- bodyMode === "graphql" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1329
- /* @__PURE__ */ jsx2(
1330
- CodeEditor,
1331
- {
1332
- value: cfg.graphqlQuery ?? "",
1333
- language: "plain",
1334
- onChange: (next) => emit({ graphqlQuery: next }),
1335
- readOnly: disabled,
1336
- minHeight: 160,
1337
- placeholder: "query ($id: ID!) {\n node(id: $id) { id }\n}",
1338
- ariaLabel: "GraphQL query"
1339
- }
1340
- ),
1341
- /* @__PURE__ */ jsxs2(
1342
- Disclosure,
1343
- {
1344
- size: "sm",
1345
- title: "Variables",
1346
- defaultOpen: Boolean(gqlVarsText.trim()) || gqlVarCount > 0,
1347
- trailing: gqlVarCount > 0 ? /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "neutral", children: gqlVarCount }) : void 0,
1348
- children: [
1349
- /* @__PURE__ */ jsx2(
1350
- CodeEditor,
1351
- {
1352
- value: gqlVarsText,
1353
- language: "json",
1354
- onChange: (next) => {
1355
- setGqlVarsText(next);
1356
- if (!next.trim()) {
1357
- setGqlVarsError(void 0);
1358
- emit({ graphqlVariables: {} });
1359
- return;
1360
- }
1361
- try {
1362
- emit({ graphqlVariables: JSON.parse(next) });
1363
- setGqlVarsError(void 0);
1364
- } catch {
1365
- setGqlVarsError("Invalid JSON");
1366
- }
1367
- },
1368
- readOnly: disabled,
1369
- minHeight: 100,
1370
- ariaLabel: "GraphQL variables (JSON)"
1371
- }
1372
- ),
1373
- gqlVarsError && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__error", children: [
1374
- "\u26A0 ",
1375
- gqlVarsError
1376
- ] })
1377
- ]
1378
- }
1379
- )
1380
- ] }),
1381
- bodyMode === "form-data" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1382
- /* @__PURE__ */ jsx2("p", { className: "ods-http-panel__desc", children: "Multipart form fields. Content-Type is set automatically with the boundary." }),
1383
- /* @__PURE__ */ jsx2(
1384
- KeyValueEditor,
1385
- {
1386
- pairs: toPairs(formData),
1387
- onChange: (pairs) => (
1388
- // Raw rows — see the params/headers note; serializing here ate
1389
- // the blank row that Add appends.
1390
- emit({ formData: pairs, bodyType: "form-data" })
1391
- ),
1392
- rowToggle: true,
1393
- keyPlaceholder: "field name",
1394
- valuePlaceholder: "value or {{var}}",
1395
- emptyLabel: "No fields yet \u2014 click Add to begin.",
1396
- addLabel: "Add",
1397
- size: "sm",
1398
- disabled
1399
- }
1400
- )
1401
- ] })
1402
- ] });
1403
- const authTab = /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1404
- connectionId ? /* @__PURE__ */ jsxs2(Banner, { variant: "success", appearance: "subtle", size: "sm", children: [
1405
- "Authentication is inherited from ",
1406
- /* @__PURE__ */ jsx2("strong", { children: selectedConnection?.name }),
1407
- " and refreshed automatically. Choose a type below only to override it for this step."
1408
- ] }) : /* @__PURE__ */ jsx2(Banner, { variant: "info", appearance: "subtle", size: "sm", children: "For OAuth 2.0 APIs that need a login screen, create an API connection and select it as the Source \u2014 the token is obtained once and refreshed automatically. Inline auth here is for non-interactive credentials only." }),
1409
- /* @__PURE__ */ jsx2(
1410
- Select,
1411
- {
1412
- id: `${uid}-auth-type`,
1413
- label: connectionId ? "Override authentication" : "Authentication",
1414
- value: auth.type,
1415
- onChange: (next) => emit({ auth: { ...auth, type: next } }),
1416
- disabled,
1417
- options: [
1418
- { value: "none", label: connectionId ? "Inherit from connection" : "No auth" },
1419
- { value: "basic", label: "Basic auth (username + password)" },
1420
- { value: "bearer", label: "Bearer token" },
1421
- { value: "apikey", label: "API key" }
1422
- ]
1423
- }
1424
- ),
1425
- auth.type === "basic" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1426
- /* @__PURE__ */ jsx2(
1427
- Input,
1428
- {
1429
- id: `${uid}-auth-user`,
1430
- label: "Username",
1431
- value: auth.username ?? "",
1432
- placeholder: "username or {{var}}",
1433
- onChange: (e) => emit({ auth: { ...auth, username: e.target.value } }),
1434
- disabled
1435
- }
1436
- ),
1437
- /* @__PURE__ */ jsx2(
1438
- PasswordInput,
1439
- {
1440
- id: `${uid}-auth-pass`,
1441
- label: "Password",
1442
- value: auth.password ?? "",
1443
- placeholder: "password or {{var}}",
1444
- onChange: (next) => emit({ auth: { ...auth, password: next } }),
1445
- disabled
1446
- }
1447
- )
1448
- ] }),
1449
- auth.type === "bearer" && /* @__PURE__ */ jsx2(
1450
- PasswordInput,
1451
- {
1452
- id: `${uid}-auth-token`,
1453
- label: "Token",
1454
- value: auth.token ?? "",
1455
- placeholder: "your-token or {{step.token}}",
1456
- onChange: (next) => emit({ auth: { ...auth, token: next } }),
1457
- disabled
1458
- }
1459
- ),
1460
- auth.type === "apikey" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1461
- /* @__PURE__ */ jsx2(
1462
- Input,
1463
- {
1464
- id: `${uid}-auth-keyname`,
1465
- label: "Key name",
1466
- value: auth.apiKeyName ?? "",
1467
- placeholder: "X-API-Key",
1468
- onChange: (e) => emit({ auth: { ...auth, apiKeyName: e.target.value } }),
1469
- disabled
1470
- }
1471
- ),
1472
- /* @__PURE__ */ jsx2(
1473
- PasswordInput,
1474
- {
1475
- id: `${uid}-auth-keyval`,
1476
- label: "Key value",
1477
- value: auth.apiKeyValue ?? "",
1478
- placeholder: "your-api-key or {{var}}",
1479
- onChange: (next) => emit({ auth: { ...auth, apiKeyValue: next } }),
1480
- disabled
1481
- }
1482
- ),
1483
- /* @__PURE__ */ jsx2(
1484
- Select,
1485
- {
1486
- id: `${uid}-auth-keyloc`,
1487
- label: "Send in",
1488
- value: auth.apiKeyLocation ?? "header",
1489
- onChange: (next) => emit({ auth: { ...auth, apiKeyLocation: next } }),
1490
- disabled,
1491
- options: [
1492
- { value: "header", label: "Request header" },
1493
- { value: "query", label: "Query parameter" }
1494
- ]
1495
- }
1496
- )
1497
- ] }),
1498
- auth.type === "none" && /* @__PURE__ */ jsx2("p", { className: "ods-http-panel__empty", children: connectionId ? "This step uses the connection's authentication." : "No authentication will be added to this request." })
1499
- ] });
1500
- const responseTab = /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1501
- /* @__PURE__ */ jsx2(
1502
- SegmentedControl,
1617
+ const handleBodyMode = (next) => {
1618
+ if (next === "graphql") {
1619
+ emit(method === "GET" ? { apiType: "graphql", method: "POST" } : { apiType: "graphql" });
1620
+ return;
1621
+ }
1622
+ emit({ apiType: "rest", bodyType: next });
1623
+ };
1624
+ const [preview, setPreview] = useState5({ phase: "idle" });
1625
+ const [schemaInfo, setSchemaInfo] = useState5(
1626
+ null
1627
+ );
1628
+ const lastSchemaKey = useRef5("");
1629
+ const inferAndEmitSchema = useCallback4(
1630
+ (response, forceRefresh = false) => {
1631
+ if (responseType === "binary") return;
1632
+ const snapshot = {
1633
+ status: response.status,
1634
+ statusText: response.statusText,
1635
+ ok: response.ok,
1636
+ headers: response.headers,
1637
+ body: response.body,
1638
+ duration: response.duration,
1639
+ attempts: 1,
1640
+ extracted: {}
1641
+ };
1642
+ const inferred = inferJsonSchema(snapshot);
1643
+ const key = JSON.stringify(inferred);
1644
+ if (!forceRefresh && key === lastSchemaKey.current) return;
1645
+ lastSchemaKey.current = key;
1646
+ emit({ _outputSchema: inferred });
1647
+ const bodySchema = inferred.properties?.body;
1648
+ const topKeys = Object.keys(bodySchema?.properties ?? inferred.properties ?? {});
1649
+ setSchemaInfo({ pathCount: countSchemaPaths(inferred), topKeys });
1650
+ },
1651
+ [emit, responseType]
1652
+ );
1653
+ const sendPreview = async () => {
1654
+ if (!onSendPreview) return;
1655
+ setPreview({ phase: "loading" });
1656
+ try {
1657
+ const response = await onSendPreview({
1658
+ method: cfg.apiType === "graphql" ? "POST" : method,
1659
+ url: connectionId ? url : fullUrl,
1660
+ connectionId: connectionId || void 0,
1661
+ headers,
1662
+ queryParams,
1663
+ bodyType: cfg.bodyType ?? "json",
1664
+ body: bodyText,
1665
+ formData,
1666
+ graphqlQuery: cfg.apiType === "graphql" ? cfg.graphqlQuery ?? "" : void 0,
1667
+ graphqlVariables: cfg.apiType === "graphql" ? gqlVarsText : void 0,
1668
+ auth,
1669
+ timeout: cfg.timeout ?? 3e4
1670
+ });
1671
+ setPreview({ phase: "success", response });
1672
+ inferAndEmitSchema(response);
1673
+ } catch (err) {
1674
+ setPreview({
1675
+ phase: "error",
1676
+ message: err instanceof Error ? err.message : "Request failed"
1677
+ });
1678
+ }
1679
+ };
1680
+ const previewUrlOk = Boolean(fullUrl) && !fullUrl.includes("{{");
1681
+ const canSend = Boolean(onSendPreview) && previewUrlOk && preview.phase !== "loading" && !disabled;
1682
+ const paramCount = queryParams.filter((r) => r.enabled !== false && r.key).length;
1683
+ const headerCount = headers.filter((r) => r.enabled !== false && r.key).length;
1684
+ const extractCount = extractRows.filter((r) => r.key).length;
1685
+ const advancedSummary = [];
1686
+ if ((cfg.timeout ?? 3e4) !== 3e4) advancedSummary.push(`${(cfg.timeout ?? 0) / 1e3}s`);
1687
+ if ((retry?.maxAttempts ?? 1) > 1) advancedSummary.push(`retry \xD7${retry?.maxAttempts}`);
1688
+ if (pagination.type !== "none") advancedSummary.push(pagination.type.replace("_", " "));
1689
+ if (extractCount > 0) advancedSummary.push(`${extractCount} extracted`);
1690
+ const kvTab = (rows, key, desc, keyPlaceholder) => /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__tab", children: [
1691
+ /* @__PURE__ */ jsx4("p", { className: "ods-http-panel__desc", children: desc }),
1692
+ /* @__PURE__ */ jsx4(
1693
+ KeyValueEditor,
1503
1694
  {
1695
+ pairs: toPairs(rows),
1696
+ onChange: (pairs) => emit({ [key]: pairs }),
1697
+ rowToggle: true,
1698
+ showDescription: true,
1699
+ keyPlaceholder,
1700
+ valuePlaceholder: "value or {{var}}",
1701
+ emptyLabel: "No entries yet \u2014 click Add to begin.",
1702
+ addLabel: "Add",
1504
1703
  size: "sm",
1505
- "aria-label": "Response type",
1506
- options: [
1507
- { value: "json", label: "JSON" },
1508
- { value: "text", label: "Text" },
1509
- { value: "binary", label: "Binary" }
1510
- ],
1511
- value: responseType,
1512
- onChange: (next) => emit({ responseType: next })
1513
- }
1514
- ),
1515
- /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1516
- responseType === "json" && "Tries to parse the response body as JSON; falls back to raw text on parse failure.",
1517
- responseType === "text" && "Returns the raw response body as a string. No JSON parsing.",
1518
- responseType === "binary" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1519
- "Returns the response as bytes with ",
1520
- /* @__PURE__ */ jsx2("code", { children: "fileName" }),
1521
- ", ",
1522
- /* @__PURE__ */ jsx2("code", { children: "contentType" }),
1523
- " and",
1524
- " ",
1525
- /* @__PURE__ */ jsx2("code", { children: "size" }),
1526
- " on the step output \u2014 for downloads feeding file actions. Pagination is not compatible with binary responses."
1527
- ] })
1528
- ] }),
1529
- /* @__PURE__ */ jsx2(
1530
- Input,
1531
- {
1532
- id: `${uid}-expected-ct`,
1533
- label: "Expected content-types (optional)",
1534
- value: expectedCtText,
1535
- placeholder: "application/pdf, application/json",
1536
- helperText: "Comma-separated. A warning is logged when the response Content-Type matches none of these.",
1537
- onChange: (e) => {
1538
- setExpectedCtText(e.target.value);
1539
- const parts = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
1540
- emit({ expectedContentTypes: parts.length ? parts : void 0 });
1541
- },
1542
1704
  disabled
1543
1705
  }
1544
- ),
1545
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__schema", children: [
1546
- /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__schema-label", children: "Data stream schema" }),
1547
- schemaInfo ? /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1548
- /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: "success", children: [
1549
- schemaInfo.pathCount,
1550
- " paths"
1551
- ] }),
1552
- " ",
1553
- "Inferred from the last test send. Top-level keys:",
1554
- " ",
1555
- /* @__PURE__ */ jsx2("code", { children: schemaInfo.topKeys.join(", ") }),
1556
- ". Downstream binding (Field Mapper, Transform, ForEach) sees this shape immediately; Save persists it."
1557
- ] }) : /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1558
- "No schema yet \u2014 hit ",
1559
- /* @__PURE__ */ jsx2("strong", { children: "Send" }),
1560
- " in the request bar. On success the response shape is inferred so downstream steps bind against real field names."
1561
- ] }),
1562
- /* @__PURE__ */ jsx2(
1563
- Button,
1564
- {
1565
- variant: "ghost",
1566
- size: "sm",
1567
- disabled: preview.phase !== "success" || responseType === "binary" || disabled,
1568
- onClick: () => {
1569
- if (preview.phase === "success") inferAndEmitSchema(preview.response, true);
1570
- },
1571
- children: "Refresh schema from last send"
1572
- }
1573
- )
1574
- ] })
1706
+ )
1575
1707
  ] });
1576
- const tabItems = [
1577
- {
1578
- value: "params",
1579
- label: paramCount ? `Params \xB7 ${paramCount}` : "Params",
1580
- icon: /* @__PURE__ */ jsx2(ListIcon, { size: "sm" }),
1581
- children: paramsTab
1582
- },
1583
- {
1584
- value: "headers",
1585
- label: headerCount ? `Headers \xB7 ${headerCount}` : "Headers",
1586
- icon: /* @__PURE__ */ jsx2(TagIcon, { size: "sm" }),
1587
- children: headersTab
1588
- },
1589
- {
1590
- value: "body",
1591
- label: bodyMode === "none" ? "Body" : `Body \xB7 ${bodyMode === "form-data" ? "form" : bodyMode}`,
1592
- icon: /* @__PURE__ */ jsx2(CodeIcon, { size: "sm" }),
1593
- children: bodyTab
1594
- },
1595
- {
1596
- value: "auth",
1597
- label: auth.type === "none" ? "Auth" : `Auth \xB7 ${auth.type}`,
1598
- icon: /* @__PURE__ */ jsx2(LockedIcon, { size: "sm" }),
1599
- children: authTab
1600
- },
1601
- {
1602
- value: "response",
1603
- label: responseType === "json" ? "Response" : `Response \xB7 ${responseType}`,
1604
- icon: /* @__PURE__ */ jsx2(DataViewAltIcon, { size: "sm" }),
1605
- children: responseTab
1606
- }
1607
- ];
1608
- return /* @__PURE__ */ jsxs2("div", { className: cn("ods-http-panel", className), children: [
1609
- connections && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__source", children: [
1610
- /* @__PURE__ */ jsx2(
1611
- Select,
1612
- {
1613
- id: `${uid}-connection`,
1614
- label: "Source",
1615
- placeholder: connectionsLoading ? "Loading\u2026" : "No connection (manual config)",
1616
- options: connections.map((c) => ({ value: c.id, label: c.name, icon: c.icon })),
1617
- value: connectionId,
1618
- onChange: (next) => emit({ connectionId: next || void 0 }),
1619
- disabled: disabled || connectionsLoading,
1620
- helperText: connectionId ? `Base URL and auth are inherited from "${selectedConnection?.name}" at runtime.` : "Select a saved API connection to inherit base URL and auth, or configure manually."
1621
- }
1622
- ),
1623
- connectionId && endpoints.length > 0 && /* @__PURE__ */ jsx2(
1624
- Combobox,
1625
- {
1626
- ariaLabel: "Endpoint",
1627
- placeholder: `Search ${endpoints.length} discovered endpoints\u2026`,
1628
- value: url,
1629
- creatable: true,
1630
- options: endpoints.map((e) => ({
1631
- value: e.path,
1632
- label: `${e.method} ${e.path}`,
1633
- description: e.summary
1634
- })),
1635
- onChange: (nextPath) => {
1636
- const match = endpoints.find((e) => e.path === nextPath);
1637
- validateUrl(nextPath, baseUrl, true);
1638
- emit(
1639
- match && HTTP_METHODS.includes(match.method.toUpperCase()) ? { url: nextPath, method: match.method.toUpperCase() } : { url: nextPath }
1640
- );
1641
- }
1642
- }
1643
- )
1644
- ] }),
1645
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__bar", children: [
1646
- /* @__PURE__ */ jsx2(
1647
- Select,
1648
- {
1649
- id: `${uid}-method`,
1650
- "aria-label": "HTTP method",
1651
- className: cn(
1652
- "ods-http-panel__method",
1653
- `ods-http-panel__method--${method.toLowerCase()}`
1654
- ),
1655
- value: method,
1656
- onChange: (next) => emit({ method: next }),
1657
- disabled,
1658
- options: HTTP_METHODS.map((m) => ({ value: m, label: m }))
1659
- }
1660
- ),
1661
- /* @__PURE__ */ jsx2(
1662
- Input,
1663
- {
1664
- id: `${uid}-url`,
1665
- "aria-label": connectionId ? "Path" : "URL",
1666
- "data-fx-droppable": "true",
1667
- className: "ods-http-panel__url",
1668
- value: url,
1669
- placeholder: connectionId ? "/v1/users" : "https://api.example.com/v1/users or {{var}}",
1670
- error: Boolean(urlError),
1671
- errorMessage: urlError,
1672
- onChange: (e) => {
1673
- validateUrl(e.target.value, baseUrl, Boolean(connectionId));
1674
- emit({ url: e.target.value });
1675
- },
1676
- disabled
1677
- }
1678
- ),
1679
- onSendPreview && /* @__PURE__ */ jsx2(
1680
- Button,
1681
- {
1682
- variant: "primary",
1683
- size: "md",
1684
- leftIcon: preview.phase === "loading" ? void 0 : /* @__PURE__ */ jsx2(PlayIcon, { size: "sm" }),
1685
- onClick: sendPreview,
1686
- disabled: !canSend,
1687
- children: preview.phase === "loading" ? "Sending\u2026" : "Send"
1688
- }
1689
- )
1690
- ] }),
1691
- !connectionId && baseUrl && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__effective", children: [
1692
- /* @__PURE__ */ jsx2("span", { children: "Effective URL:" }),
1693
- " ",
1694
- /* @__PURE__ */ jsx2("code", { children: fullUrl || "\u2014" })
1695
- ] }),
1696
- fullUrl.includes("{{") && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1697
- "\u26A0 URL contains ",
1698
- /* @__PURE__ */ jsx2("code", { children: "{{variables}}" }),
1699
- " \u2014 they resolve at run time; testing needs literal values."
1700
- ] }),
1701
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tools", children: [
1702
- /* @__PURE__ */ jsxs2("span", { className: "ods-http-panel__hint", children: [
1703
- "Use ",
1704
- /* @__PURE__ */ jsx2("code", { children: "{{stepName.fieldPath}}" }),
1705
- " to insert data from upstream steps."
1706
- ] }),
1707
- /* @__PURE__ */ jsxs2("span", { className: "ods-http-panel__tools-actions", children: [
1708
- /* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "sm", onClick: () => setCurlOpen((o) => !o), children: "Import cURL" }),
1709
- /* @__PURE__ */ jsx2(
1710
- Button,
1711
- {
1712
- variant: "ghost",
1713
- size: "sm",
1714
- className: cn(copiedCurl && "ods-http-panel__copy-done"),
1715
- leftIcon: copiedCurl ? /* @__PURE__ */ jsx2(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx2(CopyIcon, { size: "sm" }),
1716
- onClick: copyCurl,
1717
- children: copiedCurl ? "Copied" : "Copy as cURL"
1718
- }
1719
- )
1720
- ] })
1708
+ const paramsTab = kvTab(
1709
+ queryParams,
1710
+ "queryParams",
1711
+ /* @__PURE__ */ jsxs4(Fragment2, { children: [
1712
+ "Query parameters appended to the URL. Supports ",
1713
+ /* @__PURE__ */ jsx4("code", { children: "{{variable}}" }),
1714
+ " interpolation."
1721
1715
  ] }),
1722
- curlOpen && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__curl", children: [
1723
- /* @__PURE__ */ jsx2(
1724
- CodeEditor,
1725
- {
1726
- value: curlText,
1727
- language: "plain",
1728
- onChange: setCurlText,
1729
- minHeight: 90,
1730
- showLineNumbers: false,
1731
- placeholder: "curl -X POST 'https://api.example.com/v1/users' -H 'Authorization: Bearer \u2026' -d '{\u2026}'",
1732
- ariaLabel: "cURL command"
1733
- }
1734
- ),
1735
- curlError && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__error", children: [
1736
- "\u26A0 ",
1737
- curlError
1738
- ] }),
1739
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__curl-actions", children: [
1740
- /* @__PURE__ */ jsx2(Button, { variant: "secondary", size: "sm", onClick: () => setCurlOpen(false), children: "Cancel" }),
1741
- /* @__PURE__ */ jsx2(Button, { variant: "primary", size: "sm", onClick: applyCurl, disabled: !curlText.trim(), children: "Apply to request" })
1742
- ] })
1716
+ "param name"
1717
+ );
1718
+ const headersTab = kvTab(
1719
+ headers,
1720
+ "headers",
1721
+ /* @__PURE__ */ jsxs4(Fragment2, { children: [
1722
+ "Request headers. Auth headers are injected automatically from the Auth tab. Supports",
1723
+ " ",
1724
+ /* @__PURE__ */ jsx4("code", { children: "{{variable}}" }),
1725
+ " interpolation."
1743
1726
  ] }),
1744
- /* @__PURE__ */ jsx2(Tabs, { items: tabItems, defaultValue: "params", className: "ods-http-panel__tabs" }),
1745
- /* @__PURE__ */ jsxs2(
1746
- Disclosure,
1727
+ "Header-Name"
1728
+ );
1729
+ const bodyTab = /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__tab", children: [
1730
+ /* @__PURE__ */ jsx4(
1731
+ SegmentedControl,
1747
1732
  {
1748
1733
  size: "sm",
1749
- icon: /* @__PURE__ */ jsx2(GlobeIcon, { size: "sm" }),
1750
- title: "Advanced options",
1751
- description: "Base URL, timeout, retries, pagination, response extraction",
1752
- trailing: advancedSummary.length ? /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "info", children: advancedSummary.join(" \xB7 ") }) : void 0,
1753
- children: [
1754
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1755
- !connectionId && /* @__PURE__ */ jsx2(
1756
- Input,
1757
- {
1758
- id: `${uid}-base-url`,
1759
- label: "Base URL (optional)",
1760
- value: baseUrl,
1761
- placeholder: "https://api.example.com",
1762
- helperText: "Combined with the request path to form the full URL.",
1763
- onChange: (e) => {
1764
- validateUrl(url, e.target.value, Boolean(connectionId));
1765
- emit({ baseUrl: e.target.value || void 0 });
1766
- },
1767
- disabled
1768
- }
1769
- ),
1770
- /* @__PURE__ */ jsx2(
1771
- NumberInput,
1772
- {
1773
- label: "Timeout (ms)",
1774
- helperText: "Minimum 1000 ms",
1775
- value: cfg.timeout ?? 3e4,
1776
- min: 1e3,
1777
- step: 1e3,
1778
- onChange: (next) => {
1779
- if (!Number.isNaN(next) && next >= 1e3) emit({ timeout: next });
1780
- },
1781
- disabled
1782
- }
1783
- ),
1784
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__switches", children: [
1785
- /* @__PURE__ */ jsx2(
1786
- Switch,
1787
- {
1788
- id: `${uid}-fail`,
1789
- label: "Fail on non-2xx",
1790
- checked: cfg.failOnError !== false,
1791
- onChange: (next) => emit({ failOnError: next }),
1792
- disabled,
1793
- size: "sm"
1794
- }
1795
- ),
1796
- /* @__PURE__ */ jsx2(
1797
- Switch,
1798
- {
1799
- id: `${uid}-redirects`,
1800
- label: "Follow redirects",
1801
- checked: cfg.followRedirects !== false,
1802
- onChange: (next) => emit({ followRedirects: next }),
1803
- disabled,
1804
- size: "sm"
1805
- }
1806
- )
1807
- ] })
1808
- ] }),
1809
- /* @__PURE__ */ jsx2(
1810
- Disclosure,
1811
- {
1812
- size: "sm",
1813
- title: "Retry",
1814
- trailing: (retry?.maxAttempts ?? 1) > 1 ? /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: "info", children: [
1815
- retry?.maxAttempts,
1816
- " attempts"
1817
- ] }) : void 0,
1818
- children: /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1819
- /* @__PURE__ */ jsx2(
1820
- NumberInput,
1821
- {
1822
- label: "Max attempts",
1823
- helperText: "1 = no retry",
1824
- value: retry?.maxAttempts ?? 1,
1825
- min: 1,
1826
- max: 10,
1827
- onChange: (next) => {
1828
- if (!Number.isNaN(next) && next >= 1 && retry) {
1829
- emit({ retry: { ...retry, maxAttempts: next } });
1830
- }
1831
- },
1832
- disabled
1833
- }
1834
- ),
1835
- /* @__PURE__ */ jsx2(
1836
- Input,
1837
- {
1838
- id: `${uid}-retry-on`,
1839
- label: "Retry on (status codes)",
1840
- value: retryOnText,
1841
- placeholder: "429, 500, 502, 503, 504",
1842
- helperText: "Comma-separated HTTP status codes",
1843
- onChange: (e) => {
1844
- setRetryOnText(e.target.value);
1845
- const codes = e.target.value.split(",").map((s) => Number.parseInt(s.trim(), 10)).filter((n) => !Number.isNaN(n));
1846
- if (retry) emit({ retry: { ...retry, retryOn: codes } });
1847
- },
1848
- disabled
1849
- }
1850
- ),
1851
- /* @__PURE__ */ jsx2(
1852
- NumberInput,
1853
- {
1854
- label: "Initial delay (ms)",
1855
- value: retry?.initialDelayMs ?? 1e3,
1856
- min: 100,
1857
- step: 100,
1858
- onChange: (next) => {
1859
- if (!Number.isNaN(next) && retry)
1860
- emit({ retry: { ...retry, initialDelayMs: next } });
1861
- },
1862
- disabled
1863
- }
1864
- ),
1865
- /* @__PURE__ */ jsx2(
1866
- NumberInput,
1867
- {
1868
- label: "Backoff factor",
1869
- helperText: "Delay multiplies by this each retry",
1870
- value: retry?.backoffFactor ?? 2,
1871
- min: 1,
1872
- step: 0.5,
1873
- onChange: (next) => {
1874
- if (!Number.isNaN(next) && retry)
1875
- emit({ retry: { ...retry, backoffFactor: next } });
1876
- },
1877
- disabled
1878
- }
1879
- ),
1880
- /* @__PURE__ */ jsx2(
1881
- NumberInput,
1882
- {
1883
- label: "Max delay (ms)",
1884
- value: retry?.maxDelayMs ?? 3e4,
1885
- min: 1e3,
1886
- step: 1e3,
1887
- onChange: (next) => {
1888
- if (!Number.isNaN(next) && retry) emit({ retry: { ...retry, maxDelayMs: next } });
1889
- },
1890
- disabled
1891
- }
1892
- )
1893
- ] })
1894
- }
1895
- ),
1896
- /* @__PURE__ */ jsxs2(
1897
- Disclosure,
1898
- {
1899
- size: "sm",
1900
- title: "Pagination",
1901
- trailing: pagination.type !== "none" ? /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "success", children: pagination.type.replace("_", " ") }) : void 0,
1902
- children: [
1903
- /* @__PURE__ */ jsx2(
1904
- Select,
1905
- {
1906
- id: `${uid}-pag-type`,
1907
- label: "Strategy",
1908
- value: pagination.type,
1909
- onChange: (next) => emit({
1910
- pagination: { ...pagination, type: next }
1911
- }),
1912
- disabled,
1913
- options: [
1914
- { value: "none", label: "None (single request)" },
1915
- { value: "offset", label: "Offset / page number" },
1916
- { value: "cursor", label: "Cursor-based" },
1917
- { value: "link_header", label: 'Link header (rel="next")' },
1918
- { value: "next_url", label: "Next URL in response body" }
1919
- ]
1920
- }
1921
- ),
1922
- pagination.type !== "none" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1923
- /* @__PURE__ */ jsx2(
1924
- NumberInput,
1925
- {
1926
- label: "Max pages",
1927
- helperText: "Safety cap against infinite loops",
1928
- value: pagination.maxPages ?? 20,
1929
- min: 1,
1930
- onChange: (next) => {
1931
- if (!Number.isNaN(next)) emit({ pagination: { ...pagination, maxPages: next } });
1932
- },
1933
- disabled
1934
- }
1935
- ),
1936
- /* @__PURE__ */ jsx2(
1937
- Input,
1938
- {
1939
- id: `${uid}-pag-merge`,
1940
- label: "Merge key (dot path)",
1941
- value: pagination.mergeKey ?? "",
1942
- placeholder: "data.items",
1943
- helperText: "Array to merge across pages",
1944
- onChange: (e) => emit({ pagination: { ...pagination, mergeKey: e.target.value } }),
1945
- disabled
1946
- }
1947
- ),
1948
- (pagination.type === "offset" || pagination.type === "cursor") && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1949
- /* @__PURE__ */ jsx2(
1950
- Input,
1951
- {
1952
- id: `${uid}-pag-limitp`,
1953
- label: "Limit param",
1954
- value: pagination.limitParam ?? "limit",
1955
- onChange: (e) => emit({ pagination: { ...pagination, limitParam: e.target.value } }),
1956
- disabled
1957
- }
1958
- ),
1959
- /* @__PURE__ */ jsx2(
1960
- NumberInput,
1961
- {
1962
- label: "Page size",
1963
- value: pagination.limitValue ?? 100,
1964
- min: 1,
1965
- onChange: (next) => {
1966
- if (!Number.isNaN(next)) {
1967
- emit({ pagination: { ...pagination, limitValue: next } });
1968
- }
1969
- },
1970
- disabled
1971
- }
1972
- )
1973
- ] }),
1974
- pagination.type === "offset" && /* @__PURE__ */ jsx2(
1975
- Input,
1976
- {
1977
- id: `${uid}-pag-offset`,
1978
- label: "Offset param",
1979
- value: pagination.offsetParam ?? "offset",
1980
- onChange: (e) => emit({ pagination: { ...pagination, offsetParam: e.target.value } }),
1981
- disabled
1982
- }
1983
- ),
1984
- pagination.type === "cursor" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1985
- /* @__PURE__ */ jsx2(
1986
- Input,
1987
- {
1988
- id: `${uid}-pag-cres`,
1989
- label: "Cursor in response (dot path)",
1990
- value: pagination.cursorResponsePath ?? "",
1991
- placeholder: "meta.next_cursor",
1992
- onChange: (e) => emit({ pagination: { ...pagination, cursorResponsePath: e.target.value } }),
1993
- disabled
1994
- }
1995
- ),
1996
- /* @__PURE__ */ jsx2(
1997
- Input,
1998
- {
1999
- id: `${uid}-pag-creq`,
2000
- label: "Cursor request param",
2001
- value: pagination.cursorRequestParam ?? "cursor",
2002
- onChange: (e) => emit({ pagination: { ...pagination, cursorRequestParam: e.target.value } }),
2003
- disabled
2004
- }
2005
- )
2006
- ] }),
2007
- pagination.type === "next_url" && /* @__PURE__ */ jsx2(
2008
- Input,
2009
- {
2010
- id: `${uid}-pag-next`,
2011
- label: "Next URL in response (dot path)",
2012
- value: pagination.nextUrlResponsePath ?? "",
2013
- placeholder: "links.next",
2014
- onChange: (e) => emit({ pagination: { ...pagination, nextUrlResponsePath: e.target.value } }),
2015
- disabled
2016
- }
2017
- )
2018
- ] })
2019
- ]
1734
+ "aria-label": "Body type",
1735
+ options: [
1736
+ { value: "none", label: "None" },
1737
+ { value: "json", label: "JSON" },
1738
+ { value: "graphql", label: "GraphQL" },
1739
+ { value: "raw", label: "Raw" },
1740
+ { value: "form-data", label: "Form" },
1741
+ { value: "xml", label: "XML" }
1742
+ ],
1743
+ value: bodyMode,
1744
+ onChange: handleBodyMode
1745
+ }
1746
+ ),
1747
+ bodyMode === "none" && /* @__PURE__ */ jsx4("p", { className: "ods-http-panel__empty", children: "No request body will be sent." }),
1748
+ (bodyMode === "json" || bodyMode === "raw" || bodyMode === "xml") && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1749
+ /* @__PURE__ */ jsx4(
1750
+ CodeEditor,
1751
+ {
1752
+ value: bodyText,
1753
+ language: bodyMode === "json" ? "json" : "plain",
1754
+ onChange: (next) => {
1755
+ setBodyText(next);
1756
+ if (bodyMode === "json" && next.trim()) {
1757
+ try {
1758
+ emit({ body: JSON.parse(next) });
1759
+ setBodyError(void 0);
1760
+ } catch {
1761
+ setBodyError("Invalid JSON");
1762
+ emit({ body: next });
1763
+ }
1764
+ } else {
1765
+ setBodyError(void 0);
1766
+ emit({ body: next || void 0 });
2020
1767
  }
2021
- ),
2022
- /* @__PURE__ */ jsxs2(
2023
- Disclosure,
2024
- {
2025
- size: "sm",
2026
- title: "Response field extraction",
2027
- trailing: extractCount > 0 ? /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: "info", children: [
2028
- extractCount,
2029
- " field",
2030
- extractCount === 1 ? "" : "s"
2031
- ] }) : void 0,
2032
- children: [
2033
- /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
2034
- "Extract fields from the response with dot paths \u2014 available downstream as",
2035
- " ",
2036
- /* @__PURE__ */ jsx2("code", { children: "{{extracted.keyName}}" }),
2037
- "."
2038
- ] }),
2039
- /* @__PURE__ */ jsx2(
2040
- KeyValueEditor,
2041
- {
2042
- pairs: extractRows.map((r) => ({ key: r.key, value: r.value })),
2043
- onChange: (pairs) => {
2044
- const rows = pairs.map((p) => ({ key: p.key, value: p.value, enabled: true }));
2045
- setExtractRows(rows);
2046
- const extract = {};
2047
- for (const r of rows) if (r.key && r.value) extract[r.key] = r.value;
2048
- emit({ extract: Object.keys(extract).length ? extract : void 0 });
2049
- },
2050
- keyPlaceholder: "outputKey",
2051
- valuePlaceholder: "data.user.id",
2052
- emptyLabel: "No extractions yet \u2014 click Add to begin.",
2053
- addLabel: "Add",
2054
- size: "sm",
2055
- disabled
1768
+ },
1769
+ readOnly: disabled,
1770
+ minHeight: 180,
1771
+ ariaLabel: "Request body"
1772
+ }
1773
+ ),
1774
+ bodyError && /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__error", children: [
1775
+ "\u26A0 ",
1776
+ bodyError
1777
+ ] }),
1778
+ /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__desc", children: [
1779
+ "Supports ",
1780
+ /* @__PURE__ */ jsx4("code", { children: "{{variable}}" }),
1781
+ " interpolation."
1782
+ ] })
1783
+ ] }),
1784
+ bodyMode === "graphql" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1785
+ /* @__PURE__ */ jsx4(
1786
+ CodeEditor,
1787
+ {
1788
+ value: cfg.graphqlQuery ?? "",
1789
+ language: "plain",
1790
+ onChange: (next) => emit({ graphqlQuery: next }),
1791
+ readOnly: disabled,
1792
+ minHeight: 160,
1793
+ placeholder: "query ($id: ID!) {\n node(id: $id) { id }\n}",
1794
+ ariaLabel: "GraphQL query"
1795
+ }
1796
+ ),
1797
+ /* @__PURE__ */ jsxs4(
1798
+ Disclosure,
1799
+ {
1800
+ size: "sm",
1801
+ title: "Variables",
1802
+ defaultOpen: Boolean(gqlVarsText.trim()) || gqlVarCount > 0,
1803
+ trailing: gqlVarCount > 0 ? /* @__PURE__ */ jsx4(Badge, { size: "sm", variant: "neutral", children: gqlVarCount }) : void 0,
1804
+ children: [
1805
+ /* @__PURE__ */ jsx4(
1806
+ CodeEditor,
1807
+ {
1808
+ value: gqlVarsText,
1809
+ language: "json",
1810
+ onChange: (next) => {
1811
+ setGqlVarsText(next);
1812
+ if (!next.trim()) {
1813
+ setGqlVarsError(void 0);
1814
+ emit({ graphqlVariables: {} });
1815
+ return;
2056
1816
  }
2057
- )
2058
- ]
2059
- }
2060
- )
1817
+ try {
1818
+ emit({ graphqlVariables: JSON.parse(next) });
1819
+ setGqlVarsError(void 0);
1820
+ } catch {
1821
+ setGqlVarsError("Invalid JSON");
1822
+ }
1823
+ },
1824
+ readOnly: disabled,
1825
+ minHeight: 100,
1826
+ ariaLabel: "GraphQL variables (JSON)"
1827
+ }
1828
+ ),
1829
+ gqlVarsError && /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__error", children: [
1830
+ "\u26A0 ",
1831
+ gqlVarsError
1832
+ ] })
1833
+ ]
1834
+ }
1835
+ )
1836
+ ] }),
1837
+ bodyMode === "form-data" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1838
+ /* @__PURE__ */ jsx4("p", { className: "ods-http-panel__desc", children: "Multipart form fields. Content-Type is set automatically with the boundary." }),
1839
+ /* @__PURE__ */ jsx4(
1840
+ KeyValueEditor,
1841
+ {
1842
+ pairs: toPairs(formData),
1843
+ onChange: (pairs) => (
1844
+ // Raw rows — see the params/headers note; serializing here ate
1845
+ // the blank row that Add appends.
1846
+ emit({ formData: pairs, bodyType: "form-data" })
1847
+ ),
1848
+ rowToggle: true,
1849
+ keyPlaceholder: "field name",
1850
+ valuePlaceholder: "value or {{var}}",
1851
+ emptyLabel: "No fields yet \u2014 click Add to begin.",
1852
+ addLabel: "Add",
1853
+ size: "sm",
1854
+ disabled
1855
+ }
1856
+ )
1857
+ ] })
1858
+ ] });
1859
+ const authTab = /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__tab", children: [
1860
+ connectionId ? /* @__PURE__ */ jsxs4(Banner, { variant: "success", appearance: "subtle", size: "sm", children: [
1861
+ "Authentication is inherited from ",
1862
+ /* @__PURE__ */ jsx4("strong", { children: selectedConnection?.name }),
1863
+ " and refreshed automatically. Choose a type below only to override it for this step."
1864
+ ] }) : /* @__PURE__ */ jsx4(Banner, { variant: "info", appearance: "subtle", size: "sm", children: "For OAuth 2.0 APIs that need a login screen, create an API connection and select it as the Source \u2014 the token is obtained once and refreshed automatically. Inline auth here is for non-interactive credentials only." }),
1865
+ /* @__PURE__ */ jsx4(
1866
+ Select,
1867
+ {
1868
+ id: `${uid}-auth-type`,
1869
+ label: connectionId ? "Override authentication" : "Authentication",
1870
+ value: auth.type,
1871
+ onChange: (next) => emit({ auth: { ...auth, type: next } }),
1872
+ disabled,
1873
+ options: [
1874
+ { value: "none", label: connectionId ? "Inherit from connection" : "No auth" },
1875
+ { value: "basic", label: "Basic auth (username + password)" },
1876
+ { value: "bearer", label: "Bearer token" },
1877
+ { value: "apikey", label: "API key" }
2061
1878
  ]
2062
1879
  }
2063
1880
  ),
2064
- preview.phase !== "idle" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console", children: [
2065
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-head", children: [
2066
- /* @__PURE__ */ jsx2(
2067
- "code",
2068
- {
2069
- className: cn(
2070
- "ods-http-panel__console-method",
2071
- `ods-http-panel__method--${method.toLowerCase()}`
2072
- ),
2073
- children: cfg.apiType === "graphql" ? "POST" : method
2074
- }
2075
- ),
2076
- /* @__PURE__ */ jsx2("code", { className: "ods-http-panel__console-url", children: connectionId ? `${selectedConnection?.name ?? connectionId} \xB7 ${url || "\u2014"}` : fullUrl || "\u2014" }),
2077
- sectionCopyButton(fullUrl || url, "url", "request URL"),
2078
- preview.phase === "success" && /* @__PURE__ */ jsx2(
2079
- Button,
2080
- {
2081
- variant: "ghost",
2082
- size: "sm",
2083
- className: cn(copiedResult && "ods-http-panel__copy-done"),
2084
- leftIcon: copiedResult ? /* @__PURE__ */ jsx2(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx2(CopyIcon, { size: "sm" }),
2085
- onClick: copyResult,
2086
- children: copiedResult ? "Copied" : "Copy result"
2087
- }
2088
- ),
2089
- /* @__PURE__ */ jsx2(
2090
- Button,
2091
- {
2092
- variant: "ghost",
2093
- size: "sm",
2094
- leftIcon: /* @__PURE__ */ jsx2(RenewIcon, { size: "sm" }),
2095
- onClick: () => {
2096
- setPreview({ phase: "idle" });
2097
- },
2098
- disabled: preview.phase === "loading",
2099
- children: "Clear"
2100
- }
2101
- )
2102
- ] }),
2103
- preview.phase === "loading" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-loading", children: [
2104
- /* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
2105
- /* @__PURE__ */ jsx2("span", { children: "Sending request\u2026" })
2106
- ] }),
2107
- preview.phase === "error" && /* @__PURE__ */ jsxs2(Banner, { variant: "error", appearance: "subtle", size: "sm", children: [
2108
- /* @__PURE__ */ jsx2("strong", { children: "Request failed." }),
1881
+ auth.type === "basic" && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__grid", children: [
1882
+ /* @__PURE__ */ jsx4(
1883
+ Input,
1884
+ {
1885
+ id: `${uid}-auth-user`,
1886
+ label: "Username",
1887
+ value: auth.username ?? "",
1888
+ placeholder: "username or {{var}}",
1889
+ onChange: (e) => emit({ auth: { ...auth, username: e.target.value } }),
1890
+ disabled
1891
+ }
1892
+ ),
1893
+ /* @__PURE__ */ jsx4(
1894
+ PasswordInput,
1895
+ {
1896
+ id: `${uid}-auth-pass`,
1897
+ label: "Password",
1898
+ value: auth.password ?? "",
1899
+ placeholder: "password or {{var}}",
1900
+ onChange: (next) => emit({ auth: { ...auth, password: next } }),
1901
+ disabled
1902
+ }
1903
+ )
1904
+ ] }),
1905
+ auth.type === "bearer" && /* @__PURE__ */ jsx4(
1906
+ PasswordInput,
1907
+ {
1908
+ id: `${uid}-auth-token`,
1909
+ label: "Token",
1910
+ value: auth.token ?? "",
1911
+ placeholder: "your-token or {{step.token}}",
1912
+ onChange: (next) => emit({ auth: { ...auth, token: next } }),
1913
+ disabled
1914
+ }
1915
+ ),
1916
+ auth.type === "apikey" && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__grid", children: [
1917
+ /* @__PURE__ */ jsx4(
1918
+ Input,
1919
+ {
1920
+ id: `${uid}-auth-keyname`,
1921
+ label: "Key name",
1922
+ value: auth.apiKeyName ?? "",
1923
+ placeholder: "X-API-Key",
1924
+ onChange: (e) => emit({ auth: { ...auth, apiKeyName: e.target.value } }),
1925
+ disabled
1926
+ }
1927
+ ),
1928
+ /* @__PURE__ */ jsx4(
1929
+ PasswordInput,
1930
+ {
1931
+ id: `${uid}-auth-keyval`,
1932
+ label: "Key value",
1933
+ value: auth.apiKeyValue ?? "",
1934
+ placeholder: "your-api-key or {{var}}",
1935
+ onChange: (next) => emit({ auth: { ...auth, apiKeyValue: next } }),
1936
+ disabled
1937
+ }
1938
+ ),
1939
+ /* @__PURE__ */ jsx4(
1940
+ Select,
1941
+ {
1942
+ id: `${uid}-auth-keyloc`,
1943
+ label: "Send in",
1944
+ value: auth.apiKeyLocation ?? "header",
1945
+ onChange: (next) => emit({ auth: { ...auth, apiKeyLocation: next } }),
1946
+ disabled,
1947
+ options: [
1948
+ { value: "header", label: "Request header" },
1949
+ { value: "query", label: "Query parameter" }
1950
+ ]
1951
+ }
1952
+ )
1953
+ ] }),
1954
+ auth.type === "none" && /* @__PURE__ */ jsx4("p", { className: "ods-http-panel__empty", children: connectionId ? "This step uses the connection's authentication." : "No authentication will be added to this request." })
1955
+ ] });
1956
+ const responseTab = /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__tab", children: [
1957
+ /* @__PURE__ */ jsx4(
1958
+ SegmentedControl,
1959
+ {
1960
+ size: "sm",
1961
+ "aria-label": "Response type",
1962
+ options: [
1963
+ { value: "json", label: "JSON" },
1964
+ { value: "text", label: "Text" },
1965
+ { value: "binary", label: "Binary" }
1966
+ ],
1967
+ value: responseType,
1968
+ onChange: (next) => emit({ responseType: next })
1969
+ }
1970
+ ),
1971
+ /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__desc", children: [
1972
+ responseType === "json" && "Tries to parse the response body as JSON; falls back to raw text on parse failure.",
1973
+ responseType === "text" && "Returns the raw response body as a string. No JSON parsing.",
1974
+ responseType === "binary" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1975
+ "Returns the response as bytes with ",
1976
+ /* @__PURE__ */ jsx4("code", { children: "fileName" }),
1977
+ ", ",
1978
+ /* @__PURE__ */ jsx4("code", { children: "contentType" }),
1979
+ " and",
2109
1980
  " ",
2110
- preview.message
2111
- ] }),
2112
- preview.phase === "success" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-result", children: [
2113
- /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-status", children: [
2114
- /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: statusTone(preview.response.status), children: [
2115
- preview.response.status,
2116
- " ",
2117
- preview.response.statusText
2118
- ] }),
2119
- /* @__PURE__ */ jsxs2("span", { className: "ods-http-panel__meta", children: [
2120
- preview.response.duration,
2121
- "ms"
2122
- ] }),
2123
- /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__meta", children: formatBytes(preview.response.size) })
2124
- ] }),
2125
- /* @__PURE__ */ jsx2(
2126
- Accordion,
2127
- {
2128
- size: "sm",
2129
- mode: "multiple",
2130
- defaultValue: ["res-body"],
2131
- items: [
2132
- {
2133
- id: "res-headers",
2134
- label: "Response headers",
2135
- trailing: /* @__PURE__ */ jsxs2(Fragment2, { children: [
2136
- sectionCopyButton(
2137
- Object.entries(preview.response.headers).map(([k, v]) => `${k}: ${v}`).join("\n"),
2138
- "res-headers",
2139
- "response headers"
2140
- ),
2141
- /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "neutral", children: Object.keys(preview.response.headers).length })
2142
- ] }),
2143
- content: /* @__PURE__ */ jsx2("div", { className: "ods-http-panel__res-headers", children: Object.entries(preview.response.headers).map(([k, v]) => /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__res-header", children: [
2144
- /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__res-header-key", children: k }),
2145
- /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__res-header-val", children: v })
2146
- ] }, k)) })
2147
- },
2148
- {
2149
- id: "res-body",
2150
- label: "Response body",
2151
- trailing: /* @__PURE__ */ jsxs2(Fragment2, { children: [
2152
- sectionCopyButton(
2153
- typeof preview.response.body === "object" ? JSON.stringify(preview.response.body, null, 2) : String(preview.response.bodyRaw ?? preview.response.body ?? ""),
2154
- "res-body",
2155
- "response body"
2156
- ),
2157
- !preview.response.ok && /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "error", children: "Error" })
2158
- ] }),
2159
- content: (
2160
- // Size guard: the tree view renders recursively (no
2161
- // virtualization) — very large bodies fall back to text.
2162
- preview.response.body !== null && typeof preview.response.body === "object" && preview.response.size <= 15e5 ? /* @__PURE__ */ jsx2(
2163
- JsonViewer,
2164
- {
2165
- data: preview.response.body,
2166
- mode: "view",
2167
- maxHeight: 320,
2168
- copyable: true
2169
- }
2170
- ) : /* @__PURE__ */ jsx2("pre", { className: "ods-http-panel__res-body", children: typeof preview.response.body === "object" ? JSON.stringify(preview.response.body, null, 2) : String(
2171
- preview.response.bodyRaw ?? preview.response.body ?? "(empty body)"
2172
- ) })
2173
- )
2174
- }
2175
- ]
2176
- }
2177
- )
1981
+ /* @__PURE__ */ jsx4("code", { children: "size" }),
1982
+ " on the step output \u2014 for downloads feeding file actions. Pagination is not compatible with binary responses."
2178
1983
  ] })
1984
+ ] }),
1985
+ /* @__PURE__ */ jsx4(
1986
+ Input,
1987
+ {
1988
+ id: `${uid}-expected-ct`,
1989
+ label: "Expected content-types (optional)",
1990
+ value: expectedCtText,
1991
+ placeholder: "application/pdf, application/json",
1992
+ helperText: "Comma-separated. A warning is logged when the response Content-Type matches none of these.",
1993
+ onChange: (e) => {
1994
+ setExpectedCtText(e.target.value);
1995
+ const parts = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
1996
+ emit({ expectedContentTypes: parts.length ? parts : void 0 });
1997
+ },
1998
+ disabled
1999
+ }
2000
+ ),
2001
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__schema", children: [
2002
+ /* @__PURE__ */ jsx4("span", { className: "ods-http-panel__schema-label", children: "Data stream schema" }),
2003
+ schemaInfo ? /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__desc", children: [
2004
+ /* @__PURE__ */ jsxs4(Badge, { size: "sm", variant: "success", children: [
2005
+ schemaInfo.pathCount,
2006
+ " paths"
2007
+ ] }),
2008
+ " ",
2009
+ "Inferred from the last test send. Top-level keys:",
2010
+ " ",
2011
+ /* @__PURE__ */ jsx4("code", { children: schemaInfo.topKeys.join(", ") }),
2012
+ ". Downstream binding (Field Mapper, Transform, ForEach) sees this shape immediately; Save persists it."
2013
+ ] }) : /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__desc", children: [
2014
+ "No schema yet \u2014 hit ",
2015
+ /* @__PURE__ */ jsx4("strong", { children: "Send" }),
2016
+ " in the request bar. On success the response shape is inferred so downstream steps bind against real field names."
2017
+ ] }),
2018
+ /* @__PURE__ */ jsx4(
2019
+ Button,
2020
+ {
2021
+ variant: "ghost",
2022
+ size: "sm",
2023
+ disabled: preview.phase !== "success" || responseType === "binary" || disabled,
2024
+ onClick: () => {
2025
+ if (preview.phase === "success") inferAndEmitSchema(preview.response, true);
2026
+ },
2027
+ children: "Refresh schema from last send"
2028
+ }
2029
+ )
2179
2030
  ] })
2180
2031
  ] });
2181
- }
2182
-
2183
- // src/workflow/components/FxPanel/FxPanel.tsx
2184
- import {
2185
- useMemo as useMemo3,
2186
- useRef as useRef5,
2187
- useState as useState5
2188
- } from "react";
2189
- import {
2190
- ChevronDownIcon,
2191
- ChevronRightIcon,
2192
- CloseIcon,
2193
- CollapseAllIcon,
2194
- DraggableIcon,
2195
- ExpandAllIcon,
2196
- FunctionMathIcon,
2197
- SearchIcon,
2198
- ValueVariableIcon
2199
- } from "@octaviaflow/icons";
2200
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2201
- var KIND_ICON = {
2202
- function: FunctionMathIcon,
2203
- variable: ValueVariableIcon
2204
- };
2205
- function countLeaves(items) {
2206
- let n = 0;
2207
- for (const it of items) {
2208
- if (it.children && it.children.length > 0) n += countLeaves(it.children);
2209
- else n += 1;
2210
- }
2211
- return n;
2212
- }
2213
- function collectBranchIds(items, out) {
2214
- for (const it of items) {
2215
- if (it.children && it.children.length > 0) {
2216
- out.add(it.id);
2217
- collectBranchIds(it.children, out);
2218
- }
2219
- }
2220
- }
2221
- function flattenTree(items, depth, expandedItems, out) {
2222
- for (const it of items) {
2223
- const hasChildren = !!it.children && it.children.length > 0;
2224
- const expanded = hasChildren && expandedItems.has(it.id);
2225
- out.push({ item: it, depth, hasChildren, expanded });
2226
- if (expanded) flattenTree(it.children, depth + 1, expandedItems, out);
2227
- }
2228
- }
2229
- function collectHits(items, ancestors, q, out) {
2230
- for (const it of items) {
2231
- if (it.children && it.children.length > 0) {
2232
- collectHits(it.children, [...ancestors, it.label], q, out);
2233
- } else {
2234
- const path = [...ancestors, it.label].join(" / ");
2235
- const hay = `${it.label} ${it.description ?? ""} ${path} ${it.insertValue ?? ""}`.toLowerCase();
2236
- if (hay.includes(q)) out.push({ item: it, ancestors });
2237
- }
2238
- }
2239
- }
2240
- function FxPanel({
2241
- categories,
2242
- title = "Data References",
2243
- hint = "Drag onto a field, or click an item to insert it.",
2244
- search: controlledSearch,
2245
- defaultSearch = "",
2246
- onSearchChange,
2247
- expandedCategory: controlledExpanded,
2248
- defaultExpandedCategory,
2249
- onExpandedCategoryChange,
2250
- onClose,
2251
- onItemDragStart,
2252
- onItemSelect,
2253
- width = 300,
2254
- emptyLabel = "No matches",
2255
- searchPlaceholder = "Search fields, functions & variables\u2026",
2256
- className,
2257
- style
2258
- }) {
2259
- const [internalSearch, setInternalSearch] = useState5(defaultSearch);
2260
- const search = controlledSearch ?? internalSearch;
2261
- const query = search.trim().toLowerCase();
2262
- const setSearch = (next) => {
2263
- if (controlledSearch === void 0) setInternalSearch(next);
2264
- onSearchChange?.(next);
2265
- };
2266
- const visibleCategories = useMemo3(
2267
- () => categories.filter((c) => c.items.length > 0),
2268
- [categories]
2269
- );
2270
- const [internalCat, setInternalCat] = useState5(
2271
- defaultExpandedCategory !== void 0 ? defaultExpandedCategory : visibleCategories[0]?.id ?? null
2272
- );
2273
- const openCategoryId = controlledExpanded ?? internalCat;
2274
- const setOpenCategory = (id) => {
2275
- if (controlledExpanded === void 0) setInternalCat(id);
2276
- onExpandedCategoryChange?.(id);
2277
- };
2278
- const [expandedItems, setExpandedItems] = useState5(() => {
2279
- const ids = /* @__PURE__ */ new Set();
2280
- for (const cat of categories) {
2281
- for (const item of cat.items) {
2282
- if (item.children && item.children.length > 0) ids.add(item.id);
2283
- }
2284
- }
2285
- return ids;
2286
- });
2287
- const toggleItem = (id) => {
2288
- setExpandedItems((prev) => {
2289
- const next = new Set(prev);
2290
- if (next.has(id)) next.delete(id);
2291
- else next.add(id);
2292
- return next;
2293
- });
2294
- };
2295
- const setCategoryExpansion = (cat, expand) => {
2296
- const branchIds = /* @__PURE__ */ new Set();
2297
- collectBranchIds(cat.items, branchIds);
2298
- setExpandedItems((prev) => {
2299
- const next = new Set(prev);
2300
- for (const id of branchIds) {
2301
- if (expand) next.add(id);
2302
- else next.delete(id);
2303
- }
2304
- return next;
2305
- });
2306
- };
2307
- const [draggingId, setDraggingId] = useState5(null);
2308
- const [focusedId, setFocusedId] = useState5(null);
2309
- const hits = useMemo3(() => {
2310
- if (!query) return null;
2311
- const all = [];
2312
- for (const cat of visibleCategories) {
2313
- const out = [];
2314
- collectHits(cat.items, [], query, out);
2315
- for (const hit of out) all.push({ category: cat, hit });
2316
- }
2317
- return all;
2318
- }, [query, visibleCategories]);
2319
- const handleDragStart = (item, category) => (e) => {
2320
- setDraggingId(item.id);
2321
- if (onItemDragStart) {
2322
- onItemDragStart(item, category, e);
2323
- } else if (item.insertValue) {
2324
- e.dataTransfer.setData("text/plain", item.insertValue);
2325
- e.dataTransfer.setData("application/x-fx-insert", item.insertValue);
2326
- e.dataTransfer.setData(
2327
- "application/x-fx-type",
2328
- category.kind ?? "function"
2329
- );
2330
- e.dataTransfer.effectAllowed = "copy";
2032
+ const tabItems = [
2033
+ {
2034
+ value: "params",
2035
+ label: paramCount ? `Params \xB7 ${paramCount}` : "Params",
2036
+ icon: /* @__PURE__ */ jsx4(ListIcon, { size: "sm" }),
2037
+ children: paramsTab
2038
+ },
2039
+ {
2040
+ value: "headers",
2041
+ label: headerCount ? `Headers \xB7 ${headerCount}` : "Headers",
2042
+ icon: /* @__PURE__ */ jsx4(TagIcon, { size: "sm" }),
2043
+ children: headersTab
2044
+ },
2045
+ {
2046
+ value: "body",
2047
+ label: bodyMode === "none" ? "Body" : `Body \xB7 ${bodyMode === "form-data" ? "form" : bodyMode}`,
2048
+ icon: /* @__PURE__ */ jsx4(CodeIcon, { size: "sm" }),
2049
+ children: bodyTab
2050
+ },
2051
+ {
2052
+ value: "auth",
2053
+ label: auth.type === "none" ? "Auth" : `Auth \xB7 ${auth.type}`,
2054
+ icon: /* @__PURE__ */ jsx4(LockedIcon, { size: "sm" }),
2055
+ children: authTab
2056
+ },
2057
+ {
2058
+ value: "response",
2059
+ label: responseType === "json" ? "Response" : `Response \xB7 ${responseType}`,
2060
+ icon: /* @__PURE__ */ jsx4(DataViewAltIcon, { size: "sm" }),
2061
+ children: responseTab
2331
2062
  }
2332
- };
2333
- const listRef = useRef5(null);
2334
- const focusRow = (id) => {
2335
- setFocusedId(id);
2336
- requestAnimationFrame(() => {
2337
- listRef.current?.querySelector(`[data-fx-row="${CSS.escape(id)}"]`)?.focus();
2338
- });
2339
- };
2340
- const onRowKeyDown = (e, rows, row, category) => {
2341
- const idx = rows.findIndex((r) => r.item.id === row.item.id);
2342
- if (idx < 0) return;
2343
- switch (e.key) {
2344
- case "ArrowDown":
2345
- e.preventDefault();
2346
- if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
2347
- break;
2348
- case "ArrowUp":
2349
- e.preventDefault();
2350
- if (idx > 0) focusRow(rows[idx - 1].item.id);
2351
- break;
2352
- case "ArrowRight":
2353
- if (row.hasChildren) {
2354
- e.preventDefault();
2355
- if (!row.expanded) toggleItem(row.item.id);
2356
- else if (idx < rows.length - 1) focusRow(rows[idx + 1].item.id);
2063
+ ];
2064
+ return /* @__PURE__ */ jsxs4("div", { className: cn("ods-http-panel", className), children: [
2065
+ connections && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__source", children: [
2066
+ /* @__PURE__ */ jsx4(
2067
+ Select,
2068
+ {
2069
+ id: `${uid}-connection`,
2070
+ label: "Source",
2071
+ placeholder: connectionsLoading ? "Loading\u2026" : "No connection (manual config)",
2072
+ options: connections.map((c) => ({ value: c.id, label: c.name, icon: c.icon })),
2073
+ value: connectionId,
2074
+ onChange: (next) => emit({ connectionId: next || void 0 }),
2075
+ disabled: disabled || connectionsLoading,
2076
+ helperText: connectionId ? `Base URL and auth are inherited from "${selectedConnection?.name}" at runtime.` : "Select a saved API connection to inherit base URL and auth, or configure manually."
2357
2077
  }
2358
- break;
2359
- case "ArrowLeft":
2360
- if (row.hasChildren && row.expanded) {
2361
- e.preventDefault();
2362
- toggleItem(row.item.id);
2363
- } else {
2364
- for (let i = idx - 1; i >= 0; i--) {
2365
- if (rows[i].depth < row.depth) {
2366
- e.preventDefault();
2367
- focusRow(rows[i].item.id);
2368
- break;
2369
- }
2078
+ ),
2079
+ connectionId && endpoints.length > 0 && /* @__PURE__ */ jsx4(
2080
+ Combobox,
2081
+ {
2082
+ ariaLabel: "Endpoint",
2083
+ placeholder: `Search ${endpoints.length} discovered endpoints\u2026`,
2084
+ value: url,
2085
+ creatable: true,
2086
+ options: endpoints.map((e) => ({
2087
+ value: e.path,
2088
+ label: `${e.method} ${e.path}`,
2089
+ description: e.summary
2090
+ })),
2091
+ onChange: (nextPath) => {
2092
+ const match = endpoints.find((e) => e.path === nextPath);
2093
+ validateUrl(nextPath, baseUrl, true);
2094
+ emit(
2095
+ match && HTTP_METHODS.includes(match.method.toUpperCase()) ? { url: nextPath, method: match.method.toUpperCase() } : { url: nextPath }
2096
+ );
2370
2097
  }
2371
2098
  }
2372
- break;
2373
- case "Home":
2374
- e.preventDefault();
2375
- if (rows.length) focusRow(rows[0].item.id);
2376
- break;
2377
- case "End":
2378
- e.preventDefault();
2379
- if (rows.length) focusRow(rows[rows.length - 1].item.id);
2380
- break;
2381
- case "Enter":
2382
- case " ":
2383
- e.preventDefault();
2384
- if (row.hasChildren) toggleItem(row.item.id);
2385
- else onItemSelect?.(row.item, category);
2386
- break;
2387
- default:
2388
- break;
2389
- }
2390
- };
2391
- const searchRows = useMemo3(
2392
- () => hits ? hits.map(({ hit }) => ({
2393
- item: hit.item,
2394
- depth: 0,
2395
- hasChildren: false,
2396
- expanded: false
2397
- })) : [],
2398
- [hits]
2399
- );
2400
- const renderRow = (row, category, rows, crumb) => {
2401
- const { item, depth, hasChildren, expanded } = row;
2402
- const mono = category.kind === "function";
2403
- const draggable = !!item.insertValue;
2404
- const isFocused = focusedId === item.id;
2405
- const showsReference = !!item.insertValue && !item.insertValue.trim().startsWith("ods://");
2406
- const tip = /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__tip", children: [
2407
- /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__tip-head", children: [
2408
- /* @__PURE__ */ jsx3(
2409
- "span",
2410
- {
2411
- className: cn(
2412
- "ods-flow-fx-panel__tip-label",
2413
- mono && "ods-flow-fx-panel__tip-label--mono"
2414
- ),
2415
- children: item.label
2416
- }
2417
- ),
2418
- item.valueType && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__tip-type", children: item.valueType })
2099
+ )
2100
+ ] }),
2101
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__bar", children: [
2102
+ /* @__PURE__ */ jsx4(
2103
+ Select,
2104
+ {
2105
+ id: `${uid}-method`,
2106
+ "aria-label": "HTTP method",
2107
+ className: cn(
2108
+ "ods-http-panel__method",
2109
+ `ods-http-panel__method--${method.toLowerCase()}`
2110
+ ),
2111
+ value: method,
2112
+ onChange: (next) => emit({ method: next }),
2113
+ disabled,
2114
+ options: HTTP_METHODS.map((m) => ({ value: m, label: m }))
2115
+ }
2116
+ ),
2117
+ /* @__PURE__ */ jsx4(
2118
+ Input,
2119
+ {
2120
+ id: `${uid}-url`,
2121
+ "aria-label": connectionId ? "Path" : "URL",
2122
+ "data-fx-droppable": "true",
2123
+ className: "ods-http-panel__url",
2124
+ value: url,
2125
+ placeholder: connectionId ? "/v1/users" : "https://api.example.com/v1/users or {{var}}",
2126
+ error: Boolean(urlError),
2127
+ errorMessage: urlError,
2128
+ onChange: (e) => {
2129
+ validateUrl(e.target.value, baseUrl, Boolean(connectionId));
2130
+ emit({ url: e.target.value });
2131
+ },
2132
+ disabled
2133
+ }
2134
+ ),
2135
+ onSendPreview && /* @__PURE__ */ jsx4(
2136
+ Button,
2137
+ {
2138
+ variant: "primary",
2139
+ size: "md",
2140
+ leftIcon: preview.phase === "loading" ? void 0 : /* @__PURE__ */ jsx4(PlayIcon, { size: "sm" }),
2141
+ onClick: sendPreview,
2142
+ disabled: !canSend,
2143
+ children: preview.phase === "loading" ? "Sending\u2026" : "Send"
2144
+ }
2145
+ )
2146
+ ] }),
2147
+ !connectionId && baseUrl && /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__effective", children: [
2148
+ /* @__PURE__ */ jsx4("span", { children: "Effective URL:" }),
2149
+ " ",
2150
+ /* @__PURE__ */ jsx4("code", { children: fullUrl || "\u2014" })
2151
+ ] }),
2152
+ fullUrl.includes("{{") && /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__desc", children: [
2153
+ "\u26A0 URL contains ",
2154
+ /* @__PURE__ */ jsx4("code", { children: "{{variables}}" }),
2155
+ " \u2014 they resolve at run time; testing needs literal values."
2156
+ ] }),
2157
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__tools", children: [
2158
+ /* @__PURE__ */ jsxs4("span", { className: "ods-http-panel__hint", children: [
2159
+ "Use ",
2160
+ /* @__PURE__ */ jsx4("code", { children: "{{stepName.fieldPath}}" }),
2161
+ " to insert data from upstream steps."
2419
2162
  ] }),
2420
- item.description && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__tip-desc", children: item.description }),
2421
- showsReference && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__tip-ref", children: item.insertValue })
2422
- ] });
2423
- return /* @__PURE__ */ jsx3(
2424
- Tooltip,
2425
- {
2426
- content: tip,
2427
- position: "left",
2428
- disabled: !item.description && !showsReference,
2429
- children: /* @__PURE__ */ jsxs3(
2430
- "div",
2163
+ /* @__PURE__ */ jsxs4("span", { className: "ods-http-panel__tools-actions", children: [
2164
+ /* @__PURE__ */ jsx4(Button, { variant: "ghost", size: "sm", onClick: () => setCurlOpen((o) => !o), children: "Import cURL" }),
2165
+ /* @__PURE__ */ jsx4(
2166
+ Button,
2431
2167
  {
2432
- "data-fx-row": item.id,
2433
- className: cn(
2434
- "ods-flow-fx-panel__row",
2435
- hasChildren ? "ods-flow-fx-panel__row--branch" : "ods-flow-fx-panel__row--leaf",
2436
- draggingId === item.id && "ods-flow-fx-panel__row--dragging",
2437
- isFocused && "ods-flow-fx-panel__row--focused"
2438
- ),
2439
- role: "treeitem",
2440
- "aria-expanded": hasChildren ? expanded : void 0,
2441
- "aria-level": depth + 1,
2442
- tabIndex: isFocused || focusedId === null && rows[0]?.item.id === item.id ? 0 : -1,
2443
- draggable,
2444
- onDragStart: draggable ? handleDragStart(item, category) : void 0,
2445
- onDragEnd: () => setDraggingId(null),
2446
- onFocus: () => setFocusedId(item.id),
2447
- onKeyDown: (e) => onRowKeyDown(e, rows, row, category),
2448
- onClick: () => {
2449
- if (hasChildren) toggleItem(item.id);
2450
- else onItemSelect?.(item, category);
2451
- },
2452
- children: [
2453
- Array.from({ length: depth }, (_, i) => /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__indent", "aria-hidden": "true" }, i)),
2454
- hasChildren ? /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__twisty", "aria-hidden": "true", children: expanded ? /* @__PURE__ */ jsx3(ChevronDownIcon, { size: "sm" }) : /* @__PURE__ */ jsx3(ChevronRightIcon, { size: "sm" }) }) : /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__grip", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(DraggableIcon, { size: "sm" }) }),
2455
- /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-main", children: [
2456
- /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-line", children: [
2457
- mono ? /* @__PURE__ */ jsx3("code", { className: "ods-flow-fx-panel__row-label ods-flow-fx-panel__row-label--mono", children: item.label }) : /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__row-label", children: item.label }),
2458
- item.valueType && /* @__PURE__ */ jsx3(
2459
- "span",
2460
- {
2461
- className: cn(
2462
- "ods-flow-fx-panel__type",
2463
- `ods-flow-fx-panel__type--${item.valueType}`
2464
- ),
2465
- children: item.valueType
2466
- }
2467
- )
2468
- ] }),
2469
- (item.description || crumb) && /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-sub", children: [
2470
- crumb && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__crumb", children: crumb }),
2471
- item.description
2472
- ] })
2473
- ] })
2474
- ]
2475
- }
2476
- )
2477
- },
2478
- item.id
2479
- );
2480
- };
2481
- return /* @__PURE__ */ jsxs3(
2482
- "aside",
2483
- {
2484
- className: cn("ods-flow-fx-panel", className),
2485
- style: { width, ...style },
2486
- "aria-label": typeof title === "string" ? title : "Data References",
2487
- children: [
2488
- /* @__PURE__ */ jsxs3("header", { className: "ods-flow-fx-panel__header", children: [
2489
- /* @__PURE__ */ jsx3("h3", { className: "ods-flow-fx-panel__title", children: title }),
2490
- onClose && /* @__PURE__ */ jsx3(
2491
- "button",
2492
- {
2493
- type: "button",
2494
- className: "ods-flow-fx-panel__close",
2495
- onClick: onClose,
2496
- "aria-label": "Close data references",
2497
- children: /* @__PURE__ */ jsx3(CloseIcon, { size: "sm" })
2498
- }
2499
- )
2500
- ] }),
2501
- /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__search", children: [
2502
- /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__search-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(SearchIcon, { size: "sm" }) }),
2503
- /* @__PURE__ */ jsx3(
2504
- "input",
2168
+ variant: "ghost",
2169
+ size: "sm",
2170
+ className: cn(copiedCurl && "ods-http-panel__copy-done"),
2171
+ leftIcon: copiedCurl ? /* @__PURE__ */ jsx4(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx4(CopyIcon, { size: "sm" }),
2172
+ onClick: copyCurl,
2173
+ children: copiedCurl ? "Copied" : "Copy as cURL"
2174
+ }
2175
+ )
2176
+ ] })
2177
+ ] }),
2178
+ curlOpen && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__curl", children: [
2179
+ /* @__PURE__ */ jsx4(
2180
+ CodeEditor,
2181
+ {
2182
+ value: curlText,
2183
+ language: "plain",
2184
+ onChange: setCurlText,
2185
+ minHeight: 90,
2186
+ showLineNumbers: false,
2187
+ placeholder: "curl -X POST 'https://api.example.com/v1/users' -H 'Authorization: Bearer \u2026' -d '{\u2026}'",
2188
+ ariaLabel: "cURL command"
2189
+ }
2190
+ ),
2191
+ curlError && /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__error", children: [
2192
+ "\u26A0 ",
2193
+ curlError
2194
+ ] }),
2195
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__curl-actions", children: [
2196
+ /* @__PURE__ */ jsx4(Button, { variant: "secondary", size: "sm", onClick: () => setCurlOpen(false), children: "Cancel" }),
2197
+ /* @__PURE__ */ jsx4(Button, { variant: "primary", size: "sm", onClick: applyCurl, disabled: !curlText.trim(), children: "Apply to request" })
2198
+ ] })
2199
+ ] }),
2200
+ /* @__PURE__ */ jsx4(Tabs, { items: tabItems, defaultValue: "params", className: "ods-http-panel__tabs" }),
2201
+ /* @__PURE__ */ jsxs4(
2202
+ Disclosure,
2203
+ {
2204
+ size: "sm",
2205
+ icon: /* @__PURE__ */ jsx4(GlobeIcon, { size: "sm" }),
2206
+ title: "Advanced options",
2207
+ description: "Base URL, timeout, retries, pagination, response extraction",
2208
+ trailing: advancedSummary.length ? /* @__PURE__ */ jsx4(Badge, { size: "sm", variant: "info", children: advancedSummary.join(" \xB7 ") }) : void 0,
2209
+ children: [
2210
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__grid", children: [
2211
+ !connectionId && /* @__PURE__ */ jsx4(
2212
+ Input,
2213
+ {
2214
+ id: `${uid}-base-url`,
2215
+ label: "Base URL (optional)",
2216
+ value: baseUrl,
2217
+ placeholder: "https://api.example.com",
2218
+ helperText: "Combined with the request path to form the full URL.",
2219
+ onChange: (e) => {
2220
+ validateUrl(url, e.target.value, Boolean(connectionId));
2221
+ emit({ baseUrl: e.target.value || void 0 });
2222
+ },
2223
+ disabled
2224
+ }
2225
+ ),
2226
+ /* @__PURE__ */ jsx4(
2227
+ NumberInput,
2228
+ {
2229
+ label: "Timeout (ms)",
2230
+ helperText: "Minimum 1000 ms",
2231
+ value: cfg.timeout ?? 3e4,
2232
+ min: 1e3,
2233
+ step: 1e3,
2234
+ onChange: (next) => {
2235
+ if (!Number.isNaN(next) && next >= 1e3) emit({ timeout: next });
2236
+ },
2237
+ disabled
2238
+ }
2239
+ ),
2240
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__switches", children: [
2241
+ /* @__PURE__ */ jsx4(
2242
+ Switch,
2243
+ {
2244
+ id: `${uid}-fail`,
2245
+ label: "Fail on non-2xx",
2246
+ checked: cfg.failOnError !== false,
2247
+ onChange: (next) => emit({ failOnError: next }),
2248
+ disabled,
2249
+ size: "sm"
2250
+ }
2251
+ ),
2252
+ /* @__PURE__ */ jsx4(
2253
+ Switch,
2254
+ {
2255
+ id: `${uid}-redirects`,
2256
+ label: "Follow redirects",
2257
+ checked: cfg.followRedirects !== false,
2258
+ onChange: (next) => emit({ followRedirects: next }),
2259
+ disabled,
2260
+ size: "sm"
2261
+ }
2262
+ )
2263
+ ] })
2264
+ ] }),
2265
+ /* @__PURE__ */ jsx4(
2266
+ Disclosure,
2505
2267
  {
2506
- type: "text",
2507
- className: "ods-flow-fx-panel__search-input",
2508
- placeholder: searchPlaceholder,
2509
- "aria-label": "Search fields, functions and variables",
2510
- value: search,
2511
- onChange: (e) => setSearch(e.target.value)
2268
+ size: "sm",
2269
+ title: "Retry",
2270
+ trailing: (retry?.maxAttempts ?? 1) > 1 ? /* @__PURE__ */ jsxs4(Badge, { size: "sm", variant: "info", children: [
2271
+ retry?.maxAttempts,
2272
+ " attempts"
2273
+ ] }) : void 0,
2274
+ children: /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__grid", children: [
2275
+ /* @__PURE__ */ jsx4(
2276
+ NumberInput,
2277
+ {
2278
+ label: "Max attempts",
2279
+ helperText: "1 = no retry",
2280
+ value: retry?.maxAttempts ?? 1,
2281
+ min: 1,
2282
+ max: 10,
2283
+ onChange: (next) => {
2284
+ if (!Number.isNaN(next) && next >= 1 && retry) {
2285
+ emit({ retry: { ...retry, maxAttempts: next } });
2286
+ }
2287
+ },
2288
+ disabled
2289
+ }
2290
+ ),
2291
+ /* @__PURE__ */ jsx4(
2292
+ Input,
2293
+ {
2294
+ id: `${uid}-retry-on`,
2295
+ label: "Retry on (status codes)",
2296
+ value: retryOnText,
2297
+ placeholder: "429, 500, 502, 503, 504",
2298
+ helperText: "Comma-separated HTTP status codes",
2299
+ onChange: (e) => {
2300
+ setRetryOnText(e.target.value);
2301
+ const codes = e.target.value.split(",").map((s) => Number.parseInt(s.trim(), 10)).filter((n) => !Number.isNaN(n));
2302
+ if (retry) emit({ retry: { ...retry, retryOn: codes } });
2303
+ },
2304
+ disabled
2305
+ }
2306
+ ),
2307
+ /* @__PURE__ */ jsx4(
2308
+ NumberInput,
2309
+ {
2310
+ label: "Initial delay (ms)",
2311
+ value: retry?.initialDelayMs ?? 1e3,
2312
+ min: 100,
2313
+ step: 100,
2314
+ onChange: (next) => {
2315
+ if (!Number.isNaN(next) && retry)
2316
+ emit({ retry: { ...retry, initialDelayMs: next } });
2317
+ },
2318
+ disabled
2319
+ }
2320
+ ),
2321
+ /* @__PURE__ */ jsx4(
2322
+ NumberInput,
2323
+ {
2324
+ label: "Backoff factor",
2325
+ helperText: "Delay multiplies by this each retry",
2326
+ value: retry?.backoffFactor ?? 2,
2327
+ min: 1,
2328
+ step: 0.5,
2329
+ onChange: (next) => {
2330
+ if (!Number.isNaN(next) && retry)
2331
+ emit({ retry: { ...retry, backoffFactor: next } });
2332
+ },
2333
+ disabled
2334
+ }
2335
+ ),
2336
+ /* @__PURE__ */ jsx4(
2337
+ NumberInput,
2338
+ {
2339
+ label: "Max delay (ms)",
2340
+ value: retry?.maxDelayMs ?? 3e4,
2341
+ min: 1e3,
2342
+ step: 1e3,
2343
+ onChange: (next) => {
2344
+ if (!Number.isNaN(next) && retry) emit({ retry: { ...retry, maxDelayMs: next } });
2345
+ },
2346
+ disabled
2347
+ }
2348
+ )
2349
+ ] })
2512
2350
  }
2513
2351
  ),
2514
- search && /* @__PURE__ */ jsx3(
2515
- "button",
2352
+ /* @__PURE__ */ jsxs4(
2353
+ Disclosure,
2516
2354
  {
2517
- type: "button",
2518
- className: "ods-flow-fx-panel__search-clear",
2519
- onClick: () => setSearch(""),
2520
- "aria-label": "Clear search",
2521
- children: /* @__PURE__ */ jsx3(CloseIcon, { size: "sm" })
2522
- }
2523
- )
2524
- ] }),
2525
- hint && !query && /* @__PURE__ */ jsx3("p", { className: "ods-flow-fx-panel__hint", children: hint }),
2526
- /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__list", ref: listRef, role: "tree", children: [
2527
- query && hits && (hits.length > 0 ? /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__results", children: [
2528
- /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__results-count", children: [
2529
- hits.length,
2530
- " ",
2531
- hits.length === 1 ? "match" : "matches"
2532
- ] }),
2533
- hits.map(
2534
- ({ category, hit }, i) => renderRow(
2535
- searchRows[i],
2536
- category,
2537
- searchRows,
2538
- hit.ancestors.length ? hit.ancestors.join(" / ") : category.label
2539
- )
2540
- )
2541
- ] }) : /* @__PURE__ */ jsx3("div", { className: "ods-flow-fx-panel__empty", children: emptyLabel })),
2542
- !query && visibleCategories.map((cat) => {
2543
- const isOpen = openCategoryId === cat.id;
2544
- const kind = cat.kind ?? "function";
2545
- const KindIcon = KIND_ICON[kind];
2546
- const branchIds = /* @__PURE__ */ new Set();
2547
- collectBranchIds(cat.items, branchIds);
2548
- const hasTree = branchIds.size > 0;
2549
- const rows = [];
2550
- if (isOpen) flattenTree(cat.items, 0, expandedItems, rows);
2551
- const allExpanded = hasTree && [...branchIds].every((id) => expandedItems.has(id));
2552
- return /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__category", children: [
2553
- /* @__PURE__ */ jsxs3(
2554
- "div",
2555
- {
2556
- className: cn(
2557
- "ods-flow-fx-panel__cat-header",
2558
- isOpen && "ods-flow-fx-panel__cat-header--open"
2355
+ size: "sm",
2356
+ title: "Pagination",
2357
+ trailing: pagination.type !== "none" ? /* @__PURE__ */ jsx4(Badge, { size: "sm", variant: "success", children: pagination.type.replace("_", " ") }) : void 0,
2358
+ children: [
2359
+ /* @__PURE__ */ jsx4(
2360
+ Select,
2361
+ {
2362
+ id: `${uid}-pag-type`,
2363
+ label: "Strategy",
2364
+ value: pagination.type,
2365
+ onChange: (next) => emit({
2366
+ pagination: { ...pagination, type: next }
2367
+ }),
2368
+ disabled,
2369
+ options: [
2370
+ { value: "none", label: "None (single request)" },
2371
+ { value: "offset", label: "Offset / page number" },
2372
+ { value: "cursor", label: "Cursor-based" },
2373
+ { value: "link_header", label: 'Link header (rel="next")' },
2374
+ { value: "next_url", label: "Next URL in response body" }
2375
+ ]
2376
+ }
2377
+ ),
2378
+ pagination.type !== "none" && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__grid", children: [
2379
+ /* @__PURE__ */ jsx4(
2380
+ NumberInput,
2381
+ {
2382
+ label: "Max pages",
2383
+ helperText: "Safety cap against infinite loops",
2384
+ value: pagination.maxPages ?? 20,
2385
+ min: 1,
2386
+ onChange: (next) => {
2387
+ if (!Number.isNaN(next)) emit({ pagination: { ...pagination, maxPages: next } });
2388
+ },
2389
+ disabled
2390
+ }
2391
+ ),
2392
+ /* @__PURE__ */ jsx4(
2393
+ Input,
2394
+ {
2395
+ id: `${uid}-pag-merge`,
2396
+ label: "Merge key (dot path)",
2397
+ value: pagination.mergeKey ?? "",
2398
+ placeholder: "data.items",
2399
+ helperText: "Array to merge across pages",
2400
+ onChange: (e) => emit({ pagination: { ...pagination, mergeKey: e.target.value } }),
2401
+ disabled
2402
+ }
2403
+ ),
2404
+ (pagination.type === "offset" || pagination.type === "cursor") && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2405
+ /* @__PURE__ */ jsx4(
2406
+ Input,
2407
+ {
2408
+ id: `${uid}-pag-limitp`,
2409
+ label: "Limit param",
2410
+ value: pagination.limitParam ?? "limit",
2411
+ onChange: (e) => emit({ pagination: { ...pagination, limitParam: e.target.value } }),
2412
+ disabled
2413
+ }
2414
+ ),
2415
+ /* @__PURE__ */ jsx4(
2416
+ NumberInput,
2417
+ {
2418
+ label: "Page size",
2419
+ value: pagination.limitValue ?? 100,
2420
+ min: 1,
2421
+ onChange: (next) => {
2422
+ if (!Number.isNaN(next)) {
2423
+ emit({ pagination: { ...pagination, limitValue: next } });
2424
+ }
2425
+ },
2426
+ disabled
2427
+ }
2428
+ )
2429
+ ] }),
2430
+ pagination.type === "offset" && /* @__PURE__ */ jsx4(
2431
+ Input,
2432
+ {
2433
+ id: `${uid}-pag-offset`,
2434
+ label: "Offset param",
2435
+ value: pagination.offsetParam ?? "offset",
2436
+ onChange: (e) => emit({ pagination: { ...pagination, offsetParam: e.target.value } }),
2437
+ disabled
2438
+ }
2559
2439
  ),
2560
- children: [
2561
- /* @__PURE__ */ jsxs3(
2562
- "button",
2440
+ pagination.type === "cursor" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2441
+ /* @__PURE__ */ jsx4(
2442
+ Input,
2563
2443
  {
2564
- type: "button",
2565
- className: "ods-flow-fx-panel__cat-toggle",
2566
- "aria-expanded": isOpen,
2567
- onClick: () => setOpenCategory(isOpen ? null : cat.id),
2568
- children: [
2569
- /* @__PURE__ */ jsx3(
2570
- ChevronRightIcon,
2571
- {
2572
- size: "sm",
2573
- className: cn(
2574
- "ods-flow-fx-panel__chevron",
2575
- isOpen && "ods-flow-fx-panel__chevron--open"
2576
- )
2577
- }
2578
- ),
2579
- /* @__PURE__ */ jsxs3(
2580
- "span",
2581
- {
2582
- className: cn(
2583
- "ods-flow-fx-panel__badge",
2584
- `ods-flow-fx-panel__badge--${kind}`
2585
- ),
2586
- children: [
2587
- /* @__PURE__ */ jsx3(
2588
- KindIcon,
2589
- {
2590
- size: "sm",
2591
- className: "ods-flow-fx-panel__badge-icon"
2592
- }
2593
- ),
2594
- cat.label
2595
- ]
2596
- }
2597
- ),
2598
- /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__count", children: countLeaves(cat.items) })
2599
- ]
2444
+ id: `${uid}-pag-cres`,
2445
+ label: "Cursor in response (dot path)",
2446
+ value: pagination.cursorResponsePath ?? "",
2447
+ placeholder: "meta.next_cursor",
2448
+ onChange: (e) => emit({ pagination: { ...pagination, cursorResponsePath: e.target.value } }),
2449
+ disabled
2600
2450
  }
2601
2451
  ),
2602
- isOpen && hasTree && /* @__PURE__ */ jsx3(
2603
- "button",
2452
+ /* @__PURE__ */ jsx4(
2453
+ Input,
2604
2454
  {
2605
- type: "button",
2606
- className: "ods-flow-fx-panel__cat-action",
2607
- "aria-label": allExpanded ? "Collapse all" : "Expand all",
2608
- title: allExpanded ? "Collapse all" : "Expand all",
2609
- onClick: () => setCategoryExpansion(cat, !allExpanded),
2610
- children: allExpanded ? /* @__PURE__ */ jsx3(CollapseAllIcon, { size: "sm" }) : /* @__PURE__ */ jsx3(ExpandAllIcon, { size: "sm" })
2455
+ id: `${uid}-pag-creq`,
2456
+ label: "Cursor request param",
2457
+ value: pagination.cursorRequestParam ?? "cursor",
2458
+ onChange: (e) => emit({ pagination: { ...pagination, cursorRequestParam: e.target.value } }),
2459
+ disabled
2611
2460
  }
2612
2461
  )
2613
- ]
2614
- }
2615
- ),
2616
- isOpen && /* @__PURE__ */ jsx3(
2617
- "div",
2618
- {
2619
- className: "ods-flow-fx-panel__rows",
2620
- role: "group",
2621
- "aria-label": cat.label,
2622
- children: rows.map((row) => renderRow(row, cat, rows))
2623
- }
2624
- )
2625
- ] }, cat.id);
2626
- })
2627
- ] })
2628
- ]
2629
- }
2630
- );
2631
- }
2632
-
2633
- // src/workflow/components/FxPanel/DataRefButton.tsx
2634
- import { DataReferenceIcon } from "@octaviaflow/icons";
2635
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2636
- function DataRefButton({
2637
- active = false,
2638
- label = "Data ref",
2639
- className,
2640
- type = "button",
2641
- ...rest
2642
- }) {
2643
- return /* @__PURE__ */ jsxs4(
2644
- "button",
2645
- {
2646
- ...rest,
2647
- type,
2648
- className: cn(
2649
- "ods-flow-dataref-button",
2650
- active && "ods-flow-dataref-button--active",
2651
- label == null && "ods-flow-dataref-button--icon-only",
2652
- className
2653
- ),
2654
- "aria-pressed": active,
2655
- title: active ? "Hide data references" : "Show data references",
2656
- children: [
2462
+ ] }),
2463
+ pagination.type === "next_url" && /* @__PURE__ */ jsx4(
2464
+ Input,
2465
+ {
2466
+ id: `${uid}-pag-next`,
2467
+ label: "Next URL in response (dot path)",
2468
+ value: pagination.nextUrlResponsePath ?? "",
2469
+ placeholder: "links.next",
2470
+ onChange: (e) => emit({ pagination: { ...pagination, nextUrlResponsePath: e.target.value } }),
2471
+ disabled
2472
+ }
2473
+ )
2474
+ ] })
2475
+ ]
2476
+ }
2477
+ ),
2478
+ /* @__PURE__ */ jsxs4(
2479
+ Disclosure,
2480
+ {
2481
+ size: "sm",
2482
+ title: "Response field extraction",
2483
+ trailing: extractCount > 0 ? /* @__PURE__ */ jsxs4(Badge, { size: "sm", variant: "info", children: [
2484
+ extractCount,
2485
+ " field",
2486
+ extractCount === 1 ? "" : "s"
2487
+ ] }) : void 0,
2488
+ children: [
2489
+ /* @__PURE__ */ jsxs4("p", { className: "ods-http-panel__desc", children: [
2490
+ "Extract fields from the response with dot paths \u2014 available downstream as",
2491
+ " ",
2492
+ /* @__PURE__ */ jsx4("code", { children: "{{extracted.keyName}}" }),
2493
+ "."
2494
+ ] }),
2495
+ /* @__PURE__ */ jsx4(
2496
+ KeyValueEditor,
2497
+ {
2498
+ pairs: extractRows.map((r) => ({ key: r.key, value: r.value })),
2499
+ onChange: (pairs) => {
2500
+ const rows = pairs.map((p) => ({ key: p.key, value: p.value, enabled: true }));
2501
+ setExtractRows(rows);
2502
+ const extract = {};
2503
+ for (const r of rows) if (r.key && r.value) extract[r.key] = r.value;
2504
+ emit({ extract: Object.keys(extract).length ? extract : void 0 });
2505
+ },
2506
+ keyPlaceholder: "outputKey",
2507
+ valuePlaceholder: "data.user.id",
2508
+ emptyLabel: "No extractions yet \u2014 click Add to begin.",
2509
+ addLabel: "Add",
2510
+ size: "sm",
2511
+ disabled
2512
+ }
2513
+ )
2514
+ ]
2515
+ }
2516
+ )
2517
+ ]
2518
+ }
2519
+ ),
2520
+ preview.phase !== "idle" && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__console", children: [
2521
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__console-head", children: [
2657
2522
  /* @__PURE__ */ jsx4(
2658
- DataReferenceIcon,
2523
+ "code",
2524
+ {
2525
+ className: cn(
2526
+ "ods-http-panel__console-method",
2527
+ `ods-http-panel__method--${method.toLowerCase()}`
2528
+ ),
2529
+ children: cfg.apiType === "graphql" ? "POST" : method
2530
+ }
2531
+ ),
2532
+ /* @__PURE__ */ jsx4("code", { className: "ods-http-panel__console-url", children: connectionId ? `${selectedConnection?.name ?? connectionId} \xB7 ${url || "\u2014"}` : fullUrl || "\u2014" }),
2533
+ sectionCopyButton(fullUrl || url, "url", "request URL"),
2534
+ preview.phase === "success" && /* @__PURE__ */ jsx4(
2535
+ Button,
2659
2536
  {
2537
+ variant: "ghost",
2660
2538
  size: "sm",
2661
- className: "ods-flow-dataref-button__icon"
2539
+ className: cn(copiedResult && "ods-http-panel__copy-done"),
2540
+ leftIcon: copiedResult ? /* @__PURE__ */ jsx4(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx4(CopyIcon, { size: "sm" }),
2541
+ onClick: copyResult,
2542
+ children: copiedResult ? "Copied" : "Copy result"
2662
2543
  }
2663
2544
  ),
2664
- label != null && /* @__PURE__ */ jsx4("span", { className: "ods-flow-dataref-button__label", children: label })
2665
- ]
2666
- }
2667
- );
2545
+ /* @__PURE__ */ jsx4(
2546
+ Button,
2547
+ {
2548
+ variant: "ghost",
2549
+ size: "sm",
2550
+ leftIcon: /* @__PURE__ */ jsx4(RenewIcon, { size: "sm" }),
2551
+ onClick: () => {
2552
+ setPreview({ phase: "idle" });
2553
+ },
2554
+ disabled: preview.phase === "loading",
2555
+ children: "Clear"
2556
+ }
2557
+ )
2558
+ ] }),
2559
+ preview.phase === "loading" && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__console-loading", children: [
2560
+ /* @__PURE__ */ jsx4(Spinner, { size: "sm" }),
2561
+ /* @__PURE__ */ jsx4("span", { children: "Sending request\u2026" })
2562
+ ] }),
2563
+ preview.phase === "error" && /* @__PURE__ */ jsxs4(Banner, { variant: "error", appearance: "subtle", size: "sm", children: [
2564
+ /* @__PURE__ */ jsx4("strong", { children: "Request failed." }),
2565
+ " ",
2566
+ preview.message
2567
+ ] }),
2568
+ preview.phase === "success" && /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__console-result", children: [
2569
+ /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__console-status", children: [
2570
+ /* @__PURE__ */ jsxs4(Badge, { size: "sm", variant: statusTone(preview.response.status), children: [
2571
+ preview.response.status,
2572
+ " ",
2573
+ preview.response.statusText
2574
+ ] }),
2575
+ /* @__PURE__ */ jsxs4("span", { className: "ods-http-panel__meta", children: [
2576
+ preview.response.duration,
2577
+ "ms"
2578
+ ] }),
2579
+ /* @__PURE__ */ jsx4("span", { className: "ods-http-panel__meta", children: formatBytes(preview.response.size) })
2580
+ ] }),
2581
+ /* @__PURE__ */ jsx4(
2582
+ Accordion,
2583
+ {
2584
+ size: "sm",
2585
+ mode: "multiple",
2586
+ defaultValue: ["res-body"],
2587
+ items: [
2588
+ {
2589
+ id: "res-headers",
2590
+ label: "Response headers",
2591
+ trailing: /* @__PURE__ */ jsxs4(Fragment2, { children: [
2592
+ sectionCopyButton(
2593
+ Object.entries(preview.response.headers).map(([k, v]) => `${k}: ${v}`).join("\n"),
2594
+ "res-headers",
2595
+ "response headers"
2596
+ ),
2597
+ /* @__PURE__ */ jsx4(Badge, { size: "sm", variant: "neutral", children: Object.keys(preview.response.headers).length })
2598
+ ] }),
2599
+ content: /* @__PURE__ */ jsx4("div", { className: "ods-http-panel__res-headers", children: Object.entries(preview.response.headers).map(([k, v]) => /* @__PURE__ */ jsxs4("div", { className: "ods-http-panel__res-header", children: [
2600
+ /* @__PURE__ */ jsx4("span", { className: "ods-http-panel__res-header-key", children: k }),
2601
+ /* @__PURE__ */ jsx4("span", { className: "ods-http-panel__res-header-val", children: v })
2602
+ ] }, k)) })
2603
+ },
2604
+ {
2605
+ id: "res-body",
2606
+ label: "Response body",
2607
+ trailing: /* @__PURE__ */ jsxs4(Fragment2, { children: [
2608
+ sectionCopyButton(
2609
+ typeof preview.response.body === "object" ? JSON.stringify(preview.response.body, null, 2) : String(preview.response.bodyRaw ?? preview.response.body ?? ""),
2610
+ "res-body",
2611
+ "response body"
2612
+ ),
2613
+ !preview.response.ok && /* @__PURE__ */ jsx4(Badge, { size: "sm", variant: "error", children: "Error" })
2614
+ ] }),
2615
+ content: (
2616
+ // Size guard: the tree view renders recursively (no
2617
+ // virtualization) — very large bodies fall back to text.
2618
+ preview.response.body !== null && typeof preview.response.body === "object" && preview.response.size <= 15e5 ? /* @__PURE__ */ jsx4(
2619
+ JsonViewer,
2620
+ {
2621
+ data: preview.response.body,
2622
+ mode: "view",
2623
+ maxHeight: 320,
2624
+ copyable: true
2625
+ }
2626
+ ) : /* @__PURE__ */ jsx4("pre", { className: "ods-http-panel__res-body", children: typeof preview.response.body === "object" ? JSON.stringify(preview.response.body, null, 2) : String(
2627
+ preview.response.bodyRaw ?? preview.response.body ?? "(empty body)"
2628
+ ) })
2629
+ )
2630
+ }
2631
+ ]
2632
+ }
2633
+ )
2634
+ ] })
2635
+ ] })
2636
+ ] });
2668
2637
  }
2669
2638
 
2670
2639
  // src/workflow/components/NodeToolbar/NodeToolbar.tsx