@gntik-ai/templates 0.9.0 → 0.10.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.
@@ -1,17 +1,21 @@
1
1
  import {
2
2
  CodePanel,
3
+ builderNodes,
4
+ builderEdges,
5
+ cleanFlowGraph,
3
6
  type CodePanelProps,
4
7
  FlowBuilder,
5
8
  type ConsoleEntry,
6
9
  type FlowBuilderProps,
7
10
  type FlowEdge,
11
+ type FlowGraph,
8
12
  type FlowNode,
9
13
  type FlowNodeKind,
10
14
  type PaletteItem,
11
15
  } from '@gntik-ai/blocks';
12
16
  import { Save, Upload } from '@gntik-ai/icons';
13
- import { Breadcrumbs, Button, CanvasLayout, StatusTag, type BreadcrumbItem } from '@gntik-ai/ui';
14
- import { useState } from 'react';
17
+ import { Breadcrumbs, Button, CanvasLayout, StatusTag, useI18n, type BreadcrumbItem } from '@gntik-ai/ui';
18
+ import { useRef, useState } from 'react';
15
19
  import { flowBuilderBreadcrumbs, flowBuilderContent } from './data';
16
20
 
17
21
  export interface FlowBuilderPageProps<Kind extends string = FlowNodeKind> extends Pick<
@@ -25,6 +29,8 @@ export interface FlowBuilderPageProps<Kind extends string = FlowNodeKind> extend
25
29
  | 'mode'
26
30
  | 'nodeStatus'
27
31
  | 'onNodeSelect'
32
+ | 'graph'
33
+ | 'onGraphChange'
28
34
  > {
29
35
  /** Optional companion code panel; run mode forces readOnly. */
30
36
  codePanel?: CodePanelProps;
@@ -43,7 +49,7 @@ export interface FlowBuilderPageProps<Kind extends string = FlowNodeKind> extend
43
49
  defaultConsoleEntries: ConsoleEntry[];
44
50
  onConsoleChange: (entries: ConsoleEntry[]) => void;
45
51
  onConsoleClear: () => void;
46
- onSave: () => void | Promise<void>;
52
+ onSave: (graph: FlowGraph<Kind>) => void | Promise<void>;
47
53
  onPublish: () => void;
48
54
  breadcrumbs: BreadcrumbItem[];
49
55
  /** Builder height in px on wide screens. */
@@ -61,8 +67,8 @@ export default function FlowBuilderPage<Kind extends string = FlowNodeKind>(
61
67
  const {
62
68
  title = flowBuilderContent.title,
63
69
  status = flowBuilderContent.status,
64
- nodes,
65
- edges,
70
+ nodes = builderNodes,
71
+ edges = builderEdges,
66
72
  palette,
67
73
  onRun,
68
74
  consoleEntries,
@@ -80,19 +86,32 @@ export default function FlowBuilderPage<Kind extends string = FlowNodeKind>(
80
86
  mode,
81
87
  nodeStatus,
82
88
  onNodeSelect,
89
+ graph,
90
+ onGraphChange,
83
91
  codePanel,
84
92
  breadcrumbs = flowBuilderBreadcrumbs,
85
93
  height = 640,
86
94
  fullScreen = true,
87
95
  } = props;
88
96
  const [saving, setSaving] = useState(false);
89
- const [savedNote, setSavedNote] = useState(flowBuilderContent.lastSaved);
97
+ const [initialGraph] = useState<FlowGraph<Kind>>(() => ({ nodes, edges }));
98
+ const [latestGraph, setLatestGraph] = useState<FlowGraph<Kind>>();
99
+ const [saveState, setSaveState] = useState<'initial' | 'unsaved' | 'saved'>('initial');
100
+ const revision = useRef(0);
101
+ const { t } = useI18n();
102
+ const savedNote =
103
+ saveState === 'initial'
104
+ ? flowBuilderContent.lastSaved
105
+ : t(saveState === 'unsaved' ? 'actionBar.unsaved' : 'actionBar.saved');
90
106
 
91
107
  const save = async () => {
92
108
  setSaving(true);
109
+ const savedRevision = revision.current;
93
110
  try {
94
- await onSave?.();
95
- setSavedNote('All changes saved');
111
+ await onSave?.(cleanFlowGraph(graph ?? latestGraph ?? initialGraph));
112
+ if (revision.current === savedRevision) setSaveState('saved');
113
+ } catch {
114
+ // The host owns persistence errors; retain the unsaved note and allow a retry.
96
115
  } finally {
97
116
  setSaving(false);
98
117
  }
@@ -133,6 +152,13 @@ export default function FlowBuilderPage<Kind extends string = FlowNodeKind>(
133
152
  title={title}
134
153
  nodes={nodes}
135
154
  edges={edges}
155
+ graph={graph}
156
+ onGraphChange={(next, change) => {
157
+ revision.current += 1;
158
+ setLatestGraph(next);
159
+ setSaveState('unsaved');
160
+ onGraphChange?.(next, change);
161
+ }}
136
162
  palette={palette}
137
163
  onRun={onRun}
138
164
  consoleEntries={consoleEntries}
@@ -0,0 +1,17 @@
1
+ import { useState } from 'react';
2
+ import { builderNodes, builderEdges, cleanFlowGraph, type FlowGraph } from '@gntik-ai/blocks';
3
+ import WorkflowBuilderPage from '../Page';
4
+
5
+ /** Save the graph currently on screen to browser storage; products own persistence. */
6
+ export default function SaveGraph() {
7
+ const [graph, setGraph] = useState<FlowGraph>(() => cleanFlowGraph({ nodes: builderNodes, edges: builderEdges }));
8
+ return (
9
+ <WorkflowBuilderPage
10
+ graph={graph}
11
+ onGraphChange={setGraph}
12
+ onSave={(next) => {
13
+ localStorage.setItem('workflow-builder-example', JSON.stringify(next));
14
+ }}
15
+ />
16
+ );
17
+ }
@@ -6,7 +6,7 @@ export const meta: TemplateMeta = {
6
6
  priority: 'P3',
7
7
  status: 'experimental',
8
8
  description:
9
- 'Full-screen workflow editor with optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode="run", nodeStatus and onNodeSelect passed to FlowBuilder; codePanel passes readOnly, markers, onValidityChange, onMonacoReady and loader to an optional CodePanel (branch, parallel, sequence and worker-backed YAML examples). The app must import \'@gntik-ai/flow/styles.css\' once for the canvas styles. Optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus and onNodeSelect pass to FlowBuilder unchanged (see its branch, parallel and sequence examples). mode="run" hides Save, Publish and the saved notice, and preserves focus and node selection. Optional codePanel renders a companion CodePanel and passes all its extensions: readOnly, markers, onValidityChange, onMonacoReady and loader; run mode forces its readOnly to true. For example, WorkflowBuilderPage mode="run" nodeStatus={{step:"running"}} onNodeSelect={openDetails}; codePanel={{files,problems:[],loader:yamlLoader,markers}} uses consumer-owned same-origin YAML workers and inline schemas.',
9
+ 'onSave(graph) receives the graph on screen (controlled graph, latest reported edit, or initial starter graph), without selected, dragging or measured node fields. Zero-argument handlers remain valid. Optional graph and onGraphChange pass through to FlowBuilder. Committed edits show actionBar.unsaved, a resolved save shows actionBar.saved, and a rejected save retains unsaved status and releases loading. Edits during a pending save remain unsaved. examples/SaveGraph.tsx shows a controlled graph and onSave={(graph) => localStorage.setItem("workflow-builder-example", JSON.stringify(graph))}; hosts own persistence. Full-screen workflow editor with optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode="run", nodeStatus and onNodeSelect passed to FlowBuilder; codePanel passes readOnly, markers, onValidityChange, onMonacoReady and loader to an optional CodePanel (branch, parallel, sequence and worker-backed YAML examples). The app must import \'@gntik-ai/flow/styles.css\' once for the canvas styles. Optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus and onNodeSelect pass to FlowBuilder unchanged (see its branch, parallel and sequence examples). mode="run" hides Save, Publish and the saved notice, and preserves focus and node selection. Optional codePanel renders a companion CodePanel and passes all its extensions: readOnly, markers, onValidityChange, onMonacoReady and loader; run mode forces its readOnly to true. For example, WorkflowBuilderPage mode="run" nodeStatus={{step:"running"}} onNodeSelect={openDetails}; codePanel={{files,problems:[],loader:yamlLoader,markers}} uses consumer-owned same-origin YAML workers and inline schemas.',
10
10
  layout: 'CanvasLayout',
11
11
  blocks: ['flow-builder', 'code-panel'],
12
12
  };