update for blocks
This commit is contained in:
parent
7b36044c33
commit
25edbbd34c
|
|
@ -1,4 +1,4 @@
|
|||
import { INodeDefinitionType } from "./node-types";
|
||||
|
||||
|
||||
export type FlowVisibility = 'PUBLIC' | 'PRIVATE';
|
||||
|
||||
|
|
@ -14,16 +14,66 @@ export type Flow = {
|
|||
};
|
||||
|
||||
export type FlowData = {
|
||||
nodes: INodeModel[];
|
||||
connections: IConnectionModel[];
|
||||
}
|
||||
blocks: FlowBlock[];
|
||||
connections: FlowBlockConnection[];
|
||||
};
|
||||
|
||||
export type FlowBlock = {
|
||||
id: string;
|
||||
sink: boolean;
|
||||
name: string;
|
||||
position?: { x: number, y: number };
|
||||
inputs: FlowPort[];
|
||||
outputs: FlowPort[];
|
||||
specificConfiguration: FlowBlockConfiguration;
|
||||
typeName: "LLMBlock" | "HumanInteractionBlock" | string;
|
||||
};
|
||||
|
||||
export type FlowPort = {
|
||||
name: string;
|
||||
type: string;
|
||||
multiple: boolean;
|
||||
};
|
||||
|
||||
export type FlowBlockConnection = {
|
||||
id: string;
|
||||
sourceId: string;
|
||||
sourceName: string;
|
||||
targetId: string;
|
||||
targetName: string;
|
||||
};
|
||||
|
||||
export type FlowBlockConfiguration =
|
||||
| LLMBlockConfiguration
|
||||
| HumanInteractiveBlockConfiguration
|
||||
| Record<string, unknown>;
|
||||
|
||||
export type LLMDescriptor = {
|
||||
provider: string;
|
||||
model: string;
|
||||
};
|
||||
|
||||
export type LLMBlockConfiguration = {
|
||||
type: "LLMBlockConfiguration";
|
||||
name: string;
|
||||
llmDescriptor: LLMDescriptor;
|
||||
prompt: string;
|
||||
};
|
||||
|
||||
export type HumanInteractiveBlockConfiguration = {
|
||||
type: "HumanInteractiveBlockConfiguration";
|
||||
name: string;
|
||||
actionDescription: string;
|
||||
llmDescriptor: LLMDescriptor;
|
||||
inputAsList: boolean;
|
||||
outputAsList: boolean;
|
||||
};
|
||||
|
||||
export type INodeModel = {
|
||||
key: string;
|
||||
name?: string;
|
||||
position: { x: number, y: number } | null;
|
||||
parameters?: Record<string, any> | null;
|
||||
nodeDefinition: INodeDefinitionType;
|
||||
}
|
||||
|
||||
export type IConnectionModel = {
|
||||
|
|
@ -34,4 +84,3 @@ export type IConnectionModel = {
|
|||
targetField: string;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
|
||||
export interface INodeDefinitionType {
|
||||
name: string;
|
||||
category: string;
|
||||
outputs: Record<string, IOObject>;
|
||||
inputs: Record<string, IOObject>;
|
||||
fixedParameters?: Record<string, IParameter> | null;
|
||||
runtimeParameters?: Record<string, IParameter> | null;
|
||||
}
|
||||
|
||||
export interface IExecutionNodeType extends INodeDefinitionType {
|
||||
executor: string;
|
||||
outputMappers?: Record<string, IMapper>;
|
||||
inputMappers?: Record<string, IMapper>;
|
||||
}
|
||||
|
||||
export interface IInputNodeType extends INodeDefinitionType {
|
||||
simulabe: boolean;
|
||||
}
|
||||
|
||||
export interface IHumanInteractionNodeType extends INodeDefinitionType {
|
||||
simulabe: boolean;
|
||||
interactionDescription: string;
|
||||
}
|
||||
|
||||
export interface IIOutputNodeType extends INodeDefinitionType {
|
||||
}
|
||||
|
||||
export interface IMapper {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface ITraslatorMapper extends IMapper {
|
||||
translation: string;
|
||||
}
|
||||
|
||||
export interface IDirectMapper extends IMapper {
|
||||
fieldName: string;
|
||||
}
|
||||
|
||||
export interface IParserMapper extends IMapper {
|
||||
from?: string;
|
||||
to?: string;
|
||||
fieldName: string;
|
||||
multiSeparator?: string;
|
||||
}
|
||||
|
||||
export interface IParameter {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
type: ParameterType;
|
||||
required: boolean;
|
||||
validations?: IValidation[];
|
||||
specificAttributes?: any;
|
||||
};
|
||||
|
||||
export enum ParameterType {
|
||||
Text = 'Text',
|
||||
LongText = 'LongText',
|
||||
Select = 'Select',
|
||||
DynamicMultiOptions = 'DynamicMultiOptions',
|
||||
Number = 'Number',
|
||||
Boolean = 'Boolean'
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface IExecutor {
|
||||
identifier: string,
|
||||
name: string,
|
||||
description: string,
|
||||
inputs: Record<string, IOObject>,
|
||||
outputs: Record<string, IOObject>,
|
||||
mandatoryParameters: IParameter[],
|
||||
}
|
||||
|
||||
export enum IOType {
|
||||
TEXT = 'TEXT',
|
||||
//CSV = 'CSV',
|
||||
}
|
||||
|
||||
export interface IOObject {
|
||||
type: IOType;
|
||||
multiple: boolean;
|
||||
}
|
||||
|
||||
export interface IValidation {
|
||||
validator: string;
|
||||
value?: number | string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IFlowError {
|
||||
type: string;
|
||||
node: string | undefined;
|
||||
connection: string | undefined;
|
||||
input: string | undefined;
|
||||
output: string | undefined;
|
||||
parameter: string | undefined;
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { GetSchemes, ClassicPreset } from "rete";
|
||||
import { INodeModel } from "./flow";
|
||||
import { FlowBlock } from "./flow";
|
||||
|
||||
export type HFNode = ClassicPreset.Node & {
|
||||
data: INodeModel;
|
||||
data?: FlowBlock;
|
||||
};
|
||||
|
||||
export type HFConnection = ClassicPreset.Connection<HFNode, HFNode>;
|
||||
|
||||
export type HFSchemes = GetSchemes<HFNode, HFConnection>;
|
||||
export type HFSchemes = GetSchemes<HFNode, HFConnection>;
|
||||
|
|
|
|||
|
|
@ -1,100 +1,62 @@
|
|||
export const flowTest = {
|
||||
"id": "testFlow",
|
||||
"createdBy": "lucio.lelii",
|
||||
"name": "Test Flow",
|
||||
"description": "This is a test flow",
|
||||
"nodes": [
|
||||
{
|
||||
"key": "1328196f-5ddd-43b0-a702-2d1376970c70",
|
||||
"name": "Input Node",
|
||||
"position": null,
|
||||
"parameters": null,
|
||||
"nodeDefinition": {
|
||||
"category": "Input",
|
||||
"runtimeParameters": null,
|
||||
"fixedParameters": null,
|
||||
"inputs": {},
|
||||
"outputs": {
|
||||
"phrase": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
},
|
||||
"simulable": false,
|
||||
"name": "Phrase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "f142511e-f410-4974-9354-3a1e056e056f",
|
||||
"name": "translator to french",
|
||||
"position": null,
|
||||
"parameters": {
|
||||
"LLMProvider": "OllamaTestProvider",
|
||||
"LLMModel": "sam860/gemma3:270m"
|
||||
},
|
||||
"nodeDefinition": {
|
||||
"category": "Execution",
|
||||
"executor": "genericAIExecutorTest",
|
||||
"inputMappers": {
|
||||
"prompt": {
|
||||
"type": "translator",
|
||||
"translation": "translate the following phrase : \"${{phrase}}\" in french, return only the translation"
|
||||
}
|
||||
},
|
||||
"outputMappers": {
|
||||
"translated": {
|
||||
"type": "direct",
|
||||
"fieldName": "response"
|
||||
}
|
||||
},
|
||||
"name": "French Translator",
|
||||
"runtimeParameters": null,
|
||||
"fixedParameters": null,
|
||||
"inputs": {
|
||||
"phrase": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"translated": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "e3488c96-b55b-4763-bc25-54d3d3755d85",
|
||||
"name": "Output Node",
|
||||
"position": null,
|
||||
"parameters": null,
|
||||
"nodeDefinition": {
|
||||
"category": "Output",
|
||||
"runtimeParameters": null,
|
||||
"fixedParameters": null,
|
||||
"outputs": {},
|
||||
"inputs": {
|
||||
"translated": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
},
|
||||
"name": "Translated Phrase"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"key": "18739497-51e6-4d09-be40-398c3601a326",
|
||||
"from": "1328196f-5ddd-43b0-a702-2d1376970c70:phrase",
|
||||
"to": "f142511e-f410-4974-9354-3a1e056e056f:phrase"
|
||||
},
|
||||
{
|
||||
"key": "f88d9442-4a9f-4b4b-ac3f-9794dd0bf6db",
|
||||
"from": "f142511e-f410-4974-9354-3a1e056e056f:translated",
|
||||
"to": "e3488c96-b55b-4763-bc25-54d3d3755d85:translated"
|
||||
}
|
||||
],
|
||||
"public": false
|
||||
export const flowTest = {
|
||||
"name" : "Test Flow",
|
||||
"description" : "This is a test flow",
|
||||
"blocks" : [ {
|
||||
"id" : "cabd6f4e-5a05-41f8-9bf7-4de20391ac4e",
|
||||
"sink" : false,
|
||||
"name" : "first",
|
||||
"inputs" : [ {
|
||||
"name" : "name",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"outputs" : [ {
|
||||
"name" : "response",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"specificConfiguration" : {
|
||||
"type" : "LLMBlockConfiguration",
|
||||
"name" : "first",
|
||||
"llmDescriptor" : {
|
||||
"provider" : "testProvider",
|
||||
"model" : "testModel"
|
||||
},
|
||||
"prompt" : "Make a question about ${{name}}"
|
||||
},
|
||||
"typeName" : "LLMBlock"
|
||||
}, {
|
||||
"id" : "0063a3ec-3863-4045-bd3b-61eaf87b4604",
|
||||
"sink" : true,
|
||||
"name" : "interactive",
|
||||
"inputs" : [ {
|
||||
"name" : "input",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"outputs" : [ {
|
||||
"name" : "output",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"specificConfiguration" : {
|
||||
"type" : "HumanInteractiveBlockConfiguration",
|
||||
"name" : "interactive",
|
||||
"actionDescription" : "Answer the question in input",
|
||||
"llmDescriptor" : {
|
||||
"provider" : "testProvider",
|
||||
"model" : "testModel"
|
||||
},
|
||||
"inputAsList" : false,
|
||||
"outputAsList" : false
|
||||
},
|
||||
"typeName" : "HumanInteractionBlock"
|
||||
} ],
|
||||
"connections" : [ {
|
||||
"id" : "8da696c2-dc03-4717-b2ce-18637ae6f7f8",
|
||||
"sourceId" : "cabd6f4e-5a05-41f8-9bf7-4de20391ac4e",
|
||||
"sourceName" : "response",
|
||||
"targetId" : "0063a3ec-3863-4045-bd3b-61eaf87b4604",
|
||||
"targetName" : "input"
|
||||
} ]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import { take, tap } from 'rxjs';
|
|||
})
|
||||
export class Authorization {
|
||||
|
||||
private readonly apiUrl = environment.apiUrl + '/auth';
|
||||
|
||||
private user = signal<User | null>(null);
|
||||
|
||||
authCall: AuthorizationCallServiceBase = new environment.authorizationCallService();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { FlowsCallServiceBase } from "./flows-call.base";
|
|||
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||
import { Authorization } from "@services/authorization/authorization";
|
||||
import { inject } from "@angular/core";
|
||||
import { IOType } from "@models/node-types";
|
||||
|
||||
export class FlowsCallServiceFake extends FlowsCallServiceBase {
|
||||
override getFlowById(flowId: string): Observable<Flow> {
|
||||
|
|
@ -17,8 +16,8 @@ export class FlowsCallServiceFake extends FlowsCallServiceBase {
|
|||
authorizationService = inject(Authorization);
|
||||
|
||||
private data: Record<string, Flow> = {
|
||||
'1': { id: '1', name: 'A Flow', data: { nodes: [], connections: [] }, visibility: 'PUBLIC', author: 'Alice', createdAt: new Date("December 17, 2023 03:24:00"), updatedAt: new Date("January 7, 2026 12:24:00") },
|
||||
'2': { id: '2', name: 'Test Flow', data: { nodes: [], connections: [] }, visibility: 'PRIVATE', author: 'Bob', createdAt: new Date("April 25, 2025 12:24:00"), updatedAt: new Date("April 27, 2025 18:42:00") },
|
||||
'1': { id: '1', name: 'A Flow', data: { blocks: [], connections: [] }, visibility: 'PUBLIC', author: 'Alice', createdAt: new Date("December 17, 2023 03:24:00"), updatedAt: new Date("January 7, 2026 12:24:00") },
|
||||
'2': { id: '2', name: 'Test Flow', data: { blocks: [], connections: [] }, visibility: 'PRIVATE', author: 'Bob', createdAt: new Date("April 25, 2025 12:24:00"), updatedAt: new Date("April 27, 2025 18:42:00") },
|
||||
'testFlow': flowFromJson(testDataFlow)
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +32,7 @@ export class FlowsCallServiceFake extends FlowsCallServiceBase {
|
|||
|
||||
override createNewFlow(name?: string): Observable<Flow> {
|
||||
const newId = (Object.keys(this.data).length + 1).toString();
|
||||
this.data[newId] = { id: newId, name: name || `New Flow`, data: { nodes: [], connections: [] }, visibility: 'PRIVATE', author: this.authorizationService.loggedInUser()!.username, createdAt: new Date(), updatedAt: new Date() };
|
||||
this.data[newId] = { id: newId, name: name || `New Flow`, data: { blocks: [], connections: [] }, visibility: 'PRIVATE', author: this.authorizationService.loggedInUser()!.username, createdAt: new Date(), updatedAt: new Date() };
|
||||
return of(this.data[newId]);
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +54,7 @@ export function flowFromJson(raw: any): Flow {
|
|||
name: raw.name,
|
||||
description: raw.description,
|
||||
data: {
|
||||
nodes: raw.nodes,
|
||||
blocks: raw.blocks,
|
||||
connections: raw.connections
|
||||
}
|
||||
};
|
||||
|
|
@ -63,108 +62,69 @@ export function flowFromJson(raw: any): Flow {
|
|||
|
||||
|
||||
const testDataFlow ={
|
||||
"id": "my test flow",
|
||||
"author": "lucio.lelii",
|
||||
"visibility": "PUBLIC",
|
||||
"createdAt": "2026-01-21T10:38:50.671+00:00",
|
||||
"updatedAt": "2026-01-21T10:38:50.671+00:00",
|
||||
"name": "my test flow",
|
||||
"description": "This is a test flow",
|
||||
"nodes": [
|
||||
{
|
||||
"key": "3ec4a0e5-914a-41e0-ab74-d4e64a4dba09",
|
||||
"name": "Input Node",
|
||||
"position": null,
|
||||
"parameters": null,
|
||||
"nodeDefinition": {
|
||||
"category": "Input",
|
||||
"runtimeParameters": null,
|
||||
"fixedParameters": null,
|
||||
"inputs": {},
|
||||
"outputs": {
|
||||
"phrase": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
},
|
||||
"simulable": false,
|
||||
"name": "Phrase"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "80519c40-20ba-49cf-800a-598ee2e64f65",
|
||||
"name": "translator to french",
|
||||
"position": null,
|
||||
"parameters": {
|
||||
"LLMProvider": "OllamaTestProvider",
|
||||
"LLMModel": "sam860/gemma3:270m"
|
||||
},
|
||||
"nodeDefinition": {
|
||||
"category": "Execution",
|
||||
"executor": "genericAIExecutorTest",
|
||||
"inputMappers": {
|
||||
"prompt": {
|
||||
"type": "translator",
|
||||
"translation": "translate the following phrase : \"${{phrase}}\" in french, return only the translation"
|
||||
}
|
||||
},
|
||||
"outputMappers": {
|
||||
"translated": {
|
||||
"type": "direct",
|
||||
"fieldName": "response"
|
||||
}
|
||||
},
|
||||
"name": "French Translator",
|
||||
"runtimeParameters": null,
|
||||
"fixedParameters": null,
|
||||
"inputs": {
|
||||
"phrase": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"translated": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "ff349078-dce1-4ecd-8b2f-bd43100633fc",
|
||||
"name": "Output Node",
|
||||
"position": null,
|
||||
"parameters": null,
|
||||
"nodeDefinition": {
|
||||
"category": "Output",
|
||||
"runtimeParameters": null,
|
||||
"fixedParameters": null,
|
||||
"outputs": {},
|
||||
"inputs": {
|
||||
"translated": {
|
||||
"type": "TEXT",
|
||||
"multiple": false
|
||||
}
|
||||
},
|
||||
"name": "Translated Phrase"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"key": "c335e3da-2af3-4e8f-a8ee-9bfc236196ee",
|
||||
"sourceNode": "3ec4a0e5-914a-41e0-ab74-d4e64a4dba09",
|
||||
"sourceField": "phrase",
|
||||
"targetNode": "80519c40-20ba-49cf-800a-598ee2e64f65",
|
||||
"targetField": "phrase"
|
||||
},
|
||||
{
|
||||
"key": "e0239aef-d570-463d-b453-c3906ab0e4b0",
|
||||
"sourceNode": "80519c40-20ba-49cf-800a-598ee2e64f65",
|
||||
"sourceField": "translated",
|
||||
"targetNode": "ff349078-dce1-4ecd-8b2f-bd43100633fc",
|
||||
"targetField": "translated"
|
||||
}
|
||||
]
|
||||
};
|
||||
"name" : "Test Flow",
|
||||
"visibility": "PRIVATE",
|
||||
"author": "lucio",
|
||||
"description" : "This is a test flow",
|
||||
"createdAt": "2023-12-17T03:24:00.000Z",
|
||||
"updatedAt": "2026-01-07T12:24:00.000Z",
|
||||
"blocks" : [ {
|
||||
"id" : "cabd6f4e-5a05-41f8-9bf7-4de20391ac4e",
|
||||
"sink" : false,
|
||||
"name" : "first",
|
||||
"inputs" : [ {
|
||||
"name" : "name",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"outputs" : [ {
|
||||
"name" : "response",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"specificConfiguration" : {
|
||||
"type" : "LLMBlockConfiguration",
|
||||
"name" : "first",
|
||||
"llmDescriptor" : {
|
||||
"provider" : "testProvider",
|
||||
"model" : "testModel"
|
||||
},
|
||||
"prompt" : "Make a question about ${{name}}"
|
||||
},
|
||||
"typeName" : "LLMBlock"
|
||||
}, {
|
||||
"id" : "0063a3ec-3863-4045-bd3b-61eaf87b4604",
|
||||
"sink" : true,
|
||||
"name" : "interactive",
|
||||
"inputs" : [ {
|
||||
"name" : "input",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"outputs" : [ {
|
||||
"name" : "output",
|
||||
"type" : "TEXT",
|
||||
"multiple" : false
|
||||
} ],
|
||||
"specificConfiguration" : {
|
||||
"type" : "HumanInteractiveBlockConfiguration",
|
||||
"name" : "interactive",
|
||||
"actionDescription" : "Answer the question in input",
|
||||
"llmDescriptor" : {
|
||||
"provider" : "testProvider",
|
||||
"model" : "testModel"
|
||||
},
|
||||
"inputAsList" : false,
|
||||
"outputAsList" : false
|
||||
},
|
||||
"typeName" : "HumanInteractionBlock"
|
||||
} ],
|
||||
"connections" : [ {
|
||||
"id" : "8da696c2-dc03-4717-b2ce-18637ae6f7f8",
|
||||
"sourceId" : "cabd6f4e-5a05-41f8-9bf7-4de20391ac4e",
|
||||
"sourceName" : "response",
|
||||
"targetId" : "0063a3ec-3863-4045-bd3b-61eaf87b4604",
|
||||
"targetName" : "input"
|
||||
} ]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
@use "sass:math"
|
||||
|
||||
:host
|
||||
display: inline-block
|
||||
cursor: pointer
|
||||
border: 1px solid grey
|
||||
width: 10px
|
||||
height: 20px
|
||||
vertical-align: middle
|
||||
background: #fff
|
||||
z-index: 2
|
||||
box-sizing: border-box
|
||||
&:hover
|
||||
background: #ddd
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { Component, AfterViewInit, Input, HostListener, HostBinding, ChangeDetectorRef } from "@angular/core";
|
||||
import { ReteModule } from "rete-angular-plugin/21";
|
||||
|
||||
@Component({
|
||||
template: ``,
|
||||
})
|
||||
export class CustomSocket {
|
||||
@Input() data!: any;
|
||||
@Input() emit!: any;
|
||||
@Input() rendered!: any;
|
||||
|
||||
@HostBinding("title") get title() {
|
||||
return this.data.name;
|
||||
}
|
||||
|
||||
@HostBinding("style.width") w = "15px";
|
||||
@HostBinding("style.height") h = "15px";
|
||||
@HostBinding("style.display") d = "block";
|
||||
@HostBinding("style.borderRadius") br = "9999px";
|
||||
@HostBinding("style.border") border = "2px solid white";
|
||||
@HostBinding("style.cursor") cursor = "crosshair";
|
||||
|
||||
@HostBinding("style.background")
|
||||
get bg() {
|
||||
return this.data?.side === "input" ? "rgb(34,197,94)" : "rgb(99,102,241)";
|
||||
}
|
||||
|
||||
@HostBinding("style.boxShadow")
|
||||
get sh() {
|
||||
console.log("CustomSocket sh data", this.data);
|
||||
const c = this.data?.side === "input" ? "rgb(34,197,94)" : "rgb(99,102,241)";
|
||||
return `0 0 0 1px ${c}`;
|
||||
}
|
||||
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
this.cdr.detach();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.cdr.detectChanges();
|
||||
requestAnimationFrame(() => this.rendered());
|
||||
}
|
||||
}
|
||||
|
|
@ -19,4 +19,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="cursor-move relative user-select-none w-55 rounded-2xl bg-white border border-gray-200 shadow-lg overflow-visible font-sans">
|
||||
<div
|
||||
class=" relative user-select-none w-55 rounded-2xl bg-white border border-gray-200 shadow-lg overflow-visible font-sans">
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex items-center gap-2 px-3 py-2 bg-linear-to-br from-violet-600 to-indigo-500 text-white rounded-t-2xl">
|
||||
<div class="cursor-move flex items-center gap-2 px-3 py-2 bg-linear-to-br from-violet-600 to-indigo-500 text-white rounded-t-2xl">
|
||||
<div class="w-12 h-8 rounded-lg bg-white/20 flex items-center justify-center">
|
||||
<img src="llm_node.png" alt="LLM" class="w-12 h-8" />
|
||||
</div>
|
||||
|
|
@ -9,38 +9,64 @@
|
|||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="p-3 grid grid-cols-2 gap-4">
|
||||
<div class="pt-3 pb-3 grid grid-cols-2 gap-4 w-full items-start">
|
||||
|
||||
<div class="grid grid-rows-2 gap-1">
|
||||
<!-- COLONNA INPUT (sinistra) -->
|
||||
<div class="grid grid-cols-[16px_1fr] items-center h-6 w-full">
|
||||
|
||||
<div refComponent class="absolute left-1"
|
||||
<!-- Socket input -->
|
||||
<div
|
||||
refComponent
|
||||
class="relative z-20"
|
||||
[data]="{
|
||||
type: 'socket',
|
||||
side: 'input',
|
||||
key: inputKey,
|
||||
nodeId: data.id,
|
||||
payload: inputSocket }" [emit]="emit"></div>
|
||||
|
||||
<div class="text-xs text-gray-700 pl-4">
|
||||
{{ inputKey }}
|
||||
</div>
|
||||
|
||||
type: 'socket',
|
||||
side: 'input',
|
||||
key: inputKey,
|
||||
nodeId: data.id,
|
||||
payload: inputSocket
|
||||
}"
|
||||
[emit]="emit">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-rows-2 gap-1">
|
||||
<div class="text-xs text-gray-700 text-right pr-4">
|
||||
{{ outputKey }}
|
||||
</div>
|
||||
|
||||
<div refComponent class="absolute right-1"
|
||||
<!-- Label input -->
|
||||
<span
|
||||
class="inline-flex items-center justify-start px-2 h-6 w-full
|
||||
text-xs font-medium
|
||||
bg-emerald-50 text-emerald-700 border border-emerald-200">
|
||||
{{ inputKey }}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- COLONNA OUTPUT (destra) -->
|
||||
<div class="grid grid-cols-[1fr_16px] items-center h-6 w-full">
|
||||
|
||||
<!-- Label output -->
|
||||
<span
|
||||
class="inline-flex items-center justify-end px-2 h-6 w-full
|
||||
text-xs font-medium
|
||||
bg-red-50 text-red-700 border border-red-200">
|
||||
{{ outputKey }}
|
||||
</span>
|
||||
|
||||
<!-- Socket output -->
|
||||
<div
|
||||
refComponent
|
||||
class="relative z-20 justify-self-end"
|
||||
[data]="{
|
||||
type: 'socket',
|
||||
side: 'output',
|
||||
key: outputKey,
|
||||
nodeId: data.id,
|
||||
payload: outputSocket}" [emit]="emit"></div>
|
||||
type: 'socket',
|
||||
side: 'output',
|
||||
key: outputKey,
|
||||
nodeId: data.id,
|
||||
payload: outputSocket
|
||||
}"
|
||||
[emit]="emit">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
@ -35,16 +35,20 @@ export class LLMNodeComponent {
|
|||
|
||||
this.outputKey = outKey;
|
||||
this.outputSocket = (output as any).socket;
|
||||
console.log("output data", this.outputSocket);
|
||||
|
||||
const inputEntries = Object.entries(this.data.inputs);
|
||||
const [inKey, input] = inputEntries[0];
|
||||
|
||||
this.inputKey = inKey;
|
||||
this.inputSocket = (input as any).socket;
|
||||
console.log("input data", this.inputSocket);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.rendered();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, ElementRef, HostBinding, Input } from "@angular/core";
|
||||
import { ChangeDetectorRef, Component, ElementRef, HostBinding, Input } from "@angular/core";
|
||||
import { ReteModule } from "rete-angular-plugin/21";
|
||||
|
||||
@Component({
|
||||
|
|
@ -7,8 +7,7 @@ import { ReteModule } from "rete-angular-plugin/21";
|
|||
host: {
|
||||
refComponent: '',
|
||||
class: `
|
||||
cursor-pointer relative -left-1 w-[15px] h-[15px] flex items-center justify-center rounded-full bg-green-500 border-2 border-white
|
||||
shadow-[0_0_0_1px_rgb(34,197,94)]
|
||||
cursor-crosshair relative -left-3 w-1 h-6 flex items-center justify-center rounded-full bg-green-500 shadow-[0_0_0_1px_rgb(34,197,94)] z-50
|
||||
`
|
||||
},
|
||||
template: ``
|
||||
|
|
@ -32,4 +31,14 @@ export class InputSocket {
|
|||
this.rendered?.();
|
||||
});
|
||||
}
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
this.cdr.detach();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.cdr.detectChanges();
|
||||
requestAnimationFrame(() => this.rendered());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, ElementRef, HostBinding, Input } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, HostBinding, HostListener, Input } from '@angular/core';
|
||||
import { ReteModule } from 'rete-angular-plugin/21';
|
||||
|
||||
@Component({
|
||||
|
|
@ -7,8 +7,8 @@ import { ReteModule } from 'rete-angular-plugin/21';
|
|||
host: {
|
||||
refComponent: '',
|
||||
class: `
|
||||
cursor-pointer relative -right-1 w-[15px] h-[15px] flex items-center justify-center rounded-full bg-indigo-500 border-2 border-white
|
||||
shadow-[0_0_0_1px_rgb(99,102,241)]
|
||||
cursor-crosshair relative -right-2 w-1 h-6 flex items-center justify-center rounded-full bg-red-500
|
||||
shadow-[0_0_0_1px_rgb(239,68,68)]
|
||||
`
|
||||
},
|
||||
template: ``
|
||||
|
|
@ -25,10 +25,19 @@ export class OutputSocket {
|
|||
ngOnInit(): void {
|
||||
console.log("OutputSocket ngOnInit", this.data);
|
||||
}
|
||||
|
||||
|
||||
ngAfterViewInit() {
|
||||
requestAnimationFrame(() => {
|
||||
this.rendered?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
this.cdr.detach();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.cdr.detectChanges();
|
||||
requestAnimationFrame(() => this.rendered());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,28 @@
|
|||
import { Injector, Input } from "@angular/core";
|
||||
import { NodeEditor, GetSchemes, ClassicPreset } from "rete";
|
||||
import { Injector } from "@angular/core";
|
||||
import { NodeEditor, ClassicPreset } from "rete";
|
||||
import { AreaPlugin, AreaExtensions } from "rete-area-plugin";
|
||||
import {
|
||||
ConnectionPlugin,
|
||||
Presets as ConnectionPresets
|
||||
} from "rete-connection-plugin";
|
||||
import { AngularPlugin, Presets, AngularArea2D, NodeComponent } from "rete-angular-plugin/21";
|
||||
import { AngularPlugin, Presets, AngularArea2D } from "rete-angular-plugin/21";
|
||||
import { InputNodeComponent } from "@shared/nodes/input/input-node.component";
|
||||
import { OutputNodeComponent } from "@shared/nodes/output/output-node.component";
|
||||
import { HFNode, HFSchemes } from "@models/nodes";
|
||||
import { FlowData, INodeModel } from "@models/flow";
|
||||
import { FlowBlock, FlowData } from "@models/flow";
|
||||
import { LLMNodeComponent } from "@shared/nodes/llm/llm";
|
||||
import { OutputSocket } from "@shared/sockets/output/output";
|
||||
import { InputSocket } from "@shared/sockets/input/input";
|
||||
import { CustomSocket } from "@shared/custom-socket/custom-socket";
|
||||
|
||||
type Schemes = GetSchemes<
|
||||
ClassicPreset.Node,
|
||||
ClassicPreset.Connection<ClassicPreset.Node, ClassicPreset.Node>
|
||||
>;
|
||||
type AreaExtra = AngularArea2D<Schemes>;
|
||||
type AreaExtra = AngularArea2D<HFSchemes>;
|
||||
|
||||
export async function createEditor(container: HTMLElement, injector: Injector, flowData: FlowData) {
|
||||
|
||||
const editor = new NodeEditor<HFSchemes>();
|
||||
const area = new AreaPlugin<HFSchemes, AreaExtra>(container);
|
||||
const connection = new ConnectionPlugin<Schemes, AreaExtra>();
|
||||
const render = new AngularPlugin<Schemes, AreaExtra>({ injector });
|
||||
const connection = new ConnectionPlugin<HFSchemes, AreaExtra>();
|
||||
const render = new AngularPlugin<HFSchemes, AreaExtra>({ injector });
|
||||
|
||||
AreaExtensions.selectableNodes(area, AreaExtensions.selector(), {
|
||||
accumulating: AreaExtensions.accumulateOnCtrl()
|
||||
});
|
||||
|
||||
|
||||
render.addPreset(
|
||||
Presets.classic.setup({
|
||||
|
|
@ -43,22 +36,22 @@ export async function createEditor(container: HTMLElement, injector: Injector, f
|
|||
}
|
||||
return LLMNodeComponent;
|
||||
},
|
||||
socket(context) {
|
||||
const side = context.side;
|
||||
return side === "input" ? InputSocket : OutputSocket;
|
||||
|
||||
socket() {
|
||||
return CustomSocket;
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
area.addPipe((c) => {
|
||||
if (c.type === "render") console.log(c.data);
|
||||
editor.addPipe((c) => {
|
||||
if (c.type === "connectioncreate") console.log(c.data);
|
||||
return c;
|
||||
});
|
||||
|
||||
connection.addPreset(ConnectionPresets.classic.setup());
|
||||
|
||||
AreaExtensions.simpleNodesOrder(area);
|
||||
|
||||
editor.use(area);
|
||||
area.use(connection);
|
||||
area.use(render);
|
||||
|
|
@ -73,86 +66,95 @@ export async function createEditor(container: HTMLElement, injector: Injector, f
|
|||
}
|
||||
|
||||
export function exportGraph(editor: NodeEditor<HFSchemes>) {
|
||||
const nodes: INodeModel[] = editor.getNodes().map(node => ({
|
||||
key: node.data.key,
|
||||
name: node.data.name,
|
||||
position: node.data.position,
|
||||
nodeDefinition: node.data.nodeDefinition,
|
||||
parameters: node.data.parameters,
|
||||
const nodeIdToBlockId = new Map<string, string>();
|
||||
const blocks: FlowBlock[] = editor.getNodes().map((node) => {
|
||||
const blockData = node.data;
|
||||
const blockId = blockData?.id ?? node.id;
|
||||
nodeIdToBlockId.set(node.id, blockId);
|
||||
|
||||
}));
|
||||
const inputs = Object.entries(node.inputs).map(([name, input]) => ({
|
||||
name,
|
||||
type: ((input as any).socket?.name as string) ?? "ANY",
|
||||
multiple: false
|
||||
}));
|
||||
|
||||
const connections = editor.getConnections().map(c => ({
|
||||
id: c.id,
|
||||
from: {
|
||||
nodeId: c.source,
|
||||
output: c.sourceOutput
|
||||
},
|
||||
to: {
|
||||
nodeId: c.target,
|
||||
input: c.targetInput
|
||||
}
|
||||
const outputs = Object.entries(node.outputs).map(([name, output]) => ({
|
||||
name,
|
||||
type: ((output as any).socket?.name as string) ?? "ANY",
|
||||
multiple: false
|
||||
}));
|
||||
|
||||
return {
|
||||
id: blockId,
|
||||
sink: blockData?.sink ?? false,
|
||||
name: blockData?.name ?? node.label,
|
||||
position: blockData?.position,
|
||||
inputs,
|
||||
outputs,
|
||||
specificConfiguration: blockData?.specificConfiguration ?? {},
|
||||
typeName: blockData?.typeName ?? "LLMBlock"
|
||||
};
|
||||
});
|
||||
|
||||
const connections = editor.getConnections().map((c) => ({
|
||||
id: String(c.id),
|
||||
sourceId: nodeIdToBlockId.get(c.source) ?? c.source,
|
||||
sourceName: c.sourceOutput,
|
||||
targetId: nodeIdToBlockId.get(c.target) ?? c.target,
|
||||
targetName: c.targetInput
|
||||
}));
|
||||
|
||||
return {
|
||||
nodes,
|
||||
blocks,
|
||||
connections
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async function loadFlowData(editor: NodeEditor<HFSchemes>, flowData: FlowData) {
|
||||
if (flowData.nodes.length === 0) return;
|
||||
const socket = new ClassicPreset.Socket("socket");
|
||||
const nodeMapping = new Map<string, string>();
|
||||
for (const nodeData of flowData.nodes) {
|
||||
if (!nodeData.nodeDefinition) continue;
|
||||
const node: HFNode = new ClassicPreset.Node(nodeData.nodeDefinition.category) as HFNode;
|
||||
node.data = nodeData;
|
||||
nodeMapping.set(nodeData.key, node.id);
|
||||
Object.entries(nodeData.nodeDefinition?.outputs).forEach(([output, def]) => {
|
||||
console.log("Adding output", output);
|
||||
node.addOutput(output,
|
||||
new ClassicPreset.Output(
|
||||
socket, output
|
||||
)
|
||||
);
|
||||
});
|
||||
Object.entries(nodeData.nodeDefinition?.inputs).forEach(([input, def]) => {
|
||||
console.log("Adding input", input);
|
||||
node.addInput(input,
|
||||
new ClassicPreset.Input(
|
||||
socket, input
|
||||
)
|
||||
);
|
||||
});
|
||||
if (!flowData.blocks?.length) return;
|
||||
|
||||
const sockets = new Map<string, ClassicPreset.Socket>();
|
||||
const getSocket = (type: string) => {
|
||||
if (!sockets.has(type)) sockets.set(type, new ClassicPreset.Socket(type));
|
||||
return sockets.get(type)!;
|
||||
};
|
||||
|
||||
const nodeMapping = new Map<string, any>();
|
||||
|
||||
for (const block of flowData.blocks) {
|
||||
const nodeLabel =
|
||||
block.typeName === "InputBlock"
|
||||
? "Input"
|
||||
: block.typeName === "OutputBlock"
|
||||
? "Output"
|
||||
: block.typeName;
|
||||
const node = new ClassicPreset.Node(nodeLabel) as HFNode;
|
||||
node.data = block;
|
||||
|
||||
nodeMapping.set(block.id, node.id);
|
||||
|
||||
for (const output of block.outputs ?? []) {
|
||||
node.addOutput(output.name, new ClassicPreset.Output(getSocket(output.type ?? "ANY")));
|
||||
}
|
||||
|
||||
for (const input of block.inputs ?? []) {
|
||||
node.addInput(input.name, new ClassicPreset.Input(getSocket(input.type ?? "ANY")));
|
||||
}
|
||||
|
||||
await editor.addNode(node);
|
||||
/*if (nodeData.position != null)
|
||||
area.translate(node.id, { x: nodeData.position.x, y: nodeData.position.y });*/
|
||||
}
|
||||
|
||||
for (const connections of flowData.connections) {
|
||||
{
|
||||
if (!nodeMapping.has(connections.sourceNode) || !nodeMapping.has(connections.targetNode)) {
|
||||
console.warn("Cannot create connection, node not found", connections);
|
||||
continue;
|
||||
}
|
||||
const targetNode = editor.getNode(nodeMapping.get(connections.targetNode)!) as HFNode;
|
||||
const node = editor.getNode(nodeMapping.get(connections.sourceNode)!) as HFNode;
|
||||
if (node && targetNode) {
|
||||
console.log("Creating connection from", node.id, "to", targetNode.id);
|
||||
const connection = new ClassicPreset.Connection(
|
||||
node,
|
||||
connections.sourceField,
|
||||
targetNode,
|
||||
connections.targetField
|
||||
);
|
||||
await editor.addConnection(connection);
|
||||
}
|
||||
for (const c of flowData.connections ?? []) {
|
||||
if (!nodeMapping.has(c.sourceId) || !nodeMapping.has(c.targetId)) continue;
|
||||
|
||||
}
|
||||
const sourceNode = editor.getNode(nodeMapping.get(c.sourceId)) as any;
|
||||
const targetNode = editor.getNode(nodeMapping.get(c.targetId)) as any;
|
||||
|
||||
await editor.addConnection(
|
||||
new ClassicPreset.Connection(sourceNode, c.sourceName, targetNode, c.targetName)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue