@arronqzy/react-blueprint 1.0.0
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/.eslintrc.cjs +5 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/aix-ppc64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/android-arm.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/android-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/android-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/darwin-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/darwin-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/freebsd-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/freebsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-arm.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-ia32.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-loong64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-mips64el.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-ppc64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-riscv64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-s390x.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/linux-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/netbsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/openbsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/sunos-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/win32-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/win32-ia32.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@esbuild/win32-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-android-arm-eabi.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-android-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-darwin-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-darwin-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-freebsd-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-freebsd-x64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-arm64-musl.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-riscv64-musl.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-s390x-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-x64-gnu.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-linux-x64-musl.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-win32-arm64-msvc.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-win32-ia32-msvc.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/@rollup/rollup-win32-x64-msvc.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/eslint.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/fsevents.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/react-dom.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/react-is.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-darwin-64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-darwin-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-linux-64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-linux-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-windows-64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/turbo-windows-arm64.json +1 -0
- package/.pnpm-cache/metadata/registry.npmjs.org/typescript.json +1 -0
- package/.turbo/turbo-lint.log +4 -0
- package/dist/assets/index-CneNgzWb.css +1 -0
- package/dist/assets/index-DsVhHV0-.js +226 -0
- package/dist/index.html +14 -0
- package/index.html +13 -0
- package/index.ts +70 -0
- package/package.json +43 -0
- package/postcss.config.cjs +6 -0
- package/src/BlueprintCanvasContext.tsx +61 -0
- package/src/BlueprintContextMenu.tsx +108 -0
- package/src/BlueprintNodeConfigSidebar.tsx +358 -0
- package/src/blueprint.css +321 -0
- package/src/blueprintNodeTypes.ts +20 -0
- package/src/components/BlueprintExecutionLogPanel.tsx +427 -0
- package/src/components/BlueprintLibrarySelect.tsx +68 -0
- package/src/components/BlueprintMetaDialog.tsx +96 -0
- package/src/components/BlueprintNodeSwitchTaskDialog.tsx +58 -0
- package/src/components/BlueprintPanelToolbar.tsx +664 -0
- package/src/components/ClockNodeConfigPanel.tsx +139 -0
- package/src/components/FetchNodeConfigPanel.tsx +716 -0
- package/src/components/FetchUrlAutocomplete.tsx +190 -0
- package/src/components/JsonNodeConfigPanel.tsx +79 -0
- package/src/components/LogicNodeConfigPanel.tsx +80 -0
- package/src/components/ViewElementMultiSelect.tsx +140 -0
- package/src/createBlueprintEdgeTypes.tsx +10 -0
- package/src/dev-seed.ts +34 -0
- package/src/dev.css +38 -0
- package/src/edges/BlueprintSmoothEdge.tsx +35 -0
- package/src/fetch-config-task-store.ts +197 -0
- package/src/flowCoordinates.ts +19 -0
- package/src/flowDefaults.ts +9 -0
- package/src/graph/blueprint-graph.ts +261 -0
- package/src/graph/document.ts +384 -0
- package/src/graph/index.ts +7 -0
- package/src/graph/node-summary.ts +88 -0
- package/src/graph/node-types.ts +9 -0
- package/src/graph/react-flow-adapter.ts +127 -0
- package/src/graph/sync-edges.ts +69 -0
- package/src/graph/sync-nodes.ts +104 -0
- package/src/hooks/useBlueprintDebugSession.ts +515 -0
- package/src/hooks/useBlueprintFlowColorMode.ts +8 -0
- package/src/hooks/useBlueprintFlowState.ts +486 -0
- package/src/hooks/useBlueprintFlowViewport.ts +54 -0
- package/src/hooks/useBlueprintNodeSelectionGuard.ts +63 -0
- package/src/hooks/useBlueprintPageLifecycle.ts +245 -0
- package/src/index.tsx +271 -0
- package/src/library/blueprint-io.ts +108 -0
- package/src/library/blueprint-library-db.ts +112 -0
- package/src/library/execution-log-db.ts +171 -0
- package/src/library/execution-log-settings.ts +50 -0
- package/src/library/swagger-docs.ts +56 -0
- package/src/library/types.ts +35 -0
- package/src/main.tsx +43 -0
- package/src/nodes/AndFlowNode.tsx +59 -0
- package/src/nodes/BlueprintFlowNode.tsx +26 -0
- package/src/nodes/BlueprintNodeCard.tsx +169 -0
- package/src/nodes/BlueprintNodeShell.tsx +45 -0
- package/src/nodes/ClockFlowNode.tsx +67 -0
- package/src/nodes/FetchFlowNode.tsx +26 -0
- package/src/nodes/JsonFlowNode.tsx +26 -0
- package/src/nodes/LifecycleFlowNode.tsx +44 -0
- package/src/nodes/LogicFlowNode.tsx +26 -0
- package/src/runtime/document-to-runnable-graph.ts +48 -0
- package/src/runtime/execution-overlay.ts +169 -0
- package/src/types.ts +1 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.ts +6 -0
- package/tsconfig.json +12 -0
- package/vite.config.ts +16 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { cn } from "@arronqzy/ui";
|
|
2
|
+
|
|
3
|
+
export type BlueprintNodeCardProps = {
|
|
4
|
+
nodeId: string;
|
|
5
|
+
label: string;
|
|
6
|
+
meta?: string;
|
|
7
|
+
/** 节点关键配置摘要,显示在名称下方 */
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
/** 调试执行进度(如时钟 2/3) */
|
|
10
|
+
progressLabel?: string;
|
|
11
|
+
variant?: "blueprint" | "logic" | "and" | "lifecycle" | "fetch" | "json" | "clock";
|
|
12
|
+
selected?: boolean;
|
|
13
|
+
hideLeadingDot?: boolean;
|
|
14
|
+
onSelect?: (nodeId: string) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const variantStyle = {
|
|
18
|
+
blueprint: {
|
|
19
|
+
accent: "border-l-primary",
|
|
20
|
+
badge: "bg-primary/10 text-primary",
|
|
21
|
+
dot: "bg-primary",
|
|
22
|
+
},
|
|
23
|
+
logic: {
|
|
24
|
+
accent: "border-l-sky-500 dark:border-l-sky-400",
|
|
25
|
+
badge: "bg-sky-500/10 text-sky-700 dark:text-sky-300",
|
|
26
|
+
dot: "bg-sky-500",
|
|
27
|
+
},
|
|
28
|
+
and: {
|
|
29
|
+
accent: "border-l-indigo-500 dark:border-l-indigo-400",
|
|
30
|
+
badge: "bg-indigo-500/10 text-indigo-700 dark:text-indigo-300",
|
|
31
|
+
dot: "bg-indigo-500",
|
|
32
|
+
},
|
|
33
|
+
lifecycle: {
|
|
34
|
+
accent: "border-l-amber-500 dark:border-l-amber-400",
|
|
35
|
+
badge: "bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
36
|
+
dot: "bg-amber-500",
|
|
37
|
+
},
|
|
38
|
+
fetch: {
|
|
39
|
+
accent: "border-l-violet-500 dark:border-l-violet-400",
|
|
40
|
+
badge: "bg-violet-500/10 text-violet-700 dark:text-violet-300",
|
|
41
|
+
dot: "bg-violet-500",
|
|
42
|
+
},
|
|
43
|
+
json: {
|
|
44
|
+
accent: "border-l-teal-500 dark:border-l-teal-400",
|
|
45
|
+
badge: "bg-teal-500/10 text-teal-700 dark:text-teal-300",
|
|
46
|
+
dot: "bg-teal-500",
|
|
47
|
+
},
|
|
48
|
+
clock: {
|
|
49
|
+
accent: "border-l-rose-500 dark:border-l-rose-400",
|
|
50
|
+
badge: "bg-rose-500/10 text-rose-700 dark:text-rose-300",
|
|
51
|
+
dot: "bg-rose-500",
|
|
52
|
+
},
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
function NodeGrip() {
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className="flex shrink-0 flex-col gap-[3px] opacity-35"
|
|
59
|
+
aria-hidden="true"
|
|
60
|
+
>
|
|
61
|
+
<span className="flex gap-[3px]">
|
|
62
|
+
<span className="h-[3px] w-[3px] rounded-full bg-current" />
|
|
63
|
+
<span className="h-[3px] w-[3px] rounded-full bg-current" />
|
|
64
|
+
</span>
|
|
65
|
+
<span className="flex gap-[3px]">
|
|
66
|
+
<span className="h-[3px] w-[3px] rounded-full bg-current" />
|
|
67
|
+
<span className="h-[3px] w-[3px] rounded-full bg-current" />
|
|
68
|
+
</span>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function BlueprintNodeCard({
|
|
74
|
+
nodeId,
|
|
75
|
+
label,
|
|
76
|
+
meta,
|
|
77
|
+
subtitle,
|
|
78
|
+
progressLabel,
|
|
79
|
+
variant = "blueprint",
|
|
80
|
+
selected = false,
|
|
81
|
+
hideLeadingDot = false,
|
|
82
|
+
onSelect,
|
|
83
|
+
}: BlueprintNodeCardProps) {
|
|
84
|
+
const v = variantStyle[variant];
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
data-blueprint-node-card
|
|
89
|
+
className={cn(
|
|
90
|
+
"bp-node-card w-[168px] overflow-hidden rounded-lg border border-border bg-card text-card-foreground shadow-sm",
|
|
91
|
+
"transition-[box-shadow,border-color] duration-150",
|
|
92
|
+
hideLeadingDot
|
|
93
|
+
? "border-t-[3px] border-t-amber-500 dark:border-t-amber-400"
|
|
94
|
+
: cn("border-l-[3px]", v.accent),
|
|
95
|
+
selected &&
|
|
96
|
+
"border-primary/50 shadow-[0_0_0_1px_hsl(var(--primary)/0.35),0_4px_12px_hsl(var(--primary)/0.12)]"
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<div
|
|
100
|
+
className={cn(
|
|
101
|
+
"bp-flow-drag-handle flex cursor-grab items-center gap-2 border-b border-border/50 px-2 py-1.5",
|
|
102
|
+
"bg-muted/25 text-muted-foreground active:cursor-grabbing"
|
|
103
|
+
)}
|
|
104
|
+
title="拖拽移动"
|
|
105
|
+
>
|
|
106
|
+
<NodeGrip />
|
|
107
|
+
{meta ? (
|
|
108
|
+
<span
|
|
109
|
+
className={cn(
|
|
110
|
+
"min-w-0 flex-1 truncate rounded px-1.5 py-0.5 text-[10px] font-medium leading-none tracking-wide",
|
|
111
|
+
v.badge
|
|
112
|
+
)}
|
|
113
|
+
title={meta}
|
|
114
|
+
>
|
|
115
|
+
{meta}
|
|
116
|
+
</span>
|
|
117
|
+
) : null}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className={cn(
|
|
123
|
+
"nodrag flex w-full text-left outline-none transition-colors hover:bg-accent/30",
|
|
124
|
+
"focus-visible:ring-1 focus-visible:ring-primary focus-visible:ring-inset",
|
|
125
|
+
hideLeadingDot
|
|
126
|
+
? cn("px-2.5 py-2", subtitle ? "items-start" : "items-center")
|
|
127
|
+
: "items-start gap-2 px-2.5 py-2"
|
|
128
|
+
)}
|
|
129
|
+
onClick={(e) => {
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
onSelect?.(nodeId);
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{!hideLeadingDot ? (
|
|
135
|
+
<span
|
|
136
|
+
className={cn("mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full", v.dot)}
|
|
137
|
+
aria-hidden="true"
|
|
138
|
+
/>
|
|
139
|
+
) : null}
|
|
140
|
+
<div className="min-w-0 flex-1">
|
|
141
|
+
<div className="flex items-start gap-1.5">
|
|
142
|
+
<div className="min-w-0 flex-1 truncate text-[13px] font-medium leading-snug text-foreground">
|
|
143
|
+
{label}
|
|
144
|
+
</div>
|
|
145
|
+
{progressLabel ? (
|
|
146
|
+
<span
|
|
147
|
+
className={cn(
|
|
148
|
+
"shrink-0 rounded px-1.5 py-0.5 text-[10px] font-semibold leading-none",
|
|
149
|
+
v.badge
|
|
150
|
+
)}
|
|
151
|
+
title="已发送信号次数"
|
|
152
|
+
>
|
|
153
|
+
{progressLabel}
|
|
154
|
+
</span>
|
|
155
|
+
) : null}
|
|
156
|
+
</div>
|
|
157
|
+
{subtitle ? (
|
|
158
|
+
<div
|
|
159
|
+
className="mt-0.5 truncate font-mono text-[10px] leading-tight text-muted-foreground"
|
|
160
|
+
title={subtitle}
|
|
161
|
+
>
|
|
162
|
+
{subtitle}
|
|
163
|
+
</div>
|
|
164
|
+
) : null}
|
|
165
|
+
</div>
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Handle, Position } from "@xyflow/react";
|
|
2
|
+
import { cn } from "@arronqzy/ui";
|
|
3
|
+
|
|
4
|
+
import type { BlueprintNodeExecutionTone } from "../runtime/execution-overlay";
|
|
5
|
+
import { BlueprintNodeCard, type BlueprintNodeCardProps } from "./BlueprintNodeCard";
|
|
6
|
+
|
|
7
|
+
export type BlueprintNodeShellProps = BlueprintNodeCardProps & {
|
|
8
|
+
/** 用户在画布上点选的配置节点 */
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
/** 调试执行中当前节点的信号高亮(与用户选中独立) */
|
|
11
|
+
executionTone?: BlueprintNodeExecutionTone | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** 左进右出的标准节点连线布局 */
|
|
15
|
+
export function BlueprintNodeShell({
|
|
16
|
+
selected = false,
|
|
17
|
+
executionTone = null,
|
|
18
|
+
...cardProps
|
|
19
|
+
}: BlueprintNodeShellProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={cn(
|
|
23
|
+
"bp-node",
|
|
24
|
+
executionTone === "success" && "bp-node--execution-true",
|
|
25
|
+
executionTone === "error" && "bp-node--execution-false"
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
<Handle
|
|
29
|
+
type="target"
|
|
30
|
+
position={Position.Left}
|
|
31
|
+
id="in"
|
|
32
|
+
className="bp-flow-handle bp-flow-handle--target"
|
|
33
|
+
title="真/假信号输入"
|
|
34
|
+
/>
|
|
35
|
+
<BlueprintNodeCard {...cardProps} selected={selected} />
|
|
36
|
+
<Handle
|
|
37
|
+
type="source"
|
|
38
|
+
position={Position.Right}
|
|
39
|
+
id="out"
|
|
40
|
+
className="bp-flow-handle bp-flow-handle--source"
|
|
41
|
+
title="真/假信号输出"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { NodeProps } from "@xyflow/react";
|
|
2
|
+
import { cn } from "@arronqzy/ui";
|
|
3
|
+
|
|
4
|
+
import { resolveBlueprintNodeTypeLabel } from "../graph/document";
|
|
5
|
+
import { resolveBlueprintNodeSummary } from "../graph/node-summary";
|
|
6
|
+
import {
|
|
7
|
+
useBlueprintClockAbort,
|
|
8
|
+
useBlueprintNodeSelect,
|
|
9
|
+
useClockNodeCanAbort,
|
|
10
|
+
} from "../BlueprintCanvasContext";
|
|
11
|
+
import { resolveBlueprintNodeExecutionTone } from "../runtime/execution-overlay";
|
|
12
|
+
import type { BlueprintFlowNodeData } from "../types";
|
|
13
|
+
import { BlueprintNodeShell } from "./BlueprintNodeShell";
|
|
14
|
+
|
|
15
|
+
function ClockAbortIcon({ className }: { className?: string }) {
|
|
16
|
+
return (
|
|
17
|
+
<svg viewBox="0 0 24 24" className={className} aria-hidden>
|
|
18
|
+
<rect x="6" y="6" width="12" height="12" rx="1.5" fill="currentColor" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 时钟节点:收到真信号后按配置间隔输出含当前时间的真信号 */
|
|
24
|
+
export function ClockFlowNode({ id, data }: NodeProps) {
|
|
25
|
+
const nodeData = data as BlueprintFlowNodeData;
|
|
26
|
+
const onSelect = useBlueprintNodeSelect();
|
|
27
|
+
const onAbortClock = useBlueprintClockAbort();
|
|
28
|
+
const canAbort = useClockNodeCanAbort(id);
|
|
29
|
+
const progress = nodeData.clockEmitProgress;
|
|
30
|
+
const progressLabel = progress
|
|
31
|
+
? `${progress.current}/${progress.total}`
|
|
32
|
+
: undefined;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="relative">
|
|
36
|
+
<BlueprintNodeShell
|
|
37
|
+
nodeId={id}
|
|
38
|
+
label={nodeData.label}
|
|
39
|
+
meta={resolveBlueprintNodeTypeLabel(nodeData)}
|
|
40
|
+
subtitle={resolveBlueprintNodeSummary(nodeData)}
|
|
41
|
+
progressLabel={progressLabel}
|
|
42
|
+
variant="clock"
|
|
43
|
+
selected={Boolean(nodeData.isSelected)}
|
|
44
|
+
executionTone={resolveBlueprintNodeExecutionTone(nodeData)}
|
|
45
|
+
onSelect={onSelect}
|
|
46
|
+
/>
|
|
47
|
+
{canAbort ? (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
className={cn(
|
|
51
|
+
"nodrag nopan absolute right-1.5 top-1.5 z-10 flex h-5 w-5 items-center justify-center",
|
|
52
|
+
"rounded border border-rose-500/50 bg-rose-500/15 text-rose-600 shadow-sm",
|
|
53
|
+
"hover:bg-rose-500/25 dark:text-rose-300"
|
|
54
|
+
)}
|
|
55
|
+
title="中止时钟:停止剩余次数与正在执行的下游任务"
|
|
56
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
57
|
+
onClick={(e) => {
|
|
58
|
+
e.stopPropagation();
|
|
59
|
+
onAbortClock(id);
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<ClockAbortIcon className="h-3 w-3" />
|
|
63
|
+
</button>
|
|
64
|
+
) : null}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NodeProps } from "@xyflow/react";
|
|
2
|
+
|
|
3
|
+
import { resolveBlueprintNodeTypeLabel } from "../graph/document";
|
|
4
|
+
import { resolveBlueprintNodeSummary } from "../graph/node-summary";
|
|
5
|
+
import { useBlueprintNodeSelect } from "../BlueprintCanvasContext";
|
|
6
|
+
import { resolveBlueprintNodeExecutionTone } from "../runtime/execution-overlay";
|
|
7
|
+
import type { BlueprintFlowNodeData } from "../types";
|
|
8
|
+
import { BlueprintNodeShell } from "./BlueprintNodeShell";
|
|
9
|
+
|
|
10
|
+
export function FetchFlowNode({ id, data }: NodeProps) {
|
|
11
|
+
const nodeData = data as BlueprintFlowNodeData;
|
|
12
|
+
const onSelect = useBlueprintNodeSelect();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<BlueprintNodeShell
|
|
16
|
+
nodeId={id}
|
|
17
|
+
label={nodeData.label}
|
|
18
|
+
meta={resolveBlueprintNodeTypeLabel(nodeData)}
|
|
19
|
+
subtitle={resolveBlueprintNodeSummary(nodeData)}
|
|
20
|
+
variant="fetch"
|
|
21
|
+
selected={Boolean(nodeData.isSelected)}
|
|
22
|
+
executionTone={resolveBlueprintNodeExecutionTone(nodeData)}
|
|
23
|
+
onSelect={onSelect}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NodeProps } from "@xyflow/react";
|
|
2
|
+
|
|
3
|
+
import { resolveBlueprintNodeTypeLabel } from "../graph/document";
|
|
4
|
+
import { resolveBlueprintNodeSummary } from "../graph/node-summary";
|
|
5
|
+
import { useBlueprintNodeSelect } from "../BlueprintCanvasContext";
|
|
6
|
+
import { resolveBlueprintNodeExecutionTone } from "../runtime/execution-overlay";
|
|
7
|
+
import type { BlueprintFlowNodeData } from "../types";
|
|
8
|
+
import { BlueprintNodeShell } from "./BlueprintNodeShell";
|
|
9
|
+
|
|
10
|
+
export function JsonFlowNode({ id, data }: NodeProps) {
|
|
11
|
+
const nodeData = data as BlueprintFlowNodeData;
|
|
12
|
+
const onSelect = useBlueprintNodeSelect();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<BlueprintNodeShell
|
|
16
|
+
nodeId={id}
|
|
17
|
+
label={nodeData.label}
|
|
18
|
+
meta={resolveBlueprintNodeTypeLabel(nodeData)}
|
|
19
|
+
subtitle={resolveBlueprintNodeSummary(nodeData)}
|
|
20
|
+
variant="json"
|
|
21
|
+
selected={Boolean(nodeData.isSelected)}
|
|
22
|
+
executionTone={resolveBlueprintNodeExecutionTone(nodeData)}
|
|
23
|
+
onSelect={onSelect}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
|
2
|
+
import { cn } from "@arronqzy/ui";
|
|
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 { BlueprintNodeCard } from "./BlueprintNodeCard";
|
|
10
|
+
|
|
11
|
+
/** 生命周期节点:无输入口,仅在对应生命周期触发时从右侧输出口发出信号 */
|
|
12
|
+
export function LifecycleFlowNode({ id, data }: NodeProps) {
|
|
13
|
+
const nodeData = data as BlueprintFlowNodeData;
|
|
14
|
+
const onSelect = useBlueprintNodeSelect();
|
|
15
|
+
const executionTone = resolveBlueprintNodeExecutionTone(nodeData);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={cn(
|
|
20
|
+
"bp-node bp-node--lifecycle",
|
|
21
|
+
executionTone === "success" && "bp-node--execution-true",
|
|
22
|
+
executionTone === "error" && "bp-node--execution-false"
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<BlueprintNodeCard
|
|
26
|
+
nodeId={id}
|
|
27
|
+
label={nodeData.label}
|
|
28
|
+
meta={resolveBlueprintNodeTypeLabel(nodeData)}
|
|
29
|
+
subtitle={resolveBlueprintNodeSummary(nodeData)}
|
|
30
|
+
variant="lifecycle"
|
|
31
|
+
selected={Boolean(nodeData.isSelected)}
|
|
32
|
+
hideLeadingDot
|
|
33
|
+
onSelect={onSelect}
|
|
34
|
+
/>
|
|
35
|
+
<Handle
|
|
36
|
+
type="source"
|
|
37
|
+
position={Position.Right}
|
|
38
|
+
id="out"
|
|
39
|
+
className="bp-flow-handle bp-flow-handle--source"
|
|
40
|
+
title="生命周期满足时输出真/假信号"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NodeProps } from "@xyflow/react";
|
|
2
|
+
|
|
3
|
+
import { resolveBlueprintNodeTypeLabel } from "../graph/document";
|
|
4
|
+
import { resolveBlueprintNodeSummary } from "../graph/node-summary";
|
|
5
|
+
import { useBlueprintNodeSelect } from "../BlueprintCanvasContext";
|
|
6
|
+
import { resolveBlueprintNodeExecutionTone } from "../runtime/execution-overlay";
|
|
7
|
+
import type { BlueprintFlowNodeData } from "../types";
|
|
8
|
+
import { BlueprintNodeShell } from "./BlueprintNodeShell";
|
|
9
|
+
|
|
10
|
+
export function LogicFlowNode({ id, data }: NodeProps) {
|
|
11
|
+
const nodeData = data as BlueprintFlowNodeData;
|
|
12
|
+
const onSelect = useBlueprintNodeSelect();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<BlueprintNodeShell
|
|
16
|
+
nodeId={id}
|
|
17
|
+
label={nodeData.label}
|
|
18
|
+
meta={resolveBlueprintNodeTypeLabel(nodeData)}
|
|
19
|
+
subtitle={resolveBlueprintNodeSummary(nodeData)}
|
|
20
|
+
variant="logic"
|
|
21
|
+
selected={Boolean(nodeData.isSelected)}
|
|
22
|
+
executionTone={resolveBlueprintNodeExecutionTone(nodeData)}
|
|
23
|
+
onSelect={onSelect}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VIEW_NODE_TYPE,
|
|
3
|
+
type RunnableGraph,
|
|
4
|
+
} from "@arronqzy/blueprint-dsl";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
resolveBlueprintConfigSource,
|
|
8
|
+
resolveViewElementIds,
|
|
9
|
+
type BlueprintDocument,
|
|
10
|
+
} from "../graph/document";
|
|
11
|
+
|
|
12
|
+
export function documentToRunnableGraph(
|
|
13
|
+
document: BlueprintDocument,
|
|
14
|
+
options?: {
|
|
15
|
+
libraryNameById?: ReadonlyMap<string, string>;
|
|
16
|
+
}
|
|
17
|
+
): RunnableGraph {
|
|
18
|
+
return {
|
|
19
|
+
nodes: document.nodes.map((node) => {
|
|
20
|
+
const configSource = resolveBlueprintConfigSource(node);
|
|
21
|
+
const viewElementIds = resolveViewElementIds(node);
|
|
22
|
+
return {
|
|
23
|
+
id: node.id,
|
|
24
|
+
nodeType:
|
|
25
|
+
configSource === "view" ? VIEW_NODE_TYPE : node.nodeType,
|
|
26
|
+
lifecyclePhase: node.lifecyclePhase,
|
|
27
|
+
libraryBlueprintId: node.libraryBlueprintId,
|
|
28
|
+
viewElementIds:
|
|
29
|
+
viewElementIds.length > 0 ? viewElementIds : undefined,
|
|
30
|
+
fetchConfig: node.fetchConfig,
|
|
31
|
+
jsonConfig: node.jsonConfig,
|
|
32
|
+
logicConfig: node.logicConfig,
|
|
33
|
+
clockConfig: node.clockConfig,
|
|
34
|
+
blueprintName: node.libraryBlueprintId
|
|
35
|
+
? (options?.libraryNameById?.get(node.libraryBlueprintId) ??
|
|
36
|
+
node.label)
|
|
37
|
+
: undefined,
|
|
38
|
+
};
|
|
39
|
+
}),
|
|
40
|
+
edges: document.edges.map((edge) => ({
|
|
41
|
+
source: edge.source,
|
|
42
|
+
target: edge.target,
|
|
43
|
+
sourceHandle: edge.sourceHandle,
|
|
44
|
+
targetHandle: edge.targetHandle,
|
|
45
|
+
})),
|
|
46
|
+
allowFalseSignalPropagation: document.allowFalseSignalPropagation,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLOCK_NODE_TYPE,
|
|
3
|
+
type ExecutionTraceEntry,
|
|
4
|
+
} from "@arronqzy/blueprint-dsl";
|
|
5
|
+
|
|
6
|
+
import type { BlueprintGraph } from "../graph/blueprint-graph";
|
|
7
|
+
|
|
8
|
+
export type BlueprintNodeSignalKind = "true" | "false";
|
|
9
|
+
|
|
10
|
+
export type BlueprintEdgeSignalKind = BlueprintNodeSignalKind | "clock-green" | "clock-blue";
|
|
11
|
+
|
|
12
|
+
export type BlueprintExecutionOverlay = {
|
|
13
|
+
/** 当前执行到的节点(最后一条 trace 对应节点) */
|
|
14
|
+
activeNodeId: string | null;
|
|
15
|
+
/** 当前执行节点的主输出信号类型 */
|
|
16
|
+
activeNodeSignalKind: BlueprintNodeSignalKind | null;
|
|
17
|
+
/** 边 id -> 该边最近一次流出的信号类型 */
|
|
18
|
+
edgeSignals: Record<string, BlueprintEdgeSignalKind>;
|
|
19
|
+
/** 时钟节点输出口连线对应当前 tick(用于同色系再次输出时触发重置动画) */
|
|
20
|
+
clockEdgeTicks?: Record<string, number>;
|
|
21
|
+
/** 时钟节点已发送信号进度(调试用) */
|
|
22
|
+
clockNodeProgress?: Record<string, { current: number; total: number }>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function readOutputSignalKind(value: unknown): BlueprintNodeSignalKind | null {
|
|
26
|
+
if (!value || typeof value !== "object" || !("kind" in value)) return null;
|
|
27
|
+
const kind = (value as { kind: unknown }).kind;
|
|
28
|
+
if (kind === "true" || kind === "false") return kind;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveOutputEdgeSignalKind(
|
|
33
|
+
entry: ExecutionTraceEntry,
|
|
34
|
+
value: unknown
|
|
35
|
+
): BlueprintEdgeSignalKind | null {
|
|
36
|
+
const kind = readOutputSignalKind(value);
|
|
37
|
+
if (!kind) return null;
|
|
38
|
+
// 时钟每次输出真信号时连线均显示为绿色(与其它真信号一致)
|
|
39
|
+
if (entry.nodeType === CLOCK_NODE_TYPE && kind === "true") {
|
|
40
|
+
return "true";
|
|
41
|
+
}
|
|
42
|
+
return kind;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function applyClockEdgeTicks(
|
|
46
|
+
graph: BlueprintGraph,
|
|
47
|
+
entry: ExecutionTraceEntry,
|
|
48
|
+
clockEdgeTicks: Record<string, number>
|
|
49
|
+
) {
|
|
50
|
+
const tickIndex = entry.clockTickIndex ?? 1;
|
|
51
|
+
for (const edge of graph.document.edges) {
|
|
52
|
+
if (
|
|
53
|
+
edge.source === entry.nodeId &&
|
|
54
|
+
(edge.sourceHandle ?? "out") === "out"
|
|
55
|
+
) {
|
|
56
|
+
clockEdgeTicks[edge.id] = tickIndex;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function findLastClockTraceEntry(
|
|
62
|
+
entries: ExecutionTraceEntry[]
|
|
63
|
+
): ExecutionTraceEntry | undefined {
|
|
64
|
+
return [...entries]
|
|
65
|
+
.reverse()
|
|
66
|
+
.find(
|
|
67
|
+
(entry) =>
|
|
68
|
+
entry.nodeType === CLOCK_NODE_TYPE &&
|
|
69
|
+
getTraceEntryOutputKind(entry) === "true"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function applyEntryOutputEdgeSignals(
|
|
74
|
+
graph: BlueprintGraph,
|
|
75
|
+
entry: ExecutionTraceEntry,
|
|
76
|
+
edgeSignals: Record<string, BlueprintEdgeSignalKind>
|
|
77
|
+
) {
|
|
78
|
+
for (const [port, value] of Object.entries(entry.outputs)) {
|
|
79
|
+
const kind = resolveOutputEdgeSignalKind(entry, value);
|
|
80
|
+
if (!kind) continue;
|
|
81
|
+
|
|
82
|
+
for (const edge of graph.document.edges) {
|
|
83
|
+
if (
|
|
84
|
+
edge.source === entry.nodeId &&
|
|
85
|
+
(edge.sourceHandle ?? "out") === port
|
|
86
|
+
) {
|
|
87
|
+
edgeSignals[edge.id] = kind;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getTraceEntryOutputKind(
|
|
94
|
+
entry: ExecutionTraceEntry,
|
|
95
|
+
port = "out"
|
|
96
|
+
): BlueprintNodeSignalKind | null {
|
|
97
|
+
if (entry.error) return "false";
|
|
98
|
+
return readOutputSignalKind(entry.outputs[port]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isTraceHaltedByFalseSignal(
|
|
102
|
+
entries: ExecutionTraceEntry[]
|
|
103
|
+
): boolean {
|
|
104
|
+
if (entries.length === 0) return false;
|
|
105
|
+
return getTraceEntryOutputKind(entries[entries.length - 1]!) === "false";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function shouldHaltDebugOnFalseSignal(
|
|
109
|
+
entries: ExecutionTraceEntry[],
|
|
110
|
+
allowFalseSignalPropagation = false
|
|
111
|
+
): boolean {
|
|
112
|
+
if (allowFalseSignalPropagation) return false;
|
|
113
|
+
return isTraceHaltedByFalseSignal(entries);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function buildExecutionOverlay(
|
|
117
|
+
graph: BlueprintGraph,
|
|
118
|
+
entries: ExecutionTraceEntry[]
|
|
119
|
+
): BlueprintExecutionOverlay | null {
|
|
120
|
+
if (entries.length === 0) return null;
|
|
121
|
+
|
|
122
|
+
const lastEntry = entries[entries.length - 1]!;
|
|
123
|
+
const edgeSignals: Record<string, BlueprintEdgeSignalKind> = {};
|
|
124
|
+
const clockEdgeTicks: Record<string, number> = {};
|
|
125
|
+
|
|
126
|
+
// 按执行顺序累积各输出口最近一次信号;未再次输出的连线保持变色
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
applyEntryOutputEdgeSignals(graph, entry, edgeSignals);
|
|
129
|
+
if (
|
|
130
|
+
entry.nodeType === CLOCK_NODE_TYPE &&
|
|
131
|
+
getTraceEntryOutputKind(entry) === "true"
|
|
132
|
+
) {
|
|
133
|
+
applyClockEdgeTicks(graph, entry, clockEdgeTicks);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const lastClockEntry = findLastClockTraceEntry(entries);
|
|
138
|
+
|
|
139
|
+
const clockNodeProgress: Record<string, { current: number; total: number }> =
|
|
140
|
+
{};
|
|
141
|
+
if (lastClockEntry) {
|
|
142
|
+
clockNodeProgress[lastClockEntry.nodeId] = {
|
|
143
|
+
current: lastClockEntry.clockTickIndex ?? 1,
|
|
144
|
+
total: lastClockEntry.clockTickTotal ?? 1,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
activeNodeId: lastEntry.nodeId,
|
|
150
|
+
activeNodeSignalKind: getTraceEntryOutputKind(lastEntry),
|
|
151
|
+
edgeSignals,
|
|
152
|
+
clockEdgeTicks:
|
|
153
|
+
Object.keys(clockEdgeTicks).length > 0 ? clockEdgeTicks : undefined,
|
|
154
|
+
clockNodeProgress:
|
|
155
|
+
Object.keys(clockNodeProgress).length > 0 ? clockNodeProgress : undefined,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type BlueprintNodeExecutionTone = "success" | "error";
|
|
160
|
+
|
|
161
|
+
export function resolveBlueprintNodeExecutionTone(data: {
|
|
162
|
+
isExecutionActive?: boolean;
|
|
163
|
+
executionSignalKind?: BlueprintNodeSignalKind | null;
|
|
164
|
+
}): BlueprintNodeExecutionTone | undefined {
|
|
165
|
+
if (!data.isExecutionActive) return undefined;
|
|
166
|
+
if (data.executionSignalKind === "false") return "error";
|
|
167
|
+
if (data.executionSignalKind === "true") return "success";
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { BlueprintFlowNodeData } from "./graph/react-flow-adapter";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@arronqzy/typescript-config/react-library.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@arronqzy/blueprint-dsl": ["../blueprint-dsl/src/index.ts"],
|
|
7
|
+
"@arronqzy/ui": ["../ui/index.ts"]
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"include": ["."],
|
|
11
|
+
"exclude": ["node_modules"]
|
|
12
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import react from "@vitejs/plugin-react";
|
|
4
|
+
import { defineConfig } from "vite";
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
"@arronqzy/ui": path.resolve(process.cwd(), "../ui/index.ts"),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
server: {
|
|
14
|
+
port: 31012,
|
|
15
|
+
},
|
|
16
|
+
});
|