@arronqzy/react-blueprint 1.0.6 → 1.0.7
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.
- package/package.json +5 -5
- package/src/BlueprintNodeConfigSidebar.tsx +43 -23
- package/src/components/ClockNodeConfigPanel.tsx +17 -24
- package/src/components/ConfigHintIcon.tsx +83 -0
- package/src/components/FetchNodeConfigPanel.tsx +137 -28
- package/src/components/JsonNodeConfigPanel.tsx +5 -6
- package/src/components/LogicNodeConfigPanel.tsx +5 -6
- package/src/graph/document.ts +1 -0
- package/src/runtime/document-to-runnable-graph.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/react-blueprint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"dayjs": "^1.11.13",
|
|
13
13
|
"react-dom": "^19",
|
|
14
14
|
"@xyflow/react": "^12.9.3",
|
|
15
|
-
"@arronqzy/blueprint-dsl": "1.0.
|
|
16
|
-
"@arronqzy/i18n": "0.1.3",
|
|
15
|
+
"@arronqzy/blueprint-dsl": "1.0.6",
|
|
17
16
|
"@arronqzy/react-rx-store": "1.1.4",
|
|
17
|
+
"@arronqzy/i18n": "0.1.5",
|
|
18
18
|
"@arronqzy/ui": "1.0.5"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"typescript": "^5.5.4",
|
|
30
30
|
"vite": "^5.1.4",
|
|
31
31
|
"@arronqzy/tailwind": "1.0.2",
|
|
32
|
-
"@arronqzy/
|
|
33
|
-
"@arronqzy/
|
|
32
|
+
"@arronqzy/eslint-config": "0.0.0",
|
|
33
|
+
"@arronqzy/typescript-config": "1.0.0"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [],
|
|
36
36
|
"author": "",
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
LIFECYCLE_NODE_TYPE,
|
|
12
12
|
PAGE_LIFECYCLE_PHASES,
|
|
13
|
+
type ExecutionTraceEntry,
|
|
13
14
|
type PageLifecyclePhase,
|
|
14
15
|
} from "@arronqzy/blueprint-dsl";
|
|
15
16
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
@@ -19,6 +20,7 @@ import { ClockNodeConfigPanel } from "./components/ClockNodeConfigPanel";
|
|
|
19
20
|
import { JsonNodeConfigPanel } from "./components/JsonNodeConfigPanel";
|
|
20
21
|
import { LogicNodeConfigPanel } from "./components/LogicNodeConfigPanel";
|
|
21
22
|
import { ViewElementMultiSelect } from "./components/ViewElementMultiSelect";
|
|
23
|
+
import { ConfigFieldLabel, ConfigHintIcon, ConfigSectionTitle } from "./components/ConfigHintIcon";
|
|
22
24
|
import {
|
|
23
25
|
getLifecyclePhaseLabel,
|
|
24
26
|
patchNodeConfigSource,
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
resolveBlueprintConfigSource,
|
|
27
29
|
resolveViewElementIds,
|
|
28
30
|
type BlueprintConfigSource,
|
|
31
|
+
type BlueprintGraphEdge,
|
|
29
32
|
type BlueprintGraphNode,
|
|
30
33
|
type BlueprintNodeRole,
|
|
31
34
|
} from "./graph/document";
|
|
@@ -42,6 +45,9 @@ export type BlueprintLibraryOption = {
|
|
|
42
45
|
|
|
43
46
|
export type BlueprintNodeConfigSidebarProps = {
|
|
44
47
|
node: BlueprintGraphNode;
|
|
48
|
+
graphNodes?: BlueprintGraphNode[];
|
|
49
|
+
graphEdges?: BlueprintGraphEdge[];
|
|
50
|
+
traceEntries?: ExecutionTraceEntry[];
|
|
45
51
|
viewElementOptions?: BlueprintViewElementOption[];
|
|
46
52
|
blueprintLibraryOptions?: BlueprintLibraryOption[];
|
|
47
53
|
allowFalseSignalPropagation?: boolean;
|
|
@@ -81,6 +87,9 @@ const ROLE_LABEL_KEYS: Record<BlueprintNodeRole, string> = {
|
|
|
81
87
|
|
|
82
88
|
export function BlueprintNodeConfigSidebar({
|
|
83
89
|
node,
|
|
90
|
+
graphNodes = [],
|
|
91
|
+
graphEdges = [],
|
|
92
|
+
traceEntries = [],
|
|
84
93
|
viewElementOptions = [],
|
|
85
94
|
blueprintLibraryOptions = [],
|
|
86
95
|
allowFalseSignalPropagation = false,
|
|
@@ -181,7 +190,10 @@ export function BlueprintNodeConfigSidebar({
|
|
|
181
190
|
|
|
182
191
|
{configSource === "view" ? (
|
|
183
192
|
<div className="block space-y-1">
|
|
184
|
-
<
|
|
193
|
+
<ConfigFieldLabel
|
|
194
|
+
label={t("blueprint.config.linkedViewNodes")}
|
|
195
|
+
hint={t("blueprint.config.viewMultiHint")}
|
|
196
|
+
/>
|
|
185
197
|
<ViewElementMultiSelect
|
|
186
198
|
options={viewElementOptions}
|
|
187
199
|
value={linkedViewElementIds}
|
|
@@ -194,11 +206,7 @@ export function BlueprintNodeConfigSidebar({
|
|
|
194
206
|
})
|
|
195
207
|
}
|
|
196
208
|
/>
|
|
197
|
-
{linkedViewElementIds.length
|
|
198
|
-
<p className="text-[11px] text-muted-foreground">
|
|
199
|
-
{t("blueprint.config.viewMultiHint")}
|
|
200
|
-
</p>
|
|
201
|
-
) : (
|
|
209
|
+
{linkedViewElementIds.length > 0 ? (
|
|
202
210
|
<p className="text-[11px] text-muted-foreground">
|
|
203
211
|
{t("blueprint.config.linkedViewCount", {
|
|
204
212
|
count: linkedViewElementIds.length,
|
|
@@ -207,16 +215,16 @@ export function BlueprintNodeConfigSidebar({
|
|
|
207
215
|
.join("、"),
|
|
208
216
|
})}
|
|
209
217
|
</p>
|
|
210
|
-
)}
|
|
218
|
+
) : null}
|
|
211
219
|
</div>
|
|
212
220
|
) : null}
|
|
213
221
|
|
|
214
222
|
{configSource === "blueprint" ? (
|
|
215
223
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
216
|
-
<
|
|
217
|
-
|
|
218
|
-
{t("blueprint.config.blueprintAttrsHint")}
|
|
219
|
-
|
|
224
|
+
<ConfigSectionTitle
|
|
225
|
+
title={t("blueprint.config.blueprintAttrs")}
|
|
226
|
+
hint={t("blueprint.config.blueprintAttrsHint")}
|
|
227
|
+
/>
|
|
220
228
|
<label className="block space-y-1">
|
|
221
229
|
<span className="text-muted-foreground">{t("blueprint.config.refLibrary")}</span>
|
|
222
230
|
<Select
|
|
@@ -264,13 +272,19 @@ export function BlueprintNodeConfigSidebar({
|
|
|
264
272
|
|
|
265
273
|
{configSource === "lifecycle" ? (
|
|
266
274
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
267
|
-
<div className="
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
275
|
+
<div className="flex items-center gap-1.5">
|
|
276
|
+
<div className="font-medium text-foreground">
|
|
277
|
+
{t("blueprint.config.lifecycleHook")}
|
|
278
|
+
</div>
|
|
279
|
+
<ConfigHintIcon label={t("blueprint.config.lifecycleHook")}>
|
|
280
|
+
<p>{t("blueprint.config.lifecycleNoInput")}</p>
|
|
281
|
+
<p>
|
|
282
|
+
{node.lifecyclePhase === "blueprintActivated"
|
|
283
|
+
? t("blueprint.config.lifecycleBlueprintActivatedHint")
|
|
284
|
+
: t("blueprint.config.lifecyclePageHint")}
|
|
285
|
+
</p>
|
|
286
|
+
</ConfigHintIcon>
|
|
287
|
+
</div>
|
|
274
288
|
<label className="block space-y-1">
|
|
275
289
|
<span className="text-muted-foreground">{t("blueprint.config.listenPhase")}</span>
|
|
276
290
|
<Select
|
|
@@ -305,7 +319,13 @@ export function BlueprintNodeConfigSidebar({
|
|
|
305
319
|
) : null}
|
|
306
320
|
|
|
307
321
|
{configSource === "fetch" ? (
|
|
308
|
-
<FetchNodeConfigPanel
|
|
322
|
+
<FetchNodeConfigPanel
|
|
323
|
+
node={node}
|
|
324
|
+
graphNodes={graphNodes}
|
|
325
|
+
graphEdges={graphEdges}
|
|
326
|
+
traceEntries={traceEntries}
|
|
327
|
+
onUpdateNode={onUpdateNode}
|
|
328
|
+
/>
|
|
309
329
|
) : null}
|
|
310
330
|
|
|
311
331
|
{configSource === "json" ? (
|
|
@@ -318,10 +338,10 @@ export function BlueprintNodeConfigSidebar({
|
|
|
318
338
|
|
|
319
339
|
{configSource === "and" ? (
|
|
320
340
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
{t("blueprint.config.andHint")}
|
|
324
|
-
|
|
341
|
+
<ConfigSectionTitle
|
|
342
|
+
title={t("blueprint.config.andTitle")}
|
|
343
|
+
hint={t("blueprint.config.andHint")}
|
|
344
|
+
/>
|
|
325
345
|
</div>
|
|
326
346
|
) : null}
|
|
327
347
|
|
|
@@ -5,6 +5,7 @@ import { useI18n } from "@arronqzy/i18n/react";
|
|
|
5
5
|
|
|
6
6
|
import type { BlueprintGraphNode } from "../graph/document";
|
|
7
7
|
import { resolveNodeClockConfig } from "../graph/document";
|
|
8
|
+
import { ConfigFieldLabel, ConfigSectionTitle } from "./ConfigHintIcon";
|
|
8
9
|
|
|
9
10
|
export type ClockNodeConfigPanelProps = {
|
|
10
11
|
node: BlueprintGraphNode;
|
|
@@ -73,17 +74,16 @@ export function ClockNodeConfigPanel({
|
|
|
73
74
|
|
|
74
75
|
return (
|
|
75
76
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
76
|
-
<
|
|
77
|
-
{t("blueprint.config.clockTitle")}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{t("blueprint.config.clockHint")}
|
|
81
|
-
</p>
|
|
77
|
+
<ConfigSectionTitle
|
|
78
|
+
title={t("blueprint.config.clockTitle")}
|
|
79
|
+
hint={t("blueprint.config.clockHint")}
|
|
80
|
+
/>
|
|
82
81
|
|
|
83
82
|
<label className="block space-y-1">
|
|
84
|
-
<
|
|
85
|
-
{t("blueprint.config.clockIntervalSeconds")}
|
|
86
|
-
|
|
83
|
+
<ConfigFieldLabel
|
|
84
|
+
label={t("blueprint.config.clockIntervalSeconds")}
|
|
85
|
+
hint={t("blueprint.config.clockIntervalHint")}
|
|
86
|
+
/>
|
|
87
87
|
<Input
|
|
88
88
|
type="number"
|
|
89
89
|
min={0}
|
|
@@ -92,15 +92,13 @@ export function ClockNodeConfigPanel({
|
|
|
92
92
|
onChange={handleIntervalChange}
|
|
93
93
|
className="h-8"
|
|
94
94
|
/>
|
|
95
|
-
<p className="text-[11px] text-muted-foreground">
|
|
96
|
-
{t("blueprint.config.clockIntervalHint")}
|
|
97
|
-
</p>
|
|
98
95
|
</label>
|
|
99
96
|
|
|
100
97
|
<label className="block space-y-1">
|
|
101
|
-
<
|
|
102
|
-
{t("blueprint.config.outputCount")}
|
|
103
|
-
|
|
98
|
+
<ConfigFieldLabel
|
|
99
|
+
label={t("blueprint.config.outputCount")}
|
|
100
|
+
hint={t("blueprint.config.outputCountHint")}
|
|
101
|
+
/>
|
|
104
102
|
<Input
|
|
105
103
|
type="number"
|
|
106
104
|
min={1}
|
|
@@ -109,9 +107,6 @@ export function ClockNodeConfigPanel({
|
|
|
109
107
|
onChange={handleOutputCountChange}
|
|
110
108
|
className="h-8"
|
|
111
109
|
/>
|
|
112
|
-
<p className="text-[11px] text-muted-foreground">
|
|
113
|
-
{t("blueprint.config.outputCountHint")}
|
|
114
|
-
</p>
|
|
115
110
|
</label>
|
|
116
111
|
|
|
117
112
|
<label className="flex items-start gap-2">
|
|
@@ -127,9 +122,10 @@ export function ClockNodeConfigPanel({
|
|
|
127
122
|
</label>
|
|
128
123
|
|
|
129
124
|
<label className="block space-y-1">
|
|
130
|
-
<
|
|
131
|
-
{t("blueprint.config.timeFormat")}
|
|
132
|
-
|
|
125
|
+
<ConfigFieldLabel
|
|
126
|
+
label={t("blueprint.config.timeFormat")}
|
|
127
|
+
hint={t("blueprint.config.timeFormatHint")}
|
|
128
|
+
/>
|
|
133
129
|
<Input
|
|
134
130
|
value={clockConfig.timeFormat}
|
|
135
131
|
onChange={handleFormatChange}
|
|
@@ -137,9 +133,6 @@ export function ClockNodeConfigPanel({
|
|
|
137
133
|
className="h-8 font-mono text-[11px]"
|
|
138
134
|
placeholder="YYYY-MM-DD HH:mm:ss"
|
|
139
135
|
/>
|
|
140
|
-
<p className="text-[11px] text-muted-foreground">
|
|
141
|
-
{t("blueprint.config.timeFormatHint")}
|
|
142
|
-
</p>
|
|
143
136
|
</label>
|
|
144
137
|
</div>
|
|
145
138
|
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
3
|
+
import {
|
|
4
|
+
Tooltip,
|
|
5
|
+
TooltipContent,
|
|
6
|
+
TooltipProvider,
|
|
7
|
+
TooltipTrigger,
|
|
8
|
+
} from "@arronqzy/ui";
|
|
9
|
+
|
|
10
|
+
export type ConfigHintIconProps = {
|
|
11
|
+
label?: string;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
className?: string;
|
|
14
|
+
contentClassName?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function ConfigHintIcon({
|
|
18
|
+
label,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
contentClassName,
|
|
22
|
+
}: ConfigHintIconProps) {
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
const resolvedLabel = label ?? t("common.hint");
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<TooltipProvider delayDuration={120}>
|
|
28
|
+
<Tooltip>
|
|
29
|
+
<TooltipTrigger asChild>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
className={
|
|
33
|
+
className ??
|
|
34
|
+
"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-border text-[10px] leading-none text-muted-foreground hover:bg-accent/50"
|
|
35
|
+
}
|
|
36
|
+
aria-label={t("common.hintAria", { label: resolvedLabel })}
|
|
37
|
+
>
|
|
38
|
+
?
|
|
39
|
+
</button>
|
|
40
|
+
</TooltipTrigger>
|
|
41
|
+
<TooltipContent
|
|
42
|
+
side="top"
|
|
43
|
+
className={`z-[10120] max-w-[360px] space-y-1.5 text-[11px] leading-5 ${contentClassName ?? ""}`}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</TooltipContent>
|
|
47
|
+
</Tooltip>
|
|
48
|
+
</TooltipProvider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function ConfigSectionTitle({
|
|
53
|
+
title,
|
|
54
|
+
hint,
|
|
55
|
+
}: {
|
|
56
|
+
title: string;
|
|
57
|
+
hint: ReactNode;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex items-center gap-1.5">
|
|
61
|
+
<div className="font-medium text-foreground">{title}</div>
|
|
62
|
+
<ConfigHintIcon label={title}>{hint}</ConfigHintIcon>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function ConfigFieldLabel({
|
|
68
|
+
label,
|
|
69
|
+
hint,
|
|
70
|
+
}: {
|
|
71
|
+
label: string;
|
|
72
|
+
hint?: ReactNode;
|
|
73
|
+
}) {
|
|
74
|
+
if (!hint) {
|
|
75
|
+
return <span className="text-muted-foreground">{label}</span>;
|
|
76
|
+
}
|
|
77
|
+
return (
|
|
78
|
+
<span className="inline-flex items-center gap-1 text-muted-foreground">
|
|
79
|
+
{label}
|
|
80
|
+
<ConfigHintIcon label={label}>{hint}</ConfigHintIcon>
|
|
81
|
+
</span>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useState, type ChangeEvent } from "react";
|
|
1
|
+
import { useCallback, useMemo, useState, type ChangeEvent } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Button,
|
|
4
4
|
Input,
|
|
@@ -10,11 +10,14 @@ import {
|
|
|
10
10
|
cn,
|
|
11
11
|
} from "@arronqzy/ui";
|
|
12
12
|
import type {
|
|
13
|
+
ExecutionTraceEntry,
|
|
13
14
|
FetchHttpMethod,
|
|
14
15
|
FetchRequestConfig,
|
|
15
16
|
FetchResponseType,
|
|
16
17
|
} from "@arronqzy/blueprint-dsl";
|
|
17
18
|
import {
|
|
19
|
+
applyFetchConfigScope,
|
|
20
|
+
draftFetchHeadersText,
|
|
18
21
|
FETCH_CACHES,
|
|
19
22
|
FETCH_CREDENTIALS,
|
|
20
23
|
FETCH_HTTP_METHODS,
|
|
@@ -22,9 +25,14 @@ import {
|
|
|
22
25
|
FETCH_NODE_TYPE,
|
|
23
26
|
FETCH_REDIRECTS,
|
|
24
27
|
FETCH_RESPONSE_TYPES,
|
|
28
|
+
fetchConfigHasScopeTemplate,
|
|
29
|
+
latestTraceOutputsByNode,
|
|
30
|
+
parseFetchHeadersJson,
|
|
31
|
+
resolveFetchIncomingScope,
|
|
32
|
+
resolveFetchRequestUrl,
|
|
25
33
|
} from "@arronqzy/blueprint-dsl";
|
|
26
34
|
|
|
27
|
-
import type { BlueprintGraphNode } from "../graph/document";
|
|
35
|
+
import type { BlueprintGraphEdge, BlueprintGraphNode } from "../graph/document";
|
|
28
36
|
import { resolveNodeFetchConfig } from "../graph/document";
|
|
29
37
|
import {
|
|
30
38
|
cancelSwaggerLoadTask,
|
|
@@ -36,6 +44,7 @@ import {
|
|
|
36
44
|
} from "../fetch-config-task-store";
|
|
37
45
|
import { useI18n } from "@arronqzy/i18n/react";
|
|
38
46
|
|
|
47
|
+
import { ConfigFieldLabel, ConfigHintIcon, ConfigSectionTitle } from "./ConfigHintIcon";
|
|
39
48
|
import { FetchUrlAutocomplete } from "./FetchUrlAutocomplete";
|
|
40
49
|
|
|
41
50
|
function SendIcon({ className }: { className?: string }) {
|
|
@@ -231,6 +240,9 @@ function LoaderIcon({ className }: { className?: string }) {
|
|
|
231
240
|
|
|
232
241
|
export type FetchNodeConfigPanelProps = {
|
|
233
242
|
node: BlueprintGraphNode;
|
|
243
|
+
graphNodes?: BlueprintGraphNode[];
|
|
244
|
+
graphEdges?: BlueprintGraphEdge[];
|
|
245
|
+
traceEntries?: ExecutionTraceEntry[];
|
|
234
246
|
onUpdateNode: (
|
|
235
247
|
nodeId: string,
|
|
236
248
|
patch: Partial<Pick<BlueprintGraphNode, "fetchConfig" | "configSource">>
|
|
@@ -251,6 +263,9 @@ function patchFetchConfig(
|
|
|
251
263
|
|
|
252
264
|
export function FetchNodeConfigPanel({
|
|
253
265
|
node,
|
|
266
|
+
graphNodes = [],
|
|
267
|
+
graphEdges = [],
|
|
268
|
+
traceEntries = [],
|
|
254
269
|
onUpdateNode,
|
|
255
270
|
}: FetchNodeConfigPanelProps) {
|
|
256
271
|
const { t } = useI18n();
|
|
@@ -268,6 +283,55 @@ export function FetchNodeConfigPanel({
|
|
|
268
283
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
269
284
|
const [fetchValidationError, setFetchValidationError] = useState<string | null>(null);
|
|
270
285
|
const swaggerError = validationError ?? swaggerTaskError;
|
|
286
|
+
const incomingScope = useMemo(() => {
|
|
287
|
+
const outputs = latestTraceOutputsByNode(traceEntries);
|
|
288
|
+
return resolveFetchIncomingScope({
|
|
289
|
+
fetchNodeId: node.id,
|
|
290
|
+
nodes: graphNodes,
|
|
291
|
+
edges: graphEdges,
|
|
292
|
+
getOutput: (sourceId, port) => outputs[sourceId]?.[port],
|
|
293
|
+
});
|
|
294
|
+
}, [graphEdges, graphNodes, node.id, traceEntries]);
|
|
295
|
+
const usesScopeTemplate = fetchConfigHasScopeTemplate(fetchConfig);
|
|
296
|
+
const resolvedFetchConfig = useMemo(
|
|
297
|
+
() => applyFetchConfigScope(fetchConfig, incomingScope),
|
|
298
|
+
[fetchConfig, incomingScope]
|
|
299
|
+
);
|
|
300
|
+
const resolvedUrlPreview = useMemo(() => {
|
|
301
|
+
if (!usesScopeTemplate) return "";
|
|
302
|
+
try {
|
|
303
|
+
return resolveFetchRequestUrl(resolvedFetchConfig);
|
|
304
|
+
} catch {
|
|
305
|
+
return resolvedFetchConfig.url?.trim() ?? "";
|
|
306
|
+
}
|
|
307
|
+
}, [resolvedFetchConfig, usesScopeTemplate]);
|
|
308
|
+
const incomingScopeJson = useMemo(() => {
|
|
309
|
+
if (incomingScope === undefined) return "";
|
|
310
|
+
try {
|
|
311
|
+
return JSON.stringify(incomingScope, null, 2);
|
|
312
|
+
} catch {
|
|
313
|
+
return String(incomingScope);
|
|
314
|
+
}
|
|
315
|
+
}, [incomingScope]);
|
|
316
|
+
const headersDraft = draftFetchHeadersText(fetchConfig);
|
|
317
|
+
const resolvedHeadersPreview = useMemo(() => {
|
|
318
|
+
if (!usesScopeTemplate) return "";
|
|
319
|
+
const headers = resolvedFetchConfig.headers;
|
|
320
|
+
if (!headers || Object.keys(headers).length === 0) return "";
|
|
321
|
+
try {
|
|
322
|
+
return JSON.stringify(headers, null, 2);
|
|
323
|
+
} catch {
|
|
324
|
+
return "";
|
|
325
|
+
}
|
|
326
|
+
}, [resolvedFetchConfig.headers, usesScopeTemplate]);
|
|
327
|
+
const incomingHasPendingValue = useMemo(() => {
|
|
328
|
+
if (!incomingScope || typeof incomingScope !== "object") return false;
|
|
329
|
+
const entries =
|
|
330
|
+
"kind" in incomingScope
|
|
331
|
+
? [incomingScope]
|
|
332
|
+
: Object.values(incomingScope as Record<string, { kind?: string }>);
|
|
333
|
+
return entries.some((entry) => entry && entry.kind === "pending");
|
|
334
|
+
}, [incomingScope]);
|
|
271
335
|
|
|
272
336
|
const setUrlInputMode = useCallback(
|
|
273
337
|
(mode: "swagger" | "manual") => {
|
|
@@ -307,30 +371,72 @@ export function FetchNodeConfigPanel({
|
|
|
307
371
|
}, [node.id]);
|
|
308
372
|
|
|
309
373
|
const handleSendFetchDebug = useCallback(() => {
|
|
310
|
-
const
|
|
374
|
+
const resolved = applyFetchConfigScope(fetchConfig, incomingScope);
|
|
375
|
+
const url = resolved.url?.trim();
|
|
311
376
|
if (!url) {
|
|
312
|
-
setFetchValidationError(
|
|
377
|
+
setFetchValidationError(
|
|
378
|
+
usesScopeTemplate && fetchConfig.url?.trim()
|
|
379
|
+
? t("blueprint.config.fetchScopeUnresolved")
|
|
380
|
+
: t("blueprint.config.fillRequestUrlFirst")
|
|
381
|
+
);
|
|
313
382
|
return;
|
|
314
383
|
}
|
|
315
384
|
|
|
316
385
|
setFetchValidationError(null);
|
|
317
386
|
startFetchDebugTask({
|
|
318
387
|
nodeId: node.id,
|
|
319
|
-
config:
|
|
388
|
+
config: resolved,
|
|
320
389
|
});
|
|
321
|
-
}, [fetchConfig, node.id]);
|
|
390
|
+
}, [fetchConfig, incomingScope, node.id, t, usesScopeTemplate]);
|
|
322
391
|
|
|
323
392
|
const handleAbortFetchDebug = useCallback(() => {
|
|
324
393
|
cancelFetchDebugTask(node.id);
|
|
325
394
|
setFetchValidationError(null);
|
|
326
395
|
}, [node.id]);
|
|
327
396
|
|
|
397
|
+
const handleHeadersBlur = useCallback(
|
|
398
|
+
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
399
|
+
const headersJson = e.target.value;
|
|
400
|
+
const parsed = parseFetchHeadersJson(headersJson);
|
|
401
|
+
onUpdateNode(
|
|
402
|
+
node.id,
|
|
403
|
+
patchFetchConfig(node, {
|
|
404
|
+
headersJson,
|
|
405
|
+
...(parsed ? { headers: parsed } : {}),
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
},
|
|
409
|
+
[node, onUpdateNode]
|
|
410
|
+
);
|
|
411
|
+
|
|
328
412
|
return (
|
|
329
413
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
330
|
-
<
|
|
331
|
-
|
|
332
|
-
{t("blueprint.config.fetchHint")}
|
|
333
|
-
|
|
414
|
+
<ConfigSectionTitle
|
|
415
|
+
title={t("blueprint.config.fetchTitle")}
|
|
416
|
+
hint={t("blueprint.config.fetchHint")}
|
|
417
|
+
/>
|
|
418
|
+
<div className="space-y-1 rounded-md border border-border/60 bg-background/70 p-2">
|
|
419
|
+
<div className="flex items-center gap-1.5 text-[11px] font-medium text-foreground">
|
|
420
|
+
{t("blueprint.config.fetchScopeTitle")}
|
|
421
|
+
<ConfigHintIcon label={t("blueprint.config.fetchScopeTitle")}>
|
|
422
|
+
<p>{t("blueprint.config.fetchScopeHint")}</p>
|
|
423
|
+
<p>{t("blueprint.config.fetchScopeEmpty")}</p>
|
|
424
|
+
<p>{t("blueprint.config.fetchScopePending")}</p>
|
|
425
|
+
</ConfigHintIcon>
|
|
426
|
+
</div>
|
|
427
|
+
{incomingScope === undefined ? null : (
|
|
428
|
+
<>
|
|
429
|
+
{incomingHasPendingValue ? (
|
|
430
|
+
<p className="text-[11px] text-amber-700 dark:text-amber-300">
|
|
431
|
+
{t("blueprint.config.fetchScopePending")}
|
|
432
|
+
</p>
|
|
433
|
+
) : null}
|
|
434
|
+
<pre className="max-h-[180px] overflow-auto rounded border border-border/60 bg-muted/30 p-2 font-mono text-[10px] leading-relaxed text-foreground">
|
|
435
|
+
{incomingScopeJson}
|
|
436
|
+
</pre>
|
|
437
|
+
</>
|
|
438
|
+
)}
|
|
439
|
+
</div>
|
|
334
440
|
|
|
335
441
|
<label className="block space-y-1">
|
|
336
442
|
<span className="text-muted-foreground">{t("blueprint.config.swaggerUrlOptional")}</span>
|
|
@@ -440,7 +546,7 @@ export function FetchNodeConfigPanel({
|
|
|
440
546
|
patchFetchConfig(node, { apiBaseUrl: e.target.value })
|
|
441
547
|
)
|
|
442
548
|
}
|
|
443
|
-
placeholder="https://api.example.com/v1"
|
|
549
|
+
placeholder="https://api.example.com/v1/{scope?.value?.tenant}"
|
|
444
550
|
className="h-8 font-mono text-[11px]"
|
|
445
551
|
/>
|
|
446
552
|
</label>
|
|
@@ -489,7 +595,7 @@ export function FetchNodeConfigPanel({
|
|
|
489
595
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
490
596
|
onUpdateNode(node.id, patchFetchConfig(node, { url: e.target.value }))
|
|
491
597
|
}
|
|
492
|
-
placeholder="https://api.example.com/
|
|
598
|
+
placeholder="https://api.example.com/users/{scope?.value?.id}"
|
|
493
599
|
className="h-8 flex-1 font-mono text-[11px]"
|
|
494
600
|
/>
|
|
495
601
|
<AsyncSendButton
|
|
@@ -504,6 +610,11 @@ export function FetchNodeConfigPanel({
|
|
|
504
610
|
/>
|
|
505
611
|
</div>
|
|
506
612
|
)}
|
|
613
|
+
{usesScopeTemplate && resolvedUrlPreview ? (
|
|
614
|
+
<p className="break-all font-mono text-[10px] text-muted-foreground">
|
|
615
|
+
{t("blueprint.config.fetchScopeResolvedUrl")}: {resolvedUrlPreview}
|
|
616
|
+
</p>
|
|
617
|
+
) : null}
|
|
507
618
|
{loadingFetchDebug ? (
|
|
508
619
|
<p className="flex items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
509
620
|
<LoaderIcon className="h-3.5 w-3.5 shrink-0" />
|
|
@@ -541,25 +652,23 @@ export function FetchNodeConfigPanel({
|
|
|
541
652
|
</label>
|
|
542
653
|
|
|
543
654
|
<label className="block space-y-1">
|
|
544
|
-
<
|
|
655
|
+
<ConfigFieldLabel
|
|
656
|
+
label={t("blueprint.config.requestHeadersJson")}
|
|
657
|
+
hint={t("blueprint.config.fetchHeadersHint")}
|
|
658
|
+
/>
|
|
545
659
|
<textarea
|
|
546
|
-
defaultValue={
|
|
547
|
-
key={`${node.id}-headers-${
|
|
548
|
-
onBlur={
|
|
549
|
-
try {
|
|
550
|
-
const headers = JSON.parse(e.target.value || "{}") as Record<
|
|
551
|
-
string,
|
|
552
|
-
string
|
|
553
|
-
>;
|
|
554
|
-
onUpdateNode(node.id, patchFetchConfig(node, { headers }));
|
|
555
|
-
} catch {
|
|
556
|
-
/* 保留上次有效值 */
|
|
557
|
-
}
|
|
558
|
-
}}
|
|
660
|
+
defaultValue={headersDraft}
|
|
661
|
+
key={`${node.id}-headers-${headersDraft}`}
|
|
662
|
+
onBlur={handleHeadersBlur}
|
|
559
663
|
rows={4}
|
|
560
664
|
className="w-full rounded-md border border-input bg-background px-2 py-1.5 font-mono text-[11px] leading-relaxed text-foreground shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary"
|
|
561
|
-
placeholder='{"
|
|
665
|
+
placeholder='{"Authorization":"Bearer {scope?.value?.token}"}'
|
|
562
666
|
/>
|
|
667
|
+
{usesScopeTemplate && resolvedHeadersPreview ? (
|
|
668
|
+
<p className="whitespace-pre-wrap break-all font-mono text-[10px] text-muted-foreground">
|
|
669
|
+
{t("blueprint.config.fetchScopeResolvedHeaders")}: {resolvedHeadersPreview}
|
|
670
|
+
</p>
|
|
671
|
+
) : null}
|
|
563
672
|
</label>
|
|
564
673
|
|
|
565
674
|
<label className="block space-y-1">
|
|
@@ -571,7 +680,7 @@ export function FetchNodeConfigPanel({
|
|
|
571
680
|
}
|
|
572
681
|
rows={4}
|
|
573
682
|
className="w-full rounded-md border border-input bg-background px-2 py-1.5 font-mono text-[11px] leading-relaxed text-foreground shadow-sm outline-none focus-visible:ring-1 focus-visible:ring-primary"
|
|
574
|
-
placeholder='{"
|
|
683
|
+
placeholder='{"id":"{scope?.value?.id}"}'
|
|
575
684
|
/>
|
|
576
685
|
</label>
|
|
577
686
|
|
|
@@ -5,6 +5,7 @@ import { useI18n } from "@arronqzy/i18n/react";
|
|
|
5
5
|
|
|
6
6
|
import type { BlueprintGraphNode } from "../graph/document";
|
|
7
7
|
import { resolveNodeJsonConfig } from "../graph/document";
|
|
8
|
+
import { ConfigSectionTitle } from "./ConfigHintIcon";
|
|
8
9
|
|
|
9
10
|
export type JsonNodeConfigPanelProps = {
|
|
10
11
|
node: BlueprintGraphNode;
|
|
@@ -53,12 +54,10 @@ export function JsonNodeConfigPanel({
|
|
|
53
54
|
|
|
54
55
|
return (
|
|
55
56
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
56
|
-
<
|
|
57
|
-
{t("blueprint.config.jsonTitle")}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{t("blueprint.config.jsonHint")}
|
|
61
|
-
</p>
|
|
57
|
+
<ConfigSectionTitle
|
|
58
|
+
title={t("blueprint.config.jsonTitle")}
|
|
59
|
+
hint={t("blueprint.config.jsonHint")}
|
|
60
|
+
/>
|
|
62
61
|
|
|
63
62
|
<label className="block space-y-1">
|
|
64
63
|
<span className="text-muted-foreground">
|
|
@@ -5,6 +5,7 @@ import { useI18n } from "@arronqzy/i18n/react";
|
|
|
5
5
|
|
|
6
6
|
import type { BlueprintGraphNode } from "../graph/document";
|
|
7
7
|
import { resolveNodeLogicConfig } from "../graph/document";
|
|
8
|
+
import { ConfigSectionTitle } from "./ConfigHintIcon";
|
|
8
9
|
|
|
9
10
|
export type LogicNodeConfigPanelProps = {
|
|
10
11
|
node: BlueprintGraphNode;
|
|
@@ -52,12 +53,10 @@ export function LogicNodeConfigPanel({
|
|
|
52
53
|
|
|
53
54
|
return (
|
|
54
55
|
<div className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5">
|
|
55
|
-
<
|
|
56
|
-
{t("blueprint.config.logicTitle")}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{t("blueprint.config.logicHint")}
|
|
60
|
-
</p>
|
|
56
|
+
<ConfigSectionTitle
|
|
57
|
+
title={t("blueprint.config.logicTitle")}
|
|
58
|
+
hint={t("blueprint.config.logicHint")}
|
|
59
|
+
/>
|
|
61
60
|
|
|
62
61
|
<label className="block space-y-1">
|
|
63
62
|
<span className="text-muted-foreground">
|
package/src/graph/document.ts
CHANGED