@arronqzy/vue-blueprint 0.1.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/README.md +50 -0
- package/package.json +44 -0
- package/src/BlueprintCanvasContext.ts +71 -0
- package/src/BlueprintNodeConfigSidebar.vue +338 -0
- package/src/blueprint.css +327 -0
- package/src/blueprintNodeTypes.ts +20 -0
- package/src/components/BluePrintVueRoot.vue +73 -0
- package/src/components/BlueprintCanvas.vue +220 -0
- package/src/components/BlueprintContextMenu.vue +114 -0
- package/src/components/BlueprintExecutionLogPanel.vue +294 -0
- package/src/components/BlueprintMetaDialog.vue +80 -0
- package/src/components/BlueprintNodeSwitchTaskDialog.vue +41 -0
- package/src/components/ClockNodeConfigPanel.vue +124 -0
- package/src/components/FetchNodeConfigPanel.vue +559 -0
- package/src/components/FetchUrlAutocomplete.vue +174 -0
- package/src/components/JsonNodeConfigPanel.vue +73 -0
- package/src/components/LogicNodeConfigPanel.vue +73 -0
- package/src/components/ViewElementMultiSelect.vue +50 -0
- package/src/composables/useBlueprintDebugSession.ts +441 -0
- package/src/composables/useBlueprintFlowState.ts +486 -0
- package/src/composables/useBlueprintFlowViewport.ts +65 -0
- package/src/composables/useBlueprintNodeSelectionGuard.ts +41 -0
- package/src/composables/useBlueprintPageLifecycle.ts +244 -0
- package/src/createBlueprintEdgeTypes.ts +10 -0
- package/src/edges/BlueprintSmoothEdge.vue +31 -0
- package/src/env.d.ts +7 -0
- package/src/fetch-config-task-store.ts +206 -0
- package/src/flowCoordinates.ts +19 -0
- package/src/flowDefaults.ts +9 -0
- package/src/graph/blueprint-graph.ts +265 -0
- package/src/graph/document.ts +422 -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/sync-edges.ts +69 -0
- package/src/graph/sync-nodes.ts +110 -0
- package/src/graph/vue-flow-adapter.ts +127 -0
- package/src/index.ts +37 -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/nodes/AndFlowNode.vue +60 -0
- package/src/nodes/BlueprintFlowNode.vue +26 -0
- package/src/nodes/BlueprintNodeCard.vue +155 -0
- package/src/nodes/BlueprintNodeShell.vue +70 -0
- package/src/nodes/ClockFlowNode.vue +60 -0
- package/src/nodes/FetchFlowNode.vue +26 -0
- package/src/nodes/JsonFlowNode.vue +26 -0
- package/src/nodes/LifecycleFlowNode.vue +45 -0
- package/src/nodes/LogicFlowNode.vue +26 -0
- package/src/runtime/document-to-runnable-graph.ts +51 -0
- package/src/runtime/execution-overlay.ts +169 -0
- package/src/types.ts +1 -0
- package/src/utils/cn.ts +3 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PAGE_LIFECYCLE_LABELS,
|
|
3
|
+
resolveFetchRequestUrl,
|
|
4
|
+
} from "@arronqzy/blueprint-dsl";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
resolveBlueprintConfigSource,
|
|
8
|
+
resolveNodeClockConfig,
|
|
9
|
+
resolveNodeFetchConfig,
|
|
10
|
+
resolveNodeJsonConfig,
|
|
11
|
+
resolveNodeLogicConfig,
|
|
12
|
+
resolveViewElementIds,
|
|
13
|
+
} from "./document";
|
|
14
|
+
import type { BlueprintFlowNodeData } from "./vue-flow-adapter";
|
|
15
|
+
|
|
16
|
+
function truncate(text: string, max: number): string {
|
|
17
|
+
const trimmed = text.trim();
|
|
18
|
+
if (trimmed.length <= max) return trimmed;
|
|
19
|
+
return `${trimmed.slice(0, Math.max(0, max - 1))}…`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 画布节点正文区展示的配置摘要 */
|
|
23
|
+
export function resolveBlueprintNodeSummary(
|
|
24
|
+
data: BlueprintFlowNodeData
|
|
25
|
+
): string | undefined {
|
|
26
|
+
const configSource = resolveBlueprintConfigSource(data);
|
|
27
|
+
|
|
28
|
+
switch (configSource) {
|
|
29
|
+
case "fetch": {
|
|
30
|
+
const config = resolveNodeFetchConfig(data);
|
|
31
|
+
if (!config.url?.trim()) return "未配置 URL";
|
|
32
|
+
let url = config.url.trim();
|
|
33
|
+
try {
|
|
34
|
+
url = resolveFetchRequestUrl(config);
|
|
35
|
+
} catch {
|
|
36
|
+
/* 保留原始输入 */
|
|
37
|
+
}
|
|
38
|
+
const method = config.method ?? "GET";
|
|
39
|
+
return truncate(`${method} ${url}`, 44);
|
|
40
|
+
}
|
|
41
|
+
case "clock": {
|
|
42
|
+
const clock = resolveNodeClockConfig(data);
|
|
43
|
+
const parts = [
|
|
44
|
+
`${clock.outputCount} 次`,
|
|
45
|
+
clock.intervalSeconds > 0 ? `间隔 ${clock.intervalSeconds}s` : null,
|
|
46
|
+
clock.emitImmediately ? "立即首发" : "延迟首发",
|
|
47
|
+
].filter(Boolean);
|
|
48
|
+
return parts.join(" · ");
|
|
49
|
+
}
|
|
50
|
+
case "json": {
|
|
51
|
+
const json = resolveNodeJsonConfig(data).jsonString?.trim();
|
|
52
|
+
if (!json) return "空 JSON";
|
|
53
|
+
return truncate(json.replace(/\s+/g, " "), 44);
|
|
54
|
+
}
|
|
55
|
+
case "logic": {
|
|
56
|
+
const code = resolveNodeLogicConfig(data).sourceCode?.trim();
|
|
57
|
+
if (!code) return "未配置脚本";
|
|
58
|
+
const firstLine =
|
|
59
|
+
code.split("\n").find((line) => line.trim())?.trim() ?? code;
|
|
60
|
+
return truncate(firstLine, 44);
|
|
61
|
+
}
|
|
62
|
+
case "blueprint": {
|
|
63
|
+
if (data.libraryBlueprintLabel) {
|
|
64
|
+
return truncate(data.libraryBlueprintLabel, 44);
|
|
65
|
+
}
|
|
66
|
+
if (data.libraryBlueprintId) {
|
|
67
|
+
return truncate(data.libraryBlueprintId, 44);
|
|
68
|
+
}
|
|
69
|
+
return "未选择蓝图";
|
|
70
|
+
}
|
|
71
|
+
case "lifecycle": {
|
|
72
|
+
const phase = data.lifecyclePhase ?? "mounted";
|
|
73
|
+
return PAGE_LIFECYCLE_LABELS[phase as keyof typeof PAGE_LIFECYCLE_LABELS] ?? phase;
|
|
74
|
+
}
|
|
75
|
+
case "view": {
|
|
76
|
+
const ids = resolveViewElementIds(data);
|
|
77
|
+
if (ids.length === 0) return "未关联视图节点";
|
|
78
|
+
if (ids.length === 1) {
|
|
79
|
+
return truncate(`视图节点 ${ids[0]}`, 44);
|
|
80
|
+
}
|
|
81
|
+
return `已关联 ${ids.length} 个视图节点`;
|
|
82
|
+
}
|
|
83
|
+
case "and":
|
|
84
|
+
return "两路输入均为真时输出";
|
|
85
|
+
default:
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Edge } from "@vue-flow/core";
|
|
2
|
+
|
|
3
|
+
import type { BlueprintGraphEdge } from "./document";
|
|
4
|
+
import { syncEdgesFromFlow, toVueFlowEdges } from "./vue-flow-adapter";
|
|
5
|
+
import type { BlueprintGraph } from "./blueprint-graph";
|
|
6
|
+
|
|
7
|
+
/** 边线描边(写入 edge.style;使用 hex 避免 SVG style 解析 hsl 异常) */
|
|
8
|
+
export const BP_EDGE_STYLE = {
|
|
9
|
+
stroke: "#2563eb",
|
|
10
|
+
strokeWidth: 2.5,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export const BP_FLOW_EDGE_TYPE = "blueprintSmooth" as const;
|
|
14
|
+
|
|
15
|
+
export function flowEdgesToGraphEdges(flowEdges: Edge[]): BlueprintGraphEdge[] {
|
|
16
|
+
return syncEdgesFromFlow(
|
|
17
|
+
flowEdges.map((e) => ({
|
|
18
|
+
id: e.id,
|
|
19
|
+
source: e.source,
|
|
20
|
+
target: e.target,
|
|
21
|
+
sourceHandle: e.sourceHandle ?? "out",
|
|
22
|
+
targetHandle: e.targetHandle ?? "in",
|
|
23
|
+
}))
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function graphToFlowEdges(graph: BlueprintGraph): Edge[] {
|
|
28
|
+
return normalizeFlowEdges(toVueFlowEdges(graph.document) as Edge[]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function edgeListSignature(edges: { id: string }[]): string {
|
|
32
|
+
return edges
|
|
33
|
+
.map((e) => e.id)
|
|
34
|
+
.sort()
|
|
35
|
+
.join(",");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function edgeConnectionKey(edge: {
|
|
39
|
+
source: string;
|
|
40
|
+
target: string;
|
|
41
|
+
sourceHandle?: string | null;
|
|
42
|
+
targetHandle?: string | null;
|
|
43
|
+
}): string {
|
|
44
|
+
return `${edge.source}|${edge.sourceHandle ?? "out"}|${edge.target}|${edge.targetHandle ?? "in"}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 避免 onConnect 与 onEdgesChange 各加一条时重复 */
|
|
48
|
+
export function dedupeFlowEdges(edges: Edge[]): Edge[] {
|
|
49
|
+
const seen = new Set<string>();
|
|
50
|
+
const out: Edge[] = [];
|
|
51
|
+
for (const e of edges) {
|
|
52
|
+
const key = edgeConnectionKey(e);
|
|
53
|
+
if (seen.has(key)) continue;
|
|
54
|
+
seen.add(key);
|
|
55
|
+
out.push(e);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeFlowEdges(edges: Edge[]): Edge[] {
|
|
61
|
+
return dedupeFlowEdges(edges).map((e) => ({
|
|
62
|
+
...e,
|
|
63
|
+
type: BP_FLOW_EDGE_TYPE,
|
|
64
|
+
zIndex: 1000,
|
|
65
|
+
selectable: true,
|
|
66
|
+
deletable: true,
|
|
67
|
+
style: { ...BP_EDGE_STYLE },
|
|
68
|
+
})) as Edge[];
|
|
69
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Connection, Edge, Node } from "@vue-flow/core";
|
|
2
|
+
|
|
3
|
+
import type { BlueprintGraph } from "./blueprint-graph";
|
|
4
|
+
import {
|
|
5
|
+
collectPositionUpdates,
|
|
6
|
+
toVueFlowNodes,
|
|
7
|
+
type BlueprintFlowNodeData,
|
|
8
|
+
} from "./vue-flow-adapter";
|
|
9
|
+
|
|
10
|
+
/** 从 graph 同步节点时保留 VF 已测量的宽高,否则边无法绘制 */
|
|
11
|
+
type FlowNodeWithMeasured = Node & {
|
|
12
|
+
measured?: { width?: number; height?: number };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function mergeMeasuredFlowNodes<T extends FlowNodeWithMeasured>(
|
|
16
|
+
incoming: T[],
|
|
17
|
+
previous: T[]
|
|
18
|
+
): T[] {
|
|
19
|
+
const prevById = new Map(previous.map((n) => [n.id, n]));
|
|
20
|
+
return incoming.map((n) => {
|
|
21
|
+
const prev = prevById.get(n.id);
|
|
22
|
+
if (!prev) return n;
|
|
23
|
+
return {
|
|
24
|
+
...n,
|
|
25
|
+
measured: prev.measured ?? n.measured,
|
|
26
|
+
width: prev.width ?? n.width,
|
|
27
|
+
height: prev.height ?? n.height,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function nodeListSignature(nodes: { id: string }[]): string {
|
|
33
|
+
return nodes
|
|
34
|
+
.map((n) => n.id)
|
|
35
|
+
.sort()
|
|
36
|
+
.join(",");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 含 role / 配置 / 名称等影响 React Flow 节点展示与端口的字段 */
|
|
40
|
+
export function nodeStructureSignature(
|
|
41
|
+
nodes: Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
role: string;
|
|
44
|
+
label: string;
|
|
45
|
+
configSource?: string;
|
|
46
|
+
lifecyclePhase?: string;
|
|
47
|
+
libraryBlueprintId?: string;
|
|
48
|
+
nodeType?: string;
|
|
49
|
+
viewElementId?: string;
|
|
50
|
+
viewElementIds?: string[];
|
|
51
|
+
fetchConfig?: { url?: string; method?: string; apiBaseUrl?: string; swaggerDocsUrl?: string };
|
|
52
|
+
jsonConfig?: { jsonString?: string };
|
|
53
|
+
logicConfig?: { sourceCode?: string };
|
|
54
|
+
clockConfig?: { intervalSeconds?: number; timeFormat?: string; outputCount?: number; emitImmediately?: boolean };
|
|
55
|
+
}>
|
|
56
|
+
): string {
|
|
57
|
+
return nodes
|
|
58
|
+
.map(
|
|
59
|
+
(n) =>
|
|
60
|
+
`${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 ?? ""}`
|
|
61
|
+
)
|
|
62
|
+
.sort()
|
|
63
|
+
.join("|");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function graphToFlowNodes(
|
|
67
|
+
graph: BlueprintGraph,
|
|
68
|
+
selectedNodeId: string | null,
|
|
69
|
+
libraryNameById?: ReadonlyMap<string, string>
|
|
70
|
+
): Node<BlueprintFlowNodeData>[] {
|
|
71
|
+
const base = toVueFlowNodes(graph.document) as Node<BlueprintFlowNodeData>[];
|
|
72
|
+
return base.map((n) => ({
|
|
73
|
+
...n,
|
|
74
|
+
selected: false,
|
|
75
|
+
data: {
|
|
76
|
+
...n.data,
|
|
77
|
+
libraryBlueprintLabel: n.data!.libraryBlueprintId
|
|
78
|
+
? libraryNameById?.get(n.data!.libraryBlueprintId)
|
|
79
|
+
: undefined,
|
|
80
|
+
isSelected: selectedNodeId === n.id,
|
|
81
|
+
},
|
|
82
|
+
})) as Node<BlueprintFlowNodeData>[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function resolveConnection(
|
|
86
|
+
connection: Connection,
|
|
87
|
+
rfNodes: Node<BlueprintFlowNodeData>[]
|
|
88
|
+
): Connection | null {
|
|
89
|
+
const nodeIds = new Set(rfNodes.map((n) => n.id));
|
|
90
|
+
const source = connection.source ?? "";
|
|
91
|
+
const target = connection.target ?? "";
|
|
92
|
+
|
|
93
|
+
if (!source || !target || source === target) return null;
|
|
94
|
+
if (!nodeIds.has(source) || !nodeIds.has(target)) return null;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
source,
|
|
98
|
+
target,
|
|
99
|
+
sourceHandle: connection.sourceHandle ?? "out",
|
|
100
|
+
targetHandle: connection.targetHandle ?? "in",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function filterEdgesWithValidNodes(edges: Edge[], nodeIds: Set<string>): Edge[] {
|
|
105
|
+
return edges.filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function applyFlowNodePositions(graph: BlueprintGraph, rfNodes: Node[]) {
|
|
109
|
+
return graph.applyNodePositions(collectPositionUpdates(rfNodes));
|
|
110
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BlueprintDocument,
|
|
3
|
+
BlueprintGraphEdge,
|
|
4
|
+
BlueprintGraphNode,
|
|
5
|
+
} from "./document";
|
|
6
|
+
import { createNodeId } from "./document";
|
|
7
|
+
|
|
8
|
+
export type BlueprintFlowNodeData = {
|
|
9
|
+
label: string;
|
|
10
|
+
role: BlueprintGraphNode["role"];
|
|
11
|
+
nodeType: string;
|
|
12
|
+
configSource?: BlueprintGraphNode["configSource"];
|
|
13
|
+
viewElementId?: string;
|
|
14
|
+
viewElementIds?: string[];
|
|
15
|
+
parentId?: string;
|
|
16
|
+
nestedBlueprintId?: string;
|
|
17
|
+
libraryBlueprintId?: string;
|
|
18
|
+
libraryBlueprintLabel?: string;
|
|
19
|
+
lifecyclePhase?: BlueprintGraphNode["lifecyclePhase"];
|
|
20
|
+
fetchConfig?: BlueprintGraphNode["fetchConfig"];
|
|
21
|
+
jsonConfig?: BlueprintGraphNode["jsonConfig"];
|
|
22
|
+
clockConfig?: BlueprintGraphNode["clockConfig"];
|
|
23
|
+
logicConfig?: BlueprintGraphNode["logicConfig"];
|
|
24
|
+
/** 调试执行时时钟节点已发送次数 */
|
|
25
|
+
clockEmitProgress?: { current: number; total: number } | null;
|
|
26
|
+
/** 由画布注入,勿写入图数据 */
|
|
27
|
+
isSelected?: boolean;
|
|
28
|
+
/** 调试执行时高亮当前执行到的节点 */
|
|
29
|
+
isExecutionActive?: boolean;
|
|
30
|
+
/** 调试执行时当前节点主输出信号类型 */
|
|
31
|
+
executionSignalKind?: "true" | "false" | null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** 与 BlueprintNodeCard 固定宽度一致,供 RF 在 measured 前计算连线路径 */
|
|
35
|
+
export const BP_FLOW_NODE_WIDTH = 168;
|
|
36
|
+
export const BP_FLOW_NODE_HEIGHT = 92;
|
|
37
|
+
|
|
38
|
+
export type VueFlowNodeView = {
|
|
39
|
+
id: string;
|
|
40
|
+
type: string;
|
|
41
|
+
position: { x: number; y: number };
|
|
42
|
+
width?: number;
|
|
43
|
+
height?: number;
|
|
44
|
+
dragHandle?: string;
|
|
45
|
+
data: BlueprintFlowNodeData;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type VueFlowEdgeView = {
|
|
49
|
+
id: string;
|
|
50
|
+
type?: string;
|
|
51
|
+
source: string;
|
|
52
|
+
target: string;
|
|
53
|
+
sourceHandle?: string | null;
|
|
54
|
+
targetHandle?: string | null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function toVueFlowNodes(document: BlueprintDocument): VueFlowNodeView[] {
|
|
58
|
+
return document.nodes.map((n) => ({
|
|
59
|
+
id: n.id,
|
|
60
|
+
type:
|
|
61
|
+
n.role === "blueprint"
|
|
62
|
+
? "blueprint"
|
|
63
|
+
: n.role === "lifecycle"
|
|
64
|
+
? "lifecycle"
|
|
65
|
+
: n.role === "clock"
|
|
66
|
+
? "clock"
|
|
67
|
+
: n.role === "and"
|
|
68
|
+
? "and"
|
|
69
|
+
: n.role === "fetch"
|
|
70
|
+
? "fetch"
|
|
71
|
+
: n.role === "json"
|
|
72
|
+
? "json"
|
|
73
|
+
: "logic",
|
|
74
|
+
position: { ...n.position },
|
|
75
|
+
width: BP_FLOW_NODE_WIDTH,
|
|
76
|
+
height: BP_FLOW_NODE_HEIGHT,
|
|
77
|
+
zIndex: 0,
|
|
78
|
+
connectable: true,
|
|
79
|
+
dragHandle: ".bp-flow-drag-handle",
|
|
80
|
+
data: {
|
|
81
|
+
label: n.label,
|
|
82
|
+
role: n.role,
|
|
83
|
+
nodeType: n.nodeType,
|
|
84
|
+
configSource: n.configSource,
|
|
85
|
+
viewElementId: n.viewElementId,
|
|
86
|
+
viewElementIds: n.viewElementIds,
|
|
87
|
+
parentId: n.parentId,
|
|
88
|
+
nestedBlueprintId: n.nestedBlueprintId,
|
|
89
|
+
libraryBlueprintId: n.libraryBlueprintId,
|
|
90
|
+
lifecyclePhase: n.lifecyclePhase,
|
|
91
|
+
fetchConfig: n.fetchConfig,
|
|
92
|
+
jsonConfig: n.jsonConfig,
|
|
93
|
+
clockConfig: n.clockConfig,
|
|
94
|
+
logicConfig: n.logicConfig,
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function toVueFlowEdges(document: BlueprintDocument): VueFlowEdgeView[] {
|
|
100
|
+
return document.edges.map((e) => ({
|
|
101
|
+
id: e.id,
|
|
102
|
+
type: "smoothstep",
|
|
103
|
+
source: e.source,
|
|
104
|
+
target: e.target,
|
|
105
|
+
sourceHandle: e.sourceHandle ?? "out",
|
|
106
|
+
targetHandle: e.targetHandle ?? "in",
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function syncEdgesFromFlow(flowEdges: VueFlowEdgeView[]): BlueprintGraphEdge[] {
|
|
111
|
+
return flowEdges.map((e) => ({
|
|
112
|
+
id: e.id || createNodeId("edge"),
|
|
113
|
+
source: e.source,
|
|
114
|
+
target: e.target,
|
|
115
|
+
sourceHandle: e.sourceHandle ?? undefined,
|
|
116
|
+
targetHandle: e.targetHandle ?? undefined,
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function collectPositionUpdates(
|
|
121
|
+
nodes: Array<{ id: string; position: { x: number; y: number } }>
|
|
122
|
+
): Array<{ id: string; position: { x: number; y: number } }> {
|
|
123
|
+
return nodes.map((n) => ({
|
|
124
|
+
id: n.id,
|
|
125
|
+
position: { x: n.position.x, y: n.position.y },
|
|
126
|
+
}));
|
|
127
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export { BlueprintGraph } from "./graph/blueprint-graph";
|
|
2
|
+
export * from "./graph";
|
|
3
|
+
export { documentToRunnableGraph } from "./runtime/document-to-runnable-graph";
|
|
4
|
+
export type { BlueprintExecutionOverlay } from "./runtime/execution-overlay";
|
|
5
|
+
export type { BlueprintFlowNodeData } from "./types";
|
|
6
|
+
export { default as BluePrintVueRoot } from "./components/BluePrintVueRoot.vue";
|
|
7
|
+
export { default as BlueprintCanvas } from "./components/BlueprintCanvas.vue";
|
|
8
|
+
export { default as BlueprintNodeConfigSidebar } from "./BlueprintNodeConfigSidebar.vue";
|
|
9
|
+
export type {
|
|
10
|
+
BlueprintNodeConfigSidebarProps,
|
|
11
|
+
BlueprintViewElementOption,
|
|
12
|
+
BlueprintLibraryOption,
|
|
13
|
+
} from "./BlueprintNodeConfigSidebar.vue";
|
|
14
|
+
export { default as BlueprintExecutionLogPanel } from "./components/BlueprintExecutionLogPanel.vue";
|
|
15
|
+
export type { BlueprintExecutionLogPanelProps } from "./components/BlueprintExecutionLogPanel.vue";
|
|
16
|
+
export { default as BlueprintMetaDialog } from "./components/BlueprintMetaDialog.vue";
|
|
17
|
+
export type { BlueprintMetaDialogProps } from "./components/BlueprintMetaDialog.vue";
|
|
18
|
+
export { default as BlueprintNodeSwitchTaskDialog } from "./components/BlueprintNodeSwitchTaskDialog.vue";
|
|
19
|
+
export type { BlueprintNodeSwitchTaskDialogProps } from "./components/BlueprintNodeSwitchTaskDialog.vue";
|
|
20
|
+
export type { ExecutionLogSettings } from "./library/execution-log-settings";
|
|
21
|
+
export type { ExecutionTraceEntry, ExecutionRunRecord } from "@arronqzy/blueprint-dsl";
|
|
22
|
+
export { resolveBlueprintConfigSource, resolveBlueprintNodeTypeLabel } from "./graph/document";
|
|
23
|
+
export type { BlueprintConfigSource } from "./graph/document";
|
|
24
|
+
export * from "./library/blueprint-io";
|
|
25
|
+
export * from "./library/blueprint-library-db";
|
|
26
|
+
export type * from "./library/types";
|
|
27
|
+
export { abortClockNode, stopAllClockSchedules } from "@arronqzy/blueprint-dsl";
|
|
28
|
+
export { useBlueprintPageLifecycle } from "./composables/useBlueprintPageLifecycle";
|
|
29
|
+
export type { UseBlueprintPageLifecycleOptions } from "./composables/useBlueprintPageLifecycle";
|
|
30
|
+
export { useBlueprintDebugSession } from "./composables/useBlueprintDebugSession";
|
|
31
|
+
export type {
|
|
32
|
+
LifecycleNodeOption,
|
|
33
|
+
UseBlueprintDebugSessionOptions,
|
|
34
|
+
UseBlueprintDebugSessionReturn,
|
|
35
|
+
} from "./composables/useBlueprintDebugSession";
|
|
36
|
+
export { useBlueprintNodeSelectionGuard } from "./composables/useBlueprintNodeSelectionGuard";
|
|
37
|
+
export type { PendingBlueprintNodeSwitch } from "./composables/useBlueprintNodeSelectionGuard";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { BlueprintDocument } from "../graph/document";
|
|
2
|
+
import { createNodeId } from "../graph/document";
|
|
3
|
+
import {
|
|
4
|
+
BLUEPRINT_EXPORT_KIND,
|
|
5
|
+
BLUEPRINT_EXPORT_VERSION,
|
|
6
|
+
type BlueprintExportPayload,
|
|
7
|
+
type BlueprintLibraryRecord,
|
|
8
|
+
type BlueprintLibrarySource,
|
|
9
|
+
type BlueprintMetaDraft,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
export function createLibraryBlueprintId() {
|
|
13
|
+
return createNodeId("lib_bp");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function blueprintDocumentsEqual(
|
|
17
|
+
a: BlueprintDocument,
|
|
18
|
+
b: BlueprintDocument
|
|
19
|
+
): boolean {
|
|
20
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildBlueprintExportPayload(
|
|
24
|
+
document: BlueprintDocument,
|
|
25
|
+
meta: BlueprintMetaDraft
|
|
26
|
+
): BlueprintExportPayload {
|
|
27
|
+
return {
|
|
28
|
+
kind: BLUEPRINT_EXPORT_KIND,
|
|
29
|
+
version: BLUEPRINT_EXPORT_VERSION,
|
|
30
|
+
name: meta.name.trim() || "未命名蓝图",
|
|
31
|
+
remark: meta.remark.trim() || undefined,
|
|
32
|
+
exportedAt: Date.now(),
|
|
33
|
+
document: {
|
|
34
|
+
...document,
|
|
35
|
+
name: meta.name.trim() || document.name,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseBlueprintImportFile(raw: unknown): BlueprintExportPayload {
|
|
41
|
+
if (!raw || typeof raw !== "object") {
|
|
42
|
+
throw new Error("invalid-format");
|
|
43
|
+
}
|
|
44
|
+
const data = raw as Partial<BlueprintExportPayload>;
|
|
45
|
+
if (data.kind !== BLUEPRINT_EXPORT_KIND || data.version !== BLUEPRINT_EXPORT_VERSION) {
|
|
46
|
+
throw new Error("unsupported-version");
|
|
47
|
+
}
|
|
48
|
+
if (!data.document || !Array.isArray(data.document.nodes) || !Array.isArray(data.document.edges)) {
|
|
49
|
+
throw new Error("invalid-document");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
kind: BLUEPRINT_EXPORT_KIND,
|
|
53
|
+
version: BLUEPRINT_EXPORT_VERSION,
|
|
54
|
+
name: typeof data.name === "string" ? data.name : "导入的蓝图",
|
|
55
|
+
remark: typeof data.remark === "string" ? data.remark : undefined,
|
|
56
|
+
exportedAt: typeof data.exportedAt === "number" ? data.exportedAt : Date.now(),
|
|
57
|
+
document: data.document,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function downloadBlueprintExport(payload: BlueprintExportPayload) {
|
|
62
|
+
const blob = new Blob([JSON.stringify(payload, null, 2)], {
|
|
63
|
+
type: "application/json",
|
|
64
|
+
});
|
|
65
|
+
const url = URL.createObjectURL(blob);
|
|
66
|
+
const anchor = document.createElement("a");
|
|
67
|
+
anchor.href = url;
|
|
68
|
+
const safeName = payload.name
|
|
69
|
+
.replace(/[\\/:*?"<>|]/g, "-")
|
|
70
|
+
.replace(/\s+/g, "-");
|
|
71
|
+
anchor.download = `${safeName || "blueprint"}-${Date.now()}.json`;
|
|
72
|
+
anchor.click();
|
|
73
|
+
URL.revokeObjectURL(url);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function buildLibraryRecord(args: {
|
|
77
|
+
document: BlueprintDocument;
|
|
78
|
+
meta: BlueprintMetaDraft;
|
|
79
|
+
source: BlueprintLibrarySource;
|
|
80
|
+
id?: string;
|
|
81
|
+
createdAt?: number;
|
|
82
|
+
}): BlueprintLibraryRecord {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const name = args.meta.name.trim() || "未命名蓝图";
|
|
85
|
+
return {
|
|
86
|
+
id: args.id ?? createLibraryBlueprintId(),
|
|
87
|
+
name,
|
|
88
|
+
remark: args.meta.remark.trim() || undefined,
|
|
89
|
+
source: args.source,
|
|
90
|
+
createdAt: args.createdAt ?? now,
|
|
91
|
+
updatedAt: now,
|
|
92
|
+
document: {
|
|
93
|
+
...args.document,
|
|
94
|
+
name,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function libraryRecordFromImport(payload: BlueprintExportPayload): BlueprintLibraryRecord {
|
|
100
|
+
return buildLibraryRecord({
|
|
101
|
+
document: payload.document,
|
|
102
|
+
meta: {
|
|
103
|
+
name: payload.name,
|
|
104
|
+
remark: payload.remark ?? "",
|
|
105
|
+
},
|
|
106
|
+
source: "imported",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BlueprintLibraryListItem,
|
|
3
|
+
BlueprintLibraryRecord,
|
|
4
|
+
} from "./types";
|
|
5
|
+
|
|
6
|
+
const DB_NAME = "arronqzy-blueprint-library";
|
|
7
|
+
const DB_VERSION = 1;
|
|
8
|
+
const STORE_NAME = "blueprints";
|
|
9
|
+
|
|
10
|
+
function openDb(): Promise<IDBDatabase> {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
13
|
+
request.onerror = () => reject(request.error ?? new Error("indexeddb-open-failed"));
|
|
14
|
+
request.onupgradeneeded = () => {
|
|
15
|
+
const db = request.result;
|
|
16
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
17
|
+
const store = db.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
18
|
+
store.createIndex("source", "source", { unique: false });
|
|
19
|
+
store.createIndex("updatedAt", "updatedAt", { unique: false });
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
request.onsuccess = () => resolve(request.result);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runTransaction<T>(
|
|
27
|
+
mode: IDBTransactionMode,
|
|
28
|
+
runner: (store: IDBObjectStore) => IDBRequest<T>
|
|
29
|
+
): Promise<T> {
|
|
30
|
+
return openDb().then(
|
|
31
|
+
(db) =>
|
|
32
|
+
new Promise<T>((resolve, reject) => {
|
|
33
|
+
const tx = db.transaction(STORE_NAME, mode);
|
|
34
|
+
const store = tx.objectStore(STORE_NAME);
|
|
35
|
+
const request = runner(store);
|
|
36
|
+
let result!: T;
|
|
37
|
+
|
|
38
|
+
request.onerror = () =>
|
|
39
|
+
reject(request.error ?? new Error("indexeddb-request-failed"));
|
|
40
|
+
request.onsuccess = () => {
|
|
41
|
+
result = request.result as T;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
tx.oncomplete = () => {
|
|
45
|
+
db.close();
|
|
46
|
+
resolve(result);
|
|
47
|
+
};
|
|
48
|
+
tx.onerror = () => {
|
|
49
|
+
db.close();
|
|
50
|
+
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
51
|
+
};
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function listBlueprintLibrary(): Promise<BlueprintLibraryListItem[]> {
|
|
57
|
+
const records = await runTransaction<BlueprintLibraryRecord[]>("readonly", (store) =>
|
|
58
|
+
store.getAll()
|
|
59
|
+
);
|
|
60
|
+
return records
|
|
61
|
+
.map((record) => ({
|
|
62
|
+
id: record.id,
|
|
63
|
+
name: record.name,
|
|
64
|
+
remark: record.remark,
|
|
65
|
+
source: record.source,
|
|
66
|
+
updatedAt: record.updatedAt,
|
|
67
|
+
}))
|
|
68
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function getBlueprintLibraryRecord(
|
|
72
|
+
id: string
|
|
73
|
+
): Promise<BlueprintLibraryRecord | null> {
|
|
74
|
+
const record = await runTransaction<BlueprintLibraryRecord | undefined>("readonly", (store) =>
|
|
75
|
+
store.get(id)
|
|
76
|
+
);
|
|
77
|
+
return record ?? null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function putBlueprintLibraryRecord(
|
|
81
|
+
record: BlueprintLibraryRecord
|
|
82
|
+
): Promise<BlueprintLibraryRecord> {
|
|
83
|
+
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record));
|
|
84
|
+
return record;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function updateBlueprintLibraryMeta(
|
|
88
|
+
id: string,
|
|
89
|
+
patch: { name?: string; remark?: string }
|
|
90
|
+
): Promise<BlueprintLibraryRecord | null> {
|
|
91
|
+
const existing = await getBlueprintLibraryRecord(id);
|
|
92
|
+
if (!existing) return null;
|
|
93
|
+
|
|
94
|
+
const name = patch.name?.trim() || existing.name;
|
|
95
|
+
const record: BlueprintLibraryRecord = {
|
|
96
|
+
...existing,
|
|
97
|
+
name,
|
|
98
|
+
remark: patch.remark !== undefined ? patch.remark.trim() || undefined : existing.remark,
|
|
99
|
+
updatedAt: Date.now(),
|
|
100
|
+
document: {
|
|
101
|
+
...existing.document,
|
|
102
|
+
name,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await putBlueprintLibraryRecord(record);
|
|
107
|
+
return record;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function deleteBlueprintLibraryRecord(id: string): Promise<void> {
|
|
111
|
+
await runTransaction<undefined>("readwrite", (store) => store.delete(id));
|
|
112
|
+
}
|