Support nested canvas workflow imports
This commit is contained in:
@@ -239,14 +239,70 @@ function firstArray(...values: unknown[]) {
|
||||
return values.find((value): value is unknown[] => Array.isArray(value)) || [];
|
||||
}
|
||||
|
||||
const EXTERNAL_NODE_ARRAY_KEYS = ['nodes', 'elements', 'items', 'cells', 'blocks'] as const;
|
||||
const EXTERNAL_CONNECTION_ARRAY_KEYS = ['connections', 'edges', 'links', 'lines', 'cells'] as const;
|
||||
const EXTERNAL_CANVAS_CONTAINER_KEYS = ['canvas', 'graph', 'workflow', 'project', 'scene', 'document', 'data', 'state', 'project_json', 'projectData'] as const;
|
||||
|
||||
function isLikelyExternalConnectionType(value: unknown) {
|
||||
const normalized = typeof value === 'string' ? value.toLowerCase().replace(/[_\s-]+/g, '') : '';
|
||||
return normalized === 'edge' || normalized === 'line' || normalized === 'link' || normalized === 'connection';
|
||||
}
|
||||
|
||||
function isLikelyExternalNode(value: unknown) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
||||
const node = value as Record<string, unknown>;
|
||||
const data = asRecord(node.data);
|
||||
const settings = asRecord(node.settings || data.settings || data);
|
||||
const rawType = firstString(node.type, node.shape, data.type, settings.type);
|
||||
if (isLikelyExternalConnectionType(rawType)) return false;
|
||||
if (rawType) return true;
|
||||
if (firstString(node.nodeName, node.title, node.name, data.label, data.title, settings.title) && (node.position || firstNumber(node.x, data.x) !== undefined)) return true;
|
||||
return !!firstString(node.prompt, settings.prompt, node.text, settings.text, node.content, data.text, data.prompt);
|
||||
}
|
||||
|
||||
function isLikelyExternalConnection(value: unknown) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
||||
const connection = value as Record<string, unknown>;
|
||||
const source = asRecord(connection.source);
|
||||
const target = asRecord(connection.target);
|
||||
if (isLikelyExternalConnectionType(firstString(connection.type, connection.shape))) return true;
|
||||
return !!(
|
||||
firstString(connection.sourceNodeId, connection.from, connection.source, source.nodeId, source.cell, source.id)
|
||||
&& firstString(connection.targetNodeId, connection.to, connection.target, target.nodeId, target.cell, target.id)
|
||||
);
|
||||
}
|
||||
|
||||
function findExternalArray(
|
||||
value: unknown,
|
||||
keys: readonly string[],
|
||||
predicate: (item: unknown) => boolean,
|
||||
depth = 0,
|
||||
): unknown[] {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value) || depth > 5) return [];
|
||||
const input = value as Record<string, unknown>;
|
||||
for (const key of keys) {
|
||||
const direct = input[key];
|
||||
if (Array.isArray(direct) && direct.some(predicate)) return direct;
|
||||
}
|
||||
const preferredContainers = EXTERNAL_CANVAS_CONTAINER_KEYS
|
||||
.map(key => input[key])
|
||||
.filter(item => item && typeof item === 'object' && !Array.isArray(item));
|
||||
const otherContainers = Object.entries(input)
|
||||
.filter(([key, item]) => !EXTERNAL_CANVAS_CONTAINER_KEYS.includes(key as typeof EXTERNAL_CANVAS_CONTAINER_KEYS[number]) && item && typeof item === 'object' && !Array.isArray(item))
|
||||
.map(([, item]) => item);
|
||||
for (const child of [...preferredContainers, ...otherContainers]) {
|
||||
const nested = findExternalArray(child, keys, predicate, depth + 1);
|
||||
if (nested.length > 0) return nested;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getExternalNodes(value: unknown): unknown[] {
|
||||
const input = asRecord(value);
|
||||
return firstArray(input.nodes, input.elements, input.items, input.cells, input.blocks);
|
||||
return findExternalArray(value, EXTERNAL_NODE_ARRAY_KEYS, isLikelyExternalNode);
|
||||
}
|
||||
|
||||
function getExternalConnections(value: unknown): unknown[] {
|
||||
const input = asRecord(value);
|
||||
return firstArray(input.connections, input.edges, input.links, input.lines);
|
||||
return findExternalArray(value, EXTERNAL_CONNECTION_ARRAY_KEYS, isLikelyExternalConnection);
|
||||
}
|
||||
|
||||
function isExternalCanvasProject(value: unknown) {
|
||||
@@ -367,7 +423,7 @@ function convertExternalCanvasProject(value: unknown, assetMap?: ExternalAssetMa
|
||||
const view = asRecord(input.view || input.viewport);
|
||||
const createdAt = nowIso();
|
||||
const nodes = getExternalNodes(value).map((rawNode, index): CanvasNode | null => {
|
||||
if (!rawNode || typeof rawNode !== 'object') return null;
|
||||
if (!isLikelyExternalNode(rawNode)) return null;
|
||||
const node = rawNode as Record<string, unknown>;
|
||||
const position = asRecord(node.position);
|
||||
const size = asRecord(node.size);
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "backups"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user