hadinicknam commited on
Commit
30a2a5d
·
1 Parent(s): 7f99254

Add the form in a node

Browse files
frontend/src/App.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState, useCallback } from 'react';
2
  import axios from 'axios';
3
  import ReactFlow, {
4
  MiniMap,
@@ -8,8 +8,10 @@ import ReactFlow, {
8
  useEdgesState,
9
  addEdge,
10
  } from 'reactflow';
 
11
 
12
  import 'reactflow/dist/style.css';
 
13
 
14
  const initialNodes = [];
15
  const initialEdges = [];
@@ -20,6 +22,8 @@ function App() {
20
  const [spaceId, setSpaceId] = useState('');
21
  const [error, setError] = useState(null);
22
 
 
 
23
  const onConnect = useCallback(
24
  (params) => setEdges((eds) => addEdge(params, eds)),
25
  [setEdges],
@@ -30,23 +34,28 @@ function App() {
30
  setError('Please enter a Hugging Face Space ID');
31
  return;
32
  }
33
- setError(null); // Clear previous errors
34
  try {
35
  const response = await axios.get(`http://localhost:8000/api/space/?space_id=${spaceId}`);
36
  const { endpoints } = response.data;
37
 
38
- // For simplicity, we'll just use the first priority endpoint
39
  const priorityEndpoint = endpoints.find(e => e.category === 'priority') || endpoints[0];
40
 
41
  if (priorityEndpoint) {
42
  const newNode = {
43
  id: `node-${Date.now()}`,
44
- type: 'default',
45
- data: { label: `${spaceId}
46
- (${priorityEndpoint.api_name})` },
 
 
 
 
47
  position: { x: 250, y: 5 },
48
  };
49
  setNodes([newNode]);
 
 
50
  }
51
 
52
  } catch (err) {
@@ -80,6 +89,7 @@ function App() {
80
  onNodesChange={onNodesChange}
81
  onEdgesChange={onEdgesChange}
82
  onConnect={onConnect}
 
83
  >
84
  <Controls />
85
  <MiniMap />
 
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
  import axios from 'axios';
3
  import ReactFlow, {
4
  MiniMap,
 
8
  useEdgesState,
9
  addEdge,
10
  } from 'reactflow';
11
+ import CustomNode from './CustomNode';
12
 
13
  import 'reactflow/dist/style.css';
14
+ import './CustomNode.css';
15
 
16
  const initialNodes = [];
17
  const initialEdges = [];
 
22
  const [spaceId, setSpaceId] = useState('');
23
  const [error, setError] = useState(null);
24
 
25
+ const nodeTypes = useMemo(() => ({ custom: CustomNode }), []);
26
+
27
  const onConnect = useCallback(
28
  (params) => setEdges((eds) => addEdge(params, eds)),
29
  [setEdges],
 
34
  setError('Please enter a Hugging Face Space ID');
35
  return;
36
  }
37
+ setError(null);
38
  try {
39
  const response = await axios.get(`http://localhost:8000/api/space/?space_id=${spaceId}`);
40
  const { endpoints } = response.data;
41
 
 
42
  const priorityEndpoint = endpoints.find(e => e.category === 'priority') || endpoints[0];
43
 
44
  if (priorityEndpoint) {
45
  const newNode = {
46
  id: `node-${Date.now()}`,
47
+ type: 'custom', // Use the custom node type
48
+ data: {
49
+ label: spaceId,
50
+ apiName: priorityEndpoint.api_name,
51
+ inputs: priorityEndpoint.inputs,
52
+ outputs: priorityEndpoint.outputs,
53
+ },
54
  position: { x: 250, y: 5 },
55
  };
56
  setNodes([newNode]);
57
+ } else {
58
+ setError("No usable API endpoints found for this space.");
59
  }
60
 
61
  } catch (err) {
 
89
  onNodesChange={onNodesChange}
90
  onEdgesChange={onEdgesChange}
91
  onConnect={onConnect}
92
+ nodeTypes={nodeTypes} // Register the custom node
93
  >
94
  <Controls />
95
  <MiniMap />
frontend/src/CustomNode.css ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .custom-node {
2
+ border: 1px solid #777;
3
+ border-radius: 8px;
4
+ background: #f9f9f9;
5
+ width: 300px;
6
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
7
+ }
8
+
9
+ .custom-node-header {
10
+ background: #e0e0e0;
11
+ padding: 10px;
12
+ border-top-left-radius: 7px;
13
+ border-top-right-radius: 7px;
14
+ border-bottom: 1px solid #ccc;
15
+ }
16
+
17
+ .custom-node-header strong {
18
+ font-size: 14px;
19
+ display: block;
20
+ }
21
+
22
+ .custom-node-header p {
23
+ font-family: monospace;
24
+ font-size: 12px;
25
+ color: #333;
26
+ margin: 4px 0 0;
27
+ }
28
+
29
+ .custom-node-content {
30
+ padding: 10px;
31
+ }
32
+
33
+ .custom-node-input-row {
34
+ display: flex;
35
+ flex-direction: column;
36
+ margin-bottom: 10px;
37
+ }
38
+
39
+ .custom-node-input-row:last-child {
40
+ margin-bottom: 0;
41
+ }
42
+
43
+ .custom-node-input-row label {
44
+ font-size: 12px;
45
+ margin-bottom: 4px;
46
+ font-weight: bold;
47
+ color: #555;
48
+ }
49
+
50
+ .custom-node-input-row input[type="text"],
51
+ .custom-node-input-row input[type="number"],
52
+ .custom-node-input-row textarea,
53
+ .custom-node-input-row select {
54
+ width: 100%;
55
+ padding: 6px;
56
+ border: 1px solid #ccc;
57
+ border-radius: 4px;
58
+ box-sizing: border-box;
59
+ font-size: 12px;
60
+ }
61
+
62
+ .custom-node-input-row input[type="range"] {
63
+ width: 100%;
64
+ }
65
+
66
+ .custom-node-input-row.checkbox {
67
+ flex-direction: row;
68
+ align-items: center;
69
+ }
70
+
71
+ .custom-node-input-row.checkbox input {
72
+ margin-right: 8px;
73
+ }
74
+
75
+ .custom-node-input-row.checkbox label {
76
+ margin-bottom: 0;
77
+ font-weight: normal;
78
+ }
frontend/src/CustomNode.js ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Handle, Position } from 'reactflow';
3
+ import './CustomNode.css';
4
+
5
+ const renderInput = (input) => {
6
+ const { type, label, props = {} } = input;
7
+ const id = `input-${input.id}`;
8
+
9
+ switch (type) {
10
+ case 'textbox':
11
+ return (
12
+ <div key={id} className="custom-node-input-row">
13
+ <label htmlFor={id}>{label}</label>
14
+ <textarea id={id} rows={2} defaultValue={props.value || ''} />
15
+ </div>
16
+ );
17
+ case 'number':
18
+ return (
19
+ <div key={id} className="custom-node-input-row">
20
+ <label htmlFor={id}>{label}</label>
21
+ <input id={id} type="number" defaultValue={props.value || 0} />
22
+ </div>
23
+ );
24
+ case 'slider':
25
+ return (
26
+ <div key={id} className="custom-node-input-row">
27
+ <label htmlFor={id}>{label} ({props.value || props.minimum})</label>
28
+ <input
29
+ id={id}
30
+ type="range"
31
+ min={props.minimum || 0}
32
+ max={props.maximum || 100}
33
+ defaultValue={props.value || props.minimum}
34
+ />
35
+ </div>
36
+ );
37
+ case 'checkbox':
38
+ return (
39
+ <div key={id} className="custom-node-input-row checkbox">
40
+ <input id={id} type="checkbox" defaultChecked={props.value || false} />
41
+ <label htmlFor={id}>{label}</label>
42
+ </div>
43
+ );
44
+ case 'dropdown':
45
+ return (
46
+ <div key={id} className="custom-node-input-row">
47
+ <label htmlFor={id}>{label}</label>
48
+ <select id={id} defaultValue={props.value}>
49
+ {props.choices?.map((choice, index) => (
50
+ <option key={index} value={choice}>
51
+ {choice}
52
+ </option>
53
+ ))}
54
+ </select>
55
+ </div>
56
+ );
57
+ case 'image':
58
+ case 'audio':
59
+ case 'video':
60
+ case 'file':
61
+ return (
62
+ <div key={id} className="custom-node-input-row">
63
+ <label htmlFor={id}>{label} ({type})</label>
64
+ <input id={id} type="file" />
65
+ </div>
66
+ );
67
+ default:
68
+ return (
69
+ <div key={id} className="custom-node-input-row">
70
+ <label>{label} ({type})</label>
71
+ <span>Unsupported type</span>
72
+ </div>
73
+ );
74
+ }
75
+ };
76
+
77
+ const CustomNode = ({ data }) => {
78
+ return (
79
+ <div className="custom-node">
80
+ <Handle type="target" position={Position.Top} />
81
+ <div className="custom-node-header">
82
+ <strong>{data.label}</strong>
83
+ <p>/{data.apiName}</p>
84
+ </div>
85
+ <div className="custom-node-content">
86
+ {data.inputs?.map(renderInput)}
87
+ </div>
88
+ <Handle type="source" position={Position.Bottom} />
89
+ </div>
90
+ );
91
+ };
92
+
93
+ export default CustomNode;