@arronqzy/react-blueprint 1.0.11 → 1.0.13
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 +3 -3
- package/src/BlueprintContextMenu.tsx +1 -1
- package/src/BlueprintNodeConfigSidebar.tsx +14 -0
- package/src/blueprintNodeTypes.ts +2 -0
- package/src/components/StorageNodeConfigPanel.tsx +240 -0
- package/src/graph/blueprint-graph.ts +16 -0
- package/src/graph/document.ts +53 -1
- package/src/graph/index.ts +1 -0
- package/src/graph/node-summary.ts +24 -0
- package/src/graph/node-types.ts +1 -0
- package/src/graph/parse-blueprint-data.ts +106 -0
- package/src/graph/react-flow-adapter.ts +4 -0
- package/src/graph/sync-nodes.ts +5 -1
- package/src/hooks/useBlueprintDebugSession.ts +22 -15
- package/src/library/blueprint-library-db.ts +31 -17
- package/src/library/execution-log-db.ts +73 -57
- package/src/library/execution-log-settings.ts +14 -4
- package/src/nodes/BlueprintNodeCard.tsx +6 -1
- package/src/nodes/StorageFlowNode.tsx +28 -0
- package/src/runtime/document-to-runnable-graph.ts +6 -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.13",
|
|
4
4
|
"description": "",
|
|
5
5
|
"files": [
|
|
6
6
|
"index.ts",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dayjs": "^1.11.13",
|
|
19
19
|
"react-dom": "^19",
|
|
20
20
|
"@xyflow/react": "^12.9.3",
|
|
21
|
-
"@arronqzy/
|
|
22
|
-
"@arronqzy/
|
|
21
|
+
"@arronqzy/i18n": "0.1.8",
|
|
22
|
+
"@arronqzy/blueprint-dsl": "1.0.11",
|
|
23
23
|
"@arronqzy/react-rx-store": "1.1.5",
|
|
24
24
|
"@arronqzy/ui": "1.0.6"
|
|
25
25
|
},
|
|
@@ -12,7 +12,7 @@ export type BlueprintContextMenuState =
|
|
|
12
12
|
clientX: number;
|
|
13
13
|
clientY: number;
|
|
14
14
|
nodeId: string;
|
|
15
|
-
role: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "clock";
|
|
15
|
+
role: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "storage" | "clock";
|
|
16
16
|
}
|
|
17
17
|
| {
|
|
18
18
|
kind: "edge";
|
|
@@ -21,6 +21,7 @@ import { useI18n } from "@arronqzy/i18n/react";
|
|
|
21
21
|
import { FetchNodeConfigPanel } from "./components/FetchNodeConfigPanel";
|
|
22
22
|
import { ClockNodeConfigPanel } from "./components/ClockNodeConfigPanel";
|
|
23
23
|
import { JsonNodeConfigPanel } from "./components/JsonNodeConfigPanel";
|
|
24
|
+
import { StorageNodeConfigPanel } from "./components/StorageNodeConfigPanel";
|
|
24
25
|
import { LogicNodeConfigPanel } from "./components/LogicNodeConfigPanel";
|
|
25
26
|
import { ViewElementMultiSelect } from "./components/ViewElementMultiSelect";
|
|
26
27
|
import { ConfigFieldLabel, ConfigHintIcon, ConfigSectionTitle } from "./components/ConfigHintIcon";
|
|
@@ -74,6 +75,7 @@ export type BlueprintNodeConfigSidebarProps = {
|
|
|
74
75
|
| "lifecyclePhase"
|
|
75
76
|
| "fetchConfig"
|
|
76
77
|
| "jsonConfig"
|
|
78
|
+
| "storageConfig"
|
|
77
79
|
| "logicConfig"
|
|
78
80
|
| "clockConfig"
|
|
79
81
|
| "eventConfig"
|
|
@@ -88,6 +90,7 @@ const ROLE_LABEL_KEYS: Record<BlueprintNodeRole, string> = {
|
|
|
88
90
|
and: "blueprint.config.roleAnd",
|
|
89
91
|
fetch: "blueprint.config.roleFetch",
|
|
90
92
|
json: "blueprint.config.roleJson",
|
|
93
|
+
storage: "blueprint.config.roleStorage",
|
|
91
94
|
logic: "blueprint.config.roleLogic",
|
|
92
95
|
clock: "blueprint.config.roleClock",
|
|
93
96
|
event: "blueprint.config.roleEvent",
|
|
@@ -195,6 +198,7 @@ export function BlueprintNodeConfigSidebar({
|
|
|
195
198
|
<SelectItem value="lifecycle">{t("blueprint.config.configLifecycle")}</SelectItem>
|
|
196
199
|
<SelectItem value="fetch">{t("blueprint.config.configFetch")}</SelectItem>
|
|
197
200
|
<SelectItem value="json">{t("blueprint.config.configJson")}</SelectItem>
|
|
201
|
+
<SelectItem value="storage">{t("blueprint.config.configStorage")}</SelectItem>
|
|
198
202
|
<SelectItem value="clock">{t("blueprint.config.configClock")}</SelectItem>
|
|
199
203
|
<SelectItem value="event">{t("blueprint.config.configEvent")}</SelectItem>
|
|
200
204
|
<SelectItem value="view">{t("blueprint.config.configView")}</SelectItem>
|
|
@@ -399,6 +403,16 @@ export function BlueprintNodeConfigSidebar({
|
|
|
399
403
|
<JsonNodeConfigPanel node={node} onUpdateNode={onUpdateNode} />
|
|
400
404
|
) : null}
|
|
401
405
|
|
|
406
|
+
{configSource === "storage" ? (
|
|
407
|
+
<StorageNodeConfigPanel
|
|
408
|
+
node={node}
|
|
409
|
+
graphNodes={graphNodes}
|
|
410
|
+
graphEdges={graphEdges}
|
|
411
|
+
traceEntries={traceEntries}
|
|
412
|
+
onUpdateNode={onUpdateNode}
|
|
413
|
+
/>
|
|
414
|
+
) : null}
|
|
415
|
+
|
|
402
416
|
{configSource === "clock" ? (
|
|
403
417
|
<ClockNodeConfigPanel node={node} onUpdateNode={onUpdateNode} />
|
|
404
418
|
) : null}
|
|
@@ -5,6 +5,7 @@ import { AndFlowNode } from "./nodes/AndFlowNode";
|
|
|
5
5
|
import { ClockFlowNode } from "./nodes/ClockFlowNode";
|
|
6
6
|
import { FetchFlowNode } from "./nodes/FetchFlowNode";
|
|
7
7
|
import { JsonFlowNode } from "./nodes/JsonFlowNode";
|
|
8
|
+
import { StorageFlowNode } from "./nodes/StorageFlowNode";
|
|
8
9
|
import { LifecycleFlowNode } from "./nodes/LifecycleFlowNode";
|
|
9
10
|
import { EventFlowNode } from "./nodes/EventFlowNode";
|
|
10
11
|
import { LogicFlowNode } from "./nodes/LogicFlowNode";
|
|
@@ -16,6 +17,7 @@ export const blueprintNodeTypes = {
|
|
|
16
17
|
clock: ClockFlowNode,
|
|
17
18
|
fetch: FetchFlowNode,
|
|
18
19
|
json: JsonFlowNode,
|
|
20
|
+
storage: StorageFlowNode,
|
|
19
21
|
logic: LogicFlowNode,
|
|
20
22
|
lifecycle: LifecycleFlowNode,
|
|
21
23
|
event: EventFlowNode,
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState, type ChangeEvent } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Checkbox,
|
|
4
|
+
Input,
|
|
5
|
+
Select,
|
|
6
|
+
SelectContent,
|
|
7
|
+
SelectItem,
|
|
8
|
+
SelectTrigger,
|
|
9
|
+
SelectValue,
|
|
10
|
+
} from "@arronqzy/ui";
|
|
11
|
+
import type {
|
|
12
|
+
ExecutionTraceEntry,
|
|
13
|
+
StorageKind,
|
|
14
|
+
StorageReadConfig,
|
|
15
|
+
StorageSetConfig,
|
|
16
|
+
} from "@arronqzy/blueprint-dsl";
|
|
17
|
+
import {
|
|
18
|
+
latestTraceOutputsByNode,
|
|
19
|
+
resolveFetchIncomingScope,
|
|
20
|
+
resolveFetchScopeAutocompleteRoot,
|
|
21
|
+
uniqueStorageKinds,
|
|
22
|
+
} from "@arronqzy/blueprint-dsl";
|
|
23
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
24
|
+
|
|
25
|
+
import type { BlueprintGraphEdge, BlueprintGraphNode } from "../graph/document";
|
|
26
|
+
import { resolveNodeStorageConfig } from "../graph/document";
|
|
27
|
+
import { ConfigFieldLabel, ConfigSectionTitle } from "./ConfigHintIcon";
|
|
28
|
+
import { ScopeTemplateAutocompleteHost } from "./ScopeTemplateAutocompleteHost";
|
|
29
|
+
|
|
30
|
+
export type StorageNodeConfigPanelProps = {
|
|
31
|
+
node: BlueprintGraphNode;
|
|
32
|
+
graphNodes?: BlueprintGraphNode[];
|
|
33
|
+
graphEdges?: BlueprintGraphEdge[];
|
|
34
|
+
traceEntries?: ExecutionTraceEntry[];
|
|
35
|
+
onUpdateNode: (
|
|
36
|
+
nodeId: string,
|
|
37
|
+
patch: Partial<Pick<BlueprintGraphNode, "storageConfig" | "configSource">>
|
|
38
|
+
) => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function patchStorageConfig(
|
|
42
|
+
node: BlueprintGraphNode,
|
|
43
|
+
patch: {
|
|
44
|
+
read?: Partial<StorageReadConfig>;
|
|
45
|
+
set?: Partial<StorageSetConfig>;
|
|
46
|
+
}
|
|
47
|
+
) {
|
|
48
|
+
const current = resolveNodeStorageConfig(node);
|
|
49
|
+
return {
|
|
50
|
+
storageConfig: {
|
|
51
|
+
read: { ...current.read, ...patch.read },
|
|
52
|
+
set: { ...current.set, ...patch.set },
|
|
53
|
+
},
|
|
54
|
+
configSource: "storage" as const,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function StorageNodeConfigPanel({
|
|
59
|
+
node,
|
|
60
|
+
graphNodes = [],
|
|
61
|
+
graphEdges = [],
|
|
62
|
+
traceEntries = [],
|
|
63
|
+
onUpdateNode,
|
|
64
|
+
}: StorageNodeConfigPanelProps) {
|
|
65
|
+
const { t } = useI18n();
|
|
66
|
+
const [formEl, setFormEl] = useState<HTMLDivElement | null>(null);
|
|
67
|
+
const storageConfig = resolveNodeStorageConfig(node);
|
|
68
|
+
|
|
69
|
+
const incomingScope = useMemo(() => {
|
|
70
|
+
const outputs = latestTraceOutputsByNode(traceEntries);
|
|
71
|
+
return resolveFetchIncomingScope({
|
|
72
|
+
fetchNodeId: node.id,
|
|
73
|
+
nodes: graphNodes,
|
|
74
|
+
edges: graphEdges,
|
|
75
|
+
getOutput: (sourceId, port) => outputs[sourceId]?.[port],
|
|
76
|
+
});
|
|
77
|
+
}, [graphEdges, graphNodes, node.id, traceEntries]);
|
|
78
|
+
const autocompleteScope = resolveFetchScopeAutocompleteRoot(incomingScope);
|
|
79
|
+
|
|
80
|
+
const handleReadStorageChange = useCallback(
|
|
81
|
+
(value: string) => {
|
|
82
|
+
onUpdateNode(
|
|
83
|
+
node.id,
|
|
84
|
+
patchStorageConfig(node, {
|
|
85
|
+
read: { storage: value === "session" ? "session" : "local" },
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
[node, onUpdateNode]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const handleReadKeyChange = useCallback(
|
|
93
|
+
(e: ChangeEvent<HTMLInputElement>) => {
|
|
94
|
+
onUpdateNode(
|
|
95
|
+
node.id,
|
|
96
|
+
patchStorageConfig(node, { read: { key: e.target.value } })
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
[node, onUpdateNode]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const handleSetStorageToggle = useCallback(
|
|
103
|
+
(kind: StorageKind, checked: boolean) => {
|
|
104
|
+
const next = new Set(storageConfig.set.storages);
|
|
105
|
+
if (checked) next.add(kind);
|
|
106
|
+
else next.delete(kind);
|
|
107
|
+
onUpdateNode(
|
|
108
|
+
node.id,
|
|
109
|
+
patchStorageConfig(node, {
|
|
110
|
+
set: { storages: uniqueStorageKinds([...next]) },
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
[node, onUpdateNode, storageConfig.set.storages]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const handleSetKeyChange = useCallback(
|
|
118
|
+
(e: ChangeEvent<HTMLInputElement>) => {
|
|
119
|
+
onUpdateNode(
|
|
120
|
+
node.id,
|
|
121
|
+
patchStorageConfig(node, { set: { key: e.target.value } })
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
[node, onUpdateNode]
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const handleSetValueChange = useCallback(
|
|
128
|
+
(e: ChangeEvent<HTMLInputElement>) => {
|
|
129
|
+
onUpdateNode(
|
|
130
|
+
node.id,
|
|
131
|
+
patchStorageConfig(node, { set: { value: e.target.value } })
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
[node, onUpdateNode]
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div
|
|
139
|
+
ref={setFormEl}
|
|
140
|
+
className="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5"
|
|
141
|
+
>
|
|
142
|
+
<ScopeTemplateAutocompleteHost
|
|
143
|
+
scope={autocompleteScope}
|
|
144
|
+
container={formEl}
|
|
145
|
+
/>
|
|
146
|
+
<ConfigSectionTitle
|
|
147
|
+
title={t("blueprint.config.storageTitle")}
|
|
148
|
+
hint={t("blueprint.config.storageHint")}
|
|
149
|
+
/>
|
|
150
|
+
|
|
151
|
+
<div className="space-y-2 rounded-md border border-border/60 bg-background/70 p-2">
|
|
152
|
+
<ConfigSectionTitle
|
|
153
|
+
title={t("blueprint.config.storageReadTitle")}
|
|
154
|
+
hint={t("blueprint.config.storageReadHint")}
|
|
155
|
+
/>
|
|
156
|
+
<label className="block space-y-1">
|
|
157
|
+
<ConfigFieldLabel label={t("blueprint.config.storageTarget")} />
|
|
158
|
+
<Select
|
|
159
|
+
value={storageConfig.read.storage}
|
|
160
|
+
onValueChange={handleReadStorageChange}
|
|
161
|
+
>
|
|
162
|
+
<SelectTrigger className="h-8">
|
|
163
|
+
<SelectValue />
|
|
164
|
+
</SelectTrigger>
|
|
165
|
+
<SelectContent>
|
|
166
|
+
<SelectItem value="session">
|
|
167
|
+
{t("blueprint.config.storageSession")}
|
|
168
|
+
</SelectItem>
|
|
169
|
+
<SelectItem value="local">
|
|
170
|
+
{t("blueprint.config.storageLocal")}
|
|
171
|
+
</SelectItem>
|
|
172
|
+
</SelectContent>
|
|
173
|
+
</Select>
|
|
174
|
+
</label>
|
|
175
|
+
<label className="block space-y-1">
|
|
176
|
+
<ConfigFieldLabel label={t("blueprint.config.storageKey")} />
|
|
177
|
+
<Input
|
|
178
|
+
value={storageConfig.read.key}
|
|
179
|
+
onChange={handleReadKeyChange}
|
|
180
|
+
spellCheck={false}
|
|
181
|
+
className="h-8 font-mono text-[11px]"
|
|
182
|
+
placeholder="key"
|
|
183
|
+
/>
|
|
184
|
+
</label>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<div className="space-y-2 rounded-md border border-border/60 bg-background/70 p-2">
|
|
188
|
+
<ConfigSectionTitle
|
|
189
|
+
title={t("blueprint.config.storageSetTitle")}
|
|
190
|
+
hint={t("blueprint.config.storageSetHint")}
|
|
191
|
+
/>
|
|
192
|
+
<div className="space-y-1.5">
|
|
193
|
+
<ConfigFieldLabel label={t("blueprint.config.storageTarget")} />
|
|
194
|
+
{(["session", "local"] as const).map((kind) => {
|
|
195
|
+
const checked = storageConfig.set.storages.includes(kind);
|
|
196
|
+
return (
|
|
197
|
+
<label key={kind} className="flex items-center gap-2">
|
|
198
|
+
<Checkbox
|
|
199
|
+
checked={checked}
|
|
200
|
+
className="h-3.5 w-3.5 border-2 border-foreground/80 bg-background ring-1 ring-foreground/40 data-[state=checked]:border-primary data-[state=checked]:ring-primary/40"
|
|
201
|
+
onCheckedChange={(nextChecked: boolean | "indeterminate") => {
|
|
202
|
+
handleSetStorageToggle(kind, nextChecked === true);
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
<span>
|
|
206
|
+
{kind === "session"
|
|
207
|
+
? t("blueprint.config.storageSession")
|
|
208
|
+
: t("blueprint.config.storageLocal")}
|
|
209
|
+
</span>
|
|
210
|
+
</label>
|
|
211
|
+
);
|
|
212
|
+
})}
|
|
213
|
+
</div>
|
|
214
|
+
<label className="block space-y-1">
|
|
215
|
+
<ConfigFieldLabel label={t("blueprint.config.storageKey")} />
|
|
216
|
+
<Input
|
|
217
|
+
value={storageConfig.set.key}
|
|
218
|
+
onChange={handleSetKeyChange}
|
|
219
|
+
spellCheck={false}
|
|
220
|
+
className="h-8 font-mono text-[11px]"
|
|
221
|
+
placeholder="key"
|
|
222
|
+
/>
|
|
223
|
+
</label>
|
|
224
|
+
<label className="block space-y-1">
|
|
225
|
+
<ConfigFieldLabel
|
|
226
|
+
label={t("blueprint.config.storageValue")}
|
|
227
|
+
hint={t("blueprint.config.storageValueHint")}
|
|
228
|
+
/>
|
|
229
|
+
<Input
|
|
230
|
+
value={storageConfig.set.value}
|
|
231
|
+
onChange={handleSetValueChange}
|
|
232
|
+
spellCheck={false}
|
|
233
|
+
className="h-8 font-mono text-[11px]"
|
|
234
|
+
placeholder="{scope.value}"
|
|
235
|
+
/>
|
|
236
|
+
</label>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveNodeEventConfig,
|
|
9
9
|
resolveNodeFetchConfig,
|
|
10
10
|
resolveNodeJsonConfig,
|
|
11
|
+
resolveNodeStorageConfig,
|
|
11
12
|
resolveNodeLogicConfig,
|
|
12
13
|
resolveRunnableNodeType,
|
|
13
14
|
sanitizeBlueprintDocument,
|
|
@@ -197,6 +198,7 @@ export class BlueprintGraph {
|
|
|
197
198
|
| "lifecyclePhase"
|
|
198
199
|
| "fetchConfig"
|
|
199
200
|
| "jsonConfig"
|
|
201
|
+
| "storageConfig"
|
|
200
202
|
| "logicConfig"
|
|
201
203
|
| "clockConfig"
|
|
202
204
|
| "eventConfig"
|
|
@@ -241,6 +243,20 @@ export class BlueprintGraph {
|
|
|
241
243
|
...patch.jsonConfig,
|
|
242
244
|
};
|
|
243
245
|
}
|
|
246
|
+
if (patch.storageConfig) {
|
|
247
|
+
nextNode.storageConfig = {
|
|
248
|
+
...resolveNodeStorageConfig(n),
|
|
249
|
+
...patch.storageConfig,
|
|
250
|
+
read: {
|
|
251
|
+
...resolveNodeStorageConfig(n).read,
|
|
252
|
+
...patch.storageConfig.read,
|
|
253
|
+
},
|
|
254
|
+
set: {
|
|
255
|
+
...resolveNodeStorageConfig(n).set,
|
|
256
|
+
...patch.storageConfig.set,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
244
260
|
if (patch.logicConfig) {
|
|
245
261
|
nextNode.logicConfig = {
|
|
246
262
|
...resolveNodeLogicConfig(n),
|
package/src/graph/document.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
JsonNodeConfig,
|
|
5
5
|
LogicNodeConfig,
|
|
6
6
|
PageLifecyclePhase,
|
|
7
|
+
StorageNodeConfig,
|
|
7
8
|
ViewEventNodeConfig,
|
|
8
9
|
ViewEventType,
|
|
9
10
|
} from "@arronqzy/blueprint-dsl";
|
|
@@ -16,13 +17,16 @@ import {
|
|
|
16
17
|
DEFAULT_JSON_NODE_CONFIG,
|
|
17
18
|
DEFAULT_LOGIC_NODE_CONFIG,
|
|
18
19
|
DEFAULT_LOGIC_NODE_TYPE,
|
|
20
|
+
DEFAULT_STORAGE_NODE_CONFIG,
|
|
19
21
|
DEFAULT_VIEW_EVENT_NODE_CONFIG,
|
|
20
22
|
EVENT_NODE_TYPE,
|
|
21
23
|
FETCH_NODE_TYPE,
|
|
22
24
|
JSON_NODE_TYPE,
|
|
23
25
|
LIFECYCLE_NODE_TYPE,
|
|
24
26
|
normalizeClockConfig,
|
|
27
|
+
normalizeStorageConfig,
|
|
25
28
|
normalizeViewEventConfig,
|
|
29
|
+
STORAGE_NODE_TYPE,
|
|
26
30
|
VIEW_NODE_TYPE,
|
|
27
31
|
} from "@arronqzy/blueprint-dsl";
|
|
28
32
|
|
|
@@ -31,8 +35,9 @@ export type { JsonNodeConfig as BlueprintJsonConfig } from "@arronqzy/blueprint-
|
|
|
31
35
|
export type { LogicNodeConfig as BlueprintLogicConfig } from "@arronqzy/blueprint-dsl";
|
|
32
36
|
export type { ClockNodeConfig as BlueprintClockConfig } from "@arronqzy/blueprint-dsl";
|
|
33
37
|
export type { ViewEventNodeConfig as BlueprintEventConfig } from "@arronqzy/blueprint-dsl";
|
|
38
|
+
export type { StorageNodeConfig as BlueprintStorageConfig } from "@arronqzy/blueprint-dsl";
|
|
34
39
|
|
|
35
|
-
export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock" | "event";
|
|
40
|
+
export type BlueprintNodeRole = "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "storage" | "clock" | "event";
|
|
36
41
|
|
|
37
42
|
/** 决定右侧配置面板展示哪类配置 */
|
|
38
43
|
export type BlueprintConfigSource =
|
|
@@ -43,6 +48,7 @@ export type BlueprintConfigSource =
|
|
|
43
48
|
| "view"
|
|
44
49
|
| "fetch"
|
|
45
50
|
| "json"
|
|
51
|
+
| "storage"
|
|
46
52
|
| "clock"
|
|
47
53
|
| "event";
|
|
48
54
|
|
|
@@ -71,6 +77,8 @@ export type BlueprintGraphNode = {
|
|
|
71
77
|
fetchConfig?: FetchRequestConfig;
|
|
72
78
|
/** JSON 节点的 JSON 字符串配置 */
|
|
73
79
|
jsonConfig?: JsonNodeConfig;
|
|
80
|
+
/** 存储节点的读写配置 */
|
|
81
|
+
storageConfig?: StorageNodeConfig;
|
|
74
82
|
/** 逻辑节点的 JavaScript 脚本配置 */
|
|
75
83
|
logicConfig?: LogicNodeConfig;
|
|
76
84
|
/** 时钟节点的间隔与时间格式配置 */
|
|
@@ -108,8 +116,10 @@ export function resolveBlueprintConfigSource(
|
|
|
108
116
|
if (node.role === "and") return "and";
|
|
109
117
|
if (node.role === "fetch") return "fetch";
|
|
110
118
|
if (node.role === "json") return "json";
|
|
119
|
+
if (node.role === "storage") return "storage";
|
|
111
120
|
if (node.nodeType === FETCH_NODE_TYPE) return "fetch";
|
|
112
121
|
if (node.nodeType === JSON_NODE_TYPE) return "json";
|
|
122
|
+
if (node.nodeType === STORAGE_NODE_TYPE) return "storage";
|
|
113
123
|
if (node.nodeType === CLOCK_NODE_TYPE) return "clock";
|
|
114
124
|
if (node.nodeType === EVENT_NODE_TYPE) return "event";
|
|
115
125
|
if (node.nodeType === VIEW_NODE_TYPE) return "view";
|
|
@@ -187,6 +197,8 @@ export function resolveRunnableNodeType(
|
|
|
187
197
|
return FETCH_NODE_TYPE;
|
|
188
198
|
case "json":
|
|
189
199
|
return JSON_NODE_TYPE;
|
|
200
|
+
case "storage":
|
|
201
|
+
return STORAGE_NODE_TYPE;
|
|
190
202
|
case "logic":
|
|
191
203
|
return DEFAULT_LOGIC_NODE_TYPE;
|
|
192
204
|
default:
|
|
@@ -210,6 +222,7 @@ export const BLUEPRINT_CONFIG_TYPE_LABEL_KEYS: Record<
|
|
|
210
222
|
event: "blueprint.node.typeEvent",
|
|
211
223
|
fetch: "blueprint.node.typeFetch",
|
|
212
224
|
json: "blueprint.node.typeJson",
|
|
225
|
+
storage: "blueprint.node.typeStorage",
|
|
213
226
|
clock: "blueprint.node.typeClock",
|
|
214
227
|
};
|
|
215
228
|
|
|
@@ -300,6 +313,15 @@ export function resolveNodeJsonConfig(
|
|
|
300
313
|
};
|
|
301
314
|
}
|
|
302
315
|
|
|
316
|
+
export function resolveNodeStorageConfig(
|
|
317
|
+
node: Pick<BlueprintGraphNode, "storageConfig">
|
|
318
|
+
): StorageNodeConfig {
|
|
319
|
+
return normalizeStorageConfig({
|
|
320
|
+
...DEFAULT_STORAGE_NODE_CONFIG,
|
|
321
|
+
...node.storageConfig,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
303
325
|
export function resolveNodeLogicConfig(
|
|
304
326
|
node: Pick<BlueprintGraphNode, "logicConfig">
|
|
305
327
|
): LogicNodeConfig {
|
|
@@ -399,6 +421,9 @@ export function sanitizeBlueprintDocument(
|
|
|
399
421
|
if (next.role === "json") {
|
|
400
422
|
return { ...next, configSource: "json" as const };
|
|
401
423
|
}
|
|
424
|
+
if (next.role === "storage") {
|
|
425
|
+
return { ...next, configSource: "storage" as const };
|
|
426
|
+
}
|
|
402
427
|
if (next.role === "and") {
|
|
403
428
|
return { ...next, configSource: "and" as const };
|
|
404
429
|
}
|
|
@@ -452,6 +477,7 @@ export function patchNodeConfigSource(
|
|
|
452
477
|
logicConfig: undefined,
|
|
453
478
|
clockConfig: undefined,
|
|
454
479
|
eventConfig: undefined,
|
|
480
|
+
storageConfig: undefined,
|
|
455
481
|
};
|
|
456
482
|
}
|
|
457
483
|
|
|
@@ -469,6 +495,7 @@ export function patchNodeConfigSource(
|
|
|
469
495
|
jsonConfig: undefined,
|
|
470
496
|
logicConfig: undefined,
|
|
471
497
|
eventConfig: undefined,
|
|
498
|
+
storageConfig: undefined,
|
|
472
499
|
};
|
|
473
500
|
}
|
|
474
501
|
|
|
@@ -486,6 +513,7 @@ export function patchNodeConfigSource(
|
|
|
486
513
|
logicConfig: undefined,
|
|
487
514
|
clockConfig: undefined,
|
|
488
515
|
eventConfig: undefined,
|
|
516
|
+
storageConfig: undefined,
|
|
489
517
|
};
|
|
490
518
|
}
|
|
491
519
|
|
|
@@ -503,6 +531,25 @@ export function patchNodeConfigSource(
|
|
|
503
531
|
logicConfig: undefined,
|
|
504
532
|
clockConfig: undefined,
|
|
505
533
|
eventConfig: undefined,
|
|
534
|
+
storageConfig: undefined,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (configSource === "storage") {
|
|
539
|
+
return {
|
|
540
|
+
role: "storage",
|
|
541
|
+
nodeType: STORAGE_NODE_TYPE,
|
|
542
|
+
configSource: "storage",
|
|
543
|
+
storageConfig: resolveNodeStorageConfig(node),
|
|
544
|
+
lifecyclePhase: undefined,
|
|
545
|
+
viewElementId: undefined,
|
|
546
|
+
viewElementIds: undefined,
|
|
547
|
+
libraryBlueprintId: undefined,
|
|
548
|
+
fetchConfig: undefined,
|
|
549
|
+
jsonConfig: undefined,
|
|
550
|
+
logicConfig: undefined,
|
|
551
|
+
clockConfig: undefined,
|
|
552
|
+
eventConfig: undefined,
|
|
506
553
|
};
|
|
507
554
|
}
|
|
508
555
|
|
|
@@ -520,6 +567,7 @@ export function patchNodeConfigSource(
|
|
|
520
567
|
logicConfig: undefined,
|
|
521
568
|
clockConfig: undefined,
|
|
522
569
|
eventConfig: undefined,
|
|
570
|
+
storageConfig: undefined,
|
|
523
571
|
};
|
|
524
572
|
}
|
|
525
573
|
|
|
@@ -537,6 +585,7 @@ export function patchNodeConfigSource(
|
|
|
537
585
|
logicConfig: resolveNodeLogicConfig(node),
|
|
538
586
|
clockConfig: undefined,
|
|
539
587
|
eventConfig: undefined,
|
|
588
|
+
storageConfig: undefined,
|
|
540
589
|
};
|
|
541
590
|
}
|
|
542
591
|
|
|
@@ -554,6 +603,7 @@ export function patchNodeConfigSource(
|
|
|
554
603
|
logicConfig: undefined,
|
|
555
604
|
clockConfig: undefined,
|
|
556
605
|
eventConfig: undefined,
|
|
606
|
+
storageConfig: undefined,
|
|
557
607
|
};
|
|
558
608
|
}
|
|
559
609
|
|
|
@@ -569,6 +619,7 @@ export function patchNodeConfigSource(
|
|
|
569
619
|
jsonConfig: undefined,
|
|
570
620
|
logicConfig: undefined,
|
|
571
621
|
clockConfig: undefined,
|
|
622
|
+
storageConfig: undefined,
|
|
572
623
|
};
|
|
573
624
|
}
|
|
574
625
|
|
|
@@ -583,5 +634,6 @@ export function patchNodeConfigSource(
|
|
|
583
634
|
logicConfig: undefined,
|
|
584
635
|
clockConfig: undefined,
|
|
585
636
|
eventConfig: undefined,
|
|
637
|
+
storageConfig: undefined,
|
|
586
638
|
};
|
|
587
639
|
}
|
package/src/graph/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
resolveNodeFetchConfig,
|
|
10
10
|
resolveNodeJsonConfig,
|
|
11
11
|
resolveNodeLogicConfig,
|
|
12
|
+
resolveNodeStorageConfig,
|
|
12
13
|
resolveViewElementIds,
|
|
13
14
|
resolveNodeEventConfig,
|
|
14
15
|
getViewEventTypeLabel,
|
|
@@ -65,6 +66,29 @@ export function resolveBlueprintNodeSummary(
|
|
|
65
66
|
if (!json) return t("blueprint.node.summaryEmptyJson");
|
|
66
67
|
return truncate(json.replace(/\s+/g, " "), 44);
|
|
67
68
|
}
|
|
69
|
+
case "storage": {
|
|
70
|
+
const storage = resolveNodeStorageConfig(data);
|
|
71
|
+
const setKey = storage.set.key.trim();
|
|
72
|
+
const readKey = storage.read.key.trim();
|
|
73
|
+
const hasSet = Boolean(setKey) && storage.set.storages.length > 0;
|
|
74
|
+
const hasRead = Boolean(readKey);
|
|
75
|
+
if (hasSet && hasRead) {
|
|
76
|
+
return truncate(
|
|
77
|
+
t("blueprint.node.summaryStorageSetRead", {
|
|
78
|
+
setKey,
|
|
79
|
+
readKey,
|
|
80
|
+
}),
|
|
81
|
+
44
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (hasSet) {
|
|
85
|
+
return truncate(t("blueprint.node.summaryStorageSet", { key: setKey }), 44);
|
|
86
|
+
}
|
|
87
|
+
if (hasRead) {
|
|
88
|
+
return truncate(t("blueprint.node.summaryStorageRead", { key: readKey }), 44);
|
|
89
|
+
}
|
|
90
|
+
return t("blueprint.node.summaryStorageUnset");
|
|
91
|
+
}
|
|
68
92
|
case "logic": {
|
|
69
93
|
const code = resolveNodeLogicConfig(data).sourceCode?.trim();
|
|
70
94
|
if (!code) return t("blueprint.node.summaryNoScript");
|
package/src/graph/node-types.ts
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeBlueprintDocument,
|
|
3
|
+
type BlueprintDocument,
|
|
4
|
+
type BlueprintGraphEdge,
|
|
5
|
+
type BlueprintGraphNode,
|
|
6
|
+
type BlueprintNodeRole,
|
|
7
|
+
} from "./document";
|
|
8
|
+
|
|
9
|
+
export type ParseCheckResult = {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const NODE_ROLES = new Set<BlueprintNodeRole>([
|
|
15
|
+
"blueprint",
|
|
16
|
+
"logic",
|
|
17
|
+
"and",
|
|
18
|
+
"lifecycle",
|
|
19
|
+
"fetch",
|
|
20
|
+
"json",
|
|
21
|
+
"storage",
|
|
22
|
+
"clock",
|
|
23
|
+
"event",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
27
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function extractBlueprintDocument(data: unknown): unknown {
|
|
31
|
+
if (!isRecord(data)) return data;
|
|
32
|
+
if (isRecord(data.blueprintDocument)) return data.blueprintDocument;
|
|
33
|
+
if (isRecord(data.document) && Array.isArray(data.document.nodes)) {
|
|
34
|
+
return data.document;
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isFiniteNumber(value: unknown): value is number {
|
|
40
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isBlueprintNode(value: unknown): value is BlueprintGraphNode {
|
|
44
|
+
if (!isRecord(value)) return false;
|
|
45
|
+
if (typeof value.id !== "string" || !value.id) return false;
|
|
46
|
+
if (typeof value.role !== "string" || !NODE_ROLES.has(value.role as BlueprintNodeRole)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (!isRecord(value.position) || !isFiniteNumber(value.position.x) || !isFiniteNumber(value.position.y)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isBlueprintEdge(value: unknown): value is BlueprintGraphEdge {
|
|
56
|
+
if (!isRecord(value)) return false;
|
|
57
|
+
return (
|
|
58
|
+
typeof value.id === "string" &&
|
|
59
|
+
value.id.length > 0 &&
|
|
60
|
+
typeof value.source === "string" &&
|
|
61
|
+
value.source.length > 0 &&
|
|
62
|
+
typeof value.target === "string" &&
|
|
63
|
+
value.target.length > 0
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 校验数据能否被蓝图解析(工作区 `blueprintDocument`、蓝图导出 JSON 或裸文档均可)。
|
|
69
|
+
*/
|
|
70
|
+
export function parseBlueprintData(
|
|
71
|
+
data: unknown
|
|
72
|
+
): ParseCheckResult & { value?: BlueprintDocument } {
|
|
73
|
+
if (data == null) {
|
|
74
|
+
return { ok: false, error: "empty" };
|
|
75
|
+
}
|
|
76
|
+
const raw = extractBlueprintDocument(data);
|
|
77
|
+
if (!isRecord(raw)) {
|
|
78
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(raw.nodes) || !Array.isArray(raw.edges)) {
|
|
81
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
82
|
+
}
|
|
83
|
+
if (!raw.nodes.every(isBlueprintNode) || !raw.edges.every(isBlueprintEdge)) {
|
|
84
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const value = sanitizeBlueprintDocument({
|
|
88
|
+
id: typeof raw.id === "string" && raw.id ? raw.id : "default",
|
|
89
|
+
name: typeof raw.name === "string" ? raw.name : undefined,
|
|
90
|
+
nodes: raw.nodes as BlueprintGraphNode[],
|
|
91
|
+
edges: raw.edges as BlueprintGraphEdge[],
|
|
92
|
+
allowFalseSignalPropagation:
|
|
93
|
+
typeof raw.allowFalseSignalPropagation === "boolean"
|
|
94
|
+
? raw.allowFalseSignalPropagation
|
|
95
|
+
: undefined,
|
|
96
|
+
});
|
|
97
|
+
return { ok: true, value };
|
|
98
|
+
} catch {
|
|
99
|
+
return { ok: false, error: "invalid-blueprint-data" };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function validateBlueprintData(data: unknown): ParseCheckResult {
|
|
104
|
+
const parsed = parseBlueprintData(data);
|
|
105
|
+
return parsed.ok ? { ok: true } : { ok: false, error: parsed.error };
|
|
106
|
+
}
|
|
@@ -19,6 +19,7 @@ export type BlueprintFlowNodeData = {
|
|
|
19
19
|
lifecyclePhase?: BlueprintGraphNode["lifecyclePhase"];
|
|
20
20
|
fetchConfig?: BlueprintGraphNode["fetchConfig"];
|
|
21
21
|
jsonConfig?: BlueprintGraphNode["jsonConfig"];
|
|
22
|
+
storageConfig?: BlueprintGraphNode["storageConfig"];
|
|
22
23
|
clockConfig?: BlueprintGraphNode["clockConfig"];
|
|
23
24
|
eventConfig?: BlueprintGraphNode["eventConfig"];
|
|
24
25
|
logicConfig?: BlueprintGraphNode["logicConfig"];
|
|
@@ -71,6 +72,8 @@ export function toReactFlowNodes(document: BlueprintDocument): ReactFlowNodeView
|
|
|
71
72
|
? "fetch"
|
|
72
73
|
: n.role === "json"
|
|
73
74
|
? "json"
|
|
75
|
+
: n.role === "storage"
|
|
76
|
+
? "storage"
|
|
74
77
|
: n.role === "event"
|
|
75
78
|
? "event"
|
|
76
79
|
: "logic",
|
|
@@ -93,6 +96,7 @@ export function toReactFlowNodes(document: BlueprintDocument): ReactFlowNodeView
|
|
|
93
96
|
lifecyclePhase: n.lifecyclePhase,
|
|
94
97
|
fetchConfig: n.fetchConfig,
|
|
95
98
|
jsonConfig: n.jsonConfig,
|
|
99
|
+
storageConfig: n.storageConfig,
|
|
96
100
|
clockConfig: n.clockConfig,
|
|
97
101
|
eventConfig: n.eventConfig,
|
|
98
102
|
logicConfig: n.logicConfig,
|
package/src/graph/sync-nodes.ts
CHANGED
|
@@ -46,6 +46,10 @@ export function nodeStructureSignature(
|
|
|
46
46
|
viewElementIds?: string[];
|
|
47
47
|
fetchConfig?: { url?: string; method?: string; apiBaseUrl?: string; swaggerDocsUrl?: string };
|
|
48
48
|
jsonConfig?: { jsonString?: string };
|
|
49
|
+
storageConfig?: {
|
|
50
|
+
read?: { storage?: string; key?: string };
|
|
51
|
+
set?: { storages?: string[]; key?: string; value?: string };
|
|
52
|
+
};
|
|
49
53
|
logicConfig?: { sourceCode?: string };
|
|
50
54
|
clockConfig?: { intervalSeconds?: number; timeFormat?: string; outputCount?: number; emitImmediately?: boolean };
|
|
51
55
|
eventConfig?: { eventTypes?: string[] };
|
|
@@ -54,7 +58,7 @@ export function nodeStructureSignature(
|
|
|
54
58
|
return nodes
|
|
55
59
|
.map(
|
|
56
60
|
(n) =>
|
|
57
|
-
`${n.id}:${n.role}:${n.label}:${n.configSource ?? ""}:${n.lifecyclePhase ?? ""}:${n.libraryBlueprintId ?? ""}:${n.nodeType ?? ""}:${(n.viewElementIds ?? (n.viewElementId ? [n.viewElementId] : [])).join(",")}:${n.fetchConfig?.url ?? ""}:${n.fetchConfig?.method ?? ""}:${n.fetchConfig?.apiBaseUrl ?? ""}:${n.fetchConfig?.swaggerDocsUrl ?? ""}:${n.jsonConfig?.jsonString ?? ""}:${n.logicConfig?.sourceCode ?? ""}:${n.clockConfig?.intervalSeconds ?? ""}:${n.clockConfig?.timeFormat ?? ""}:${n.clockConfig?.outputCount ?? ""}:${n.clockConfig?.emitImmediately ?? ""}:${(n.eventConfig?.eventTypes ?? []).join(",")}`
|
|
61
|
+
`${n.id}:${n.role}:${n.label}:${n.configSource ?? ""}:${n.lifecyclePhase ?? ""}:${n.libraryBlueprintId ?? ""}:${n.nodeType ?? ""}:${(n.viewElementIds ?? (n.viewElementId ? [n.viewElementId] : [])).join(",")}:${n.fetchConfig?.url ?? ""}:${n.fetchConfig?.method ?? ""}:${n.fetchConfig?.apiBaseUrl ?? ""}:${n.fetchConfig?.swaggerDocsUrl ?? ""}:${n.jsonConfig?.jsonString ?? ""}:${n.storageConfig?.read?.storage ?? ""}:${n.storageConfig?.read?.key ?? ""}:${(n.storageConfig?.set?.storages ?? []).join(",")}:${n.storageConfig?.set?.key ?? ""}:${n.storageConfig?.set?.value ?? ""}:${n.logicConfig?.sourceCode ?? ""}:${n.clockConfig?.intervalSeconds ?? ""}:${n.clockConfig?.timeFormat ?? ""}:${n.clockConfig?.outputCount ?? ""}:${n.clockConfig?.emitImmediately ?? ""}:${(n.eventConfig?.eventTypes ?? []).join(",")}`
|
|
58
62
|
)
|
|
59
63
|
.sort()
|
|
60
64
|
.join("|");
|
|
@@ -46,6 +46,8 @@ export type UseBlueprintDebugSessionOptions = {
|
|
|
46
46
|
viewElementIds: string[],
|
|
47
47
|
scope: unknown
|
|
48
48
|
) => void;
|
|
49
|
+
/** 隔离 IndexedDB / localStorage;空则用全局库 */
|
|
50
|
+
nameSpace?: string | null;
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
function buildNodeLabelMap(graph: BlueprintGraph): Record<string, string> {
|
|
@@ -66,6 +68,7 @@ export function useBlueprintDebugSession({
|
|
|
66
68
|
libraryNameById,
|
|
67
69
|
onExecutionBlocked,
|
|
68
70
|
onViewScopeUpdate,
|
|
71
|
+
nameSpace = null,
|
|
69
72
|
}: UseBlueprintDebugSessionOptions) {
|
|
70
73
|
const runnerRef = useRef<BlueprintGraphRunner | null>(null);
|
|
71
74
|
const runIdRef = useRef<string | null>(null);
|
|
@@ -79,8 +82,12 @@ export function useBlueprintDebugSession({
|
|
|
79
82
|
const [logPanelOpen, setLogPanelOpen] = useState(false);
|
|
80
83
|
const [running, setRunning] = useState(false);
|
|
81
84
|
const [settings, setSettings] = useState<ExecutionLogSettings>(() =>
|
|
82
|
-
readExecutionLogSettings()
|
|
85
|
+
readExecutionLogSettings(nameSpace)
|
|
83
86
|
);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
setSettings(readExecutionLogSettings(nameSpace));
|
|
90
|
+
}, [nameSpace]);
|
|
84
91
|
const [savedRuns, setSavedRuns] = useState<ExecutionRunRecord[]>([]);
|
|
85
92
|
const [totalSavedRunCount, setTotalSavedRunCount] = useState(0);
|
|
86
93
|
|
|
@@ -172,13 +179,13 @@ export function useBlueprintDebugSession({
|
|
|
172
179
|
]);
|
|
173
180
|
|
|
174
181
|
const refreshSavedRuns = useCallback(async () => {
|
|
175
|
-
const all = await listExecutionRunRecords();
|
|
182
|
+
const all = await listExecutionRunRecords(undefined, nameSpace);
|
|
176
183
|
setTotalSavedRunCount(all.length);
|
|
177
184
|
const items = blueprintId
|
|
178
185
|
? all.filter((item) => item.blueprintId === blueprintId)
|
|
179
186
|
: all;
|
|
180
187
|
setSavedRuns(items);
|
|
181
|
-
}, [blueprintId]);
|
|
188
|
+
}, [blueprintId, nameSpace]);
|
|
182
189
|
|
|
183
190
|
useEffect(() => {
|
|
184
191
|
if (!logPanelOpen) return;
|
|
@@ -187,19 +194,19 @@ export function useBlueprintDebugSession({
|
|
|
187
194
|
|
|
188
195
|
const enforceSavedRunLimits = useCallback(async () => {
|
|
189
196
|
const cutoff = retentionCutoffMs(settings.retentionDays);
|
|
190
|
-
await purgeExecutionRunsOlderThan(cutoff);
|
|
191
|
-
await trimExecutionRunRecordsToMax(settings.maxSavedRuns);
|
|
197
|
+
await purgeExecutionRunsOlderThan(cutoff, nameSpace);
|
|
198
|
+
await trimExecutionRunRecordsToMax(settings.maxSavedRuns, nameSpace);
|
|
192
199
|
await refreshSavedRuns();
|
|
193
|
-
}, [refreshSavedRuns, settings.maxSavedRuns, settings.retentionDays]);
|
|
200
|
+
}, [nameSpace, refreshSavedRuns, settings.maxSavedRuns, settings.retentionDays]);
|
|
194
201
|
|
|
195
202
|
const applyRetention = enforceSavedRunLimits;
|
|
196
203
|
|
|
197
204
|
const clearAllSavedRuns = useCallback(async () => {
|
|
198
|
-
const removed = await clearAllExecutionRunRecords();
|
|
205
|
+
const removed = await clearAllExecutionRunRecords(nameSpace);
|
|
199
206
|
setSavedRuns([]);
|
|
200
207
|
setTotalSavedRunCount(0);
|
|
201
208
|
return removed;
|
|
202
|
-
}, []);
|
|
209
|
+
}, [nameSpace]);
|
|
203
210
|
|
|
204
211
|
const buildRunRecord = useCallback(
|
|
205
212
|
(
|
|
@@ -229,10 +236,10 @@ export function useBlueprintDebugSession({
|
|
|
229
236
|
const persistRunIfNeeded = useCallback(
|
|
230
237
|
async (record: ExecutionRunRecord) => {
|
|
231
238
|
if (!settings.autoSave) return;
|
|
232
|
-
await putExecutionRunRecord(record);
|
|
239
|
+
await putExecutionRunRecord(record, nameSpace);
|
|
233
240
|
await enforceSavedRunLimits();
|
|
234
241
|
},
|
|
235
|
-
[enforceSavedRunLimits, settings.autoSave]
|
|
242
|
+
[enforceSavedRunLimits, nameSpace, settings.autoSave]
|
|
236
243
|
);
|
|
237
244
|
|
|
238
245
|
const beginRun = useCallback(() => {
|
|
@@ -420,9 +427,9 @@ export function useBlueprintDebugSession({
|
|
|
420
427
|
runnerRef.current?.isDebugQueueEmpty() ? "completed" : "paused",
|
|
421
428
|
entries
|
|
422
429
|
);
|
|
423
|
-
await putExecutionRunRecord(record);
|
|
430
|
+
await putExecutionRunRecord(record, nameSpace);
|
|
424
431
|
await enforceSavedRunLimits();
|
|
425
|
-
}, [buildRunRecord, enforceSavedRunLimits, entries]);
|
|
432
|
+
}, [buildRunRecord, enforceSavedRunLimits, entries, nameSpace]);
|
|
426
433
|
|
|
427
434
|
const exportCurrentRun = useCallback(() => {
|
|
428
435
|
if (entries.length === 0) return;
|
|
@@ -437,17 +444,17 @@ export function useBlueprintDebugSession({
|
|
|
437
444
|
(patch: Partial<ExecutionLogSettings>) => {
|
|
438
445
|
setSettings((prev) => {
|
|
439
446
|
const next = { ...prev, ...patch };
|
|
440
|
-
writeExecutionLogSettings(next);
|
|
447
|
+
writeExecutionLogSettings(next, nameSpace);
|
|
441
448
|
return next;
|
|
442
449
|
});
|
|
443
450
|
if (patch.maxSavedRuns !== undefined) {
|
|
444
451
|
const maxSavedRuns = Math.max(1, Math.floor(patch.maxSavedRuns));
|
|
445
|
-
void trimExecutionRunRecordsToMax(maxSavedRuns).then(() =>
|
|
452
|
+
void trimExecutionRunRecordsToMax(maxSavedRuns, nameSpace).then(() =>
|
|
446
453
|
refreshSavedRuns()
|
|
447
454
|
);
|
|
448
455
|
}
|
|
449
456
|
},
|
|
450
|
-
[refreshSavedRuns]
|
|
457
|
+
[nameSpace, refreshSavedRuns]
|
|
451
458
|
);
|
|
452
459
|
|
|
453
460
|
const resetSession = useCallback(() => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
1
2
|
import type {
|
|
2
3
|
BlueprintLibraryListItem,
|
|
3
4
|
BlueprintLibraryRecord,
|
|
@@ -7,9 +8,9 @@ const DB_NAME = "arronqzy-blueprint-library";
|
|
|
7
8
|
const DB_VERSION = 1;
|
|
8
9
|
const STORE_NAME = "blueprints";
|
|
9
10
|
|
|
10
|
-
function openDb(): Promise<IDBDatabase> {
|
|
11
|
+
function openDb(nameSpace?: string | null): Promise<IDBDatabase> {
|
|
11
12
|
return new Promise((resolve, reject) => {
|
|
12
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
13
|
+
const request = indexedDB.open(appStorageKey(DB_NAME, nameSpace), DB_VERSION);
|
|
13
14
|
request.onerror = () => reject(request.error ?? new Error("indexeddb-open-failed"));
|
|
14
15
|
request.onupgradeneeded = () => {
|
|
15
16
|
const db = request.result;
|
|
@@ -25,9 +26,10 @@ function openDb(): Promise<IDBDatabase> {
|
|
|
25
26
|
|
|
26
27
|
function runTransaction<T>(
|
|
27
28
|
mode: IDBTransactionMode,
|
|
28
|
-
runner: (store: IDBObjectStore) => IDBRequest<T
|
|
29
|
+
runner: (store: IDBObjectStore) => IDBRequest<T>,
|
|
30
|
+
nameSpace?: string | null
|
|
29
31
|
): Promise<T> {
|
|
30
|
-
return openDb().then(
|
|
32
|
+
return openDb(nameSpace).then(
|
|
31
33
|
(db) =>
|
|
32
34
|
new Promise<T>((resolve, reject) => {
|
|
33
35
|
const tx = db.transaction(STORE_NAME, mode);
|
|
@@ -53,9 +55,13 @@ function runTransaction<T>(
|
|
|
53
55
|
);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
export async function listBlueprintLibrary(
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
export async function listBlueprintLibrary(
|
|
59
|
+
nameSpace?: string | null
|
|
60
|
+
): Promise<BlueprintLibraryListItem[]> {
|
|
61
|
+
const records = await runTransaction<BlueprintLibraryRecord[]>(
|
|
62
|
+
"readonly",
|
|
63
|
+
(store) => store.getAll(),
|
|
64
|
+
nameSpace
|
|
59
65
|
);
|
|
60
66
|
return records
|
|
61
67
|
.map((record) => ({
|
|
@@ -69,26 +75,31 @@ export async function listBlueprintLibrary(): Promise<BlueprintLibraryListItem[]
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export async function getBlueprintLibraryRecord(
|
|
72
|
-
id: string
|
|
78
|
+
id: string,
|
|
79
|
+
nameSpace?: string | null
|
|
73
80
|
): Promise<BlueprintLibraryRecord | null> {
|
|
74
|
-
const record = await runTransaction<BlueprintLibraryRecord | undefined>(
|
|
75
|
-
|
|
81
|
+
const record = await runTransaction<BlueprintLibraryRecord | undefined>(
|
|
82
|
+
"readonly",
|
|
83
|
+
(store) => store.get(id),
|
|
84
|
+
nameSpace
|
|
76
85
|
);
|
|
77
86
|
return record ?? null;
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
export async function putBlueprintLibraryRecord(
|
|
81
|
-
record: BlueprintLibraryRecord
|
|
90
|
+
record: BlueprintLibraryRecord,
|
|
91
|
+
nameSpace?: string | null
|
|
82
92
|
): Promise<BlueprintLibraryRecord> {
|
|
83
|
-
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record));
|
|
93
|
+
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record), nameSpace);
|
|
84
94
|
return record;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
export async function updateBlueprintLibraryMeta(
|
|
88
98
|
id: string,
|
|
89
|
-
patch: { name?: string; remark?: string }
|
|
99
|
+
patch: { name?: string; remark?: string },
|
|
100
|
+
nameSpace?: string | null
|
|
90
101
|
): Promise<BlueprintLibraryRecord | null> {
|
|
91
|
-
const existing = await getBlueprintLibraryRecord(id);
|
|
102
|
+
const existing = await getBlueprintLibraryRecord(id, nameSpace);
|
|
92
103
|
if (!existing) return null;
|
|
93
104
|
|
|
94
105
|
const name = patch.name?.trim() || existing.name;
|
|
@@ -103,10 +114,13 @@ export async function updateBlueprintLibraryMeta(
|
|
|
103
114
|
},
|
|
104
115
|
};
|
|
105
116
|
|
|
106
|
-
await putBlueprintLibraryRecord(record);
|
|
117
|
+
await putBlueprintLibraryRecord(record, nameSpace);
|
|
107
118
|
return record;
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
export async function deleteBlueprintLibraryRecord(
|
|
111
|
-
|
|
121
|
+
export async function deleteBlueprintLibraryRecord(
|
|
122
|
+
id: string,
|
|
123
|
+
nameSpace?: string | null
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
await runTransaction<undefined>("readwrite", (store) => store.delete(id), nameSpace);
|
|
112
126
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
1
2
|
import type { ExecutionRunRecord } from "@arronqzy/blueprint-dsl";
|
|
2
3
|
|
|
3
4
|
const DB_NAME = "arronqzy-blueprint-execution-log";
|
|
4
5
|
const DB_VERSION = 1;
|
|
5
6
|
const STORE_NAME = "runs";
|
|
6
7
|
|
|
7
|
-
function openDb(): Promise<IDBDatabase> {
|
|
8
|
+
function openDb(nameSpace?: string | null): Promise<IDBDatabase> {
|
|
8
9
|
return new Promise((resolve, reject) => {
|
|
9
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
10
|
+
const request = indexedDB.open(appStorageKey(DB_NAME, nameSpace), DB_VERSION);
|
|
10
11
|
request.onerror = () => reject(request.error ?? new Error("indexeddb-open-failed"));
|
|
11
12
|
request.onupgradeneeded = () => {
|
|
12
13
|
const db = request.result;
|
|
@@ -22,9 +23,10 @@ function openDb(): Promise<IDBDatabase> {
|
|
|
22
23
|
|
|
23
24
|
function runTransaction<T>(
|
|
24
25
|
mode: IDBTransactionMode,
|
|
25
|
-
runner: (store: IDBObjectStore) => IDBRequest<T
|
|
26
|
+
runner: (store: IDBObjectStore) => IDBRequest<T>,
|
|
27
|
+
nameSpace?: string | null
|
|
26
28
|
): Promise<T> {
|
|
27
|
-
return openDb().then(
|
|
29
|
+
return openDb(nameSpace).then(
|
|
28
30
|
(db) =>
|
|
29
31
|
new Promise<T>((resolve, reject) => {
|
|
30
32
|
const tx = db.transaction(STORE_NAME, mode);
|
|
@@ -50,28 +52,55 @@ function runTransaction<T>(
|
|
|
50
52
|
);
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
function deleteMany(ids: string[], nameSpace?: string | null): Promise<void> {
|
|
56
|
+
return openDb(nameSpace).then(
|
|
57
|
+
(db) =>
|
|
58
|
+
new Promise<void>((resolve, reject) => {
|
|
59
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
60
|
+
const store = tx.objectStore(STORE_NAME);
|
|
61
|
+
for (const id of ids) {
|
|
62
|
+
store.delete(id);
|
|
63
|
+
}
|
|
64
|
+
tx.oncomplete = () => {
|
|
65
|
+
db.close();
|
|
66
|
+
resolve();
|
|
67
|
+
};
|
|
68
|
+
tx.onerror = () => {
|
|
69
|
+
db.close();
|
|
70
|
+
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
71
|
+
};
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
export async function putExecutionRunRecord(
|
|
54
|
-
record: ExecutionRunRecord
|
|
77
|
+
record: ExecutionRunRecord,
|
|
78
|
+
nameSpace?: string | null
|
|
55
79
|
): Promise<ExecutionRunRecord> {
|
|
56
|
-
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record));
|
|
80
|
+
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record), nameSpace);
|
|
57
81
|
return record;
|
|
58
82
|
}
|
|
59
83
|
|
|
60
84
|
export async function getExecutionRunRecord(
|
|
61
|
-
runId: string
|
|
85
|
+
runId: string,
|
|
86
|
+
nameSpace?: string | null
|
|
62
87
|
): Promise<ExecutionRunRecord | null> {
|
|
63
88
|
const record = await runTransaction<ExecutionRunRecord | undefined>(
|
|
64
89
|
"readonly",
|
|
65
|
-
(store) => store.get(runId)
|
|
90
|
+
(store) => store.get(runId),
|
|
91
|
+
nameSpace
|
|
66
92
|
);
|
|
67
93
|
return record ?? null;
|
|
68
94
|
}
|
|
69
95
|
|
|
70
96
|
export async function listExecutionRunRecords(
|
|
71
|
-
blueprintId?: string | null
|
|
97
|
+
blueprintId?: string | null,
|
|
98
|
+
nameSpace?: string | null
|
|
72
99
|
): Promise<ExecutionRunRecord[]> {
|
|
73
|
-
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
74
|
-
|
|
100
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
101
|
+
"readonly",
|
|
102
|
+
(store) => store.getAll(),
|
|
103
|
+
nameSpace
|
|
75
104
|
);
|
|
76
105
|
const filtered = blueprintId
|
|
77
106
|
? records.filter((item) => item.blueprintId === blueprintId)
|
|
@@ -79,27 +108,37 @@ export async function listExecutionRunRecords(
|
|
|
79
108
|
return filtered.sort((a, b) => b.startedAt - a.startedAt);
|
|
80
109
|
}
|
|
81
110
|
|
|
82
|
-
export async function deleteExecutionRunRecord(
|
|
83
|
-
|
|
111
|
+
export async function deleteExecutionRunRecord(
|
|
112
|
+
runId: string,
|
|
113
|
+
nameSpace?: string | null
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
await runTransaction<undefined>("readwrite", (store) => store.delete(runId), nameSpace);
|
|
84
116
|
}
|
|
85
117
|
|
|
86
|
-
export async function clearAllExecutionRunRecords(
|
|
87
|
-
|
|
88
|
-
|
|
118
|
+
export async function clearAllExecutionRunRecords(
|
|
119
|
+
nameSpace?: string | null
|
|
120
|
+
): Promise<number> {
|
|
121
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
122
|
+
"readonly",
|
|
123
|
+
(store) => store.getAll(),
|
|
124
|
+
nameSpace
|
|
89
125
|
);
|
|
90
126
|
if (records.length === 0) return 0;
|
|
91
127
|
|
|
92
|
-
await runTransaction<undefined>("readwrite", (store) => store.clear());
|
|
128
|
+
await runTransaction<undefined>("readwrite", (store) => store.clear(), nameSpace);
|
|
93
129
|
return records.length;
|
|
94
130
|
}
|
|
95
131
|
|
|
96
132
|
export async function trimExecutionRunRecordsToMax(
|
|
97
|
-
maxCount: number
|
|
133
|
+
maxCount: number,
|
|
134
|
+
nameSpace?: string | null
|
|
98
135
|
): Promise<number> {
|
|
99
136
|
if (!Number.isFinite(maxCount) || maxCount < 1) return 0;
|
|
100
137
|
|
|
101
|
-
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
102
|
-
|
|
138
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
139
|
+
"readonly",
|
|
140
|
+
(store) => store.getAll(),
|
|
141
|
+
nameSpace
|
|
103
142
|
);
|
|
104
143
|
if (records.length <= maxCount) return 0;
|
|
105
144
|
|
|
@@ -107,52 +146,29 @@ export async function trimExecutionRunRecordsToMax(
|
|
|
107
146
|
const overflow = sorted.slice(maxCount);
|
|
108
147
|
if (overflow.length === 0) return 0;
|
|
109
148
|
|
|
110
|
-
await
|
|
111
|
-
(
|
|
112
|
-
|
|
113
|
-
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
114
|
-
const store = tx.objectStore(STORE_NAME);
|
|
115
|
-
for (const item of overflow) {
|
|
116
|
-
store.delete(item.runId);
|
|
117
|
-
}
|
|
118
|
-
tx.oncomplete = () => {
|
|
119
|
-
db.close();
|
|
120
|
-
resolve();
|
|
121
|
-
};
|
|
122
|
-
tx.onerror = () => {
|
|
123
|
-
db.close();
|
|
124
|
-
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
125
|
-
};
|
|
126
|
-
})
|
|
149
|
+
await deleteMany(
|
|
150
|
+
overflow.map((item) => item.runId),
|
|
151
|
+
nameSpace
|
|
127
152
|
);
|
|
128
153
|
|
|
129
154
|
return overflow.length;
|
|
130
155
|
}
|
|
131
156
|
|
|
132
|
-
export async function purgeExecutionRunsOlderThan(
|
|
133
|
-
|
|
134
|
-
|
|
157
|
+
export async function purgeExecutionRunsOlderThan(
|
|
158
|
+
cutoffMs: number,
|
|
159
|
+
nameSpace?: string | null
|
|
160
|
+
): Promise<number> {
|
|
161
|
+
const records = await runTransaction<ExecutionRunRecord[]>(
|
|
162
|
+
"readonly",
|
|
163
|
+
(store) => store.getAll(),
|
|
164
|
+
nameSpace
|
|
135
165
|
);
|
|
136
166
|
const stale = records.filter((item) => item.startedAt < cutoffMs);
|
|
137
167
|
if (stale.length === 0) return 0;
|
|
138
168
|
|
|
139
|
-
await
|
|
140
|
-
(
|
|
141
|
-
|
|
142
|
-
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
143
|
-
const store = tx.objectStore(STORE_NAME);
|
|
144
|
-
for (const item of stale) {
|
|
145
|
-
store.delete(item.runId);
|
|
146
|
-
}
|
|
147
|
-
tx.oncomplete = () => {
|
|
148
|
-
db.close();
|
|
149
|
-
resolve();
|
|
150
|
-
};
|
|
151
|
-
tx.onerror = () => {
|
|
152
|
-
db.close();
|
|
153
|
-
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
154
|
-
};
|
|
155
|
-
})
|
|
169
|
+
await deleteMany(
|
|
170
|
+
stale.map((item) => item.runId),
|
|
171
|
+
nameSpace
|
|
156
172
|
);
|
|
157
173
|
|
|
158
174
|
return stale.length;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { appStorageKey } from "@arronqzy/blueprint-dsl";
|
|
2
|
+
|
|
1
3
|
export type ExecutionLogSettings = {
|
|
2
4
|
retentionDays: number;
|
|
3
5
|
autoSave: boolean;
|
|
@@ -13,9 +15,11 @@ export const DEFAULT_EXECUTION_LOG_SETTINGS: ExecutionLogSettings = {
|
|
|
13
15
|
maxSavedRuns: 80,
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
export function readExecutionLogSettings(
|
|
18
|
+
export function readExecutionLogSettings(
|
|
19
|
+
nameSpace?: string | null
|
|
20
|
+
): ExecutionLogSettings {
|
|
17
21
|
try {
|
|
18
|
-
const raw = localStorage.getItem(SETTINGS_KEY);
|
|
22
|
+
const raw = localStorage.getItem(appStorageKey(SETTINGS_KEY, nameSpace));
|
|
19
23
|
if (!raw) return { ...DEFAULT_EXECUTION_LOG_SETTINGS };
|
|
20
24
|
const parsed = JSON.parse(raw) as Partial<ExecutionLogSettings>;
|
|
21
25
|
return {
|
|
@@ -37,9 +41,15 @@ export function readExecutionLogSettings(): ExecutionLogSettings {
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
export function writeExecutionLogSettings(
|
|
44
|
+
export function writeExecutionLogSettings(
|
|
45
|
+
settings: ExecutionLogSettings,
|
|
46
|
+
nameSpace?: string | null
|
|
47
|
+
) {
|
|
41
48
|
try {
|
|
42
|
-
localStorage.setItem(
|
|
49
|
+
localStorage.setItem(
|
|
50
|
+
appStorageKey(SETTINGS_KEY, nameSpace),
|
|
51
|
+
JSON.stringify(settings)
|
|
52
|
+
);
|
|
43
53
|
} catch {
|
|
44
54
|
// ignore storage errors
|
|
45
55
|
}
|
|
@@ -9,7 +9,7 @@ export type BlueprintNodeCardProps = {
|
|
|
9
9
|
subtitle?: string;
|
|
10
10
|
/** 调试执行进度(如时钟 2/3) */
|
|
11
11
|
progressLabel?: string;
|
|
12
|
-
variant?: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "clock";
|
|
12
|
+
variant?: "blueprint" | "logic" | "and" | "lifecycle" | "event" | "fetch" | "json" | "storage" | "clock";
|
|
13
13
|
selected?: boolean;
|
|
14
14
|
hideLeadingDot?: boolean;
|
|
15
15
|
onSelect?: (nodeId: string) => void;
|
|
@@ -51,6 +51,11 @@ const variantStyle = {
|
|
|
51
51
|
badge: "bg-teal-500/10 text-teal-700 dark:text-teal-300",
|
|
52
52
|
dot: "bg-teal-500",
|
|
53
53
|
},
|
|
54
|
+
storage: {
|
|
55
|
+
accent: "border-l-emerald-500 dark:border-l-emerald-400",
|
|
56
|
+
badge: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
57
|
+
dot: "bg-emerald-500",
|
|
58
|
+
},
|
|
54
59
|
clock: {
|
|
55
60
|
accent: "border-l-rose-500 dark:border-l-rose-400",
|
|
56
61
|
badge: "bg-rose-500/10 text-rose-700 dark:text-rose-300",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useI18n } from "@arronqzy/i18n/react";
|
|
2
|
+
import type { NodeProps } from "@xyflow/react";
|
|
3
|
+
|
|
4
|
+
import { resolveBlueprintNodeTypeLabel } from "../graph/document";
|
|
5
|
+
import { resolveBlueprintNodeSummary } from "../graph/node-summary";
|
|
6
|
+
import { useBlueprintNodeSelect } from "../BlueprintCanvasContext";
|
|
7
|
+
import { resolveBlueprintNodeExecutionTone } from "../runtime/execution-overlay";
|
|
8
|
+
import type { BlueprintFlowNodeData } from "../types";
|
|
9
|
+
import { BlueprintNodeShell } from "./BlueprintNodeShell";
|
|
10
|
+
|
|
11
|
+
export function StorageFlowNode({ id, data }: NodeProps) {
|
|
12
|
+
const { t } = useI18n();
|
|
13
|
+
const nodeData = data as BlueprintFlowNodeData;
|
|
14
|
+
const onSelect = useBlueprintNodeSelect();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<BlueprintNodeShell
|
|
18
|
+
nodeId={id}
|
|
19
|
+
label={nodeData.label}
|
|
20
|
+
meta={resolveBlueprintNodeTypeLabel(nodeData, t)}
|
|
21
|
+
subtitle={resolveBlueprintNodeSummary(nodeData, t)}
|
|
22
|
+
variant="storage"
|
|
23
|
+
selected={Boolean(nodeData.isSelected)}
|
|
24
|
+
executionTone={resolveBlueprintNodeExecutionTone(nodeData)}
|
|
25
|
+
onSelect={onSelect}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FETCH_NODE_TYPE,
|
|
3
|
+
STORAGE_NODE_TYPE,
|
|
3
4
|
type RunnableGraph,
|
|
4
5
|
} from "@arronqzy/blueprint-dsl";
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
8
|
resolveNodeFetchConfig,
|
|
9
|
+
resolveNodeStorageConfig,
|
|
8
10
|
resolveRunnableNodeType,
|
|
9
11
|
resolveViewElementIds,
|
|
10
12
|
type BlueprintDocument,
|
|
@@ -34,6 +36,10 @@ export function documentToRunnableGraph(
|
|
|
34
36
|
? resolveNodeFetchConfig(node)
|
|
35
37
|
: node.fetchConfig,
|
|
36
38
|
jsonConfig: node.jsonConfig,
|
|
39
|
+
storageConfig:
|
|
40
|
+
nodeType === STORAGE_NODE_TYPE
|
|
41
|
+
? resolveNodeStorageConfig(node)
|
|
42
|
+
: node.storageConfig,
|
|
37
43
|
logicConfig: node.logicConfig,
|
|
38
44
|
clockConfig: node.clockConfig,
|
|
39
45
|
eventConfig: node.eventConfig,
|