xyflow change to flowgraam
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user