mirror of
https://github.com/ParisNeo/lollms-webui.git
synced 2024-12-19 20:37:51 +00:00
upgraded lollms flow
This commit is contained in:
parent
630ab5591d
commit
f961f1fc8a
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user