mirror of
https://github.com/open-gitagent/langship.sh.git
synced 2026-08-03 07:21:04 +02:00
feat(web): initialize Next.js project with Tailwind CSS and TypeScript setup
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lyzrai/flow/pkg/models"
|
||||
)
|
||||
|
||||
// n8nWorkflowJSON mirrors the raw n8n export format for unmarshalling.
|
||||
type n8nWorkflowJSON struct {
|
||||
Name string `json:"name"`
|
||||
Nodes []models.NodeDef `json:"nodes"`
|
||||
Connections map[string]struct {
|
||||
Main []json.RawMessage `json:"main"`
|
||||
} `json:"connections"`
|
||||
Settings map[string]any `json:"settings"`
|
||||
}
|
||||
|
||||
type connectionTarget struct {
|
||||
Node string `json:"node"`
|
||||
Type string `json:"type"`
|
||||
Index int `json:"index"`
|
||||
}
|
||||
|
||||
// n8nToFlow maps specific n8n node types to flow-native equivalents.
|
||||
// Unmapped types are translated automatically via prefix replacement.
|
||||
var n8nToFlow = map[string]string{
|
||||
// All triggers → single flow trigger type
|
||||
"n8n-nodes-base.webhook": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.scheduleTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.manualTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.formTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.activationTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.errorTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.executeWorkflowTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.n8nTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.sseTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.workflowTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.localFileTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.emailReadImap": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.rssFeedReadTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.evaluationTrigger": "flow-nodes-base.trigger",
|
||||
|
||||
"n8n-nodes-base.start": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.cron": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-base.interval": "flow-nodes-base.trigger",
|
||||
|
||||
"n8n-nodes-base.function": "flow-nodes-base.code",
|
||||
"n8n-nodes-base.functionItem": "flow-nodes-base.code",
|
||||
|
||||
"n8n-nodes-base.spreadsheetFile": "flow-nodes-base.convertToFile",
|
||||
"n8n-nodes-base.moveBinaryData": "flow-nodes-base.convertToFile",
|
||||
"n8n-nodes-base.readBinaryFile": "flow-nodes-base.readWriteFile",
|
||||
"n8n-nodes-base.readBinaryFiles": "flow-nodes-base.readWriteFile",
|
||||
"n8n-nodes-base.writeBinaryFile": "flow-nodes-base.readWriteFile",
|
||||
"n8n-nodes-base.readPdf": "flow-nodes-base.extractFromFile",
|
||||
"n8n-nodes-base.htmlExtract": "flow-nodes-base.html",
|
||||
"n8n-nodes-base.transform": "flow-nodes-base.set",
|
||||
"n8n-nodes-base.noop": "flow-nodes-base.noOp",
|
||||
|
||||
"n8n-nodes-langchain.chatTrigger": "flow-nodes-base.trigger",
|
||||
"n8n-nodes-langchain.mcpTrigger": "flow-nodes-base.trigger",
|
||||
}
|
||||
|
||||
// TranslateNodeType converts an n8n node type string to its flow-native equivalent.
|
||||
// Types already using the flow-nodes-base prefix are returned as-is.
|
||||
func TranslateNodeType(n8nType string) string {
|
||||
if strings.HasPrefix(n8nType, "flow-nodes-base.") {
|
||||
return n8nType
|
||||
}
|
||||
|
||||
cleaned := strings.TrimPrefix(n8nType, "@n8n/")
|
||||
|
||||
if flowType, ok := n8nToFlow[cleaned]; ok {
|
||||
return flowType
|
||||
}
|
||||
|
||||
if strings.HasPrefix(cleaned, "n8n-nodes-base.") {
|
||||
return strings.Replace(cleaned, "n8n-nodes-base.", "flow-nodes-base.", 1)
|
||||
}
|
||||
if strings.HasPrefix(cleaned, "n8n-nodes-langchain.") {
|
||||
return strings.Replace(cleaned, "n8n-nodes-langchain.", "flow-nodes-base.", 1)
|
||||
}
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
// ParseWorkflow parses raw n8n workflow JSON bytes into a WorkflowDefinition.
|
||||
func ParseWorkflow(data []byte) (*models.WorkflowDefinition, error) {
|
||||
var raw n8nWorkflowJSON
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal workflow JSON: %w", err)
|
||||
}
|
||||
|
||||
if len(raw.Nodes) == 0 {
|
||||
return nil, fmt.Errorf("workflow has no nodes")
|
||||
}
|
||||
|
||||
for i := range raw.Nodes {
|
||||
raw.Nodes[i].Type = TranslateNodeType(raw.Nodes[i].Type)
|
||||
}
|
||||
|
||||
triggerCount := 0
|
||||
for _, n := range raw.Nodes {
|
||||
if n.Type == "flow-nodes-base.trigger" {
|
||||
triggerCount++
|
||||
}
|
||||
}
|
||||
if triggerCount == 0 {
|
||||
return nil, fmt.Errorf("workflow must have exactly one trigger node (found 0)")
|
||||
}
|
||||
if triggerCount > 1 {
|
||||
return nil, fmt.Errorf("workflow must have exactly one trigger node (found %d)", triggerCount)
|
||||
}
|
||||
|
||||
nodeNames := make(map[string]bool, len(raw.Nodes))
|
||||
for _, n := range raw.Nodes {
|
||||
nodeNames[n.Name] = true
|
||||
}
|
||||
|
||||
var connections []models.ConnectionDef
|
||||
|
||||
for sourceName, connData := range raw.Connections {
|
||||
if !nodeNames[sourceName] {
|
||||
continue
|
||||
}
|
||||
|
||||
for outputIndex, rawTargets := range connData.Main {
|
||||
var targets []connectionTarget
|
||||
if err := json.Unmarshal(rawTargets, &targets); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, t := range targets {
|
||||
if t.Node == "" {
|
||||
continue
|
||||
}
|
||||
if !nodeNames[t.Node] {
|
||||
continue
|
||||
}
|
||||
connections = append(connections, models.ConnectionDef{
|
||||
SourceNode: sourceName,
|
||||
SourceOutputIndex: outputIndex,
|
||||
TargetNode: t.Node,
|
||||
TargetInputIndex: t.Index,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &models.WorkflowDefinition{
|
||||
Name: raw.Name,
|
||||
Nodes: raw.Nodes,
|
||||
Connections: connections,
|
||||
Settings: raw.Settings,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user