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.
2. **Workflow**: Manages the entire workflow, including nodes and their connections.
3. **WorkflowVisualizer**: Handles the visual representation and interaction of the workflow.
1. `WorkflowNode`: Represents a single node in the workflow.
- Constructor: `new WorkflowNode(id, name, inputs, outputs, operation, options, x, y)`
- 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:
1. **number**: Represents numeric values (integers or floating-point numbers).
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.
3. `WorkflowVisualizer`: Handles visualization using SVG.
- Constructor: `new WorkflowVisualizer(containerId)`
- Key methods: `addNode()`, `connectNodes()`, `execute()`, `saveToJSON()`, `loadFromJSON()`
## 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
<script src="/lollms_assets/js/lollms_flow"></script>
```
2. Define node operations:
```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
const visualizer = new WorkflowVisualizer("workflow-container");
```
5. Execute workflow:
```javascript
const results = visualizer.execute();
```
### 2. Define Node Operations
6. Save/Load workflow:
```javascript
const json = visualizer.saveToJSON();
visualizer.loadFromJSON(json, nodeOperations);
```
```javascript
const nodeOperations = {
"Add": (inputs) => ({ sum: inputs.a + inputs.b }),
"Multiply": (inputs) => ({ product: inputs.x * inputs.y }),
"Output": (inputs) => console.log("Result:", inputs.result)
};
```
## Key Features
### 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
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);
```
## Best Practices
### 4. Connect Nodes
```javascript
visualizer.connectNodes(sourceId, sourceOutput, targetId, targetInput);
```
### 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.
- Use unique node IDs
- Define clear input/output types
- Implement error handling in operations
- Use descriptive names for nodes and sockets

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
- 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
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.
## 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
<script src="/lollms_assets/js/lollms_flow"></script>
#### Constructor
```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
<script src="http://localhost:9600/lollms_assets/js/lollms_flow"></script>
- `connect(outputIndex, targetNode, inputIndex)`: Connect this node's output to another node's input
- `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
1. Create a container for the workflow in your HTML:
```html
<div id="workflow-container"></div>
```
2. Initialize the WorkflowVisualizer:
1. Create a WorkflowVisualizer instance:
```javascript
const visualizer = new WorkflowVisualizer("workflow-container");
const visualizer = new WorkflowVisualizer('workflow-container');
```
3. Define node operations:
2. Define node operations:
```javascript
const nodeOperations = {
"Add": (inputs) => ({ sum: inputs.a + inputs.b }),
"Multiply": (inputs) => ({ product: inputs.x * inputs.y }),
"Output": (inputs) => console.log("Result:", inputs.result)
'Add': (inputs) => ({ result: inputs.a + inputs.b }),
'Multiply': (inputs) => ({ result: inputs.a * inputs.b })
};
```
4. Create and add nodes:
3. Create and add nodes:
```javascript
const addNode = new WorkflowNode(0, "Add", [
{ name: "a", type: "number" },
{ name: "b", type: "number" }
], [
{ name: "sum", type: "number" }
], nodeOperations["Add"], 50, 50);
const addNode = new WorkflowNode('1', 'Add',
[{ name: 'a', type: 'number' }, { name: 'b', type: 'number' }],
[{ name: 'result', type: 'number' }],
nodeOperations['Add']
);
visualizer.addNode(addNode);
```
5. Connect nodes:
4. Connect nodes:
```javascript
visualizer.connectNodes(0, 0, 1, 0);
visualizer.connectNodes('1', 0, '2', 0);
```
6. Execute the workflow:
5. Execute the workflow:
```javascript
const results = visualizer.execute();
console.log(results);
```
## API Reference
### 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
6. Save and load workflows:
```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", [
{ name: "a", type: "number" },
{ name: "b", type: "number" }
], [
{ name: "sum", type: "number" }
], nodeOperations["Add"], 50, 50);
1. First, let's define the node operation:
const multiplyNode = new WorkflowNode(1, "Multiply", [
{ name: "x", type: "number" },
{ name: "y", type: "number" }
], [
{ 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);
```javascript
const nodeOperations = {
'TextInput': (inputs, options) => ({ text: options.inputText })
};
```
## 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
// By ParisNeo
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.name = name;
this.inputs = inputs;
this.outputs = outputs;
this.operation = operation;
this.options = options;
this.inputConnections = {};
this.outputConnections = {};
this.x = x;
@ -23,7 +24,7 @@ class WorkflowNode {
}
execute(inputs) {
return this.operation(inputs);
return this.operation(inputs, this.options);
}
toJSON() {
@ -32,13 +33,14 @@ class WorkflowNode {
name: this.name,
inputs: this.inputs,
outputs: this.outputs,
options: this.options,
x: this.x,
y: this.y
};
}
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.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('mouseup', this.onMouseUp.bind(this));
}
@ -155,100 +186,198 @@ class WorkflowVisualizer {
}
}
drawNode(node) {
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.setAttribute("transform", `translate(${node.x}, ${node.y})`);
g.setAttribute("data-id", node.id);
g.classList.add("node");
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("width", "120");
rect.setAttribute("height", "60");
rect.setAttribute("fill", "lightblue");
rect.setAttribute("stroke", "black");
rect.setAttribute("width", "140");
rect.setAttribute("height", this.calculateNodeHeight(node));
rect.setAttribute("rx", "5");
rect.setAttribute("ry", "5");
rect.setAttribute("fill", node.color || "#f0f0f0");
rect.setAttribute("stroke", "#333");
rect.setAttribute("stroke-width", "2");
g.appendChild(rect);
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.setAttribute("x", "60");
text.setAttribute("y", "35");
text.setAttribute("x", "70");
text.setAttribute("y", "20");
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;
g.appendChild(text);
// Draw input sockets
node.inputs.forEach((input, index) => {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
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);
});
this.drawSockets(g, node.inputs, 'input');
this.drawSockets(g, node.outputs, 'output');
this.drawOptions(g, node.options);
g.addEventListener('mousedown', this.onNodeMouseDown.bind(this));
this.svg.appendChild(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) {
const sourceNode = this.workflow.nodes[sourceId];
const targetNode = this.workflow.nodes[targetId];
const line = document.createElementNS("http://www.w3.org/2000/svg", "path");
const sourcePosX = sourceNode.x + 120;
const sourcePosY = sourceNode.y + (sourceOutput + 1) * 15;
const targetPosX = targetNode.x;
const targetPosY = targetNode.y + (targetInput + 1) * 15;
const midX = (sourcePosX + targetPosX) / 2;
const path = this.createConnectionPath(sourceNode, sourceOutput, targetNode, targetInput);
this.svg.appendChild(path);
this.connectionElements.push({ path, sourceId, sourceOutput, targetId, targetInput });
}
const d = `M ${sourcePosX} ${sourcePosY} C ${midX} ${sourcePosY}, ${midX} ${targetPosY}, ${targetPosX} ${targetPosY}`;
line.setAttribute("d", d);
line.setAttribute("fill", "none");
line.setAttribute("stroke", "black");
this.svg.appendChild(line);
this.connectionElements.push({ line, sourceId, sourceOutput, targetId, targetInput });
createConnectionPath(sourceNode, sourceOutput, targetNode, targetInput) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
const d = this.calculatePathD(sourceNode, sourceOutput, targetNode, targetInput);
path.setAttribute("d", d);
path.setAttribute("fill", "none");
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() {
this.connectionElements.forEach(conn => {
const sourceNode = this.workflow.nodes[conn.sourceId];
const targetNode = this.workflow.nodes[conn.targetId];
const sourcePosX = sourceNode.x + 120;
const sourcePosY = sourceNode.y + (conn.sourceOutput + 1) * 15;
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);
const d = this.calculatePathD(sourceNode, conn.sourceOutput, targetNode, conn.targetInput);
conn.path.setAttribute("d", d);
});
}
getColorForType(type) {
const colors = {
number: "blue",
string: "green",
boolean: "red",
object: "purple"
number: "#4285F4",
string: "#34A853",
boolean: "#EA4335",
object: "#FBBC05"
};
return colors[type] || "gray";
return colors[type] || "#9E9E9E";
}
onMouseDown(event) {
const target = event.target.closest("g");
if (target) {
this.draggedNode = this.workflow.nodes[target.getAttribute("data-id")];
const rect = target.getBoundingClientRect();
this.offsetX = event.clientX - rect.left;
this.offsetY = event.clientY - rect.top;
}
onNodeMouseDown(event) {
if (event.target.classList.contains('socket')) return;
const nodeElement = event.currentTarget;
this.draggedNode = this.workflow.nodes[nodeElement.getAttribute("data-id")];
const rect = nodeElement.getBoundingClientRect();
this.offsetX = event.clientX - rect.left;
this.offsetY = event.clientY - rect.top;
nodeElement.setAttribute("filter", "url(#dropShadow)");
}
onMouseMove(event) {
@ -259,10 +388,72 @@ class WorkflowVisualizer {
this.nodeElements[this.draggedNode.id].setAttribute("transform", `translate(${this.draggedNode.x}, ${this.draggedNode.y})`);
this.updateConnections();
}
if (this.tempLine) {
const rect = this.svg.getBoundingClientRect();
this.updateTempLine(event.clientX - rect.left, event.clientY - rect.top);
}
}
onMouseUp() {
this.draggedNode = null;
onMouseUp(event) {
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() {
@ -291,6 +482,7 @@ class WorkflowVisualizer {
redraw() {
this.svg.innerHTML = '';
this.addDefs();
this.nodeElements = {};
this.connectionElements = [];
this.workflow.nodeList.forEach(node => this.drawNode(node));

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

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