Spaces:
Sleeping
Sleeping
Commit ·
30a2a5d
1
Parent(s): 7f99254
Add the form in a node
Browse files- frontend/src/App.js +16 -6
- frontend/src/CustomNode.css +78 -0
- frontend/src/CustomNode.js +93 -0
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);
|
| 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: '
|
| 45 |
-
data: {
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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;
|