@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,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
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { ExecutionRunRecord } from "@arronqzy/blueprint-dsl";
|
|
2
|
+
|
|
3
|
+
const DB_NAME = "arronqzy-blueprint-execution-log";
|
|
4
|
+
const DB_VERSION = 1;
|
|
5
|
+
const STORE_NAME = "runs";
|
|
6
|
+
|
|
7
|
+
function openDb(): Promise<IDBDatabase> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
10
|
+
request.onerror = () => reject(request.error ?? new Error("indexeddb-open-failed"));
|
|
11
|
+
request.onupgradeneeded = () => {
|
|
12
|
+
const db = request.result;
|
|
13
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
14
|
+
const store = db.createObjectStore(STORE_NAME, { keyPath: "runId" });
|
|
15
|
+
store.createIndex("startedAt", "startedAt", { unique: false });
|
|
16
|
+
store.createIndex("blueprintId", "blueprintId", { unique: false });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
request.onsuccess = () => resolve(request.result);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runTransaction<T>(
|
|
24
|
+
mode: IDBTransactionMode,
|
|
25
|
+
runner: (store: IDBObjectStore) => IDBRequest<T>
|
|
26
|
+
): Promise<T> {
|
|
27
|
+
return openDb().then(
|
|
28
|
+
(db) =>
|
|
29
|
+
new Promise<T>((resolve, reject) => {
|
|
30
|
+
const tx = db.transaction(STORE_NAME, mode);
|
|
31
|
+
const store = tx.objectStore(STORE_NAME);
|
|
32
|
+
const request = runner(store);
|
|
33
|
+
let result!: T;
|
|
34
|
+
|
|
35
|
+
request.onerror = () =>
|
|
36
|
+
reject(request.error ?? new Error("indexeddb-request-failed"));
|
|
37
|
+
request.onsuccess = () => {
|
|
38
|
+
result = request.result as T;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
tx.oncomplete = () => {
|
|
42
|
+
db.close();
|
|
43
|
+
resolve(result);
|
|
44
|
+
};
|
|
45
|
+
tx.onerror = () => {
|
|
46
|
+
db.close();
|
|
47
|
+
reject(tx.error ?? new Error("indexeddb-transaction-failed"));
|
|
48
|
+
};
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function putExecutionRunRecord(
|
|
54
|
+
record: ExecutionRunRecord
|
|
55
|
+
): Promise<ExecutionRunRecord> {
|
|
56
|
+
await runTransaction<IDBValidKey>("readwrite", (store) => store.put(record));
|
|
57
|
+
return record;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function getExecutionRunRecord(
|
|
61
|
+
runId: string
|
|
62
|
+
): Promise<ExecutionRunRecord | null> {
|
|
63
|
+
const record = await runTransaction<ExecutionRunRecord | undefined>(
|
|
64
|
+
"readonly",
|
|
65
|
+
(store) => store.get(runId)
|
|
66
|
+
);
|
|
67
|
+
return record ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function listExecutionRunRecords(
|
|
71
|
+
blueprintId?: string | null
|
|
72
|
+
): Promise<ExecutionRunRecord[]> {
|
|
73
|
+
const records = await runTransaction<ExecutionRunRecord[]>("readonly", (store) =>
|
|
74
|
+
store.getAll()
|
|
75
|
+
);
|
|
76
|
+
const filtered = blueprintId
|
|
77
|
+
? records.filter((item) => item.blueprintId === blueprintId)
|
|
78
|
+
: records;
|
|
79
|
+
return filtered.sort((a, b) => b.startedAt - a.startedAt);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function deleteExecutionRunRecord(runId: string): Promise<void> {
|
|
83
|
+
await runTransaction<undefined>("readwrite", (store) => store.delete(runId));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function clearAllExecutionRunRecords(): Promise<number> {
|
|
87
|
+
const records = await runTransaction<ExecutionRunRecord[]>("readonly", (store) =>
|
|
88
|
+
store.getAll()
|
|
89
|
+
);
|
|
90
|
+
if (records.length === 0) return 0;
|
|
91
|
+
|
|
92
|
+
await runTransaction<undefined>("readwrite", (store) => store.clear());
|
|
93
|
+
return records.length;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function trimExecutionRunRecordsToMax(
|
|
97
|
+
maxCount: number
|
|
98
|
+
): Promise<number> {
|
|
99
|
+
if (!Number.isFinite(maxCount) || maxCount < 1) return 0;
|
|
100
|
+
|
|
101
|
+
const records = await runTransaction<ExecutionRunRecord[]>("readonly", (store) =>
|
|
102
|
+
store.getAll()
|
|
103
|
+
);
|
|
104
|
+
if (records.length <= maxCount) return 0;
|
|
105
|
+
|
|
106
|
+
const sorted = [...records].sort((a, b) => b.startedAt - a.startedAt);
|
|
107
|
+
const overflow = sorted.slice(maxCount);
|
|
108
|
+
if (overflow.length === 0) return 0;
|
|
109
|
+
|
|
110
|
+
await openDb().then(
|
|
111
|
+
(db) =>
|
|
112
|
+
new Promise<void>((resolve, reject) => {
|
|
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
|
+
})
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return overflow.length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function purgeExecutionRunsOlderThan(cutoffMs: number): Promise<number> {
|
|
133
|
+
const records = await runTransaction<ExecutionRunRecord[]>("readonly", (store) =>
|
|
134
|
+
store.getAll()
|
|
135
|
+
);
|
|
136
|
+
const stale = records.filter((item) => item.startedAt < cutoffMs);
|
|
137
|
+
if (stale.length === 0) return 0;
|
|
138
|
+
|
|
139
|
+
await openDb().then(
|
|
140
|
+
(db) =>
|
|
141
|
+
new Promise<void>((resolve, reject) => {
|
|
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
|
+
})
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return stale.length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function downloadExecutionRunExport(record: ExecutionRunRecord) {
|
|
162
|
+
const blob = new Blob([JSON.stringify(record, null, 2)], {
|
|
163
|
+
type: "application/json",
|
|
164
|
+
});
|
|
165
|
+
const url = URL.createObjectURL(blob);
|
|
166
|
+
const anchor = document.createElement("a");
|
|
167
|
+
anchor.href = url;
|
|
168
|
+
anchor.download = `blueprint-run-${record.runId}.json`;
|
|
169
|
+
anchor.click();
|
|
170
|
+
URL.revokeObjectURL(url);
|
|
171
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type ExecutionLogSettings = {
|
|
2
|
+
retentionDays: number;
|
|
3
|
+
autoSave: boolean;
|
|
4
|
+
/** IndexedDB 中最多保留的日志条数 */
|
|
5
|
+
maxSavedRuns: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const SETTINGS_KEY = "arronqzy-blueprint-execution-log-settings";
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_EXECUTION_LOG_SETTINGS: ExecutionLogSettings = {
|
|
11
|
+
retentionDays: 7,
|
|
12
|
+
autoSave: true,
|
|
13
|
+
maxSavedRuns: 80,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function readExecutionLogSettings(): ExecutionLogSettings {
|
|
17
|
+
try {
|
|
18
|
+
const raw = localStorage.getItem(SETTINGS_KEY);
|
|
19
|
+
if (!raw) return { ...DEFAULT_EXECUTION_LOG_SETTINGS };
|
|
20
|
+
const parsed = JSON.parse(raw) as Partial<ExecutionLogSettings>;
|
|
21
|
+
return {
|
|
22
|
+
retentionDays:
|
|
23
|
+
typeof parsed.retentionDays === "number" && parsed.retentionDays > 0
|
|
24
|
+
? parsed.retentionDays
|
|
25
|
+
: DEFAULT_EXECUTION_LOG_SETTINGS.retentionDays,
|
|
26
|
+
autoSave:
|
|
27
|
+
typeof parsed.autoSave === "boolean"
|
|
28
|
+
? parsed.autoSave
|
|
29
|
+
: DEFAULT_EXECUTION_LOG_SETTINGS.autoSave,
|
|
30
|
+
maxSavedRuns:
|
|
31
|
+
typeof parsed.maxSavedRuns === "number" && parsed.maxSavedRuns > 0
|
|
32
|
+
? Math.floor(parsed.maxSavedRuns)
|
|
33
|
+
: DEFAULT_EXECUTION_LOG_SETTINGS.maxSavedRuns,
|
|
34
|
+
};
|
|
35
|
+
} catch {
|
|
36
|
+
return { ...DEFAULT_EXECUTION_LOG_SETTINGS };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function writeExecutionLogSettings(settings: ExecutionLogSettings) {
|
|
41
|
+
try {
|
|
42
|
+
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
|
43
|
+
} catch {
|
|
44
|
+
// ignore storage errors
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function retentionCutoffMs(retentionDays: number) {
|
|
49
|
+
return Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
50
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSwaggerDocument,
|
|
3
|
+
type ParsedSwaggerDocument,
|
|
4
|
+
} from "@arronqzy/blueprint-dsl";
|
|
5
|
+
|
|
6
|
+
export async function loadSwaggerDocument(
|
|
7
|
+
docsUrl: string,
|
|
8
|
+
signal?: AbortSignal
|
|
9
|
+
): Promise<ParsedSwaggerDocument> {
|
|
10
|
+
const trimmed = docsUrl.trim();
|
|
11
|
+
if (!trimmed) {
|
|
12
|
+
throw new Error("请输入 Swagger 文档 URL");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (signal?.aborted) {
|
|
16
|
+
throw new DOMException("Aborted", "AbortError");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let response: Response;
|
|
20
|
+
try {
|
|
21
|
+
response = await fetch(trimmed, {
|
|
22
|
+
method: "GET",
|
|
23
|
+
credentials: "same-origin",
|
|
24
|
+
signal,
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (signal?.aborted || (error instanceof DOMException && error.name === "AbortError")) {
|
|
28
|
+
throw new DOMException("Aborted", "AbortError");
|
|
29
|
+
}
|
|
30
|
+
throw new Error(
|
|
31
|
+
error instanceof Error ? error.message : "无法请求 Swagger 文档"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
throw new DOMException("Aborted", "AbortError");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Swagger 文档请求失败: HTTP ${response.status}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
44
|
+
if (contentType.includes("text/html")) {
|
|
45
|
+
throw new Error("返回内容为 HTML,请填写 OpenAPI/Swagger 的 JSON 地址");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let spec: unknown;
|
|
49
|
+
try {
|
|
50
|
+
spec = await response.json();
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error("Swagger 文档不是有效的 JSON");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return parseSwaggerDocument(spec, trimmed);
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BlueprintDocument } from "../graph/document";
|
|
2
|
+
|
|
3
|
+
export type BlueprintLibrarySource = "saved" | "imported";
|
|
4
|
+
|
|
5
|
+
export type BlueprintLibraryRecord = {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
remark?: string;
|
|
9
|
+
source: BlueprintLibrarySource;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
updatedAt: number;
|
|
12
|
+
document: BlueprintDocument;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type BlueprintLibraryListItem = Pick<
|
|
16
|
+
BlueprintLibraryRecord,
|
|
17
|
+
"id" | "name" | "remark" | "source" | "updatedAt"
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
export const BLUEPRINT_EXPORT_KIND = "arronqzy-blueprint" as const;
|
|
21
|
+
export const BLUEPRINT_EXPORT_VERSION = 1 as const;
|
|
22
|
+
|
|
23
|
+
export type BlueprintExportPayload = {
|
|
24
|
+
kind: typeof BLUEPRINT_EXPORT_KIND;
|
|
25
|
+
version: typeof BLUEPRINT_EXPORT_VERSION;
|
|
26
|
+
name: string;
|
|
27
|
+
remark?: string;
|
|
28
|
+
exportedAt: number;
|
|
29
|
+
document: BlueprintDocument;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type BlueprintMetaDraft = {
|
|
33
|
+
name: string;
|
|
34
|
+
remark: string;
|
|
35
|
+
};
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { ThemeProvider } from "@arronqzy/ui";
|
|
4
|
+
import "@arronqzy/ui/styles.css";
|
|
5
|
+
import "@xyflow/react/dist/style.css";
|
|
6
|
+
import "./dev.css";
|
|
7
|
+
import "./blueprint.css";
|
|
8
|
+
|
|
9
|
+
import { BluePrintReactRoot } from ".";
|
|
10
|
+
import { createDevBlueprintGraph } from "./dev-seed";
|
|
11
|
+
|
|
12
|
+
function DevApp() {
|
|
13
|
+
const [graph, setGraph] = useState(() => createDevBlueprintGraph());
|
|
14
|
+
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<header className="bp-dev-toolbar">
|
|
19
|
+
<strong>Blueprint Dev</strong>
|
|
20
|
+
<span className="text-muted-foreground">
|
|
21
|
+
右键画布添加节点 · 从右侧圆点拖到左侧圆点连线 · 初始应看到 A→B 蓝线
|
|
22
|
+
</span>
|
|
23
|
+
<span className="text-muted-foreground">
|
|
24
|
+
节点 {graph.document.nodes.length} · 边 {graph.document.edges.length}
|
|
25
|
+
</span>
|
|
26
|
+
</header>
|
|
27
|
+
<div className="bp-dev-canvas">
|
|
28
|
+
<BluePrintReactRoot
|
|
29
|
+
graph={graph}
|
|
30
|
+
onGraphChange={setGraph}
|
|
31
|
+
selectedNodeId={selectedNodeId}
|
|
32
|
+
onSelectNode={setSelectedNodeId}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createRoot(document.getElementById("app")!).render(
|
|
40
|
+
<ThemeProvider defaultTheme="light" enableSystem={false}>
|
|
41
|
+
<DevApp />
|
|
42
|
+
</ThemeProvider>
|
|
43
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
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 AndFlowNode({ 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--and",
|
|
21
|
+
executionTone === "success" && "bp-node--execution-true",
|
|
22
|
+
executionTone === "error" && "bp-node--execution-false"
|
|
23
|
+
)}
|
|
24
|
+
>
|
|
25
|
+
<Handle
|
|
26
|
+
type="target"
|
|
27
|
+
position={Position.Left}
|
|
28
|
+
id="inA"
|
|
29
|
+
className="bp-flow-handle bp-flow-handle--target"
|
|
30
|
+
style={{ top: "35%" }}
|
|
31
|
+
title="输入 A(多连线为或)"
|
|
32
|
+
/>
|
|
33
|
+
<Handle
|
|
34
|
+
type="target"
|
|
35
|
+
position={Position.Left}
|
|
36
|
+
id="inB"
|
|
37
|
+
className="bp-flow-handle bp-flow-handle--target"
|
|
38
|
+
style={{ top: "65%" }}
|
|
39
|
+
title="输入 B(多连线为或)"
|
|
40
|
+
/>
|
|
41
|
+
<BlueprintNodeCard
|
|
42
|
+
nodeId={id}
|
|
43
|
+
label={nodeData.label}
|
|
44
|
+
meta={resolveBlueprintNodeTypeLabel(nodeData)}
|
|
45
|
+
subtitle={resolveBlueprintNodeSummary(nodeData)}
|
|
46
|
+
variant="and"
|
|
47
|
+
selected={Boolean(nodeData.isSelected)}
|
|
48
|
+
onSelect={onSelect}
|
|
49
|
+
/>
|
|
50
|
+
<Handle
|
|
51
|
+
type="source"
|
|
52
|
+
position={Position.Right}
|
|
53
|
+
id="out"
|
|
54
|
+
className="bp-flow-handle bp-flow-handle--source"
|
|
55
|
+
title="两路均为真时输出真信号"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -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 BlueprintFlowNode({ 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="blueprint"
|
|
21
|
+
selected={Boolean(nodeData.isSelected)}
|
|
22
|
+
executionTone={resolveBlueprintNodeExecutionTone(nodeData)}
|
|
23
|
+
onSelect={onSelect}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|