solved node input output issue
This commit is contained in:
parent
2154b51096
commit
ee4505bfea
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
|
|
@ -2,9 +2,7 @@ import { GetSchemes, ClassicPreset } from "rete";
|
|||
import { INodeModel } from "./flow";
|
||||
|
||||
export type HFNode = ClassicPreset.Node & {
|
||||
data?: {
|
||||
value: INodeModel;
|
||||
};
|
||||
data: INodeModel;
|
||||
};
|
||||
|
||||
export type HFConnection = ClassicPreset.Connection<HFNode, HFNode>;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,17 @@
|
|||
<div class="rounded-xl bg-white shadow-md border border-gray-200 cursor-pointer">
|
||||
<div class="p-3 flex items-center justify-between gap-2">
|
||||
<p class="text-sm font-semibold text-gray-700">
|
||||
{{ data.label }}
|
||||
</p>
|
||||
<div class="relative rounded-xl bg-white shadow-md border border-gray-200 overflow-visible">
|
||||
<!-- header/row relativa -->
|
||||
<div class="relative w-full p-3 flex items-center justify-between">
|
||||
<p class="text-sm font-semibold text-gray-700">{{ data.label }}</p>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<div
|
||||
refComponent
|
||||
[data]="{
|
||||
type: 'socket',
|
||||
side: 'output',
|
||||
key: outputKey,
|
||||
nodeId: data.id,
|
||||
payload: outputSocket
|
||||
}"
|
||||
[emit]="emit"
|
||||
></div>
|
||||
</div>
|
||||
<div refComponent
|
||||
[data]="{
|
||||
type: 'socket',
|
||||
side: 'output',
|
||||
key: outputKey,
|
||||
nodeId: data.id,
|
||||
payload: outputSocket
|
||||
}"
|
||||
[emit]="emit"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2,11 +2,9 @@ import {
|
|||
Component,
|
||||
Input,
|
||||
HostBinding,
|
||||
ChangeDetectorRef,
|
||||
} from "@angular/core";
|
||||
import { CommonModule, KeyValue } from "@angular/common";
|
||||
import { ReteModule } from "rete-angular-plugin/21";
|
||||
import { HFNode } from "@models/nodes";
|
||||
import { ClassicPreset } from "rete";
|
||||
|
||||
@Component({
|
||||
|
|
@ -22,7 +20,6 @@ export class InputNodeComponent {
|
|||
@Input() emit!: (data: any) => void;
|
||||
@Input() rendered!: () => void;
|
||||
|
||||
seed = 0;
|
||||
|
||||
@HostBinding("class.selected") get selected() {
|
||||
return this.data.selected;
|
||||
|
|
@ -35,6 +32,8 @@ export class InputNodeComponent {
|
|||
const entries = Object.entries(this.data.outputs);
|
||||
const [key, output] = entries[0];
|
||||
|
||||
console.log("InputNodeComponent ngOnInit", key, output);
|
||||
|
||||
this.outputKey = key;
|
||||
this.outputSocket = (output as any).socket;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
/* Nodo base */
|
||||
.node {
|
||||
width: 220px;
|
||||
border-radius: 14px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb; /* gray-200 */
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: linear-gradient(135deg, #7c3aed, #6366f1); /* violet → indigo */
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Icona LLM */
|
||||
.icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* Titolo */
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.body {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Riga input/output */
|
||||
.io {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Label IO */
|
||||
.label {
|
||||
font-size: 12px;
|
||||
color: #374151; /* gray-700 */
|
||||
}
|
||||
|
||||
/* Socket */
|
||||
.socket {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 999px;
|
||||
background: #6366f1;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 0 0 1px #6366f1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
.socket:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<div class="relative w-55 rounded-2xl bg-white border border-gray-200 shadow-lg overflow-visible font-sans">
|
||||
<!-- Header -->
|
||||
<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>
|
||||
<span class="text-sm font-semibold truncate">LLM Node</span>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="p-3 grid grid-cols-2 gap-4">
|
||||
|
||||
<div class="grid grid-rows-2 gap-1">
|
||||
|
||||
<div refComponent class="absolute left-1"
|
||||
[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>
|
||||
|
||||
</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"
|
||||
[data]="{
|
||||
type: 'socket',
|
||||
side: 'output',
|
||||
key: outputKey,
|
||||
nodeId: data.id,
|
||||
payload: outputSocket}" [emit]="emit"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Llm } from './llm';
|
||||
|
||||
describe('Llm', () => {
|
||||
let component: Llm;
|
||||
let fixture: ComponentFixture<Llm>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Llm]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Llm);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, HostBinding, Input } from '@angular/core';
|
||||
import { ClassicPreset } from 'rete';
|
||||
import { ReteModule } from 'rete-angular-plugin/21';
|
||||
|
||||
@Component({
|
||||
selector: 'app-llm',
|
||||
imports: [CommonModule, ReteModule],
|
||||
templateUrl: './llm.html',
|
||||
styleUrl: './llm.css',
|
||||
host: {
|
||||
"data-testid": "node"
|
||||
}
|
||||
})
|
||||
export class LLMNodeComponent {
|
||||
|
||||
@Input() data!: any;
|
||||
@Input() emit!: (data: any) => void;
|
||||
@Input() rendered!: () => void;
|
||||
|
||||
|
||||
@HostBinding("class.selected") get selected() {
|
||||
return this.data.selected;
|
||||
}
|
||||
|
||||
outputKey!: string;
|
||||
outputSocket!: ClassicPreset.Socket;
|
||||
|
||||
inputKey!: string;
|
||||
inputSocket!: ClassicPreset.Socket;
|
||||
|
||||
ngOnInit() {
|
||||
const outputEntries = Object.entries(this.data.outputs);
|
||||
const [outKey, output] = outputEntries[0];
|
||||
|
||||
this.outputKey = outKey;
|
||||
this.outputSocket = (output as any).socket;
|
||||
|
||||
const inputEntries = Object.entries(this.data.inputs);
|
||||
const [inKey, input] = inputEntries[0];
|
||||
|
||||
this.inputKey = inKey;
|
||||
this.inputSocket = (input as any).socket;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.rendered();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,20 +1,15 @@
|
|||
<div class="rounded-xl bg-white shadow-md border border-gray-200 cursor-pointer">
|
||||
<div class="p-3 flex items-center justify-between gap-2">
|
||||
<div class="flex justify-start">
|
||||
<div
|
||||
refComponent
|
||||
[data]="{
|
||||
<div class="relative rounded-xl bg-white shadow-md border border-gray-200 overflow-visible">
|
||||
<!-- header/row relativa -->
|
||||
<div class="relative p-3 flex items-center justify-between overflow-visible">
|
||||
<div refComponent [data]="{
|
||||
type: 'socket',
|
||||
side: 'input',
|
||||
key: inputKey,
|
||||
nodeId: data.id,
|
||||
payload: inputSocket
|
||||
}"
|
||||
[emit]="emit"
|
||||
></div>
|
||||
</div>
|
||||
<p class="text-sm font-semibold text-gray-700">
|
||||
{{ data.label }}
|
||||
</p>
|
||||
}" [emit]="emit"></div>
|
||||
<p class="text-sm font-semibold text-gray-700">{{ data.label }}</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2,13 +2,10 @@ import {
|
|||
Component,
|
||||
Input,
|
||||
HostBinding,
|
||||
ChangeDetectorRef,
|
||||
OnChanges
|
||||
} from "@angular/core";
|
||||
import { ClassicPreset } from "rete";
|
||||
import { CommonModule, KeyValue } from "@angular/common";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ReteModule } from "rete-angular-plugin/21";
|
||||
import { HFNode } from "@models/nodes";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./output-node.component.html",
|
||||
|
|
@ -23,8 +20,6 @@ export class OutputNodeComponent {
|
|||
@Input() emit!: (data: any) => void;
|
||||
@Input() rendered!: () => void;
|
||||
|
||||
seed = 0;
|
||||
|
||||
@HostBinding("class.selected") get selected() {
|
||||
return this.data.selected;
|
||||
}
|
||||
|
|
@ -40,6 +35,8 @@ export class OutputNodeComponent {
|
|||
const entries = Object.entries(this.data.inputs);
|
||||
const [key, input] = entries[0];
|
||||
|
||||
console.log("OutputNodeComponent ngOnInit", key, input);
|
||||
|
||||
this.inputKey = key;
|
||||
this.inputSocket = (input as any).socket;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { Component, ElementRef, HostBinding, Input } from "@angular/core";
|
||||
import { ReteModule } from "rete-angular-plugin/21";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [ReteModule],
|
||||
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)]
|
||||
`
|
||||
},
|
||||
template: ``
|
||||
})
|
||||
export class InputSocket {
|
||||
@Input() data!: any;
|
||||
@Input() emit!: (data: any) => void;
|
||||
@Input() rendered!: () => void;
|
||||
|
||||
@HostBinding("title") get title() {
|
||||
return this.data?.name ?? "";
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log("InputSocket ngOnInit", this.data);
|
||||
}
|
||||
|
||||
|
||||
ngAfterViewInit() {
|
||||
requestAnimationFrame(() => {
|
||||
this.rendered?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { Component, ElementRef, HostBinding, Input } from '@angular/core';
|
||||
import { ReteModule } from 'rete-angular-plugin/21';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [ReteModule],
|
||||
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)]
|
||||
`
|
||||
},
|
||||
template: ``
|
||||
})
|
||||
export class OutputSocket {
|
||||
@Input() data!: any;
|
||||
@Input() emit!: (data: any) => void;
|
||||
@Input() rendered!: () => void;
|
||||
|
||||
@HostBinding("title") get title() {
|
||||
return this.data?.name ?? "";
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log("OutputSocket ngOnInit", this.data);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
requestAnimationFrame(() => {
|
||||
this.rendered?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Injector } from "@angular/core";
|
||||
import { Injector, Input } from "@angular/core";
|
||||
import { NodeEditor, GetSchemes, ClassicPreset } from "rete";
|
||||
import { AreaPlugin, AreaExtensions } from "rete-area-plugin";
|
||||
import {
|
||||
|
|
@ -8,8 +8,11 @@ import {
|
|||
import { AngularPlugin, Presets, AngularArea2D, NodeComponent } from "rete-angular-plugin/21";
|
||||
import { InputNodeComponent } from "@shared/nodes/input/input-node.component";
|
||||
import { OutputNodeComponent } from "@shared/nodes/output/output-node.component";
|
||||
import { HFSchemes } from "@models/nodes";
|
||||
import { FlowData } from "@models/flow";
|
||||
import { HFNode, HFSchemes } from "@models/nodes";
|
||||
import { FlowData, INodeModel } from "@models/flow";
|
||||
import { LLMNodeComponent } from "@shared/nodes/llm/llm";
|
||||
import { OutputSocket } from "@shared/sockets/output/output";
|
||||
import { InputSocket } from "@shared/sockets/input/input";
|
||||
|
||||
type Schemes = GetSchemes<
|
||||
ClassicPreset.Node,
|
||||
|
|
@ -19,8 +22,8 @@ type AreaExtra = AngularArea2D<Schemes>;
|
|||
|
||||
export async function createEditor(container: HTMLElement, injector: Injector, flowData: FlowData) {
|
||||
|
||||
const editor = new NodeEditor<Schemes>();
|
||||
const area = new AreaPlugin<Schemes, AreaExtra>(container);
|
||||
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 });
|
||||
|
||||
|
|
@ -38,11 +41,13 @@ export async function createEditor(container: HTMLElement, injector: Injector, f
|
|||
if (context.payload.label === "Output") {
|
||||
return OutputNodeComponent;
|
||||
}
|
||||
|
||||
return NodeComponent;
|
||||
|
||||
return LLMNodeComponent;
|
||||
},
|
||||
|
||||
socket(context) {
|
||||
const side = context.side;
|
||||
return side === "input" ? InputSocket : OutputSocket;
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
@ -63,15 +68,17 @@ export async function createEditor(container: HTMLElement, injector: Injector, f
|
|||
if (flowData)
|
||||
await loadFlowData(editor, flowData);
|
||||
|
||||
|
||||
AreaExtensions.zoomAt(area, editor.getNodes());
|
||||
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
export function exportGraph(editor: NodeEditor<HFSchemes>) {
|
||||
const nodes = editor.getNodes().map(schema => ({
|
||||
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,
|
||||
|
||||
}));
|
||||
|
||||
|
|
@ -99,7 +106,9 @@ async function loadFlowData(editor: NodeEditor<HFSchemes>, flowData: FlowData) {
|
|||
const socket = new ClassicPreset.Socket("socket");
|
||||
const nodeMapping = new Map<string, string>();
|
||||
for (const nodeData of flowData.nodes) {
|
||||
const node = new ClassicPreset.Node(nodeData.nodeDefinition?.category || "Undefined");
|
||||
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);
|
||||
|
|
@ -123,9 +132,14 @@ async function loadFlowData(editor: NodeEditor<HFSchemes>, flowData: FlowData) {
|
|||
area.translate(node.id, { x: nodeData.position.x, y: nodeData.position.y });*/
|
||||
}
|
||||
|
||||
for (const connections of flowData.connections) { {
|
||||
const targetNode = editor.getNode(nodeMapping.get(connections.targetNode) || "");
|
||||
const node = editor.getNode(nodeMapping.get(connections.sourceNode) || "");
|
||||
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(
|
||||
|
|
|
|||
Loading…
Reference in New Issue