@octaviaflow/core 3.1.0-beta.67 → 3.1.0-beta.68

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 (70) hide show
  1. package/dist/chunk-JMSZOWT2.js +2616 -0
  2. package/dist/chunk-JMSZOWT2.js.map +1 -0
  3. package/dist/chunk-KZP2MNBO.js +2516 -0
  4. package/dist/chunk-KZP2MNBO.js.map +1 -0
  5. package/dist/chunk-S4LE4BFQ.js +5934 -0
  6. package/dist/chunk-S4LE4BFQ.js.map +1 -0
  7. package/dist/chunk-X7YETDK7.js +107 -0
  8. package/dist/chunk-X7YETDK7.js.map +1 -0
  9. package/dist/components/WorkflowShowcase/ShowcaseScene.d.ts +20 -0
  10. package/dist/components/WorkflowShowcase/ShowcaseScene.d.ts.map +1 -0
  11. package/dist/components/WorkflowShowcase/ShowcaseSignup.d.ts +14 -0
  12. package/dist/components/WorkflowShowcase/ShowcaseSignup.d.ts.map +1 -0
  13. package/dist/components/WorkflowShowcase/WorkflowShowcase.d.ts +48 -0
  14. package/dist/components/WorkflowShowcase/WorkflowShowcase.d.ts.map +1 -0
  15. package/dist/components/WorkflowShowcase/index.d.ts +3 -0
  16. package/dist/components/WorkflowShowcase/index.d.ts.map +1 -0
  17. package/dist/components/WorkflowShowcase/showcaseEngine.d.ts +102 -0
  18. package/dist/components/WorkflowShowcase/showcaseEngine.d.ts.map +1 -0
  19. package/dist/index.js +7854 -13331
  20. package/dist/index.js.map +1 -1
  21. package/dist/marketing/components/AssistantTranscript/AssistantTranscript.d.ts +15 -0
  22. package/dist/marketing/components/AssistantTranscript/AssistantTranscript.d.ts.map +1 -0
  23. package/dist/marketing/components/CtaBanner/CtaBanner.d.ts +16 -0
  24. package/dist/marketing/components/CtaBanner/CtaBanner.d.ts.map +1 -0
  25. package/dist/marketing/components/FeatureGrid/FeatureGrid.d.ts +15 -0
  26. package/dist/marketing/components/FeatureGrid/FeatureGrid.d.ts.map +1 -0
  27. package/dist/marketing/components/IntegrationsWall/IntegrationsWall.d.ts +19 -0
  28. package/dist/marketing/components/IntegrationsWall/IntegrationsWall.d.ts.map +1 -0
  29. package/dist/marketing/components/LiveConsole/LiveConsole.d.ts +21 -0
  30. package/dist/marketing/components/LiveConsole/LiveConsole.d.ts.map +1 -0
  31. package/dist/marketing/components/MarketingFooter/MarketingFooter.d.ts +25 -0
  32. package/dist/marketing/components/MarketingFooter/MarketingFooter.d.ts.map +1 -0
  33. package/dist/marketing/components/MarketingHero/MarketingHero.d.ts +11 -0
  34. package/dist/marketing/components/MarketingHero/MarketingHero.d.ts.map +1 -0
  35. package/dist/marketing/components/MarketingNav/MarketingNav.d.ts +17 -0
  36. package/dist/marketing/components/MarketingNav/MarketingNav.d.ts.map +1 -0
  37. package/dist/marketing/components/MarketingSection/MarketingSection.d.ts +15 -0
  38. package/dist/marketing/components/MarketingSection/MarketingSection.d.ts.map +1 -0
  39. package/dist/marketing/components/StatBand/StatBand.d.ts +16 -0
  40. package/dist/marketing/components/StatBand/StatBand.d.ts.map +1 -0
  41. package/dist/marketing/components/WorkflowShowcase/ShowcaseScene.d.ts +20 -0
  42. package/dist/marketing/components/WorkflowShowcase/ShowcaseScene.d.ts.map +1 -0
  43. package/dist/marketing/components/WorkflowShowcase/ShowcaseSignup.d.ts +14 -0
  44. package/dist/marketing/components/WorkflowShowcase/ShowcaseSignup.d.ts.map +1 -0
  45. package/dist/marketing/components/WorkflowShowcase/WorkflowShowcase.d.ts +48 -0
  46. package/dist/marketing/components/WorkflowShowcase/WorkflowShowcase.d.ts.map +1 -0
  47. package/dist/marketing/components/WorkflowShowcase/index.d.ts +3 -0
  48. package/dist/marketing/components/WorkflowShowcase/index.d.ts.map +1 -0
  49. package/dist/marketing/components/WorkflowShowcase/showcaseEngine.d.ts +102 -0
  50. package/dist/marketing/components/WorkflowShowcase/showcaseEngine.d.ts.map +1 -0
  51. package/dist/marketing/glyphs.d.ts +20 -0
  52. package/dist/marketing/glyphs.d.ts.map +1 -0
  53. package/dist/marketing/index.d.ts +13 -0
  54. package/dist/marketing/index.d.ts.map +1 -0
  55. package/dist/marketing.cjs +3985 -0
  56. package/dist/marketing.cjs.map +1 -0
  57. package/dist/marketing.js +1669 -0
  58. package/dist/marketing.js.map +1 -0
  59. package/dist/styles.css +1 -1
  60. package/dist/workflow/components/HttpActionPanel/HttpActionPanel.d.ts +29 -0
  61. package/dist/workflow/components/HttpActionPanel/HttpActionPanel.d.ts.map +1 -0
  62. package/dist/workflow/components/HttpActionPanel/httpActionTypes.d.ts +135 -0
  63. package/dist/workflow/components/HttpActionPanel/httpActionTypes.d.ts.map +1 -0
  64. package/dist/workflow/index.d.ts +2 -0
  65. package/dist/workflow/index.d.ts.map +1 -1
  66. package/dist/workflow.cjs +6922 -271
  67. package/dist/workflow.cjs.map +1 -1
  68. package/dist/workflow.js +1587 -56
  69. package/dist/workflow.js.map +1 -1
  70. package/package.json +6 -1
package/dist/workflow.js CHANGED
@@ -1,9 +1,15 @@
1
1
  import {
2
+ Accordion,
2
3
  ActionNode,
4
+ Badge,
5
+ Banner,
6
+ CodeEditor,
7
+ Combobox,
3
8
  ConditionNode,
4
9
  DEFAULT_NODE_HEIGHT,
5
10
  DEFAULT_NODE_KINDS,
6
11
  DEFAULT_NODE_WIDTH,
12
+ Disclosure,
7
13
  ErrorNode,
8
14
  FlowCanvas,
9
15
  FlowEdge,
@@ -12,10 +18,18 @@ import {
12
18
  GroupNode,
13
19
  Handle,
14
20
  HttpRequestNode,
21
+ JsonViewer,
22
+ KeyValueEditor,
15
23
  NodeResizer,
24
+ NumberInput,
16
25
  OutputNode,
17
26
  ParallelNode,
27
+ PasswordInput,
28
+ SegmentedControl,
29
+ Spinner,
18
30
  StickyNode,
31
+ Switch,
32
+ Tabs,
19
33
  TriggerNode,
20
34
  WaitNode,
21
35
  WebhookNode,
@@ -50,7 +64,12 @@ import {
50
64
  useSelection,
51
65
  useViewport,
52
66
  useViewportOrNull
53
- } from "./chunk-5AKA6EJB.js";
67
+ } from "./chunk-S4LE4BFQ.js";
68
+ import {
69
+ Button,
70
+ Input,
71
+ Select
72
+ } from "./chunk-KZP2MNBO.js";
54
73
  import {
55
74
  cn
56
75
  } from "./chunk-ZAUUGK2Y.js";
@@ -620,11 +639,1510 @@ function ConfigPanel({
620
639
  );
621
640
  }
622
641
 
642
+ // src/workflow/components/HttpActionPanel/HttpActionPanel.tsx
643
+ import {
644
+ CheckmarkIcon,
645
+ CodeIcon,
646
+ CopyIcon,
647
+ DataViewAltIcon,
648
+ GlobeIcon,
649
+ ListIcon,
650
+ LockedIcon,
651
+ PlayIcon,
652
+ RenewIcon,
653
+ TagIcon
654
+ } from "@octaviaflow/icons";
655
+ import { useCallback as useCallback4, useId, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
656
+
657
+ // src/workflow/components/HttpActionPanel/httpActionTypes.ts
658
+ var HTTP_METHODS = [
659
+ "GET",
660
+ "POST",
661
+ "PUT",
662
+ "PATCH",
663
+ "DELETE",
664
+ "HEAD",
665
+ "OPTIONS"
666
+ ];
667
+ var HTTP_ACTION_DEFAULTS = {
668
+ apiType: "rest",
669
+ method: "GET",
670
+ url: "",
671
+ queryParams: [],
672
+ headers: [],
673
+ bodyType: "json",
674
+ formData: [],
675
+ graphqlQuery: "",
676
+ graphqlVariables: {},
677
+ auth: { type: "none" },
678
+ timeout: 3e4,
679
+ failOnError: true,
680
+ followRedirects: true,
681
+ retry: {
682
+ maxAttempts: 1,
683
+ retryOn: [429, 500, 502, 503, 504],
684
+ initialDelayMs: 1e3,
685
+ backoffFactor: 2,
686
+ maxDelayMs: 3e4
687
+ },
688
+ pagination: { type: "none" },
689
+ extract: {},
690
+ responseType: "json",
691
+ expectedContentTypes: []
692
+ };
693
+ function normalizeKeyValues(raw) {
694
+ if (Array.isArray(raw)) {
695
+ return raw.map((r) => ({
696
+ key: String(r.key ?? ""),
697
+ value: String(r.value ?? ""),
698
+ description: r.description,
699
+ enabled: r.enabled !== false
700
+ }));
701
+ }
702
+ if (raw && typeof raw === "object") {
703
+ return Object.entries(raw).map(([key, value]) => ({
704
+ key,
705
+ value: String(value),
706
+ enabled: true
707
+ }));
708
+ }
709
+ return [];
710
+ }
711
+ function normalizeAuth(raw) {
712
+ const r = raw ?? {};
713
+ const rawType = r.type ?? "none";
714
+ const type = rawType === "oauth2" ? "bearer" : rawType;
715
+ return {
716
+ type: ["none", "basic", "bearer", "apikey"].includes(type) ? type : "none",
717
+ username: r.username ?? "",
718
+ password: r.password ?? "",
719
+ token: r.token ?? r.accessToken ?? "",
720
+ apiKeyName: r.apiKeyName ?? "X-API-Key",
721
+ apiKeyValue: r.apiKeyValue ?? "",
722
+ apiKeyLocation: r.apiKeyLocation === "query" ? "query" : "header"
723
+ };
724
+ }
725
+ function effectiveUrl(baseUrl, url) {
726
+ const base = (baseUrl ?? "").trim();
727
+ if (!base) return url;
728
+ return `${base.replace(/\/$/, "")}/${url.replace(/^\//, "")}`;
729
+ }
730
+ function serializeKeyValues(rows) {
731
+ return rows.filter((r) => r.key || r.value).map((r) => ({
732
+ key: r.key,
733
+ value: r.value,
734
+ enabled: r.enabled !== false,
735
+ ...r.description ? { description: r.description } : {}
736
+ }));
737
+ }
738
+ function validateHttpConfig(config) {
739
+ const errors = [];
740
+ const apiType = config.apiType ?? "rest";
741
+ if (apiType === "rest" && !config.method) errors.push("Method is required");
742
+ if (apiType === "graphql" && !String(config.graphqlQuery ?? "").trim()) {
743
+ errors.push("GraphQL query is required");
744
+ }
745
+ const hasConnection = Boolean(config.connectionId);
746
+ if (!config.url && !hasConnection) {
747
+ errors.push("URL is required");
748
+ } else if (config.url && !hasConnection && !config.baseUrl && !config.url.includes("{{")) {
749
+ try {
750
+ new URL(config.url);
751
+ } catch {
752
+ errors.push("Invalid URL format");
753
+ }
754
+ }
755
+ if (typeof config.timeout === "number" && config.timeout < 1e3) {
756
+ errors.push("Timeout must be at least 1000ms");
757
+ }
758
+ return { valid: errors.length === 0, errors };
759
+ }
760
+ function countGraphqlVariables(query) {
761
+ return new Set(query.match(/\$[A-Za-z_][A-Za-z0-9_]*/g) ?? []).size;
762
+ }
763
+ function shellTokens(input) {
764
+ const tokens = [];
765
+ const flat = input.replace(/\\\r?\n/g, " ");
766
+ const re = /'([^']*)'|"((?:[^"\\]|\\.)*)"|(\S+)/g;
767
+ let m = re.exec(flat);
768
+ while (m !== null) {
769
+ if (m[1] !== void 0) tokens.push(m[1]);
770
+ else if (m[2] !== void 0) tokens.push(m[2].replace(/\\(["\\])/g, "$1"));
771
+ else tokens.push(m[3]);
772
+ m = re.exec(flat);
773
+ }
774
+ return tokens;
775
+ }
776
+ function parseCurl(input) {
777
+ const tokens = shellTokens(input.trim());
778
+ if (!tokens.length || !/^curl$/i.test(tokens[0])) return null;
779
+ let method;
780
+ let url = "";
781
+ const headers = [];
782
+ const formData = [];
783
+ let bodyRaw;
784
+ let basicUser;
785
+ let followRedirects;
786
+ for (let i = 1; i < tokens.length; i++) {
787
+ const t = tokens[i];
788
+ const next = () => tokens[++i] ?? "";
789
+ if (t === "-X" || t === "--request") {
790
+ const m = next().toUpperCase();
791
+ if (HTTP_METHODS.includes(m)) method = m;
792
+ } else if (t === "-H" || t === "--header") {
793
+ const raw = next();
794
+ const idx = raw.indexOf(":");
795
+ if (idx > 0) {
796
+ headers.push({
797
+ key: raw.slice(0, idx).trim(),
798
+ value: raw.slice(idx + 1).trim(),
799
+ enabled: true
800
+ });
801
+ }
802
+ } else if (t === "-d" || t === "--data" || t === "--data-raw" || t === "--data-binary" || t === "--data-urlencode") {
803
+ bodyRaw = next();
804
+ } else if (t === "-F" || t === "--form") {
805
+ const raw = next();
806
+ const idx = raw.indexOf("=");
807
+ if (idx > 0) {
808
+ formData.push({ key: raw.slice(0, idx), value: raw.slice(idx + 1), enabled: true });
809
+ }
810
+ } else if (t === "-u" || t === "--user") {
811
+ basicUser = next();
812
+ } else if (t === "--url") {
813
+ url = next();
814
+ } else if (t === "-L" || t === "--location") {
815
+ followRedirects = true;
816
+ } else if (t.startsWith("-")) {
817
+ if ([
818
+ "-o",
819
+ "--output",
820
+ "-A",
821
+ "--user-agent",
822
+ "-e",
823
+ "--referer",
824
+ "-b",
825
+ "--cookie",
826
+ "--connect-timeout",
827
+ "-m",
828
+ "--max-time"
829
+ ].includes(t)) {
830
+ next();
831
+ }
832
+ } else if (!url) {
833
+ url = t;
834
+ }
835
+ }
836
+ if (!url) return null;
837
+ const patch = { url, apiType: "rest" };
838
+ const qIdx = url.indexOf("?");
839
+ if (qIdx >= 0) {
840
+ const query = url.slice(qIdx + 1);
841
+ patch.url = url.slice(0, qIdx);
842
+ patch.queryParams = query.split("&").filter(Boolean).map((pair) => {
843
+ const eq = pair.indexOf("=");
844
+ const rawKey = eq >= 0 ? pair.slice(0, eq) : pair;
845
+ const rawValue = eq >= 0 ? pair.slice(eq + 1) : "";
846
+ const decode = (s) => {
847
+ try {
848
+ return decodeURIComponent(s.replace(/\+/g, "%20"));
849
+ } catch {
850
+ return s;
851
+ }
852
+ };
853
+ return { key: decode(rawKey), value: decode(rawValue), enabled: true };
854
+ });
855
+ }
856
+ const authHeaderIdx = headers.findIndex((h) => h.key.toLowerCase() === "authorization");
857
+ if (authHeaderIdx >= 0 && /^bearer\s+/i.test(headers[authHeaderIdx].value)) {
858
+ patch.auth = {
859
+ type: "bearer",
860
+ token: headers[authHeaderIdx].value.replace(/^bearer\s+/i, "")
861
+ };
862
+ headers.splice(authHeaderIdx, 1);
863
+ } else if (basicUser) {
864
+ const sep = basicUser.indexOf(":");
865
+ patch.auth = {
866
+ type: "basic",
867
+ username: sep >= 0 ? basicUser.slice(0, sep) : basicUser,
868
+ password: sep >= 0 ? basicUser.slice(sep + 1) : ""
869
+ };
870
+ }
871
+ if (headers.length) patch.headers = headers;
872
+ if (followRedirects !== void 0) patch.followRedirects = followRedirects;
873
+ if (formData.length) {
874
+ patch.bodyType = "form-data";
875
+ patch.formData = formData;
876
+ patch.method = method ?? "POST";
877
+ } else if (bodyRaw !== void 0) {
878
+ try {
879
+ patch.body = JSON.parse(bodyRaw);
880
+ patch.bodyType = "json";
881
+ } catch {
882
+ patch.body = bodyRaw;
883
+ patch.bodyType = "raw";
884
+ }
885
+ patch.method = method ?? "POST";
886
+ } else {
887
+ patch.method = method ?? "GET";
888
+ patch.bodyType = "none";
889
+ }
890
+ return patch;
891
+ }
892
+ function shellQuote(value) {
893
+ return `'${value.replace(/'/g, `'\\''`)}'`;
894
+ }
895
+ function buildCurl(config) {
896
+ const parts = ["curl"];
897
+ const method = config.apiType === "graphql" ? "POST" : config.method ?? "GET";
898
+ if (method !== "GET") parts.push(`-X ${method}`);
899
+ let url = effectiveUrl(config.baseUrl, config.url);
900
+ const params = normalizeKeyValues(config.queryParams).filter((p) => p.enabled !== false && p.key);
901
+ if (params.length) {
902
+ const qs = params.map((p) => `${encodeURIComponent(p.key)}=${encodeURIComponent(p.value)}`).join("&");
903
+ url += (url.includes("?") ? "&" : "?") + qs;
904
+ }
905
+ parts.push(shellQuote(url));
906
+ for (const h of normalizeKeyValues(config.headers).filter((h2) => h2.enabled !== false && h2.key)) {
907
+ parts.push(`-H ${shellQuote(`${h.key}: ${h.value}`)}`);
908
+ }
909
+ const auth = config.auth;
910
+ if (auth?.type === "bearer" && auth.token) {
911
+ parts.push(`-H ${shellQuote(`Authorization: Bearer ${auth.token}`)}`);
912
+ } else if (auth?.type === "basic" && auth.username) {
913
+ parts.push(`-u ${shellQuote(`${auth.username}:${auth.password ?? ""}`)}`);
914
+ } else if (auth?.type === "apikey" && auth.apiKeyName && auth.apiKeyValue) {
915
+ if ((auth.apiKeyLocation ?? "header") === "header") {
916
+ parts.push(`-H ${shellQuote(`${auth.apiKeyName}: ${auth.apiKeyValue}`)}`);
917
+ }
918
+ }
919
+ if (config.apiType === "graphql") {
920
+ const payload = JSON.stringify({
921
+ query: config.graphqlQuery ?? "",
922
+ variables: typeof config.graphqlVariables === "string" ? config.graphqlVariables : config.graphqlVariables ?? {}
923
+ });
924
+ parts.push(`-H ${shellQuote("Content-Type: application/json")}`);
925
+ parts.push(`-d ${shellQuote(payload)}`);
926
+ } else if (config.bodyType === "form-data") {
927
+ for (const f of normalizeKeyValues(config.formData).filter(
928
+ (f2) => f2.enabled !== false && f2.key
929
+ )) {
930
+ parts.push(`-F ${shellQuote(`${f.key}=${f.value}`)}`);
931
+ }
932
+ } else if (config.bodyType && config.bodyType !== "none" && config.body != null) {
933
+ const raw = typeof config.body === "string" ? config.body : JSON.stringify(config.body);
934
+ if (raw) {
935
+ if (config.bodyType === "json") {
936
+ parts.push(`-H ${shellQuote("Content-Type: application/json")}`);
937
+ }
938
+ parts.push(`-d ${shellQuote(raw)}`);
939
+ }
940
+ }
941
+ if (config.followRedirects !== false) parts.push("-L");
942
+ return parts.join(" ");
943
+ }
944
+ function inferJsonSchema(value, depth = 0) {
945
+ if (value === null || value === void 0) return { type: "null" };
946
+ if (Array.isArray(value)) {
947
+ if (!value.length || depth > 6) return { type: "array" };
948
+ return { type: "array", items: inferJsonSchema(value[0], depth + 1) };
949
+ }
950
+ if (typeof value === "object") {
951
+ if (depth > 6) return { type: "object" };
952
+ const properties = {};
953
+ for (const [k, v] of Object.entries(value)) {
954
+ properties[k] = inferJsonSchema(v, depth + 1);
955
+ }
956
+ return { type: "object", properties };
957
+ }
958
+ return { type: typeof value };
959
+ }
960
+ function countSchemaPaths(schema, depth = 0) {
961
+ if (!schema || typeof schema !== "object" || depth > 8) return 0;
962
+ const s = schema;
963
+ let count = 0;
964
+ if (s.type === "object" && s.properties) {
965
+ for (const child of Object.values(s.properties)) {
966
+ count += 1 + countSchemaPaths(child, depth + 1);
967
+ }
968
+ } else if (s.type === "array" && s.items) {
969
+ count += countSchemaPaths(s.items, depth + 1);
970
+ }
971
+ return count;
972
+ }
973
+ function statusTone(status) {
974
+ if (status < 300) return "success";
975
+ if (status < 400) return "info";
976
+ if (status < 500) return "warning";
977
+ return "error";
978
+ }
979
+ function formatBytes(size) {
980
+ return size < 1024 ? `${size} B` : `${(size / 1024).toFixed(1)} KB`;
981
+ }
982
+
983
+ // src/workflow/components/HttpActionPanel/HttpActionPanel.tsx
984
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
985
+ function toPairs(rows) {
986
+ return rows.map((r) => ({
987
+ key: r.key,
988
+ value: r.value,
989
+ enabled: r.enabled !== false,
990
+ description: r.description
991
+ }));
992
+ }
993
+ function textOf(raw) {
994
+ if (typeof raw === "string") return raw;
995
+ if (raw == null) return "";
996
+ try {
997
+ return JSON.stringify(raw, null, 2);
998
+ } catch {
999
+ return "";
1000
+ }
1001
+ }
1002
+ function HttpActionPanel({
1003
+ value,
1004
+ onChange,
1005
+ disabled = false,
1006
+ connections,
1007
+ connectionsLoading = false,
1008
+ onSendPreview,
1009
+ className
1010
+ }) {
1011
+ const uid = useId();
1012
+ const cfgRef = useRef4({
1013
+ ...HTTP_ACTION_DEFAULTS,
1014
+ ...value,
1015
+ queryParams: normalizeKeyValues(value?.queryParams),
1016
+ headers: normalizeKeyValues(value?.headers),
1017
+ formData: normalizeKeyValues(value?.formData),
1018
+ auth: normalizeAuth(value?.auth),
1019
+ retry: {
1020
+ ...HTTP_ACTION_DEFAULTS.retry,
1021
+ ...value?.retry
1022
+ },
1023
+ pagination: {
1024
+ ...HTTP_ACTION_DEFAULTS.pagination,
1025
+ ...value?.pagination
1026
+ }
1027
+ });
1028
+ const [, force] = useState4(0);
1029
+ const emit = useCallback4(
1030
+ (patch) => {
1031
+ cfgRef.current = { ...cfgRef.current, ...patch };
1032
+ force((n) => n + 1);
1033
+ onChange?.(cfgRef.current);
1034
+ },
1035
+ [onChange]
1036
+ );
1037
+ const cfg = cfgRef.current;
1038
+ const connectionId = cfg.connectionId ?? "";
1039
+ const selectedConnection = connections?.find((c) => c.id === connectionId) ?? null;
1040
+ const endpoints = selectedConnection?.endpoints ?? [];
1041
+ const method = cfg.method ?? "GET";
1042
+ const url = cfg.url ?? "";
1043
+ const baseUrl = cfg.baseUrl ?? "";
1044
+ const fullUrl = connectionId ? url : effectiveUrl(baseUrl, url);
1045
+ const queryParams = normalizeKeyValues(cfg.queryParams);
1046
+ const headers = normalizeKeyValues(cfg.headers);
1047
+ const formData = normalizeKeyValues(cfg.formData);
1048
+ const auth = normalizeAuth(cfg.auth);
1049
+ const retry = cfg.retry ?? HTTP_ACTION_DEFAULTS.retry;
1050
+ const pagination = cfg.pagination ?? { type: "none" };
1051
+ const responseType = cfg.responseType ?? "json";
1052
+ const [bodyText, setBodyText] = useState4(() => textOf(cfg.body));
1053
+ const [bodyError, setBodyError] = useState4();
1054
+ const [gqlVarsText, setGqlVarsText] = useState4(() => textOf(cfg.graphqlVariables));
1055
+ const [gqlVarsError, setGqlVarsError] = useState4();
1056
+ const [retryOnText, setRetryOnText] = useState4(() => (retry?.retryOn ?? []).join(", "));
1057
+ const [expectedCtText, setExpectedCtText] = useState4(
1058
+ () => (cfg.expectedContentTypes ?? []).join(", ")
1059
+ );
1060
+ const [urlError, setUrlError] = useState4();
1061
+ const [extractRows, setExtractRows] = useState4(
1062
+ () => Object.entries(cfg.extract ?? {}).map(([key, v]) => ({ key, value: v, enabled: true }))
1063
+ );
1064
+ const [curlOpen, setCurlOpen] = useState4(false);
1065
+ const [curlText, setCurlText] = useState4("");
1066
+ const [curlError, setCurlError] = useState4();
1067
+ const [copiedCurl, setCopiedCurl] = useState4(false);
1068
+ const [copiedResult, setCopiedResult] = useState4(false);
1069
+ const applyCurl = () => {
1070
+ const patch = parseCurl(curlText);
1071
+ if (!patch) {
1072
+ setCurlError(
1073
+ "That doesn't look like a cURL command \u2014 it should start with `curl` and include a URL."
1074
+ );
1075
+ return;
1076
+ }
1077
+ setCurlError(void 0);
1078
+ setCurlText("");
1079
+ setCurlOpen(false);
1080
+ setBodyText(textOf(patch.body));
1081
+ setBodyError(void 0);
1082
+ emit({ ...patch, connectionId: void 0, baseUrl: void 0 });
1083
+ };
1084
+ const copyCurl = async () => {
1085
+ try {
1086
+ await navigator.clipboard?.writeText(buildCurl(cfg));
1087
+ setCopiedCurl(true);
1088
+ setTimeout(() => setCopiedCurl(false), 1600);
1089
+ } catch {
1090
+ }
1091
+ };
1092
+ const copyResult = async () => {
1093
+ if (preview.phase !== "success") return;
1094
+ const r = preview.response;
1095
+ const bundle = {
1096
+ request: {
1097
+ method: cfg.apiType === "graphql" ? "POST" : method,
1098
+ url: connectionId ? `${selectedConnection?.name ?? connectionId} \xB7 ${url}` : fullUrl
1099
+ },
1100
+ response: {
1101
+ status: r.status,
1102
+ statusText: r.statusText,
1103
+ ok: r.ok,
1104
+ durationMs: r.duration,
1105
+ sizeBytes: r.size,
1106
+ headers: r.headers,
1107
+ body: r.body
1108
+ }
1109
+ };
1110
+ try {
1111
+ await navigator.clipboard?.writeText(JSON.stringify(bundle, null, 2));
1112
+ setCopiedResult(true);
1113
+ setTimeout(() => setCopiedResult(false), 1600);
1114
+ } catch {
1115
+ }
1116
+ };
1117
+ const validateUrl = (nextUrl, nextBase, hasConnection) => {
1118
+ if (!nextUrl && !nextBase && !hasConnection) {
1119
+ setUrlError("URL is required");
1120
+ return;
1121
+ }
1122
+ if (!nextUrl || hasConnection || nextBase || nextUrl.includes("{{")) {
1123
+ setUrlError(void 0);
1124
+ return;
1125
+ }
1126
+ try {
1127
+ new URL(nextUrl);
1128
+ setUrlError(void 0);
1129
+ } catch {
1130
+ setUrlError("URL does not look valid");
1131
+ }
1132
+ };
1133
+ const bodyMode = cfg.apiType === "graphql" ? "graphql" : cfg.bodyType ?? "json";
1134
+ const gqlVarCount = useMemo2(
1135
+ () => countGraphqlVariables(cfg.graphqlQuery ?? ""),
1136
+ [cfg.graphqlQuery]
1137
+ );
1138
+ const handleBodyMode = (next) => {
1139
+ if (next === "graphql") {
1140
+ emit(method === "GET" ? { apiType: "graphql", method: "POST" } : { apiType: "graphql" });
1141
+ return;
1142
+ }
1143
+ emit({ apiType: "rest", bodyType: next });
1144
+ };
1145
+ const [preview, setPreview] = useState4({ phase: "idle" });
1146
+ const [schemaInfo, setSchemaInfo] = useState4(
1147
+ null
1148
+ );
1149
+ const lastSchemaKey = useRef4("");
1150
+ const inferAndEmitSchema = useCallback4(
1151
+ (response, forceRefresh = false) => {
1152
+ if (responseType === "binary") return;
1153
+ const snapshot = {
1154
+ status: response.status,
1155
+ statusText: response.statusText,
1156
+ ok: response.ok,
1157
+ headers: response.headers,
1158
+ body: response.body,
1159
+ duration: response.duration,
1160
+ attempts: 1,
1161
+ extracted: {}
1162
+ };
1163
+ const inferred = inferJsonSchema(snapshot);
1164
+ const key = JSON.stringify(inferred);
1165
+ if (!forceRefresh && key === lastSchemaKey.current) return;
1166
+ lastSchemaKey.current = key;
1167
+ emit({ _outputSchema: inferred });
1168
+ const bodySchema = inferred.properties?.body;
1169
+ const topKeys = Object.keys(bodySchema?.properties ?? inferred.properties ?? {});
1170
+ setSchemaInfo({ pathCount: countSchemaPaths(inferred), topKeys });
1171
+ },
1172
+ [emit, responseType]
1173
+ );
1174
+ const sendPreview = async () => {
1175
+ if (!onSendPreview) return;
1176
+ setPreview({ phase: "loading" });
1177
+ try {
1178
+ const response = await onSendPreview({
1179
+ method: cfg.apiType === "graphql" ? "POST" : method,
1180
+ url: connectionId ? url : fullUrl,
1181
+ connectionId: connectionId || void 0,
1182
+ headers,
1183
+ queryParams,
1184
+ bodyType: cfg.bodyType ?? "json",
1185
+ body: bodyText,
1186
+ formData,
1187
+ graphqlQuery: cfg.apiType === "graphql" ? cfg.graphqlQuery ?? "" : void 0,
1188
+ graphqlVariables: cfg.apiType === "graphql" ? gqlVarsText : void 0,
1189
+ auth,
1190
+ timeout: cfg.timeout ?? 3e4
1191
+ });
1192
+ setPreview({ phase: "success", response });
1193
+ inferAndEmitSchema(response);
1194
+ } catch (err) {
1195
+ setPreview({
1196
+ phase: "error",
1197
+ message: err instanceof Error ? err.message : "Request failed"
1198
+ });
1199
+ }
1200
+ };
1201
+ const previewUrlOk = Boolean(fullUrl) && !fullUrl.includes("{{");
1202
+ const canSend = Boolean(onSendPreview) && previewUrlOk && preview.phase !== "loading" && !disabled;
1203
+ const paramCount = queryParams.filter((r) => r.enabled !== false && r.key).length;
1204
+ const headerCount = headers.filter((r) => r.enabled !== false && r.key).length;
1205
+ const extractCount = extractRows.filter((r) => r.key).length;
1206
+ const advancedSummary = [];
1207
+ if ((cfg.timeout ?? 3e4) !== 3e4) advancedSummary.push(`${(cfg.timeout ?? 0) / 1e3}s`);
1208
+ if ((retry?.maxAttempts ?? 1) > 1) advancedSummary.push(`retry \xD7${retry?.maxAttempts}`);
1209
+ if (pagination.type !== "none") advancedSummary.push(pagination.type.replace("_", " "));
1210
+ if (extractCount > 0) advancedSummary.push(`${extractCount} extracted`);
1211
+ const kvTab = (rows, key, desc, keyPlaceholder) => /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1212
+ /* @__PURE__ */ jsx2("p", { className: "ods-http-panel__desc", children: desc }),
1213
+ /* @__PURE__ */ jsx2(
1214
+ KeyValueEditor,
1215
+ {
1216
+ pairs: toPairs(rows),
1217
+ onChange: (pairs) => emit({ [key]: serializeKeyValues(pairs) }),
1218
+ rowToggle: true,
1219
+ showDescription: true,
1220
+ keyPlaceholder,
1221
+ valuePlaceholder: "value or {{var}}",
1222
+ emptyLabel: "No entries yet \u2014 click Add to begin.",
1223
+ addLabel: "Add",
1224
+ size: "sm",
1225
+ disabled
1226
+ }
1227
+ )
1228
+ ] });
1229
+ const paramsTab = kvTab(
1230
+ queryParams,
1231
+ "queryParams",
1232
+ /* @__PURE__ */ jsxs2(Fragment2, { children: [
1233
+ "Query parameters appended to the URL. Supports ",
1234
+ /* @__PURE__ */ jsx2("code", { children: "{{variable}}" }),
1235
+ " interpolation."
1236
+ ] }),
1237
+ "param name"
1238
+ );
1239
+ const headersTab = kvTab(
1240
+ headers,
1241
+ "headers",
1242
+ /* @__PURE__ */ jsxs2(Fragment2, { children: [
1243
+ "Request headers. Auth headers are injected automatically from the Auth tab. Supports",
1244
+ " ",
1245
+ /* @__PURE__ */ jsx2("code", { children: "{{variable}}" }),
1246
+ " interpolation."
1247
+ ] }),
1248
+ "Header-Name"
1249
+ );
1250
+ const bodyTab = /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1251
+ /* @__PURE__ */ jsx2(
1252
+ SegmentedControl,
1253
+ {
1254
+ size: "sm",
1255
+ "aria-label": "Body type",
1256
+ options: [
1257
+ { value: "none", label: "None" },
1258
+ { value: "json", label: "JSON" },
1259
+ { value: "graphql", label: "GraphQL" },
1260
+ { value: "raw", label: "Raw" },
1261
+ { value: "form-data", label: "Form" },
1262
+ { value: "xml", label: "XML" }
1263
+ ],
1264
+ value: bodyMode,
1265
+ onChange: handleBodyMode
1266
+ }
1267
+ ),
1268
+ bodyMode === "none" && /* @__PURE__ */ jsx2("p", { className: "ods-http-panel__empty", children: "No request body will be sent." }),
1269
+ (bodyMode === "json" || bodyMode === "raw" || bodyMode === "xml") && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1270
+ /* @__PURE__ */ jsx2(
1271
+ CodeEditor,
1272
+ {
1273
+ value: bodyText,
1274
+ language: bodyMode === "json" ? "json" : "plain",
1275
+ onChange: (next) => {
1276
+ setBodyText(next);
1277
+ if (bodyMode === "json" && next.trim()) {
1278
+ try {
1279
+ emit({ body: JSON.parse(next) });
1280
+ setBodyError(void 0);
1281
+ } catch {
1282
+ setBodyError("Invalid JSON");
1283
+ emit({ body: next });
1284
+ }
1285
+ } else {
1286
+ setBodyError(void 0);
1287
+ emit({ body: next || void 0 });
1288
+ }
1289
+ },
1290
+ readOnly: disabled,
1291
+ minHeight: 180,
1292
+ ariaLabel: "Request body"
1293
+ }
1294
+ ),
1295
+ bodyError && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__error", children: [
1296
+ "\u26A0 ",
1297
+ bodyError
1298
+ ] }),
1299
+ /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1300
+ "Supports ",
1301
+ /* @__PURE__ */ jsx2("code", { children: "{{variable}}" }),
1302
+ " interpolation."
1303
+ ] })
1304
+ ] }),
1305
+ bodyMode === "graphql" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1306
+ /* @__PURE__ */ jsx2(
1307
+ CodeEditor,
1308
+ {
1309
+ value: cfg.graphqlQuery ?? "",
1310
+ language: "plain",
1311
+ onChange: (next) => emit({ graphqlQuery: next }),
1312
+ readOnly: disabled,
1313
+ minHeight: 160,
1314
+ placeholder: "query ($id: ID!) {\n node(id: $id) { id }\n}",
1315
+ ariaLabel: "GraphQL query"
1316
+ }
1317
+ ),
1318
+ /* @__PURE__ */ jsxs2(
1319
+ Disclosure,
1320
+ {
1321
+ size: "sm",
1322
+ title: "Variables",
1323
+ defaultOpen: Boolean(gqlVarsText.trim()) || gqlVarCount > 0,
1324
+ trailing: gqlVarCount > 0 ? /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "neutral", children: gqlVarCount }) : void 0,
1325
+ children: [
1326
+ /* @__PURE__ */ jsx2(
1327
+ CodeEditor,
1328
+ {
1329
+ value: gqlVarsText,
1330
+ language: "json",
1331
+ onChange: (next) => {
1332
+ setGqlVarsText(next);
1333
+ if (!next.trim()) {
1334
+ setGqlVarsError(void 0);
1335
+ emit({ graphqlVariables: {} });
1336
+ return;
1337
+ }
1338
+ try {
1339
+ emit({ graphqlVariables: JSON.parse(next) });
1340
+ setGqlVarsError(void 0);
1341
+ } catch {
1342
+ setGqlVarsError("Invalid JSON");
1343
+ }
1344
+ },
1345
+ readOnly: disabled,
1346
+ minHeight: 100,
1347
+ ariaLabel: "GraphQL variables (JSON)"
1348
+ }
1349
+ ),
1350
+ gqlVarsError && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__error", children: [
1351
+ "\u26A0 ",
1352
+ gqlVarsError
1353
+ ] })
1354
+ ]
1355
+ }
1356
+ )
1357
+ ] }),
1358
+ bodyMode === "form-data" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1359
+ /* @__PURE__ */ jsx2("p", { className: "ods-http-panel__desc", children: "Multipart form fields. Content-Type is set automatically with the boundary." }),
1360
+ /* @__PURE__ */ jsx2(
1361
+ KeyValueEditor,
1362
+ {
1363
+ pairs: toPairs(formData),
1364
+ onChange: (pairs) => emit({ formData: serializeKeyValues(pairs), bodyType: "form-data" }),
1365
+ rowToggle: true,
1366
+ keyPlaceholder: "field name",
1367
+ valuePlaceholder: "value or {{var}}",
1368
+ emptyLabel: "No fields yet \u2014 click Add to begin.",
1369
+ addLabel: "Add",
1370
+ size: "sm",
1371
+ disabled
1372
+ }
1373
+ )
1374
+ ] })
1375
+ ] });
1376
+ const authTab = /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1377
+ connectionId ? /* @__PURE__ */ jsxs2(Banner, { variant: "success", appearance: "subtle", size: "sm", children: [
1378
+ "Authentication is inherited from ",
1379
+ /* @__PURE__ */ jsx2("strong", { children: selectedConnection?.name }),
1380
+ " and refreshed automatically. Choose a type below only to override it for this step."
1381
+ ] }) : /* @__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." }),
1382
+ /* @__PURE__ */ jsx2(
1383
+ Select,
1384
+ {
1385
+ id: `${uid}-auth-type`,
1386
+ label: connectionId ? "Override authentication" : "Authentication",
1387
+ value: auth.type,
1388
+ onChange: (next) => emit({ auth: { ...auth, type: next } }),
1389
+ disabled,
1390
+ options: [
1391
+ { value: "none", label: connectionId ? "Inherit from connection" : "No auth" },
1392
+ { value: "basic", label: "Basic auth (username + password)" },
1393
+ { value: "bearer", label: "Bearer token" },
1394
+ { value: "apikey", label: "API key" }
1395
+ ]
1396
+ }
1397
+ ),
1398
+ auth.type === "basic" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1399
+ /* @__PURE__ */ jsx2(
1400
+ Input,
1401
+ {
1402
+ id: `${uid}-auth-user`,
1403
+ label: "Username",
1404
+ value: auth.username ?? "",
1405
+ placeholder: "username or {{var}}",
1406
+ onChange: (e) => emit({ auth: { ...auth, username: e.target.value } }),
1407
+ disabled
1408
+ }
1409
+ ),
1410
+ /* @__PURE__ */ jsx2(
1411
+ PasswordInput,
1412
+ {
1413
+ id: `${uid}-auth-pass`,
1414
+ label: "Password",
1415
+ value: auth.password ?? "",
1416
+ placeholder: "password or {{var}}",
1417
+ onChange: (next) => emit({ auth: { ...auth, password: next } }),
1418
+ disabled
1419
+ }
1420
+ )
1421
+ ] }),
1422
+ auth.type === "bearer" && /* @__PURE__ */ jsx2(
1423
+ PasswordInput,
1424
+ {
1425
+ id: `${uid}-auth-token`,
1426
+ label: "Token",
1427
+ value: auth.token ?? "",
1428
+ placeholder: "your-token or {{step.token}}",
1429
+ onChange: (next) => emit({ auth: { ...auth, token: next } }),
1430
+ disabled
1431
+ }
1432
+ ),
1433
+ auth.type === "apikey" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1434
+ /* @__PURE__ */ jsx2(
1435
+ Input,
1436
+ {
1437
+ id: `${uid}-auth-keyname`,
1438
+ label: "Key name",
1439
+ value: auth.apiKeyName ?? "",
1440
+ placeholder: "X-API-Key",
1441
+ onChange: (e) => emit({ auth: { ...auth, apiKeyName: e.target.value } }),
1442
+ disabled
1443
+ }
1444
+ ),
1445
+ /* @__PURE__ */ jsx2(
1446
+ PasswordInput,
1447
+ {
1448
+ id: `${uid}-auth-keyval`,
1449
+ label: "Key value",
1450
+ value: auth.apiKeyValue ?? "",
1451
+ placeholder: "your-api-key or {{var}}",
1452
+ onChange: (next) => emit({ auth: { ...auth, apiKeyValue: next } }),
1453
+ disabled
1454
+ }
1455
+ ),
1456
+ /* @__PURE__ */ jsx2(
1457
+ Select,
1458
+ {
1459
+ id: `${uid}-auth-keyloc`,
1460
+ label: "Send in",
1461
+ value: auth.apiKeyLocation ?? "header",
1462
+ onChange: (next) => emit({ auth: { ...auth, apiKeyLocation: next } }),
1463
+ disabled,
1464
+ options: [
1465
+ { value: "header", label: "Request header" },
1466
+ { value: "query", label: "Query parameter" }
1467
+ ]
1468
+ }
1469
+ )
1470
+ ] }),
1471
+ 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." })
1472
+ ] });
1473
+ const responseTab = /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tab", children: [
1474
+ /* @__PURE__ */ jsx2(
1475
+ SegmentedControl,
1476
+ {
1477
+ size: "sm",
1478
+ "aria-label": "Response type",
1479
+ options: [
1480
+ { value: "json", label: "JSON" },
1481
+ { value: "text", label: "Text" },
1482
+ { value: "binary", label: "Binary" }
1483
+ ],
1484
+ value: responseType,
1485
+ onChange: (next) => emit({ responseType: next })
1486
+ }
1487
+ ),
1488
+ /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1489
+ responseType === "json" && "Tries to parse the response body as JSON; falls back to raw text on parse failure.",
1490
+ responseType === "text" && "Returns the raw response body as a string. No JSON parsing.",
1491
+ responseType === "binary" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1492
+ "Returns the response as bytes with ",
1493
+ /* @__PURE__ */ jsx2("code", { children: "fileName" }),
1494
+ ", ",
1495
+ /* @__PURE__ */ jsx2("code", { children: "contentType" }),
1496
+ " and",
1497
+ " ",
1498
+ /* @__PURE__ */ jsx2("code", { children: "size" }),
1499
+ " on the step output \u2014 for downloads feeding file actions. Pagination is not compatible with binary responses."
1500
+ ] })
1501
+ ] }),
1502
+ /* @__PURE__ */ jsx2(
1503
+ Input,
1504
+ {
1505
+ id: `${uid}-expected-ct`,
1506
+ label: "Expected content-types (optional)",
1507
+ value: expectedCtText,
1508
+ placeholder: "application/pdf, application/json",
1509
+ helperText: "Comma-separated. A warning is logged when the response Content-Type matches none of these.",
1510
+ onChange: (e) => {
1511
+ setExpectedCtText(e.target.value);
1512
+ const parts = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
1513
+ emit({ expectedContentTypes: parts.length ? parts : void 0 });
1514
+ },
1515
+ disabled
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__schema", children: [
1519
+ /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__schema-label", children: "Data stream schema" }),
1520
+ schemaInfo ? /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1521
+ /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: "success", children: [
1522
+ schemaInfo.pathCount,
1523
+ " paths"
1524
+ ] }),
1525
+ " ",
1526
+ "Inferred from the last test send. Top-level keys:",
1527
+ " ",
1528
+ /* @__PURE__ */ jsx2("code", { children: schemaInfo.topKeys.join(", ") }),
1529
+ ". Downstream binding (Field Mapper, Transform, ForEach) sees this shape immediately; Save persists it."
1530
+ ] }) : /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1531
+ "No schema yet \u2014 hit ",
1532
+ /* @__PURE__ */ jsx2("strong", { children: "Send" }),
1533
+ " in the request bar. On success the response shape is inferred so downstream steps bind against real field names."
1534
+ ] }),
1535
+ /* @__PURE__ */ jsx2(
1536
+ Button,
1537
+ {
1538
+ variant: "ghost",
1539
+ size: "sm",
1540
+ disabled: preview.phase !== "success" || responseType === "binary" || disabled,
1541
+ onClick: () => {
1542
+ if (preview.phase === "success") inferAndEmitSchema(preview.response, true);
1543
+ },
1544
+ children: "Refresh schema from last send"
1545
+ }
1546
+ )
1547
+ ] })
1548
+ ] });
1549
+ const tabItems = [
1550
+ {
1551
+ value: "params",
1552
+ label: paramCount ? `Params \xB7 ${paramCount}` : "Params",
1553
+ icon: /* @__PURE__ */ jsx2(ListIcon, { size: "sm" }),
1554
+ children: paramsTab
1555
+ },
1556
+ {
1557
+ value: "headers",
1558
+ label: headerCount ? `Headers \xB7 ${headerCount}` : "Headers",
1559
+ icon: /* @__PURE__ */ jsx2(TagIcon, { size: "sm" }),
1560
+ children: headersTab
1561
+ },
1562
+ {
1563
+ value: "body",
1564
+ label: bodyMode === "none" ? "Body" : `Body \xB7 ${bodyMode === "form-data" ? "form" : bodyMode}`,
1565
+ icon: /* @__PURE__ */ jsx2(CodeIcon, { size: "sm" }),
1566
+ children: bodyTab
1567
+ },
1568
+ {
1569
+ value: "auth",
1570
+ label: auth.type === "none" ? "Auth" : `Auth \xB7 ${auth.type}`,
1571
+ icon: /* @__PURE__ */ jsx2(LockedIcon, { size: "sm" }),
1572
+ children: authTab
1573
+ },
1574
+ {
1575
+ value: "response",
1576
+ label: responseType === "json" ? "Response" : `Response \xB7 ${responseType}`,
1577
+ icon: /* @__PURE__ */ jsx2(DataViewAltIcon, { size: "sm" }),
1578
+ children: responseTab
1579
+ }
1580
+ ];
1581
+ return /* @__PURE__ */ jsxs2("div", { className: cn("ods-http-panel", className), children: [
1582
+ connections && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__source", children: [
1583
+ /* @__PURE__ */ jsx2(
1584
+ Select,
1585
+ {
1586
+ id: `${uid}-connection`,
1587
+ label: "Source",
1588
+ placeholder: connectionsLoading ? "Loading\u2026" : "No connection (manual config)",
1589
+ options: connections.map((c) => ({ value: c.id, label: c.name, icon: c.icon })),
1590
+ value: connectionId,
1591
+ onChange: (next) => emit({ connectionId: next || void 0 }),
1592
+ disabled: disabled || connectionsLoading,
1593
+ 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."
1594
+ }
1595
+ ),
1596
+ connectionId && endpoints.length > 0 && /* @__PURE__ */ jsx2(
1597
+ Combobox,
1598
+ {
1599
+ ariaLabel: "Endpoint",
1600
+ placeholder: `Search ${endpoints.length} discovered endpoints\u2026`,
1601
+ value: url,
1602
+ creatable: true,
1603
+ options: endpoints.map((e) => ({
1604
+ value: e.path,
1605
+ label: `${e.method} ${e.path}`,
1606
+ description: e.summary
1607
+ })),
1608
+ onChange: (nextPath) => {
1609
+ const match = endpoints.find((e) => e.path === nextPath);
1610
+ validateUrl(nextPath, baseUrl, true);
1611
+ emit(
1612
+ match && HTTP_METHODS.includes(match.method.toUpperCase()) ? { url: nextPath, method: match.method.toUpperCase() } : { url: nextPath }
1613
+ );
1614
+ }
1615
+ }
1616
+ )
1617
+ ] }),
1618
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__bar", children: [
1619
+ /* @__PURE__ */ jsx2(
1620
+ Select,
1621
+ {
1622
+ id: `${uid}-method`,
1623
+ "aria-label": "HTTP method",
1624
+ className: cn(
1625
+ "ods-http-panel__method",
1626
+ `ods-http-panel__method--${method.toLowerCase()}`
1627
+ ),
1628
+ value: method,
1629
+ onChange: (next) => emit({ method: next }),
1630
+ disabled,
1631
+ options: HTTP_METHODS.map((m) => ({ value: m, label: m }))
1632
+ }
1633
+ ),
1634
+ /* @__PURE__ */ jsx2(
1635
+ Input,
1636
+ {
1637
+ id: `${uid}-url`,
1638
+ "aria-label": connectionId ? "Path" : "URL",
1639
+ "data-fx-droppable": "true",
1640
+ className: "ods-http-panel__url",
1641
+ value: url,
1642
+ placeholder: connectionId ? "/v1/users" : "https://api.example.com/v1/users or {{var}}",
1643
+ error: Boolean(urlError),
1644
+ errorMessage: urlError,
1645
+ onChange: (e) => {
1646
+ validateUrl(e.target.value, baseUrl, Boolean(connectionId));
1647
+ emit({ url: e.target.value });
1648
+ },
1649
+ disabled
1650
+ }
1651
+ ),
1652
+ onSendPreview && /* @__PURE__ */ jsx2(
1653
+ Button,
1654
+ {
1655
+ variant: "primary",
1656
+ size: "md",
1657
+ leftIcon: preview.phase === "loading" ? void 0 : /* @__PURE__ */ jsx2(PlayIcon, { size: "sm" }),
1658
+ onClick: sendPreview,
1659
+ disabled: !canSend,
1660
+ children: preview.phase === "loading" ? "Sending\u2026" : "Send"
1661
+ }
1662
+ )
1663
+ ] }),
1664
+ !connectionId && baseUrl && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__effective", children: [
1665
+ /* @__PURE__ */ jsx2("span", { children: "Effective URL:" }),
1666
+ " ",
1667
+ /* @__PURE__ */ jsx2("code", { children: fullUrl || "\u2014" })
1668
+ ] }),
1669
+ fullUrl.includes("{{") && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
1670
+ "\u26A0 URL contains ",
1671
+ /* @__PURE__ */ jsx2("code", { children: "{{variables}}" }),
1672
+ " \u2014 they resolve at run time; testing needs literal values."
1673
+ ] }),
1674
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__tools", children: [
1675
+ /* @__PURE__ */ jsxs2("span", { className: "ods-http-panel__hint", children: [
1676
+ "Use ",
1677
+ /* @__PURE__ */ jsx2("code", { children: "{{stepName.fieldPath}}" }),
1678
+ " to insert data from upstream steps."
1679
+ ] }),
1680
+ /* @__PURE__ */ jsxs2("span", { className: "ods-http-panel__tools-actions", children: [
1681
+ /* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "sm", onClick: () => setCurlOpen((o) => !o), children: "Import cURL" }),
1682
+ /* @__PURE__ */ jsx2(
1683
+ Button,
1684
+ {
1685
+ variant: "ghost",
1686
+ size: "sm",
1687
+ className: cn(copiedCurl && "ods-http-panel__copy-done"),
1688
+ leftIcon: copiedCurl ? /* @__PURE__ */ jsx2(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx2(CopyIcon, { size: "sm" }),
1689
+ onClick: copyCurl,
1690
+ children: copiedCurl ? "Copied" : "Copy as cURL"
1691
+ }
1692
+ )
1693
+ ] })
1694
+ ] }),
1695
+ curlOpen && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__curl", children: [
1696
+ /* @__PURE__ */ jsx2(
1697
+ CodeEditor,
1698
+ {
1699
+ value: curlText,
1700
+ language: "plain",
1701
+ onChange: setCurlText,
1702
+ minHeight: 90,
1703
+ showLineNumbers: false,
1704
+ placeholder: "curl -X POST 'https://api.example.com/v1/users' -H 'Authorization: Bearer \u2026' -d '{\u2026}'",
1705
+ ariaLabel: "cURL command"
1706
+ }
1707
+ ),
1708
+ curlError && /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__error", children: [
1709
+ "\u26A0 ",
1710
+ curlError
1711
+ ] }),
1712
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__curl-actions", children: [
1713
+ /* @__PURE__ */ jsx2(Button, { variant: "secondary", size: "sm", onClick: () => setCurlOpen(false), children: "Cancel" }),
1714
+ /* @__PURE__ */ jsx2(Button, { variant: "primary", size: "sm", onClick: applyCurl, disabled: !curlText.trim(), children: "Apply to request" })
1715
+ ] })
1716
+ ] }),
1717
+ /* @__PURE__ */ jsx2(Tabs, { items: tabItems, defaultValue: "params", className: "ods-http-panel__tabs" }),
1718
+ /* @__PURE__ */ jsxs2(
1719
+ Disclosure,
1720
+ {
1721
+ size: "sm",
1722
+ icon: /* @__PURE__ */ jsx2(GlobeIcon, { size: "sm" }),
1723
+ title: "Advanced options",
1724
+ description: "Base URL, timeout, retries, pagination, response extraction",
1725
+ trailing: advancedSummary.length ? /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "info", children: advancedSummary.join(" \xB7 ") }) : void 0,
1726
+ children: [
1727
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1728
+ !connectionId && /* @__PURE__ */ jsx2(
1729
+ Input,
1730
+ {
1731
+ id: `${uid}-base-url`,
1732
+ label: "Base URL (optional)",
1733
+ value: baseUrl,
1734
+ placeholder: "https://api.example.com",
1735
+ helperText: "Combined with the request path to form the full URL.",
1736
+ onChange: (e) => {
1737
+ validateUrl(url, e.target.value, Boolean(connectionId));
1738
+ emit({ baseUrl: e.target.value || void 0 });
1739
+ },
1740
+ disabled
1741
+ }
1742
+ ),
1743
+ /* @__PURE__ */ jsx2(
1744
+ NumberInput,
1745
+ {
1746
+ label: "Timeout (ms)",
1747
+ helperText: "Minimum 1000 ms",
1748
+ value: cfg.timeout ?? 3e4,
1749
+ min: 1e3,
1750
+ step: 1e3,
1751
+ onChange: (next) => {
1752
+ if (!Number.isNaN(next) && next >= 1e3) emit({ timeout: next });
1753
+ },
1754
+ disabled
1755
+ }
1756
+ ),
1757
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__switches", children: [
1758
+ /* @__PURE__ */ jsx2(
1759
+ Switch,
1760
+ {
1761
+ id: `${uid}-fail`,
1762
+ label: "Fail on non-2xx",
1763
+ checked: cfg.failOnError !== false,
1764
+ onChange: (next) => emit({ failOnError: next }),
1765
+ disabled,
1766
+ size: "sm"
1767
+ }
1768
+ ),
1769
+ /* @__PURE__ */ jsx2(
1770
+ Switch,
1771
+ {
1772
+ id: `${uid}-redirects`,
1773
+ label: "Follow redirects",
1774
+ checked: cfg.followRedirects !== false,
1775
+ onChange: (next) => emit({ followRedirects: next }),
1776
+ disabled,
1777
+ size: "sm"
1778
+ }
1779
+ )
1780
+ ] })
1781
+ ] }),
1782
+ /* @__PURE__ */ jsx2(
1783
+ Disclosure,
1784
+ {
1785
+ size: "sm",
1786
+ title: "Retry",
1787
+ trailing: (retry?.maxAttempts ?? 1) > 1 ? /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: "info", children: [
1788
+ retry?.maxAttempts,
1789
+ " attempts"
1790
+ ] }) : void 0,
1791
+ children: /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1792
+ /* @__PURE__ */ jsx2(
1793
+ NumberInput,
1794
+ {
1795
+ label: "Max attempts",
1796
+ helperText: "1 = no retry",
1797
+ value: retry?.maxAttempts ?? 1,
1798
+ min: 1,
1799
+ max: 10,
1800
+ onChange: (next) => {
1801
+ if (!Number.isNaN(next) && next >= 1 && retry) {
1802
+ emit({ retry: { ...retry, maxAttempts: next } });
1803
+ }
1804
+ },
1805
+ disabled
1806
+ }
1807
+ ),
1808
+ /* @__PURE__ */ jsx2(
1809
+ Input,
1810
+ {
1811
+ id: `${uid}-retry-on`,
1812
+ label: "Retry on (status codes)",
1813
+ value: retryOnText,
1814
+ placeholder: "429, 500, 502, 503, 504",
1815
+ helperText: "Comma-separated HTTP status codes",
1816
+ onChange: (e) => {
1817
+ setRetryOnText(e.target.value);
1818
+ const codes = e.target.value.split(",").map((s) => Number.parseInt(s.trim(), 10)).filter((n) => !Number.isNaN(n));
1819
+ if (retry) emit({ retry: { ...retry, retryOn: codes } });
1820
+ },
1821
+ disabled
1822
+ }
1823
+ ),
1824
+ /* @__PURE__ */ jsx2(
1825
+ NumberInput,
1826
+ {
1827
+ label: "Initial delay (ms)",
1828
+ value: retry?.initialDelayMs ?? 1e3,
1829
+ min: 100,
1830
+ step: 100,
1831
+ onChange: (next) => {
1832
+ if (!Number.isNaN(next) && retry)
1833
+ emit({ retry: { ...retry, initialDelayMs: next } });
1834
+ },
1835
+ disabled
1836
+ }
1837
+ ),
1838
+ /* @__PURE__ */ jsx2(
1839
+ NumberInput,
1840
+ {
1841
+ label: "Backoff factor",
1842
+ helperText: "Delay multiplies by this each retry",
1843
+ value: retry?.backoffFactor ?? 2,
1844
+ min: 1,
1845
+ step: 0.5,
1846
+ onChange: (next) => {
1847
+ if (!Number.isNaN(next) && retry)
1848
+ emit({ retry: { ...retry, backoffFactor: next } });
1849
+ },
1850
+ disabled
1851
+ }
1852
+ ),
1853
+ /* @__PURE__ */ jsx2(
1854
+ NumberInput,
1855
+ {
1856
+ label: "Max delay (ms)",
1857
+ value: retry?.maxDelayMs ?? 3e4,
1858
+ min: 1e3,
1859
+ step: 1e3,
1860
+ onChange: (next) => {
1861
+ if (!Number.isNaN(next) && retry) emit({ retry: { ...retry, maxDelayMs: next } });
1862
+ },
1863
+ disabled
1864
+ }
1865
+ )
1866
+ ] })
1867
+ }
1868
+ ),
1869
+ /* @__PURE__ */ jsxs2(
1870
+ Disclosure,
1871
+ {
1872
+ size: "sm",
1873
+ title: "Pagination",
1874
+ trailing: pagination.type !== "none" ? /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "success", children: pagination.type.replace("_", " ") }) : void 0,
1875
+ children: [
1876
+ /* @__PURE__ */ jsx2(
1877
+ Select,
1878
+ {
1879
+ id: `${uid}-pag-type`,
1880
+ label: "Strategy",
1881
+ value: pagination.type,
1882
+ onChange: (next) => emit({
1883
+ pagination: { ...pagination, type: next }
1884
+ }),
1885
+ disabled,
1886
+ options: [
1887
+ { value: "none", label: "None (single request)" },
1888
+ { value: "offset", label: "Offset / page number" },
1889
+ { value: "cursor", label: "Cursor-based" },
1890
+ { value: "link_header", label: 'Link header (rel="next")' },
1891
+ { value: "next_url", label: "Next URL in response body" }
1892
+ ]
1893
+ }
1894
+ ),
1895
+ pagination.type !== "none" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__grid", children: [
1896
+ /* @__PURE__ */ jsx2(
1897
+ NumberInput,
1898
+ {
1899
+ label: "Max pages",
1900
+ helperText: "Safety cap against infinite loops",
1901
+ value: pagination.maxPages ?? 20,
1902
+ min: 1,
1903
+ onChange: (next) => {
1904
+ if (!Number.isNaN(next)) emit({ pagination: { ...pagination, maxPages: next } });
1905
+ },
1906
+ disabled
1907
+ }
1908
+ ),
1909
+ /* @__PURE__ */ jsx2(
1910
+ Input,
1911
+ {
1912
+ id: `${uid}-pag-merge`,
1913
+ label: "Merge key (dot path)",
1914
+ value: pagination.mergeKey ?? "",
1915
+ placeholder: "data.items",
1916
+ helperText: "Array to merge across pages",
1917
+ onChange: (e) => emit({ pagination: { ...pagination, mergeKey: e.target.value } }),
1918
+ disabled
1919
+ }
1920
+ ),
1921
+ (pagination.type === "offset" || pagination.type === "cursor") && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1922
+ /* @__PURE__ */ jsx2(
1923
+ Input,
1924
+ {
1925
+ id: `${uid}-pag-limitp`,
1926
+ label: "Limit param",
1927
+ value: pagination.limitParam ?? "limit",
1928
+ onChange: (e) => emit({ pagination: { ...pagination, limitParam: e.target.value } }),
1929
+ disabled
1930
+ }
1931
+ ),
1932
+ /* @__PURE__ */ jsx2(
1933
+ NumberInput,
1934
+ {
1935
+ label: "Page size",
1936
+ value: pagination.limitValue ?? 100,
1937
+ min: 1,
1938
+ onChange: (next) => {
1939
+ if (!Number.isNaN(next)) {
1940
+ emit({ pagination: { ...pagination, limitValue: next } });
1941
+ }
1942
+ },
1943
+ disabled
1944
+ }
1945
+ )
1946
+ ] }),
1947
+ pagination.type === "offset" && /* @__PURE__ */ jsx2(
1948
+ Input,
1949
+ {
1950
+ id: `${uid}-pag-offset`,
1951
+ label: "Offset param",
1952
+ value: pagination.offsetParam ?? "offset",
1953
+ onChange: (e) => emit({ pagination: { ...pagination, offsetParam: e.target.value } }),
1954
+ disabled
1955
+ }
1956
+ ),
1957
+ pagination.type === "cursor" && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1958
+ /* @__PURE__ */ jsx2(
1959
+ Input,
1960
+ {
1961
+ id: `${uid}-pag-cres`,
1962
+ label: "Cursor in response (dot path)",
1963
+ value: pagination.cursorResponsePath ?? "",
1964
+ placeholder: "meta.next_cursor",
1965
+ onChange: (e) => emit({ pagination: { ...pagination, cursorResponsePath: e.target.value } }),
1966
+ disabled
1967
+ }
1968
+ ),
1969
+ /* @__PURE__ */ jsx2(
1970
+ Input,
1971
+ {
1972
+ id: `${uid}-pag-creq`,
1973
+ label: "Cursor request param",
1974
+ value: pagination.cursorRequestParam ?? "cursor",
1975
+ onChange: (e) => emit({ pagination: { ...pagination, cursorRequestParam: e.target.value } }),
1976
+ disabled
1977
+ }
1978
+ )
1979
+ ] }),
1980
+ pagination.type === "next_url" && /* @__PURE__ */ jsx2(
1981
+ Input,
1982
+ {
1983
+ id: `${uid}-pag-next`,
1984
+ label: "Next URL in response (dot path)",
1985
+ value: pagination.nextUrlResponsePath ?? "",
1986
+ placeholder: "links.next",
1987
+ onChange: (e) => emit({ pagination: { ...pagination, nextUrlResponsePath: e.target.value } }),
1988
+ disabled
1989
+ }
1990
+ )
1991
+ ] })
1992
+ ]
1993
+ }
1994
+ ),
1995
+ /* @__PURE__ */ jsxs2(
1996
+ Disclosure,
1997
+ {
1998
+ size: "sm",
1999
+ title: "Response field extraction",
2000
+ trailing: extractCount > 0 ? /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: "info", children: [
2001
+ extractCount,
2002
+ " field",
2003
+ extractCount === 1 ? "" : "s"
2004
+ ] }) : void 0,
2005
+ children: [
2006
+ /* @__PURE__ */ jsxs2("p", { className: "ods-http-panel__desc", children: [
2007
+ "Extract fields from the response with dot paths \u2014 available downstream as",
2008
+ " ",
2009
+ /* @__PURE__ */ jsx2("code", { children: "{{extracted.keyName}}" }),
2010
+ "."
2011
+ ] }),
2012
+ /* @__PURE__ */ jsx2(
2013
+ KeyValueEditor,
2014
+ {
2015
+ pairs: extractRows.map((r) => ({ key: r.key, value: r.value })),
2016
+ onChange: (pairs) => {
2017
+ const rows = pairs.map((p) => ({ key: p.key, value: p.value, enabled: true }));
2018
+ setExtractRows(rows);
2019
+ const extract = {};
2020
+ for (const r of rows) if (r.key && r.value) extract[r.key] = r.value;
2021
+ emit({ extract: Object.keys(extract).length ? extract : void 0 });
2022
+ },
2023
+ keyPlaceholder: "outputKey",
2024
+ valuePlaceholder: "data.user.id",
2025
+ emptyLabel: "No extractions yet \u2014 click Add to begin.",
2026
+ addLabel: "Add",
2027
+ size: "sm",
2028
+ disabled
2029
+ }
2030
+ )
2031
+ ]
2032
+ }
2033
+ )
2034
+ ]
2035
+ }
2036
+ ),
2037
+ preview.phase !== "idle" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console", children: [
2038
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-head", children: [
2039
+ /* @__PURE__ */ jsx2(
2040
+ "code",
2041
+ {
2042
+ className: cn(
2043
+ "ods-http-panel__console-method",
2044
+ `ods-http-panel__method--${method.toLowerCase()}`
2045
+ ),
2046
+ children: cfg.apiType === "graphql" ? "POST" : method
2047
+ }
2048
+ ),
2049
+ /* @__PURE__ */ jsx2("code", { className: "ods-http-panel__console-url", children: connectionId ? `${selectedConnection?.name ?? connectionId} \xB7 ${url || "\u2014"}` : fullUrl || "\u2014" }),
2050
+ preview.phase === "success" && /* @__PURE__ */ jsx2(
2051
+ Button,
2052
+ {
2053
+ variant: "ghost",
2054
+ size: "sm",
2055
+ className: cn(copiedResult && "ods-http-panel__copy-done"),
2056
+ leftIcon: copiedResult ? /* @__PURE__ */ jsx2(CheckmarkIcon, { size: "sm" }) : /* @__PURE__ */ jsx2(CopyIcon, { size: "sm" }),
2057
+ onClick: copyResult,
2058
+ children: copiedResult ? "Copied" : "Copy result"
2059
+ }
2060
+ ),
2061
+ /* @__PURE__ */ jsx2(
2062
+ Button,
2063
+ {
2064
+ variant: "ghost",
2065
+ size: "sm",
2066
+ leftIcon: /* @__PURE__ */ jsx2(RenewIcon, { size: "sm" }),
2067
+ onClick: () => {
2068
+ setPreview({ phase: "idle" });
2069
+ },
2070
+ disabled: preview.phase === "loading",
2071
+ children: "Clear"
2072
+ }
2073
+ )
2074
+ ] }),
2075
+ preview.phase === "loading" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-loading", children: [
2076
+ /* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
2077
+ /* @__PURE__ */ jsx2("span", { children: "Sending request\u2026" })
2078
+ ] }),
2079
+ preview.phase === "error" && /* @__PURE__ */ jsxs2(Banner, { variant: "error", appearance: "subtle", size: "sm", children: [
2080
+ /* @__PURE__ */ jsx2("strong", { children: "Request failed." }),
2081
+ " ",
2082
+ preview.message
2083
+ ] }),
2084
+ preview.phase === "success" && /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-result", children: [
2085
+ /* @__PURE__ */ jsxs2("div", { className: "ods-http-panel__console-status", children: [
2086
+ /* @__PURE__ */ jsxs2(Badge, { size: "sm", variant: statusTone(preview.response.status), children: [
2087
+ preview.response.status,
2088
+ " ",
2089
+ preview.response.statusText
2090
+ ] }),
2091
+ /* @__PURE__ */ jsxs2("span", { className: "ods-http-panel__meta", children: [
2092
+ preview.response.duration,
2093
+ "ms"
2094
+ ] }),
2095
+ /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__meta", children: formatBytes(preview.response.size) })
2096
+ ] }),
2097
+ /* @__PURE__ */ jsx2(
2098
+ Accordion,
2099
+ {
2100
+ size: "sm",
2101
+ mode: "multiple",
2102
+ defaultValue: ["res-body"],
2103
+ items: [
2104
+ {
2105
+ id: "res-headers",
2106
+ label: "Response headers",
2107
+ trailing: /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "neutral", children: Object.keys(preview.response.headers).length }),
2108
+ 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: [
2109
+ /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__res-header-key", children: k }),
2110
+ /* @__PURE__ */ jsx2("span", { className: "ods-http-panel__res-header-val", children: v })
2111
+ ] }, k)) })
2112
+ },
2113
+ {
2114
+ id: "res-body",
2115
+ label: "Response body",
2116
+ trailing: !preview.response.ok ? /* @__PURE__ */ jsx2(Badge, { size: "sm", variant: "error", children: "Error" }) : null,
2117
+ content: (
2118
+ // Size guard: the tree view renders recursively (no
2119
+ // virtualization) — very large bodies fall back to text.
2120
+ preview.response.body !== null && typeof preview.response.body === "object" && preview.response.size <= 15e5 ? /* @__PURE__ */ jsx2(
2121
+ JsonViewer,
2122
+ {
2123
+ data: preview.response.body,
2124
+ mode: "view",
2125
+ maxHeight: 320,
2126
+ copyable: true
2127
+ }
2128
+ ) : /* @__PURE__ */ jsx2("pre", { className: "ods-http-panel__res-body", children: typeof preview.response.body === "object" ? JSON.stringify(preview.response.body, null, 2) : String(
2129
+ preview.response.bodyRaw ?? preview.response.body ?? "(empty body)"
2130
+ ) })
2131
+ )
2132
+ }
2133
+ ]
2134
+ }
2135
+ )
2136
+ ] })
2137
+ ] })
2138
+ ] });
2139
+ }
2140
+
623
2141
  // src/workflow/components/FxPanel/FxPanel.tsx
624
2142
  import {
625
- useMemo as useMemo2,
626
- useRef as useRef4,
627
- useState as useState4
2143
+ useMemo as useMemo3,
2144
+ useRef as useRef5,
2145
+ useState as useState5
628
2146
  } from "react";
629
2147
  import {
630
2148
  ChevronDownIcon,
@@ -637,7 +2155,7 @@ import {
637
2155
  SearchIcon,
638
2156
  ValueVariableIcon
639
2157
  } from "@octaviaflow/icons";
640
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2158
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
641
2159
  var KIND_ICON = {
642
2160
  function: FunctionMathIcon,
643
2161
  variable: ValueVariableIcon
@@ -696,18 +2214,18 @@ function FxPanel({
696
2214
  className,
697
2215
  style
698
2216
  }) {
699
- const [internalSearch, setInternalSearch] = useState4(defaultSearch);
2217
+ const [internalSearch, setInternalSearch] = useState5(defaultSearch);
700
2218
  const search = controlledSearch ?? internalSearch;
701
2219
  const query = search.trim().toLowerCase();
702
2220
  const setSearch = (next) => {
703
2221
  if (controlledSearch === void 0) setInternalSearch(next);
704
2222
  onSearchChange?.(next);
705
2223
  };
706
- const visibleCategories = useMemo2(
2224
+ const visibleCategories = useMemo3(
707
2225
  () => categories.filter((c) => c.items.length > 0),
708
2226
  [categories]
709
2227
  );
710
- const [internalCat, setInternalCat] = useState4(
2228
+ const [internalCat, setInternalCat] = useState5(
711
2229
  defaultExpandedCategory !== void 0 ? defaultExpandedCategory : visibleCategories[0]?.id ?? null
712
2230
  );
713
2231
  const openCategoryId = controlledExpanded ?? internalCat;
@@ -715,7 +2233,7 @@ function FxPanel({
715
2233
  if (controlledExpanded === void 0) setInternalCat(id);
716
2234
  onExpandedCategoryChange?.(id);
717
2235
  };
718
- const [expandedItems, setExpandedItems] = useState4(() => {
2236
+ const [expandedItems, setExpandedItems] = useState5(() => {
719
2237
  const ids = /* @__PURE__ */ new Set();
720
2238
  for (const cat of categories) {
721
2239
  for (const item of cat.items) {
@@ -744,9 +2262,9 @@ function FxPanel({
744
2262
  return next;
745
2263
  });
746
2264
  };
747
- const [draggingId, setDraggingId] = useState4(null);
748
- const [focusedId, setFocusedId] = useState4(null);
749
- const hits = useMemo2(() => {
2265
+ const [draggingId, setDraggingId] = useState5(null);
2266
+ const [focusedId, setFocusedId] = useState5(null);
2267
+ const hits = useMemo3(() => {
750
2268
  if (!query) return null;
751
2269
  const all = [];
752
2270
  for (const cat of visibleCategories) {
@@ -770,7 +2288,7 @@ function FxPanel({
770
2288
  e.dataTransfer.effectAllowed = "copy";
771
2289
  }
772
2290
  };
773
- const listRef = useRef4(null);
2291
+ const listRef = useRef5(null);
774
2292
  const focusRow = (id) => {
775
2293
  setFocusedId(id);
776
2294
  requestAnimationFrame(() => {
@@ -828,7 +2346,7 @@ function FxPanel({
828
2346
  break;
829
2347
  }
830
2348
  };
831
- const searchRows = useMemo2(
2349
+ const searchRows = useMemo3(
832
2350
  () => hits ? hits.map(({ hit }) => ({
833
2351
  item: hit.item,
834
2352
  depth: 0,
@@ -842,7 +2360,7 @@ function FxPanel({
842
2360
  const mono = category.kind === "function";
843
2361
  const draggable = !!item.insertValue;
844
2362
  const isFocused = focusedId === item.id;
845
- return /* @__PURE__ */ jsxs2(
2363
+ return /* @__PURE__ */ jsxs3(
846
2364
  "div",
847
2365
  {
848
2366
  "data-fx-row": item.id,
@@ -867,12 +2385,12 @@ function FxPanel({
867
2385
  },
868
2386
  title: item.insertValue,
869
2387
  children: [
870
- Array.from({ length: depth }, (_, i) => /* @__PURE__ */ jsx2("span", { className: "ods-flow-fx-panel__indent", "aria-hidden": "true" }, i)),
871
- hasChildren ? /* @__PURE__ */ jsx2("span", { className: "ods-flow-fx-panel__twisty", "aria-hidden": "true", children: expanded ? /* @__PURE__ */ jsx2(ChevronDownIcon, { size: "sm" }) : /* @__PURE__ */ jsx2(ChevronRightIcon, { size: "sm" }) }) : /* @__PURE__ */ jsx2("span", { className: "ods-flow-fx-panel__grip", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(DraggableIcon, { size: "sm" }) }),
872
- /* @__PURE__ */ jsxs2("span", { className: "ods-flow-fx-panel__row-main", children: [
873
- /* @__PURE__ */ jsxs2("span", { className: "ods-flow-fx-panel__row-line", children: [
874
- mono ? /* @__PURE__ */ jsx2("code", { className: "ods-flow-fx-panel__row-label ods-flow-fx-panel__row-label--mono", children: item.label }) : /* @__PURE__ */ jsx2("span", { className: "ods-flow-fx-panel__row-label", children: item.label }),
875
- item.valueType && /* @__PURE__ */ jsx2(
2388
+ Array.from({ length: depth }, (_, i) => /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__indent", "aria-hidden": "true" }, i)),
2389
+ 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" }) }),
2390
+ /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-main", children: [
2391
+ /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-line", children: [
2392
+ 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 }),
2393
+ item.valueType && /* @__PURE__ */ jsx3(
876
2394
  "span",
877
2395
  {
878
2396
  className: cn(
@@ -883,8 +2401,8 @@ function FxPanel({
883
2401
  }
884
2402
  )
885
2403
  ] }),
886
- (item.description || crumb) && /* @__PURE__ */ jsxs2("span", { className: "ods-flow-fx-panel__row-sub", children: [
887
- crumb && /* @__PURE__ */ jsx2("span", { className: "ods-flow-fx-panel__crumb", children: crumb }),
2404
+ (item.description || crumb) && /* @__PURE__ */ jsxs3("span", { className: "ods-flow-fx-panel__row-sub", children: [
2405
+ crumb && /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__crumb", children: crumb }),
888
2406
  item.description
889
2407
  ] })
890
2408
  ] })
@@ -893,29 +2411,29 @@ function FxPanel({
893
2411
  item.id
894
2412
  );
895
2413
  };
896
- return /* @__PURE__ */ jsxs2(
2414
+ return /* @__PURE__ */ jsxs3(
897
2415
  "aside",
898
2416
  {
899
2417
  className: cn("ods-flow-fx-panel", className),
900
2418
  style: { width, ...style },
901
2419
  "aria-label": typeof title === "string" ? title : "Data References",
902
2420
  children: [
903
- /* @__PURE__ */ jsxs2("header", { className: "ods-flow-fx-panel__header", children: [
904
- /* @__PURE__ */ jsx2("h3", { className: "ods-flow-fx-panel__title", children: title }),
905
- onClose && /* @__PURE__ */ jsx2(
2421
+ /* @__PURE__ */ jsxs3("header", { className: "ods-flow-fx-panel__header", children: [
2422
+ /* @__PURE__ */ jsx3("h3", { className: "ods-flow-fx-panel__title", children: title }),
2423
+ onClose && /* @__PURE__ */ jsx3(
906
2424
  "button",
907
2425
  {
908
2426
  type: "button",
909
2427
  className: "ods-flow-fx-panel__close",
910
2428
  onClick: onClose,
911
2429
  "aria-label": "Close data references",
912
- children: /* @__PURE__ */ jsx2(CloseIcon, { size: "sm" })
2430
+ children: /* @__PURE__ */ jsx3(CloseIcon, { size: "sm" })
913
2431
  }
914
2432
  )
915
2433
  ] }),
916
- /* @__PURE__ */ jsxs2("div", { className: "ods-flow-fx-panel__search", children: [
917
- /* @__PURE__ */ jsx2("span", { className: "ods-flow-fx-panel__search-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(SearchIcon, { size: "sm" }) }),
918
- /* @__PURE__ */ jsx2(
2434
+ /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__search", children: [
2435
+ /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__search-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(SearchIcon, { size: "sm" }) }),
2436
+ /* @__PURE__ */ jsx3(
919
2437
  "input",
920
2438
  {
921
2439
  type: "text",
@@ -926,21 +2444,21 @@ function FxPanel({
926
2444
  onChange: (e) => setSearch(e.target.value)
927
2445
  }
928
2446
  ),
929
- search && /* @__PURE__ */ jsx2(
2447
+ search && /* @__PURE__ */ jsx3(
930
2448
  "button",
931
2449
  {
932
2450
  type: "button",
933
2451
  className: "ods-flow-fx-panel__search-clear",
934
2452
  onClick: () => setSearch(""),
935
2453
  "aria-label": "Clear search",
936
- children: /* @__PURE__ */ jsx2(CloseIcon, { size: "sm" })
2454
+ children: /* @__PURE__ */ jsx3(CloseIcon, { size: "sm" })
937
2455
  }
938
2456
  )
939
2457
  ] }),
940
- hint && !query && /* @__PURE__ */ jsx2("p", { className: "ods-flow-fx-panel__hint", children: hint }),
941
- /* @__PURE__ */ jsxs2("div", { className: "ods-flow-fx-panel__list", ref: listRef, role: "tree", children: [
942
- query && hits && (hits.length > 0 ? /* @__PURE__ */ jsxs2("div", { className: "ods-flow-fx-panel__results", children: [
943
- /* @__PURE__ */ jsxs2("div", { className: "ods-flow-fx-panel__results-count", children: [
2458
+ hint && !query && /* @__PURE__ */ jsx3("p", { className: "ods-flow-fx-panel__hint", children: hint }),
2459
+ /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__list", ref: listRef, role: "tree", children: [
2460
+ query && hits && (hits.length > 0 ? /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__results", children: [
2461
+ /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__results-count", children: [
944
2462
  hits.length,
945
2463
  " ",
946
2464
  hits.length === 1 ? "match" : "matches"
@@ -953,7 +2471,7 @@ function FxPanel({
953
2471
  hit.ancestors.length ? hit.ancestors.join(" / ") : category.label
954
2472
  )
955
2473
  )
956
- ] }) : /* @__PURE__ */ jsx2("div", { className: "ods-flow-fx-panel__empty", children: emptyLabel })),
2474
+ ] }) : /* @__PURE__ */ jsx3("div", { className: "ods-flow-fx-panel__empty", children: emptyLabel })),
957
2475
  !query && visibleCategories.map((cat) => {
958
2476
  const isOpen = openCategoryId === cat.id;
959
2477
  const kind = cat.kind ?? "function";
@@ -964,8 +2482,8 @@ function FxPanel({
964
2482
  const rows = [];
965
2483
  if (isOpen) flattenTree(cat.items, 0, expandedItems, rows);
966
2484
  const allExpanded = hasTree && [...branchIds].every((id) => expandedItems.has(id));
967
- return /* @__PURE__ */ jsxs2("div", { className: "ods-flow-fx-panel__category", children: [
968
- /* @__PURE__ */ jsxs2(
2485
+ return /* @__PURE__ */ jsxs3("div", { className: "ods-flow-fx-panel__category", children: [
2486
+ /* @__PURE__ */ jsxs3(
969
2487
  "div",
970
2488
  {
971
2489
  className: cn(
@@ -973,7 +2491,7 @@ function FxPanel({
973
2491
  isOpen && "ods-flow-fx-panel__cat-header--open"
974
2492
  ),
975
2493
  children: [
976
- /* @__PURE__ */ jsxs2(
2494
+ /* @__PURE__ */ jsxs3(
977
2495
  "button",
978
2496
  {
979
2497
  type: "button",
@@ -981,7 +2499,7 @@ function FxPanel({
981
2499
  "aria-expanded": isOpen,
982
2500
  onClick: () => setOpenCategory(isOpen ? null : cat.id),
983
2501
  children: [
984
- /* @__PURE__ */ jsx2(
2502
+ /* @__PURE__ */ jsx3(
985
2503
  ChevronRightIcon,
986
2504
  {
987
2505
  size: "sm",
@@ -991,7 +2509,7 @@ function FxPanel({
991
2509
  )
992
2510
  }
993
2511
  ),
994
- /* @__PURE__ */ jsxs2(
2512
+ /* @__PURE__ */ jsxs3(
995
2513
  "span",
996
2514
  {
997
2515
  className: cn(
@@ -999,7 +2517,7 @@ function FxPanel({
999
2517
  `ods-flow-fx-panel__badge--${kind}`
1000
2518
  ),
1001
2519
  children: [
1002
- /* @__PURE__ */ jsx2(
2520
+ /* @__PURE__ */ jsx3(
1003
2521
  KindIcon,
1004
2522
  {
1005
2523
  size: "sm",
@@ -1010,11 +2528,11 @@ function FxPanel({
1010
2528
  ]
1011
2529
  }
1012
2530
  ),
1013
- /* @__PURE__ */ jsx2("span", { className: "ods-flow-fx-panel__count", children: countLeaves(cat.items) })
2531
+ /* @__PURE__ */ jsx3("span", { className: "ods-flow-fx-panel__count", children: countLeaves(cat.items) })
1014
2532
  ]
1015
2533
  }
1016
2534
  ),
1017
- isOpen && hasTree && /* @__PURE__ */ jsx2(
2535
+ isOpen && hasTree && /* @__PURE__ */ jsx3(
1018
2536
  "button",
1019
2537
  {
1020
2538
  type: "button",
@@ -1022,13 +2540,13 @@ function FxPanel({
1022
2540
  "aria-label": allExpanded ? "Collapse all" : "Expand all",
1023
2541
  title: allExpanded ? "Collapse all" : "Expand all",
1024
2542
  onClick: () => setCategoryExpansion(cat, !allExpanded),
1025
- children: allExpanded ? /* @__PURE__ */ jsx2(CollapseAllIcon, { size: "sm" }) : /* @__PURE__ */ jsx2(ExpandAllIcon, { size: "sm" })
2543
+ children: allExpanded ? /* @__PURE__ */ jsx3(CollapseAllIcon, { size: "sm" }) : /* @__PURE__ */ jsx3(ExpandAllIcon, { size: "sm" })
1026
2544
  }
1027
2545
  )
1028
2546
  ]
1029
2547
  }
1030
2548
  ),
1031
- isOpen && /* @__PURE__ */ jsx2(
2549
+ isOpen && /* @__PURE__ */ jsx3(
1032
2550
  "div",
1033
2551
  {
1034
2552
  className: "ods-flow-fx-panel__rows",
@@ -1047,7 +2565,7 @@ function FxPanel({
1047
2565
 
1048
2566
  // src/workflow/components/FxPanel/DataRefButton.tsx
1049
2567
  import { DataReferenceIcon } from "@octaviaflow/icons";
1050
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2568
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1051
2569
  function DataRefButton({
1052
2570
  active = false,
1053
2571
  label = "Data ref",
@@ -1055,7 +2573,7 @@ function DataRefButton({
1055
2573
  type = "button",
1056
2574
  ...rest
1057
2575
  }) {
1058
- return /* @__PURE__ */ jsxs3(
2576
+ return /* @__PURE__ */ jsxs4(
1059
2577
  "button",
1060
2578
  {
1061
2579
  ...rest,
@@ -1069,21 +2587,21 @@ function DataRefButton({
1069
2587
  "aria-pressed": active,
1070
2588
  title: active ? "Hide data references" : "Show data references",
1071
2589
  children: [
1072
- /* @__PURE__ */ jsx3(
2590
+ /* @__PURE__ */ jsx4(
1073
2591
  DataReferenceIcon,
1074
2592
  {
1075
2593
  size: "sm",
1076
2594
  className: "ods-flow-dataref-button__icon"
1077
2595
  }
1078
2596
  ),
1079
- label != null && /* @__PURE__ */ jsx3("span", { className: "ods-flow-dataref-button__label", children: label })
2597
+ label != null && /* @__PURE__ */ jsx4("span", { className: "ods-flow-dataref-button__label", children: label })
1080
2598
  ]
1081
2599
  }
1082
2600
  );
1083
2601
  }
1084
2602
 
1085
2603
  // src/workflow/components/NodeToolbar/NodeToolbar.tsx
1086
- import { jsx as jsx4 } from "react/jsx-runtime";
2604
+ import { jsx as jsx5 } from "react/jsx-runtime";
1087
2605
  function NodeToolbar({
1088
2606
  isVisible,
1089
2607
  position = "top",
@@ -1098,7 +2616,7 @@ function NodeToolbar({
1098
2616
  const show = isVisible ?? node.selected;
1099
2617
  if (!show) return null;
1100
2618
  const inverseScale = 1 / viewport.zoom;
1101
- return /* @__PURE__ */ jsx4(
2619
+ return /* @__PURE__ */ jsx5(
1102
2620
  "div",
1103
2621
  {
1104
2622
  className: cn("ods-node-toolbar", `ods-node-toolbar--${position}`, className),
@@ -1110,7 +2628,7 @@ function NodeToolbar({
1110
2628
  onPointerDown: (e) => e.stopPropagation(),
1111
2629
  onMouseDown: (e) => e.stopPropagation(),
1112
2630
  onClick: (e) => e.stopPropagation(),
1113
- children: /* @__PURE__ */ jsx4(
2631
+ children: /* @__PURE__ */ jsx5(
1114
2632
  "div",
1115
2633
  {
1116
2634
  className: "ods-node-toolbar__inner",
@@ -1266,7 +2784,10 @@ export {
1266
2784
  ForEachNode,
1267
2785
  FxPanel,
1268
2786
  GroupNode,
2787
+ HTTP_ACTION_DEFAULTS,
2788
+ HTTP_METHODS,
1269
2789
  Handle,
2790
+ HttpActionPanel,
1270
2791
  HttpRequestNode,
1271
2792
  NodeResizer,
1272
2793
  NodeToolbar,
@@ -1280,19 +2801,28 @@ export {
1280
2801
  applyNodeChanges,
1281
2802
  bezierPath2 as bezierPath,
1282
2803
  bezierPath as bezierPathFn,
2804
+ buildCurl,
1283
2805
  buildEdgePath,
1284
2806
  buildNodeKindRegistry,
1285
2807
  change,
1286
2808
  clampToParentExtent,
1287
2809
  collapseFor,
2810
+ countGraphqlVariables,
2811
+ countSchemaPaths,
1288
2812
  createDagreEngine,
1289
2813
  createElkEngine,
1290
2814
  descendantsOf,
2815
+ effectiveUrl,
1291
2816
  findAncestor,
1292
2817
  findContainingGroup,
1293
2818
  flowToScreen,
1294
2819
  handleCentre,
2820
+ inferJsonSchema,
2821
+ normalizeAuth,
2822
+ normalizeKeyValues,
2823
+ parseCurl,
1295
2824
  screenToFlow,
2825
+ serializeKeyValues,
1296
2826
  smoothStepPath,
1297
2827
  stepPath,
1298
2828
  straightPath,
@@ -1312,6 +2842,7 @@ export {
1312
2842
  useNodes,
1313
2843
  useSelection,
1314
2844
  useViewport,
1315
- useViewportOrNull
2845
+ useViewportOrNull,
2846
+ validateHttpConfig
1316
2847
  };
1317
2848
  //# sourceMappingURL=workflow.js.map