upgraded lollms flow

This commit is contained in:
Saifeddine ALOUI 2024-08-23 02:57:01 +02:00
parent 630ab5591d
commit f961f1fc8a
5 changed files with 526 additions and 265 deletions

View File

@ -1,101 +1,67 @@
# LollmsFlow Documentation
LollmsFlow is a JavaScript library for creating and visualizing workflows. It allows you to build, connect, and execute nodes in a workflow, as well as visualize the workflow in an interactive SVG-based interface. # Lollms Flow Library - Quick Reference
## Key Components ## Core Classes
1. **WorkflowNode**: Represents a single node in the workflow. 1. `WorkflowNode`: Represents a single node in the workflow.
2. **Workflow**: Manages the entire workflow, including nodes and their connections. - Constructor: `new WorkflowNode(id, name, inputs, outputs, operation, options, x, y)`
3. **WorkflowVisualizer**: Handles the visual representation and interaction of the workflow. - Key methods: `connect()`, `execute()`, `toJSON()`, `fromJSON()`
## Supported Data Types 2. `Workflow`: Manages the entire workflow.
- Key methods: `addNode()`, `connectNodes()`, `execute()`, `toJSON()`, `fromJSON()`
LollmsFlow supports the following data types for node inputs and outputs: 3. `WorkflowVisualizer`: Handles visualization using SVG.
- Constructor: `new WorkflowVisualizer(containerId)`
1. **number**: Represents numeric values (integers or floating-point numbers). - Key methods: `addNode()`, `connectNodes()`, `execute()`, `saveToJSON()`, `loadFromJSON()`
2. **string**: Represents text data.
3. **boolean**: Represents true/false values.
4. **object**: Represents complex data structures or custom objects.
Each data type is associated with a specific color in the workflow visualization:
- number: blue
- string: green
- boolean: red
- object: purple
Any other type not explicitly defined will be represented with a gray color.
## Basic Usage ## Basic Usage
### 0. Import the LollmsFlow Library
First, include the LollmsFlow library in your HTML file: 1. Create a visualizer:
```javascript
const visualizer = new WorkflowVisualizer('container-id');
```
```html 2. Define node operations:
<script src="/lollms_assets/js/lollms_flow"></script> ```javascript
``` const nodeOperations = {
'OperationName': (inputs, options) => ({ output: result })
};
```
3. Create and add nodes:
```javascript
const node = new WorkflowNode(id, name, inputs, outputs, operation, options);
visualizer.addNode(node);
```
### 1. Create a Workflow Visualizer 4. Connect nodes:
```javascript
visualizer.connectNodes(sourceId, sourceOutput, targetId, targetInput);
```
```javascript 5. Execute workflow:
const visualizer = new WorkflowVisualizer("workflow-container"); ```javascript
``` const results = visualizer.execute();
```
### 2. Define Node Operations 6. Save/Load workflow:
```javascript
const json = visualizer.saveToJSON();
visualizer.loadFromJSON(json, nodeOperations);
```
```javascript ## Key Features
const nodeOperations = {
"Add": (inputs) => ({ sum: inputs.a + inputs.b }),
"Multiply": (inputs) => ({ product: inputs.x * inputs.y }),
"Output": (inputs) => console.log("Result:", inputs.result)
};
```
### 3. Create and Add Nodes - SVG-based visualization with drag-and-drop
- Interactive socket connections
- Node options (checkbox, radio, select, file, textarea)
- JSON serialization and local storage integration
- Custom node and socket colors
- Event handling for interactivity
```javascript ## Best Practices
const addNode = new WorkflowNode(0, "Add",
[{ name: "a", type: "number" }, { name: "b", type: "number" }],
[{ name: "sum", type: "number" }],
nodeOperations["Add"], 50, 50
);
visualizer.addNode(addNode);
```
### 4. Connect Nodes - Use unique node IDs
- Define clear input/output types
```javascript - Implement error handling in operations
visualizer.connectNodes(sourceId, sourceOutput, targetId, targetInput); - Use descriptive names for nodes and sockets
```
### 5. Execute the Workflow
```javascript
const results = visualizer.execute();
```
## Advanced Features
- **Save/Load Workflow**: Use `saveToJSON()` and `loadFromJSON()` methods.
- **Local Storage**: Save/load workflows using `saveToLocalStorage()` and `loadFromLocalStorage()`.
- **Drag and Drop**: Nodes can be moved around in the visualizer.
## Key Methods for AI Usage
1. `addNode(node)`: Add a new node to the workflow.
2. `connectNodes(sourceId, sourceOutput, targetId, targetInput)`: Connect two nodes.
3. `execute()`: Run the workflow and get results.
4. `saveToJSON()`: Convert the workflow to a JSON string.
5. `loadFromJSON(json, nodeOperations)`: Load a workflow from a JSON string.
## Tips for AI Implementation
1. Define a set of node types and their operations, using the supported data types.
2. Create nodes dynamically based on user input or predefined templates.
3. Use the visualization features to display the workflow to users, leveraging the color-coding for different data types.
4. Implement save/load functionality to persist workflows.
5. Utilize the execution feature to process data through the workflow.
6. When creating custom nodes, ensure that the input and output types match one of the supported data types for proper visualization and connection validation.
This library provides a flexible framework for creating visual, interactive workflows in web applications. It can be particularly useful for data processing, algorithm visualization, or any application requiring a flow-based interface. The support for various data types allows for diverse and complex workflows to be created and managed effectively.

View File

@ -1,164 +1,267 @@
# Lollms Flow Certainly! I'll provide a comprehensive documentation for the Lollms Flow library. This library seems to be a powerful tool for building and visualizing workflows with nodes and connections. Here's the full documentation:
Lollms Flow is a powerful JavaScript library for building and visualizing workflows of execution. It provides an intuitive way to create, connect, and manage nodes in a workflow, as well as visualize and interact with the workflow through a drag-and-drop interface. # Lollms Flow Library Documentation
## Features ## Overview
- Create custom workflow nodes with inputs and outputs Lollms Flow is a JavaScript library for building, visualizing, and executing workflows. It provides a set of classes and methods to create nodes, connect them, and visualize the resulting workflow in an SVG-based interface.
- Connect nodes to form complex workflows
- Visualize workflows with an interactive SVG-based interface
- Drag-and-drop functionality for easy node positioning
- Save and load workflows to/from JSON
- Execute workflows and obtain results
- Integration with local storage for persistent workflows
- Customizable node operations
## Installation ## Classes
### For Lollms Users ### 1. WorkflowNode
If you're using Lollms with the server running, you can include Lollms Flow directly in your HTML: Represents a single node in the workflow.
```html #### Constructor
<script src="/lollms_assets/js/lollms_flow"></script>
```javascript
new WorkflowNode(id, name, inputs, outputs, operation, options = {}, x = 0, y = 0)
``` ```
### For Non-Lollms Users - `id`: Unique identifier for the node
- `name`: Display name of the node
- `inputs`: Array of input sockets
- `outputs`: Array of output sockets
- `operation`: Function to execute when the node is processed
- `options`: Object containing node-specific options
- `x`, `y`: Initial position of the node in the visualization
If you're not using Lollms or need to specify the full server path: #### Methods
```html - `connect(outputIndex, targetNode, inputIndex)`: Connect this node's output to another node's input
<script src="http://localhost:9600/lollms_assets/js/lollms_flow"></script> - `execute(inputs)`: Execute the node's operation
- `toJSON()`: Convert the node to a JSON representation
- `static fromJSON(json, operation)`: Create a node from a JSON representation
### 2. Workflow
Manages the entire workflow, including nodes and their connections.
#### Constructor
```javascript
new Workflow()
``` ```
Note: Make sure to activate the server of the app in Lollms, or the CORS policy may prevent access. #### Methods
- `addNode(node)`: Add a node to the workflow
- `connectNodes(sourceId, sourceOutput, targetId, targetInput)`: Connect two nodes
- `canConnect(sourceNode, sourceOutput, targetNode, targetInput)`: Check if two nodes can be connected
- `execute()`: Execute the entire workflow
- `toJSON()`: Convert the workflow to a JSON representation
- `static fromJSON(json, nodeOperations)`: Create a workflow from a JSON representation
### 3. WorkflowVisualizer
Handles the visualization of the workflow using SVG.
#### Constructor
```javascript
new WorkflowVisualizer(containerId)
```
- `containerId`: ID of the HTML element to contain the SVG visualization
#### Methods
- `addNode(node)`: Add a node to the workflow and visualize it
- `connectNodes(sourceId, sourceOutput, targetId, targetInput)`: Connect two nodes and visualize the connection
- `execute()`: Execute the workflow
- `saveToJSON()`: Save the workflow to a JSON string
- `loadFromJSON(json, nodeOperations)`: Load a workflow from a JSON string
- `saveToLocalStorage(key)`: Save the workflow to local storage
- `loadFromLocalStorage(key, nodeOperations)`: Load a workflow from local storage
- `redraw()`: Redraw the entire workflow visualization
## Usage ## Usage
1. Create a container for the workflow in your HTML: 1. Create a WorkflowVisualizer instance:
```html
<div id="workflow-container"></div>
```
2. Initialize the WorkflowVisualizer:
```javascript ```javascript
const visualizer = new WorkflowVisualizer("workflow-container"); const visualizer = new WorkflowVisualizer('workflow-container');
``` ```
3. Define node operations: 2. Define node operations:
```javascript ```javascript
const nodeOperations = { const nodeOperations = {
"Add": (inputs) => ({ sum: inputs.a + inputs.b }), 'Add': (inputs) => ({ result: inputs.a + inputs.b }),
"Multiply": (inputs) => ({ product: inputs.x * inputs.y }), 'Multiply': (inputs) => ({ result: inputs.a * inputs.b })
"Output": (inputs) => console.log("Result:", inputs.result)
}; };
``` ```
4. Create and add nodes: 3. Create and add nodes:
```javascript ```javascript
const addNode = new WorkflowNode(0, "Add", [ const addNode = new WorkflowNode('1', 'Add',
{ name: "a", type: "number" }, [{ name: 'a', type: 'number' }, { name: 'b', type: 'number' }],
{ name: "b", type: "number" } [{ name: 'result', type: 'number' }],
], [ nodeOperations['Add']
{ name: "sum", type: "number" } );
], nodeOperations["Add"], 50, 50);
visualizer.addNode(addNode); visualizer.addNode(addNode);
``` ```
5. Connect nodes: 4. Connect nodes:
```javascript ```javascript
visualizer.connectNodes(0, 0, 1, 0); visualizer.connectNodes('1', 0, '2', 0);
``` ```
6. Execute the workflow: 5. Execute the workflow:
```javascript ```javascript
const results = visualizer.execute(); const results = visualizer.execute();
console.log(results);
``` ```
## API Reference 6. Save and load workflows:
### WorkflowNode
Constructor: `WorkflowNode(id, name, inputs, outputs, operation, x = 0, y = 0)`
Methods:
- `connect(outputIndex, targetNode, inputIndex)`
- `execute(inputs)`
- `toJSON()`
- `static fromJSON(json, operation)`
### Workflow
Constructor: `Workflow()`
Methods:
- `addNode(node)`
- `connectNodes(sourceId, sourceOutput, targetId, targetInput)`
- `canConnect(sourceNode, sourceOutput, targetNode, targetInput)`
- `execute()`
- `toJSON()`
- `static fromJSON(json, nodeOperations)`
### WorkflowVisualizer
Constructor: `WorkflowVisualizer(containerId)`
Methods:
- `addNode(node)`
- `connectNodes(sourceId, sourceOutput, targetId, targetInput)`
- `execute()`
- `saveToJSON()`
- `loadFromJSON(json, nodeOperations)`
- `saveToLocalStorage(key)`
- `loadFromLocalStorage(key, nodeOperations)`
- `redraw()`
## Example
```javascript ```javascript
const visualizer = new WorkflowVisualizer("workflow-container"); const json = visualizer.saveToJSON();
visualizer.loadFromJSON(json, nodeOperations);
```
## Advanced version with options
const addNode = new WorkflowNode(0, "Add", [ 1. First, let's define the node operation:
{ name: "a", type: "number" },
{ name: "b", type: "number" }
], [
{ name: "sum", type: "number" }
], nodeOperations["Add"], 50, 50);
const multiplyNode = new WorkflowNode(1, "Multiply", [ ```javascript
{ name: "x", type: "number" }, const nodeOperations = {
{ name: "y", type: "number" } 'TextInput': (inputs, options) => ({ text: options.inputText })
], [ };
{ name: "product", type: "number" }
], nodeOperations["Multiply"], 250, 50);
const outputNode = new WorkflowNode(2, "Output", [
{ name: "result", type: "number" }
], [], nodeOperations["Output"], 450, 50);
visualizer.addNode(addNode);
visualizer.addNode(multiplyNode);
visualizer.addNode(outputNode);
visualizer.connectNodes(0, 0, 1, 0);
visualizer.connectNodes(1, 0, 2, 0);
const results = visualizer.execute();
console.log(results);
``` ```
## Contributing 2. Now, let's create the node with the textarea option:
Contributions are welcome! Please feel free to submit a Pull Request. ```javascript
const textInputNode = new WorkflowNode(
'1', // id
'Text Input', // name
[], // inputs (empty array as we don't need input sockets)
[{ name: 'text', type: 'string' }], // outputs
nodeOperations['TextInput'], // operation
{ // options
inputText: {
type: 'textarea',
value: '' // initial value
}
}
);
```
## License 3. Add the node to the visualizer:
```javascript
visualizer.addNode(textInputNode);
```
4. The WorkflowVisualizer class already handles the creation of the textarea in the `drawOptions` method. When the user types in the textarea, it will automatically update the `options.inputText.value`.
5. To execute the node and get the output:
```javascript
const results = visualizer.execute();
console.log(results['1'].text); // This will log the text entered in the textarea
```
Here's a complete example of how to set this up:
```javascript
// Assume we have already created the WorkflowVisualizer
const visualizer = new WorkflowVisualizer('workflow-container');
// Define the node operation
const nodeOperations = {
'TextInput': (inputs, options) => ({ text: options.inputText.value })
};
// Create the node
const textInputNode = new WorkflowNode(
'1',
'Text Input',
[],
[{ name: 'text', type: 'string' }],
nodeOperations['TextInput'],
{
inputText: {
type: 'textarea',
value: 'Enter your text here'
}
},
50, // x position
50 // y position
);
// Add the node to the visualizer
visualizer.addNode(textInputNode);
// To execute and get the result:
document.getElementById('executeButton').addEventListener('click', () => {
const results = visualizer.execute();
console.log('Output text:', results['1'].text);
});
```
In this setup:
1. We define a 'TextInput' node operation that simply returns the text from the options.
2. We create a WorkflowNode with no inputs, one 'text' output, and an 'inputText' option of type 'textarea'.
3. We add the node to the visualizer, which will create the visual representation including the textarea.
4. When executed, the node will output whatever text is currently in the textarea.
The user can interact with the node in the visualization, typing text into the textarea. When the workflow is executed, it will output the current content of the textarea.
This approach allows for dynamic, user-input text to be part of your workflow, which can then be processed by other nodes or used as the final output of the workflow.
## Features
- Dynamic node creation and connection
- SVG-based visualization
- Drag-and-drop node positioning
- Interactive socket connections
- Node options with various input types (checkbox, radio, select, file, textarea)
- Workflow execution
- JSON serialization and deserialization
- Local storage integration
## Customization
The library allows for extensive customization:
- Node colors can be set individually
- Socket colors are determined by data type
- Node shadows and hover effects are included
- Connection paths are drawn as curved lines
## Event Handling
The library handles various mouse events for interactivity:
- Node dragging
- Socket connection creation
- Socket highlighting on hover
## Best Practices
1. Ensure unique IDs for each node
2. Define clear input and output types for proper connections
3. Implement error handling in node operations
4. Use meaningful names for nodes and sockets
5. Regularly save workflows to prevent data loss
## Limitations
- The library currently does not support undo/redo operations
- Circular dependencies in the workflow are not handled automatically
## Future Enhancements
Potential areas for improvement include:
- Implementing undo/redo functionality
- Adding support for subflows or grouped nodes
- Enhancing the UI with zoom and pan capabilities
- Implementing a node search or categorization system
This documentation provides a comprehensive overview of the Lollms Flow library, its classes, methods, and usage. It should help users understand and effectively utilize the library for building and visualizing workflows.
This project is licensed under the MIT License.

View File

@ -2,12 +2,13 @@
// A library for building workflows of execution // A library for building workflows of execution
// By ParisNeo // By ParisNeo
class WorkflowNode { class WorkflowNode {
constructor(id, name, inputs, outputs, operation, x = 0, y = 0) { constructor(id, name, inputs, outputs, operation, options = {}, x = 0, y = 0) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.inputs = inputs; this.inputs = inputs;
this.outputs = outputs; this.outputs = outputs;
this.operation = operation; this.operation = operation;
this.options = options;
this.inputConnections = {}; this.inputConnections = {};
this.outputConnections = {}; this.outputConnections = {};
this.x = x; this.x = x;
@ -23,7 +24,7 @@ class WorkflowNode {
} }
execute(inputs) { execute(inputs) {
return this.operation(inputs); return this.operation(inputs, this.options);
} }
toJSON() { toJSON() {
@ -32,13 +33,14 @@ class WorkflowNode {
name: this.name, name: this.name,
inputs: this.inputs, inputs: this.inputs,
outputs: this.outputs, outputs: this.outputs,
options: this.options,
x: this.x, x: this.x,
y: this.y y: this.y
}; };
} }
static fromJSON(json, operation) { static fromJSON(json, operation) {
return new WorkflowNode(json.id, json.name, json.inputs, json.outputs, operation, json.x, json.y); return new WorkflowNode(json.id, json.name, json.inputs, json.outputs, operation, json.options, json.x, json.y);
} }
} }
@ -137,7 +139,36 @@ class WorkflowVisualizer {
this.offsetX = 0; this.offsetX = 0;
this.offsetY = 0; this.offsetY = 0;
this.svg.addEventListener('mousedown', this.onMouseDown.bind(this)); this.svg.setAttribute('width', '100%');
this.svg.setAttribute('height', '600px');
this.tempLine = null;
this.startSocket = null;
this.addDefs();
this.setupEventListeners();
}
addDefs() {
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
// Node shadow filter
const filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
filter.setAttribute("id", "dropShadow");
filter.innerHTML = `
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feOffset in="blur" dx="2" dy="2" result="offsetBlur"/>
<feMerge>
<feMergeNode in="offsetBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
`;
defs.appendChild(filter);
this.svg.appendChild(defs);
}
setupEventListeners() {
this.svg.addEventListener('mousemove', this.onMouseMove.bind(this)); this.svg.addEventListener('mousemove', this.onMouseMove.bind(this));
this.svg.addEventListener('mouseup', this.onMouseUp.bind(this)); this.svg.addEventListener('mouseup', this.onMouseUp.bind(this));
} }
@ -155,100 +186,198 @@ class WorkflowVisualizer {
} }
} }
drawNode(node) { drawNode(node) {
const g = document.createElementNS("http://www.w3.org/2000/svg", "g"); const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.setAttribute("transform", `translate(${node.x}, ${node.y})`); g.setAttribute("transform", `translate(${node.x}, ${node.y})`);
g.setAttribute("data-id", node.id); g.setAttribute("data-id", node.id);
g.classList.add("node");
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("width", "120"); rect.setAttribute("width", "140");
rect.setAttribute("height", "60"); rect.setAttribute("height", this.calculateNodeHeight(node));
rect.setAttribute("fill", "lightblue"); rect.setAttribute("rx", "5");
rect.setAttribute("stroke", "black"); rect.setAttribute("ry", "5");
rect.setAttribute("fill", node.color || "#f0f0f0");
rect.setAttribute("stroke", "#333");
rect.setAttribute("stroke-width", "2");
g.appendChild(rect); g.appendChild(rect);
const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", "60"); text.setAttribute("x", "70");
text.setAttribute("y", "35"); text.setAttribute("y", "20");
text.setAttribute("text-anchor", "middle"); text.setAttribute("text-anchor", "middle");
text.setAttribute("font-family", "Arial, sans-serif");
text.setAttribute("font-size", "14");
text.setAttribute("fill", "#333");
text.textContent = node.name; text.textContent = node.name;
g.appendChild(text); g.appendChild(text);
// Draw input sockets this.drawSockets(g, node.inputs, 'input');
node.inputs.forEach((input, index) => { this.drawSockets(g, node.outputs, 'output');
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); this.drawOptions(g, node.options);
circle.setAttribute("cx", "0");
circle.setAttribute("cy", (index + 1) * 15);
circle.setAttribute("r", "5");
circle.setAttribute("fill", this.getColorForType(input.type));
g.appendChild(circle);
});
// Draw output sockets
node.outputs.forEach((output, index) => {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", "120");
circle.setAttribute("cy", (index + 1) * 15);
circle.setAttribute("r", "5");
circle.setAttribute("fill", this.getColorForType(output.type));
g.appendChild(circle);
});
g.addEventListener('mousedown', this.onNodeMouseDown.bind(this));
this.svg.appendChild(g); this.svg.appendChild(g);
this.nodeElements[node.id] = g; this.nodeElements[node.id] = g;
} }
calculateNodeHeight(node) {
const baseHeight = 80;
const optionsHeight = Object.keys(node.options).length * 30;
return baseHeight + optionsHeight;
}
drawOptions(nodeGroup, options) {
let yOffset = 50;
Object.entries(options).forEach(([key, option]) => {
const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
foreignObject.setAttribute("x", "10");
foreignObject.setAttribute("y", yOffset.toString());
foreignObject.setAttribute("width", "120");
foreignObject.setAttribute("height", "25");
const div = document.createElement("div");
div.style.display = "flex";
div.style.alignItems = "center";
const label = document.createElement("label");
label.textContent = key + ": ";
label.style.marginRight = "5px";
div.appendChild(label);
let input;
switch (option.type) {
case 'checkbox':
input = document.createElement("input");
input.type = "checkbox";
input.checked = option.value;
break;
case 'radio':
option.options.forEach(optionValue => {
const radioInput = document.createElement("input");
radioInput.type = "radio";
radioInput.name = key;
radioInput.value = optionValue;
radioInput.checked = option.value === optionValue;
div.appendChild(radioInput);
const radioLabel = document.createElement("label");
radioLabel.textContent = optionValue;
div.appendChild(radioLabel);
});
break;
case 'select':
input = document.createElement("select");
option.options.forEach(optionValue => {
const optionElement = document.createElement("option");
optionElement.value = optionValue;
optionElement.textContent = optionValue;
optionElement.selected = option.value === optionValue;
input.appendChild(optionElement);
});
break;
case 'file':
input = document.createElement("input");
input.type = "file";
break;
case 'textarea':
input = document.createElement("textarea");
input.value = option.value;
input.rows = 3;
break;
default:
input = document.createElement("input");
input.type = "text";
input.value = option.value;
}
if (input) {
input.addEventListener('change', (e) => {
option.value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
});
div.appendChild(input);
}
foreignObject.appendChild(div);
nodeGroup.appendChild(foreignObject);
yOffset += 30;
});
}
drawSockets(nodeGroup, sockets, type) {
sockets.forEach((socket, index) => {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", type === 'input' ? "0" : "140");
circle.setAttribute("cy", (index + 1) * 20);
circle.setAttribute("r", "6");
circle.setAttribute("fill", this.getColorForType(socket.type));
circle.setAttribute("stroke", "#333");
circle.setAttribute("stroke-width", "2");
circle.classList.add("socket", `${type}-socket`);
circle.setAttribute("data-node-id", nodeGroup.getAttribute("data-id"));
circle.setAttribute("data-socket-index", index);
circle.setAttribute("data-socket-type", socket.type);
this.addSocketListeners(circle, type);
nodeGroup.appendChild(circle);
});
}
drawConnection(sourceId, sourceOutput, targetId, targetInput) { drawConnection(sourceId, sourceOutput, targetId, targetInput) {
const sourceNode = this.workflow.nodes[sourceId]; const sourceNode = this.workflow.nodes[sourceId];
const targetNode = this.workflow.nodes[targetId]; const targetNode = this.workflow.nodes[targetId];
const line = document.createElementNS("http://www.w3.org/2000/svg", "path"); const path = this.createConnectionPath(sourceNode, sourceOutput, targetNode, targetInput);
const sourcePosX = sourceNode.x + 120; this.svg.appendChild(path);
const sourcePosY = sourceNode.y + (sourceOutput + 1) * 15; this.connectionElements.push({ path, sourceId, sourceOutput, targetId, targetInput });
const targetPosX = targetNode.x; }
const targetPosY = targetNode.y + (targetInput + 1) * 15;
const midX = (sourcePosX + targetPosX) / 2;
const d = `M ${sourcePosX} ${sourcePosY} C ${midX} ${sourcePosY}, ${midX} ${targetPosY}, ${targetPosX} ${targetPosY}`; createConnectionPath(sourceNode, sourceOutput, targetNode, targetInput) {
line.setAttribute("d", d); const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
line.setAttribute("fill", "none"); const d = this.calculatePathD(sourceNode, sourceOutput, targetNode, targetInput);
line.setAttribute("stroke", "black"); path.setAttribute("d", d);
this.svg.appendChild(line); path.setAttribute("fill", "none");
this.connectionElements.push({ line, sourceId, sourceOutput, targetId, targetInput }); path.setAttribute("stroke", "#666");
path.setAttribute("stroke-width", "2");
return path;
}
calculatePathD(sourceNode, sourceOutput, targetNode, targetInput) {
const sourcePosX = sourceNode.x + 140;
const sourcePosY = sourceNode.y + (sourceOutput + 1) * 20;
const targetPosX = targetNode.x;
const targetPosY = targetNode.y + (targetInput + 1) * 20;
const midX = (sourcePosX + targetPosX) / 2;
return `M ${sourcePosX} ${sourcePosY} C ${midX} ${sourcePosY}, ${midX} ${targetPosY}, ${targetPosX} ${targetPosY}`;
} }
updateConnections() { updateConnections() {
this.connectionElements.forEach(conn => { this.connectionElements.forEach(conn => {
const sourceNode = this.workflow.nodes[conn.sourceId]; const sourceNode = this.workflow.nodes[conn.sourceId];
const targetNode = this.workflow.nodes[conn.targetId]; const targetNode = this.workflow.nodes[conn.targetId];
const sourcePosX = sourceNode.x + 120; const d = this.calculatePathD(sourceNode, conn.sourceOutput, targetNode, conn.targetInput);
const sourcePosY = sourceNode.y + (conn.sourceOutput + 1) * 15; conn.path.setAttribute("d", d);
const targetPosX = targetNode.x;
const targetPosY = targetNode.y + (conn.targetInput + 1) * 15;
const midX = (sourcePosX + targetPosX) / 2;
const d = `M ${sourcePosX} ${sourcePosY} C ${midX} ${sourcePosY}, ${midX} ${targetPosY}, ${targetPosX} ${targetPosY}`;
conn.line.setAttribute("d", d);
}); });
} }
getColorForType(type) { getColorForType(type) {
const colors = { const colors = {
number: "blue", number: "#4285F4",
string: "green", string: "#34A853",
boolean: "red", boolean: "#EA4335",
object: "purple" object: "#FBBC05"
}; };
return colors[type] || "gray"; return colors[type] || "#9E9E9E";
} }
onMouseDown(event) { onNodeMouseDown(event) {
const target = event.target.closest("g"); if (event.target.classList.contains('socket')) return;
if (target) { const nodeElement = event.currentTarget;
this.draggedNode = this.workflow.nodes[target.getAttribute("data-id")]; this.draggedNode = this.workflow.nodes[nodeElement.getAttribute("data-id")];
const rect = target.getBoundingClientRect(); const rect = nodeElement.getBoundingClientRect();
this.offsetX = event.clientX - rect.left; this.offsetX = event.clientX - rect.left;
this.offsetY = event.clientY - rect.top; this.offsetY = event.clientY - rect.top;
} nodeElement.setAttribute("filter", "url(#dropShadow)");
} }
onMouseMove(event) { onMouseMove(event) {
@ -259,10 +388,72 @@ class WorkflowVisualizer {
this.nodeElements[this.draggedNode.id].setAttribute("transform", `translate(${this.draggedNode.x}, ${this.draggedNode.y})`); this.nodeElements[this.draggedNode.id].setAttribute("transform", `translate(${this.draggedNode.x}, ${this.draggedNode.y})`);
this.updateConnections(); this.updateConnections();
} }
if (this.tempLine) {
const rect = this.svg.getBoundingClientRect();
this.updateTempLine(event.clientX - rect.left, event.clientY - rect.top);
}
} }
onMouseUp() { onMouseUp(event) {
this.draggedNode = null; if (this.draggedNode) {
this.nodeElements[this.draggedNode.id].removeAttribute("filter");
this.draggedNode = null;
}
if (this.tempLine && this.startSocket) {
const endSocket = event.target.closest('.input-socket');
if (endSocket && this.canConnect(this.startSocket, endSocket)) {
const sourceId = this.startSocket.getAttribute('data-node-id');
const sourceOutput = this.startSocket.getAttribute('data-socket-index');
const targetId = endSocket.getAttribute('data-node-id');
const targetInput = endSocket.getAttribute('data-socket-index');
this.connectNodes(sourceId, parseInt(sourceOutput), targetId, parseInt(targetInput));
}
this.svg.removeChild(this.tempLine);
this.tempLine = null;
this.startSocket = null;
}
}
addSocketListeners(socket, type) {
socket.addEventListener('mouseenter', () => socket.setAttribute('r', '8'));
socket.addEventListener('mouseleave', () => socket.setAttribute('r', '6'));
if (type === 'output') {
socket.addEventListener('mousedown', this.onSocketMouseDown.bind(this));
}
}
onSocketMouseDown(event) {
event.stopPropagation();
this.startSocket = event.target;
const rect = this.svg.getBoundingClientRect();
const startX = parseFloat(this.startSocket.getAttribute('cx')) + this.startSocket.parentElement.transform.baseVal[0].matrix.e;
const startY = parseFloat(this.startSocket.getAttribute('cy')) + this.startSocket.parentElement.transform.baseVal[0].matrix.f;
this.tempLine = this.createTempLine(startX, startY, event.clientX - rect.left, event.clientY - rect.top);
this.svg.appendChild(this.tempLine);
}
createTempLine(startX, startY, endX, endY) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
const d = `M ${startX} ${startY} C ${(startX + endX) / 2} ${startY}, ${(startX + endX) / 2} ${endY}, ${endX} ${endY}`;
path.setAttribute("d", d);
path.setAttribute("fill", "none");
path.setAttribute("stroke", "#666");
path.setAttribute("stroke-width", "2");
path.setAttribute("stroke-dasharray", "5,5");
return path;
}
updateTempLine(endX, endY) {
const startX = parseFloat(this.startSocket.getAttribute('cx')) + this.startSocket.parentElement.transform.baseVal[0].matrix.e;
const startY = parseFloat(this.startSocket.getAttribute('cy')) + this.startSocket.parentElement.transform.baseVal[0].matrix.f;
const d = `M ${startX} ${startY} C ${(startX + endX) / 2} ${startY}, ${(startX + endX) / 2} ${endY}, ${endX} ${endY}`;
this.tempLine.setAttribute("d", d);
}
canConnect(sourceSocket, targetSocket) {
return sourceSocket.getAttribute('data-socket-type') === targetSocket.getAttribute('data-socket-type') &&
sourceSocket.getAttribute('data-node-id') !== targetSocket.getAttribute('data-node-id');
} }
execute() { execute() {
@ -291,6 +482,7 @@ class WorkflowVisualizer {
redraw() { redraw() {
this.svg.innerHTML = ''; this.svg.innerHTML = '';
this.addDefs();
this.nodeElements = {}; this.nodeElements = {};
this.connectionElements = []; this.connectionElements = [];
this.workflow.nodeList.forEach(node => this.drawNode(node)); this.workflow.nodeList.forEach(node => this.drawNode(node));

@ -1 +1 @@
Subproject commit d6a776d592d5744b310dd6ed8a13771e76934b8f Subproject commit c9e90668d73d20b11fa02036220931e0d30724cb

@ -1 +1 @@
Subproject commit c3a351fb8db5f030cfbc4bcf3091095a21b26fed Subproject commit 567eb2b4e0a62cfb58c1ab55129aa71a29975d2a