xyflow change to flowgraam

This commit is contained in:
mlogclub
2026-06-27 21:27:57 +08:00
parent 228ad1902f
commit 689acc997c
32 changed files with 3154 additions and 5004 deletions
+152 -17
View File
@@ -2,20 +2,34 @@ package dsl
import "encoding/json"
const SchemaVersion = 2
type Definition struct {
SchemaVersion int `json:"schemaVersion"`
EntryNodeID string `json:"entryNodeId"`
Nodes []Node `json:"nodes"`
Edges []Edge `json:"edges"`
}
type Node struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Position Position `json:"position"`
Config json.RawMessage `json:"config"`
Inputs map[string]VariableSelector `json:"inputs,omitempty"`
ID string `json:"id"`
Type string `json:"type"`
Meta NodeMeta `json:"meta"`
Data NodeData `json:"data"`
Blocks []Node `json:"blocks,omitempty"`
Edges []Edge `json:"edges,omitempty"`
}
type NodeMeta struct {
Position Position `json:"position"`
}
type NodeData struct {
Title string `json:"title,omitempty"`
Config json.RawMessage `json:"config,omitempty"`
Inputs json.RawMessage `json:"inputs,omitempty"`
Outputs json.RawMessage `json:"outputs,omitempty"`
InputsValues map[string]Value `json:"inputsValues,omitempty"`
Extra map[string]json.RawMessage `json:"-"`
}
type Position struct {
@@ -24,9 +38,25 @@ type Position struct {
}
type Edge struct {
ID string `json:"id"`
Source string `json:"source"`
Target string `json:"target"`
SourceNodeID string `json:"sourceNodeID"`
TargetNodeID string `json:"targetNodeID"`
SourcePortID string `json:"sourcePortID,omitempty"`
TargetPortID string `json:"targetPortID,omitempty"`
}
type ValueType string
const (
ValueTypeConstant ValueType = "constant"
ValueTypeRef ValueType = "ref"
ValueTypeTemplate ValueType = "template"
)
type Value struct {
Type ValueType `json:"type"`
Content []string `json:"content,omitempty"`
ConstantContent any `json:"-"`
RawContent json.RawMessage `json:"-"`
}
type ConditionConfig struct {
@@ -42,13 +72,118 @@ type ConditionBranch struct {
}
type Condition struct {
Expression string `json:"expression,omitempty"`
Left *VariableSelector `json:"left,omitempty"`
Operator string `json:"operator,omitempty"`
Right any `json:"right,omitempty"`
Expression string `json:"expression,omitempty"`
Left *Value `json:"left,omitempty"`
Operator string `json:"operator,omitempty"`
Right any `json:"right,omitempty"`
}
type VariableSelector struct {
NodeID string `json:"nodeId"`
Field string `json:"field"`
func RefValue(nodeID string, field string) Value {
return Value{Type: ValueTypeRef, Content: []string{nodeID, field}}
}
func ConstantValue(value any) Value {
raw, _ := json.Marshal(value)
return Value{Type: ValueTypeConstant, ConstantContent: value, RawContent: raw}
}
func TemplateValue(value string) Value {
return Value{Type: ValueTypeTemplate, Content: []string{value}}
}
func (v Value) Ref() (nodeID string, field string, ok bool) {
if v.Type != ValueTypeRef || len(v.Content) < 2 {
return "", "", false
}
return v.Content[0], v.Content[1], true
}
func (v *Value) UnmarshalJSON(data []byte) error {
type alias struct {
Type ValueType `json:"type"`
Content json.RawMessage `json:"content"`
}
var parsed alias
if err := json.Unmarshal(data, &parsed); err != nil {
return err
}
v.Type = parsed.Type
v.RawContent = append(v.RawContent[:0], parsed.Content...)
switch parsed.Type {
case ValueTypeRef:
var content []string
if len(parsed.Content) > 0 {
if err := json.Unmarshal(parsed.Content, &content); err != nil {
return err
}
}
v.Content = content
case ValueTypeTemplate:
var content string
if len(parsed.Content) > 0 {
if err := json.Unmarshal(parsed.Content, &content); err != nil {
return err
}
}
v.Content = []string{content}
case ValueTypeConstant:
if len(parsed.Content) > 0 {
if err := json.Unmarshal(parsed.Content, &v.ConstantContent); err != nil {
return err
}
}
default:
if len(parsed.Content) > 0 {
var content []string
if err := json.Unmarshal(parsed.Content, &content); err == nil {
v.Content = content
}
}
}
return nil
}
func (v Value) MarshalJSON() ([]byte, error) {
type alias struct {
Type ValueType `json:"type"`
Content any `json:"content,omitempty"`
}
var content any
switch v.Type {
case ValueTypeRef:
content = v.Content
case ValueTypeTemplate:
if len(v.Content) > 0 {
content = v.Content[0]
}
case ValueTypeConstant:
content = v.ConstantContent
default:
if len(v.Content) > 0 {
content = v.Content
}
}
return json.Marshal(alias{Type: v.Type, Content: content})
}
func (d *NodeData) UnmarshalJSON(data []byte) error {
type alias NodeData
var parsed alias
if err := json.Unmarshal(data, &parsed); err != nil {
return err
}
extra := make(map[string]json.RawMessage)
if err := json.Unmarshal(data, &extra); err != nil {
return err
}
delete(extra, "title")
delete(extra, "config")
delete(extra, "inputs")
delete(extra, "outputs")
delete(extra, "inputsValues")
*d = NodeData(parsed)
if len(extra) > 0 {
d.Extra = extra
}
return nil
}
+83
View File
@@ -0,0 +1,83 @@
package dsl_test
import (
"encoding/json"
"testing"
"agent-desk/internal/ai/workflow/dsl"
)
func TestDefinitionUnmarshalsFlowGramStyleSchema(t *testing.T) {
raw := []byte(`{
"schemaVersion": 2,
"nodes": [{
"id": "send_1",
"type": "send_reply",
"meta": {
"position": { "x": 360, "y": 120 }
},
"data": {
"title": "发送回复",
"config": { "text": "hello" },
"inputs": {
"type": "object",
"properties": {
"replyText": { "type": "string" }
},
"required": ["replyText"]
},
"outputs": {
"type": "object",
"properties": {
"sent": { "type": "boolean" }
}
},
"inputsValues": {
"replyText": {
"type": "ref",
"content": ["start_1", "userMessage"]
}
}
}
}],
"edges": [{
"sourceNodeID": "start_1",
"targetNodeID": "send_1",
"sourcePortID": "default"
}]
}`)
var def dsl.Definition
if err := json.Unmarshal(raw, &def); err != nil {
t.Fatalf("unmarshal definition: %v", err)
}
if def.SchemaVersion != 2 {
t.Fatalf("unexpected schema version: %d", def.SchemaVersion)
}
node := def.Nodes[0]
if node.ID != "send_1" || node.Type != "send_reply" {
t.Fatalf("unexpected node identity: %#v", node)
}
if node.Meta.Position.X != 360 || node.Meta.Position.Y != 120 {
t.Fatalf("unexpected node position: %#v", node.Meta.Position)
}
if node.Data.Title != "发送回复" {
t.Fatalf("unexpected node title: %q", node.Data.Title)
}
var config map[string]string
if err := json.Unmarshal(node.Data.Config, &config); err != nil {
t.Fatalf("unmarshal config: %v", err)
}
if config["text"] != "hello" {
t.Fatalf("unexpected config: %s", node.Data.Config)
}
replyText := node.Data.InputsValues["replyText"]
if replyText.Type != dsl.ValueTypeRef || len(replyText.Content) != 2 || replyText.Content[0] != "start_1" || replyText.Content[1] != "userMessage" {
t.Fatalf("unexpected replyText value: %#v", replyText)
}
edge := def.Edges[0]
if edge.SourceNodeID != "start_1" || edge.TargetNodeID != "send_1" || edge.SourcePortID != "default" {
t.Fatalf("unexpected edge: %#v", edge)
}
}