Download src/js/workflow/serialization.ts from AUXteam/Bentopdf: direct link, hf CLI and curl.
- Browser
- Download file 6.94 kB
-
https://huggingface.co/spaces/AUXteam/Bentopdf/resolve/main/src/js/workflow/serialization.ts
- Command line
-
hf download hf://spaces/AUXteam/Bentopdf/src/js/workflow/serialization.ts
-
curl -L -o serialization.ts https://huggingface.co/spaces/AUXteam/Bentopdf/resolve/main/src/js/workflow/serialization.ts
6.94 kB
| import type { NodeEditor } from 'rete'; | |
| import type { AreaPlugin } from 'rete-area-plugin'; | |
| import type { ClassicScheme, LitArea2D } from '@retejs/lit-plugin'; | |
| import type { BaseWorkflowNode } from './nodes/base-node'; | |
| import { createNodeByType } from './nodes/registry'; | |
| import { ClassicPreset } from 'rete'; | |
| import type { SerializedWorkflow } from './types'; | |
| import { WORKFLOW_VERSION } from './types'; | |
| type AreaExtra = LitArea2D<ClassicScheme>; | |
| interface SerializedNode { | |
| id: string; | |
| type: string; | |
| position: { x: number; y: number }; | |
| controls: Record<string, unknown>; | |
| } | |
| interface SerializedConnection { | |
| id: string; | |
| source: string; | |
| sourceOutput: string; | |
| target: string; | |
| targetInput: string; | |
| } | |
| function getNodeType(node: BaseWorkflowNode): string | null { | |
| const constructorName = node.constructor.name; | |
| return constructorName || null; | |
| } | |
| function serializeWorkflow( | |
| editor: NodeEditor<ClassicScheme>, | |
| area: AreaPlugin<ClassicScheme, AreaExtra> | |
| ): SerializedWorkflow { | |
| const nodes: SerializedNode[] = []; | |
| const connections: SerializedConnection[] = []; | |
| for (const node of editor.getNodes()) { | |
| const view = area.nodeViews.get(node.id); | |
| const position = view | |
| ? { x: view.position.x, y: view.position.y } | |
| : { x: 0, y: 0 }; | |
| const controls: Record<string, unknown> = {}; | |
| for (const [key, control] of Object.entries(node.controls)) { | |
| if (control && 'value' in control) { | |
| controls[key] = (control as { value: unknown }).value; | |
| } | |
| } | |
| nodes.push({ | |
| id: node.id, | |
| type: getNodeType(node as BaseWorkflowNode) || 'unknown', | |
| position, | |
| controls, | |
| }); | |
| } | |
| for (const conn of editor.getConnections()) { | |
| connections.push({ | |
| id: conn.id, | |
| source: conn.source, | |
| sourceOutput: conn.sourceOutput as string, | |
| target: conn.target, | |
| targetInput: conn.targetInput as string, | |
| }); | |
| } | |
| return { | |
| version: WORKFLOW_VERSION, | |
| nodes, | |
| connections, | |
| } as SerializedWorkflow; | |
| } | |
| async function deserializeWorkflow( | |
| data: SerializedWorkflow, | |
| editor: NodeEditor<ClassicScheme>, | |
| area: AreaPlugin<ClassicScheme, AreaExtra> | |
| ): Promise<void> { | |
| for (const conn of editor.getConnections()) { | |
| await editor.removeConnection(conn.id); | |
| } | |
| for (const node of editor.getNodes()) { | |
| await editor.removeNode(node.id); | |
| } | |
| const idMap = new Map<string, string>(); | |
| const skippedTypes: string[] = []; | |
| for (const serializedNode of (data as any).nodes) { | |
| const node = createNodeByType(serializedNode.type); | |
| if (!node) { | |
| skippedTypes.push(serializedNode.type); | |
| continue; | |
| } | |
| for (const [key, value] of Object.entries(serializedNode.controls || {})) { | |
| const control = node.controls[key]; | |
| if (control && 'value' in control) { | |
| (control as any).value = value; | |
| } | |
| } | |
| await editor.addNode(node as any); | |
| idMap.set(serializedNode.id, node.id); | |
| await area.translate(node.id, serializedNode.position); | |
| } | |
| for (const serializedConn of (data as any).connections) { | |
| const sourceId = idMap.get(serializedConn.source); | |
| const targetId = idMap.get(serializedConn.target); | |
| if (!sourceId || !targetId) continue; | |
| const sourceNode = editor.getNode(sourceId); | |
| const targetNode = editor.getNode(targetId); | |
| if (!sourceNode || !targetNode) continue; | |
| const conn = new ClassicPreset.Connection( | |
| sourceNode, | |
| serializedConn.sourceOutput, | |
| targetNode, | |
| serializedConn.targetInput | |
| ); | |
| await editor.addConnection(conn as any); | |
| } | |
| if (skippedTypes.length > 0) { | |
| console.warn('Skipped unknown node types during load:', skippedTypes); | |
| throw new Error( | |
| `Some nodes could not be loaded: ${skippedTypes.join(', ')}. They may have been removed or renamed.` | |
| ); | |
| } | |
| } | |
| const TEMPLATES_KEY = 'bento-pdf-workflow-templates'; | |
| interface StoredTemplates { | |
| [name: string]: SerializedWorkflow; | |
| } | |
| function getStoredTemplates(): StoredTemplates { | |
| const json = localStorage.getItem(TEMPLATES_KEY); | |
| if (!json) return {}; | |
| try { | |
| return JSON.parse(json) as StoredTemplates; | |
| } catch { | |
| return {}; | |
| } | |
| } | |
| export function getSavedTemplateNames(): string[] { | |
| return Object.keys(getStoredTemplates()); | |
| } | |
| export function saveWorkflow( | |
| editor: NodeEditor<ClassicScheme>, | |
| area: AreaPlugin<ClassicScheme, AreaExtra>, | |
| name: string | |
| ): void { | |
| const data = serializeWorkflow(editor, area); | |
| const templates = getStoredTemplates(); | |
| const backup = templates[name]; | |
| templates[name] = data; | |
| try { | |
| localStorage.setItem(TEMPLATES_KEY, JSON.stringify(templates)); | |
| } catch (e) { | |
| if (backup !== undefined) { | |
| templates[name] = backup; | |
| } else { | |
| delete templates[name]; | |
| } | |
| throw new Error( | |
| 'Failed to save workflow: storage quota exceeded. Try deleting old templates.' | |
| ); | |
| } | |
| } | |
| export function templateNameExists(name: string): boolean { | |
| const templates = getStoredTemplates(); | |
| return name in templates; | |
| } | |
| export async function loadWorkflow( | |
| editor: NodeEditor<ClassicScheme>, | |
| area: AreaPlugin<ClassicScheme, AreaExtra>, | |
| name: string | |
| ): Promise<boolean> { | |
| const templates = getStoredTemplates(); | |
| const data = templates[name]; | |
| if (!data) return false; | |
| try { | |
| await deserializeWorkflow(data, editor, area); | |
| return true; | |
| } catch (err) { | |
| console.error(`Failed to load workflow "${name}":`, err); | |
| return false; | |
| } | |
| } | |
| export function deleteTemplate(name: string): void { | |
| const templates = getStoredTemplates(); | |
| delete templates[name]; | |
| localStorage.setItem(TEMPLATES_KEY, JSON.stringify(templates)); | |
| } | |
| export function exportWorkflow( | |
| editor: NodeEditor<ClassicScheme>, | |
| area: AreaPlugin<ClassicScheme, AreaExtra> | |
| ): void { | |
| const data = serializeWorkflow(editor, area); | |
| const json = JSON.stringify(data, null, 2); | |
| const blob = new Blob([json], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| try { | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'workflow.json'; | |
| a.click(); | |
| } finally { | |
| URL.revokeObjectURL(url); | |
| } | |
| } | |
| export async function importWorkflow( | |
| editor: NodeEditor<ClassicScheme>, | |
| area: AreaPlugin<ClassicScheme, AreaExtra> | |
| ): Promise<void> { | |
| return new Promise((resolve, reject) => { | |
| const input = document.createElement('input'); | |
| input.type = 'file'; | |
| input.accept = '.json'; | |
| input.onchange = async () => { | |
| const file = input.files?.[0]; | |
| if (!file) { | |
| resolve(); | |
| return; | |
| } | |
| try { | |
| const text = await file.text(); | |
| const data = JSON.parse(text) as SerializedWorkflow; | |
| await deserializeWorkflow(data, editor, area); | |
| resolve(); | |
| } catch (err) { | |
| const message = err instanceof Error ? err.message : 'Unknown error'; | |
| reject(new Error(`Failed to import workflow: ${message}`)); | |
| } | |
| }; | |
| input.click(); | |
| }); | |
| } | |