darabos commited on
Commit
342bc0e
·
1 Parent(s): 2148b2a

Add node-level error boundary.

Browse files
lynxkite-app/web/package-lock.json CHANGED
@@ -27,6 +27,7 @@
27
  "monaco-editor": "^0.52.2",
28
  "react": "^18.3.1",
29
  "react-dom": "^18.3.1",
 
30
  "react-markdown": "^9.0.1",
31
  "react-router-dom": "^7.0.2",
32
  "swr": "^2.2.5",
@@ -310,6 +311,18 @@
310
  "node": ">=6.0.0"
311
  }
312
  },
 
 
 
 
 
 
 
 
 
 
 
 
313
  "node_modules/@babel/template": {
314
  "version": "7.26.9",
315
  "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
@@ -6155,6 +6168,18 @@
6155
  "react": "^18.3.1"
6156
  }
6157
  },
 
 
 
 
 
 
 
 
 
 
 
 
6158
  "node_modules/react-markdown": {
6159
  "version": "9.0.1",
6160
  "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
@@ -6259,6 +6284,12 @@
6259
  "node": ">=8.10.0"
6260
  }
6261
  },
 
 
 
 
 
 
6262
  "node_modules/remark-parse": {
6263
  "version": "11.0.0",
6264
  "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
 
27
  "monaco-editor": "^0.52.2",
28
  "react": "^18.3.1",
29
  "react-dom": "^18.3.1",
30
+ "react-error-boundary": "^5.0.0",
31
  "react-markdown": "^9.0.1",
32
  "react-router-dom": "^7.0.2",
33
  "swr": "^2.2.5",
 
311
  "node": ">=6.0.0"
312
  }
313
  },
314
+ "node_modules/@babel/runtime": {
315
+ "version": "7.27.0",
316
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
317
+ "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
318
+ "license": "MIT",
319
+ "dependencies": {
320
+ "regenerator-runtime": "^0.14.0"
321
+ },
322
+ "engines": {
323
+ "node": ">=6.9.0"
324
+ }
325
+ },
326
  "node_modules/@babel/template": {
327
  "version": "7.26.9",
328
  "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
 
6168
  "react": "^18.3.1"
6169
  }
6170
  },
6171
+ "node_modules/react-error-boundary": {
6172
+ "version": "5.0.0",
6173
+ "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz",
6174
+ "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==",
6175
+ "license": "MIT",
6176
+ "dependencies": {
6177
+ "@babel/runtime": "^7.12.5"
6178
+ },
6179
+ "peerDependencies": {
6180
+ "react": ">=16.13.1"
6181
+ }
6182
+ },
6183
  "node_modules/react-markdown": {
6184
  "version": "9.0.1",
6185
  "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
 
6284
  "node": ">=8.10.0"
6285
  }
6286
  },
6287
+ "node_modules/regenerator-runtime": {
6288
+ "version": "0.14.1",
6289
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
6290
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
6291
+ "license": "MIT"
6292
+ },
6293
  "node_modules/remark-parse": {
6294
  "version": "11.0.0",
6295
  "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
lynxkite-app/web/package.json CHANGED
@@ -30,6 +30,7 @@
30
  "monaco-editor": "^0.52.2",
31
  "react": "^18.3.1",
32
  "react-dom": "^18.3.1",
 
33
  "react-markdown": "^9.0.1",
34
  "react-router-dom": "^7.0.2",
35
  "swr": "^2.2.5",
 
30
  "monaco-editor": "^0.52.2",
31
  "react": "^18.3.1",
32
  "react-dom": "^18.3.1",
33
+ "react-error-boundary": "^5.0.0",
34
  "react-markdown": "^9.0.1",
35
  "react-router-dom": "^7.0.2",
36
  "swr": "^2.2.5",
lynxkite-app/web/src/workspace/nodes/GraphCreationNode.tsx CHANGED
@@ -49,7 +49,7 @@ function relationsToDict(relations: any[]) {
49
 
50
  export type UpdateOptions = { delay?: number };
51
 
52
- export default function NodeWithGraphCreationView(props: any) {
53
  const reactFlow = useReactFlow();
54
  const [open, setOpen] = useState({} as { [name: string]: boolean });
55
  const display = props.data.display?.value;
@@ -229,68 +229,68 @@ export default function NodeWithGraphCreationView(props: any) {
229
  }
230
 
231
  return (
232
- <LynxKiteNode {...props}>
233
- <div className="graph-creation-view">
234
- <div className="graph-tables">
235
- <div className="graph-table-header">Node Tables</div>
236
- {display && [
237
- Object.entries(tables).map(([name, df]: [string, any]) => (
238
- <React.Fragment key={name}>
239
- {!singleTable && (
240
- <div
241
- key={`${name}-header`}
242
- className="df-head"
243
- onClick={() => setOpen({ ...open, [name]: !open[name] })}
244
- >
245
- {name}
246
- </div>
247
- )}
248
- {(singleTable || open[name]) && displayTable(name, df)}
249
- </React.Fragment>
250
- )),
251
- Object.entries(display.others || {}).map(([name, o]) => (
252
- <>
253
- <div
254
- key={name}
255
- className="df-head"
256
- onClick={() => setOpen({ ...open, [name]: !open[name] })}
257
- >
258
- {name}
259
- </div>
260
- {open[name] && <pre>{(o as any).toString()}</pre>}
261
- </>
262
- )),
263
- ]}
264
- </div>
265
- <div className="graph-relations">
266
- <div className="graph-table-header">
267
- Relationships
268
- <button className="add-relationship-button" onClick={(_) => addRelation()}>
269
- +
270
- </button>
271
- </div>
272
- {relations &&
273
- Object.entries(relations).map(([name, relation]: [string, any]) => (
274
- <React.Fragment key={name}>
275
  <div
276
  key={`${name}-header`}
277
  className="df-head"
278
  onClick={() => setOpen({ ...open, [name]: !open[name] })}
279
  >
280
  {name}
281
- <button
282
- onClick={() => {
283
- deleteRelation(relation);
284
- }}
285
- >
286
- <Trash />
287
- </button>
288
  </div>
289
- {(singleRelation || open[name]) && displayRelation(relation)}
290
- </React.Fragment>
291
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  </div>
294
- </LynxKiteNode>
295
  );
296
  }
 
 
 
49
 
50
  export type UpdateOptions = { delay?: number };
51
 
52
+ function NodeWithGraphCreationView(props: any) {
53
  const reactFlow = useReactFlow();
54
  const [open, setOpen] = useState({} as { [name: string]: boolean });
55
  const display = props.data.display?.value;
 
229
  }
230
 
231
  return (
232
+ <div className="graph-creation-view">
233
+ <div className="graph-tables">
234
+ <div className="graph-table-header">Node Tables</div>
235
+ {display && [
236
+ Object.entries(tables).map(([name, df]: [string, any]) => (
237
+ <React.Fragment key={name}>
238
+ {!singleTable && (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  <div
240
  key={`${name}-header`}
241
  className="df-head"
242
  onClick={() => setOpen({ ...open, [name]: !open[name] })}
243
  >
244
  {name}
 
 
 
 
 
 
 
245
  </div>
246
+ )}
247
+ {(singleTable || open[name]) && displayTable(name, df)}
248
+ </React.Fragment>
249
+ )),
250
+ Object.entries(display.others || {}).map(([name, o]) => (
251
+ <>
252
+ <div
253
+ key={name}
254
+ className="df-head"
255
+ onClick={() => setOpen({ ...open, [name]: !open[name] })}
256
+ >
257
+ {name}
258
+ </div>
259
+ {open[name] && <pre>{(o as any).toString()}</pre>}
260
+ </>
261
+ )),
262
+ ]}
263
+ </div>
264
+ <div className="graph-relations">
265
+ <div className="graph-table-header">
266
+ Relationships
267
+ <button className="add-relationship-button" onClick={(_) => addRelation()}>
268
+ +
269
+ </button>
270
  </div>
271
+ {relations &&
272
+ Object.entries(relations).map(([name, relation]: [string, any]) => (
273
+ <React.Fragment key={name}>
274
+ <div
275
+ key={`${name}-header`}
276
+ className="df-head"
277
+ onClick={() => setOpen({ ...open, [name]: !open[name] })}
278
+ >
279
+ {name}
280
+ <button
281
+ onClick={() => {
282
+ deleteRelation(relation);
283
+ }}
284
+ >
285
+ <Trash />
286
+ </button>
287
+ </div>
288
+ {(singleRelation || open[name]) && displayRelation(relation)}
289
+ </React.Fragment>
290
+ ))}
291
  </div>
292
+ </div>
293
  );
294
  }
295
+
296
+ export default LynxKiteNode(NodeWithGraphCreationView);
lynxkite-app/web/src/workspace/nodes/LynxKiteNode.tsx CHANGED
@@ -1,6 +1,9 @@
1
  import { Handle, NodeResizeControl, type Position, useReactFlow } from "@xyflow/react";
 
2
  // @ts-ignore
3
  import ChevronDownRight from "~icons/tabler/chevron-down-right.jsx";
 
 
4
 
5
  interface LynxKiteNodeProps {
6
  id: string;
@@ -40,7 +43,7 @@ function getHandles(inputs: object, outputs: object) {
40
  return handles;
41
  }
42
 
43
- export default function LynxKiteNode(props: LynxKiteNodeProps) {
44
  const reactFlow = useReactFlow();
45
  const data = props.data;
46
  const expanded = !data.collapsed;
@@ -72,7 +75,16 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
72
  {expanded && (
73
  <>
74
  {data.error && <div className="error">{data.error}</div>}
75
- {props.children}
 
 
 
 
 
 
 
 
 
76
  <NodeResizeControl
77
  minWidth={100}
78
  minHeight={50}
@@ -101,3 +113,13 @@ export default function LynxKiteNode(props: LynxKiteNodeProps) {
101
  </div>
102
  );
103
  }
 
 
 
 
 
 
 
 
 
 
 
1
  import { Handle, NodeResizeControl, type Position, useReactFlow } from "@xyflow/react";
2
+ import { ErrorBoundary } from "react-error-boundary";
3
  // @ts-ignore
4
  import ChevronDownRight from "~icons/tabler/chevron-down-right.jsx";
5
+ // @ts-ignore
6
+ import Skull from "~icons/tabler/skull.jsx";
7
 
8
  interface LynxKiteNodeProps {
9
  id: string;
 
43
  return handles;
44
  }
45
 
46
+ function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
47
  const reactFlow = useReactFlow();
48
  const data = props.data;
49
  const expanded = !data.collapsed;
 
75
  {expanded && (
76
  <>
77
  {data.error && <div className="error">{data.error}</div>}
78
+ <ErrorBoundary
79
+ fallback={
80
+ <p className="error" style={{ display: "flex", alignItems: "center", gap: 8 }}>
81
+ <Skull style={{ fontSize: 20 }} />
82
+ Failed to display this node.
83
+ </p>
84
+ }
85
+ >
86
+ {props.children}
87
+ </ErrorBoundary>
88
  <NodeResizeControl
89
  minWidth={100}
90
  minHeight={50}
 
113
  </div>
114
  );
115
  }
116
+
117
+ export default function LynxKiteNode(Component: React.ComponentType<any>) {
118
+ return (props: any) => {
119
+ return (
120
+ <LynxKiteNodeComponent {...props}>
121
+ <Component {...props} />
122
+ </LynxKiteNodeComponent>
123
+ );
124
+ };
125
+ }
lynxkite-app/web/src/workspace/nodes/NodeWithImage.tsx CHANGED
@@ -1,4 +1,5 @@
1
- import NodeWithParams from "./NodeWithParams";
 
2
 
3
  const NodeWithImage = (props: any) => {
4
  return (
@@ -8,4 +9,4 @@ const NodeWithImage = (props: any) => {
8
  );
9
  };
10
 
11
- export default NodeWithImage;
 
1
+ import LynxKiteNode from "./LynxKiteNode";
2
+ import { NodeWithParams } from "./NodeWithParams";
3
 
4
  const NodeWithImage = (props: any) => {
5
  return (
 
9
  );
10
  };
11
 
12
+ export default LynxKiteNode(NodeWithImage);
lynxkite-app/web/src/workspace/nodes/NodeWithMolecule.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import React, { useEffect, type CSSProperties } from "react";
2
- import NodeWithParams from "./NodeWithParams";
 
3
 
4
  const NodeWithMolecule = (props: any) => {
5
  const containerRef = React.useRef<HTMLDivElement>(null);
@@ -70,4 +71,4 @@ const NodeWithMolecule = (props: any) => {
70
  );
71
  };
72
 
73
- export default NodeWithMolecule;
 
1
  import React, { useEffect, type CSSProperties } from "react";
2
+ import LynxKiteNode from "./LynxKiteNode";
3
+ import { NodeWithParams } from "./NodeWithParams";
4
 
5
  const NodeWithMolecule = (props: any) => {
6
  const containerRef = React.useRef<HTMLDivElement>(null);
 
71
  );
72
  };
73
 
74
+ export default LynxKiteNode(NodeWithMolecule);
lynxkite-app/web/src/workspace/nodes/NodeWithParams.tsx CHANGED
@@ -8,7 +8,7 @@ import NodeParameter from "./NodeParameter";
8
 
9
  export type UpdateOptions = { delay?: number };
10
 
11
- function NodeWithParams(props: any) {
12
  const reactFlow = useReactFlow();
13
  const metaParams = props.data.meta?.params;
14
  const [collapsed, setCollapsed] = React.useState(props.collapsed);
@@ -34,7 +34,7 @@ function NodeWithParams(props: any) {
34
  const params = props.data?.params ? Object.entries(props.data.params) : [];
35
 
36
  return (
37
- <LynxKiteNode {...props}>
38
  {props.collapsed && params.length > 0 && (
39
  <div className="params-expander" onClick={() => setCollapsed(!collapsed)}>
40
  <Triangle className={`flippy ${collapsed ? "flippy-90" : ""}`} />
@@ -65,8 +65,8 @@ function NodeWithParams(props: any) {
65
  ),
66
  )}
67
  {props.children}
68
- </LynxKiteNode>
69
  );
70
  }
71
 
72
- export default NodeWithParams;
 
8
 
9
  export type UpdateOptions = { delay?: number };
10
 
11
+ export function NodeWithParams(props: any) {
12
  const reactFlow = useReactFlow();
13
  const metaParams = props.data.meta?.params;
14
  const [collapsed, setCollapsed] = React.useState(props.collapsed);
 
34
  const params = props.data?.params ? Object.entries(props.data.params) : [];
35
 
36
  return (
37
+ <>
38
  {props.collapsed && params.length > 0 && (
39
  <div className="params-expander" onClick={() => setCollapsed(!collapsed)}>
40
  <Triangle className={`flippy ${collapsed ? "flippy-90" : ""}`} />
 
65
  ),
66
  )}
67
  {props.children}
68
+ </>
69
  );
70
  }
71
 
72
+ export default LynxKiteNode(NodeWithParams);
lynxkite-app/web/src/workspace/nodes/NodeWithTableView.tsx CHANGED
@@ -17,7 +17,7 @@ function toMD(v: any): string {
17
 
18
  type OpenState = { [name: string]: boolean };
19
 
20
- export default function NodeWithTableView(props: any) {
21
  const reactFlow = useReactFlow();
22
  const [open, setOpen] = useState((props.data?.params?._tables_open ?? {}) as OpenState);
23
  const display = props.data.display?.value;
@@ -38,7 +38,7 @@ export default function NodeWithTableView(props: any) {
38
  });
39
  }
40
  return (
41
- <LynxKiteNode {...props}>
42
  {display && [
43
  dfs.map(([name, df]: [string, any]) => (
44
  <React.Fragment key={name}>
@@ -75,6 +75,8 @@ export default function NodeWithTableView(props: any) {
75
  </>
76
  )),
77
  ]}
78
- </LynxKiteNode>
79
  );
80
  }
 
 
 
17
 
18
  type OpenState = { [name: string]: boolean };
19
 
20
+ function NodeWithTableView(props: any) {
21
  const reactFlow = useReactFlow();
22
  const [open, setOpen] = useState((props.data?.params?._tables_open ?? {}) as OpenState);
23
  const display = props.data.display?.value;
 
38
  });
39
  }
40
  return (
41
+ <>
42
  {display && [
43
  dfs.map(([name, df]: [string, any]) => (
44
  <React.Fragment key={name}>
 
75
  </>
76
  )),
77
  ]}
78
+ </>
79
  );
80
  }
81
+
82
+ export default LynxKiteNode(NodeWithTableView);
lynxkite-app/web/src/workspace/nodes/NodeWithVisualization.tsx CHANGED
@@ -1,8 +1,9 @@
1
  import React, { useEffect } from "react";
2
- import NodeWithParams from "./NodeWithParams";
 
3
  const echarts = await import("echarts");
4
 
5
- const NodeWithVisualization = (props: any) => {
6
  const chartsRef = React.useRef<HTMLDivElement>(null);
7
  const chartsInstanceRef = React.useRef<echarts.ECharts>();
8
  useEffect(() => {
@@ -38,6 +39,6 @@ const NodeWithVisualization = (props: any) => {
38
  <div style={vizStyle} ref={chartsRef} />
39
  </NodeWithParams>
40
  );
41
- };
42
 
43
- export default NodeWithVisualization;
 
1
  import React, { useEffect } from "react";
2
+ import LynxKiteNode from "./LynxKiteNode";
3
+ import { NodeWithParams } from "./NodeWithParams";
4
  const echarts = await import("echarts");
5
 
6
+ function NodeWithVisualization(props: any) {
7
  const chartsRef = React.useRef<HTMLDivElement>(null);
8
  const chartsInstanceRef = React.useRef<echarts.ECharts>();
9
  useEffect(() => {
 
39
  <div style={vizStyle} ref={chartsRef} />
40
  </NodeWithParams>
41
  );
42
+ }
43
 
44
+ export default LynxKiteNode(NodeWithVisualization);