2026-05-12 13:34:28 +08:00
|
|
|
|
package node
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"ai-agent/workflow/consts/node"
|
|
|
|
|
|
nodeDto "ai-agent/workflow/model/dto/node"
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
|
|
"gitea.com/red-future/common/beans"
|
2026-06-10 14:23:55 +08:00
|
|
|
|
commonHttp "gitea.com/red-future/common/http"
|
2026-05-12 13:34:28 +08:00
|
|
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var NodeLibraryService = &nodeLibraryService{}
|
|
|
|
|
|
|
|
|
|
|
|
type nodeLibraryService struct{}
|
|
|
|
|
|
|
2026-06-10 14:23:55 +08:00
|
|
|
|
func GetModelType(ctx context.Context) (mainTypeMap map[int]string, err error) {
|
|
|
|
|
|
headers := make(map[string]string)
|
|
|
|
|
|
if r := g.RequestFromCtx(ctx); r != nil {
|
|
|
|
|
|
for k, v := range r.Request.Header {
|
|
|
|
|
|
if len(v) > 0 {
|
|
|
|
|
|
headers[k] = v[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
res := new(nodeDto.ModelTypeResponse)
|
|
|
|
|
|
err = commonHttp.Get(ctx, "model-gateway/model/listType", headers, res, nil)
|
|
|
|
|
|
// 通用过滤:只保留 能被 100 整除的主类型(100/200/300...)
|
|
|
|
|
|
mainTypeMap = make(map[int]string)
|
|
|
|
|
|
for typ, name := range res.Type {
|
|
|
|
|
|
if typ%100 == 0 {
|
|
|
|
|
|
mainTypeMap[typ] = name
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-12 13:34:28 +08:00
|
|
|
|
|
2026-06-10 14:23:55 +08:00
|
|
|
|
func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.WorkflowNodeTreeReq) (*nodeDto.WorkflowNodeTreeRes, error) {
|
2026-05-12 13:34:28 +08:00
|
|
|
|
WorkflowNodeGroups := []node.NodeGroupItem{
|
|
|
|
|
|
{
|
|
|
|
|
|
Group: node.NodeGroupComponent,
|
|
|
|
|
|
Label: node.NodeGroupNameComponent,
|
|
|
|
|
|
Items: []node.NodeItem{
|
|
|
|
|
|
{
|
2026-06-10 14:23:55 +08:00
|
|
|
|
NodeCode: node.NodeTypeTextModel,
|
|
|
|
|
|
NodeName: node.NodeNameTextModel,
|
|
|
|
|
|
ModelType: node.ModelTypeText,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
PromptOption: true,
|
|
|
|
|
|
IsSaveFile: true,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
2026-05-12 13:34:28 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-06-10 14:23:55 +08:00
|
|
|
|
NodeCode: node.NodeTypeImageModel,
|
|
|
|
|
|
NodeName: node.NodeNameImageModel,
|
|
|
|
|
|
ModelType: node.ModelTypeImage,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
PromptOption: true,
|
|
|
|
|
|
IsSaveFile: true,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
2026-05-12 13:34:28 +08:00
|
|
|
|
},
|
2026-06-10 14:23:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeVideoModel,
|
|
|
|
|
|
NodeName: node.NodeNameVideoModel,
|
|
|
|
|
|
ModelType: node.ModelTypeVideo,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
PromptOption: true,
|
|
|
|
|
|
IsSaveFile: true,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeAudioModel,
|
|
|
|
|
|
NodeName: node.NodeNameAudioModel,
|
|
|
|
|
|
ModelType: node.ModelTypeAudio,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
PromptOption: true,
|
|
|
|
|
|
IsSaveFile: true,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeBatchModel,
|
|
|
|
|
|
NodeName: node.NodeNameBatchModel,
|
|
|
|
|
|
ModelType: node.ModelTypeText,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
PromptOption: true,
|
|
|
|
|
|
IsSaveFile: true,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
|
|
|
|
|
//{
|
|
|
|
|
|
// NodeCode: node.NodeTypeSenseOptimizeModel,
|
|
|
|
|
|
// NodeName: node.NodeNameSenseOptimizeModel,
|
|
|
|
|
|
// ModelType: node.ModelTypeText,
|
|
|
|
|
|
// SkillOption: false,
|
|
|
|
|
|
// FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
// ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
//},
|
|
|
|
|
|
//{
|
|
|
|
|
|
// NodeCode: node.NodeTypeStoryOptimizeModel,
|
|
|
|
|
|
// NodeName: node.NodeNameStoryOptimizeModel,
|
|
|
|
|
|
// ModelType: node.ModelTypeText,
|
|
|
|
|
|
// SkillOption: false,
|
|
|
|
|
|
// FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
// ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
//},
|
|
|
|
|
|
//{
|
|
|
|
|
|
// NodeCode: node.NodeTypeScriptOptimizeModel,
|
|
|
|
|
|
// NodeName: node.NodeNameScriptOptimizeModel,
|
|
|
|
|
|
// ModelType: node.ModelTypeText,
|
|
|
|
|
|
// SkillOption: false,
|
|
|
|
|
|
// FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
// ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
//},
|
2026-05-12 13:34:28 +08:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Group: node.NodeGroupBase,
|
|
|
|
|
|
Label: node.NodeGroupNameBase,
|
|
|
|
|
|
Items: []node.NodeItem{
|
2026-06-10 14:23:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeDataConversionModel,
|
|
|
|
|
|
NodeName: node.NodeNameDataConversionModel,
|
|
|
|
|
|
ModelType: node.ModelTypeText,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
PromptOption: true,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
2026-05-12 13:34:28 +08:00
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeMerge,
|
|
|
|
|
|
NodeName: node.NodeNameMerge,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
2026-06-10 14:23:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeDataMerge,
|
|
|
|
|
|
NodeName: node.NodeNameDataMerge,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
2026-05-12 13:34:28 +08:00
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeJudge,
|
|
|
|
|
|
NodeName: node.NodeNameJudge,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{
|
|
|
|
|
|
{Field: "condition", Label: node.FormLabelCondition, Type: "input", Required: true},
|
|
|
|
|
|
},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeForm,
|
|
|
|
|
|
NodeName: node.NodeNameForm,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
2026-06-10 14:23:55 +08:00
|
|
|
|
{
|
|
|
|
|
|
NodeCode: node.NodeTypeHttp,
|
|
|
|
|
|
NodeName: node.NodeNameHttp,
|
|
|
|
|
|
SkillOption: false,
|
|
|
|
|
|
IsSaveFile: true,
|
|
|
|
|
|
FormConfig: []node.NodeFormField{
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "method",
|
|
|
|
|
|
Label: "请求方式",
|
|
|
|
|
|
Type: "select",
|
|
|
|
|
|
Required: true,
|
|
|
|
|
|
Options: []node.SelectOption{
|
|
|
|
|
|
{Label: "GET", Value: "GET"},
|
|
|
|
|
|
{Label: "POST", Value: "POST"},
|
|
|
|
|
|
{Label: "PUT", Value: "PUT"},
|
|
|
|
|
|
{Label: "DELETE", Value: "DELETE"},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "url",
|
|
|
|
|
|
Label: "请求地址",
|
|
|
|
|
|
Type: "input",
|
|
|
|
|
|
Required: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "headers",
|
|
|
|
|
|
Label: "请求头(支持Authorization鉴权)",
|
|
|
|
|
|
Type: "keyValue",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "bodyType",
|
|
|
|
|
|
Label: "请求体类型",
|
|
|
|
|
|
Type: "select",
|
|
|
|
|
|
Required: true,
|
|
|
|
|
|
Options: []node.SelectOption{
|
|
|
|
|
|
{Label: "无", Value: "None"},
|
|
|
|
|
|
{Label: "JSON", Value: "JSON"},
|
|
|
|
|
|
//{Label: "表单", Value: "FormUrlEncoded"},
|
|
|
|
|
|
//{Label: "文件上传", Value: "FormData"},
|
|
|
|
|
|
//{Label: "原生文本", Value: "Raw"},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "body",
|
|
|
|
|
|
Label: "请求体内容",
|
|
|
|
|
|
Type: "keyValue",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "response",
|
|
|
|
|
|
Label: "结果返回结构",
|
|
|
|
|
|
Type: "keyValue",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "responseType",
|
|
|
|
|
|
Label: "结果返回方式",
|
|
|
|
|
|
Type: "select",
|
|
|
|
|
|
Required: true,
|
|
|
|
|
|
Options: []node.SelectOption{
|
|
|
|
|
|
{Label: "同步返回", Value: "sync"},
|
|
|
|
|
|
{Label: "等候回调", Value: "callback"},
|
|
|
|
|
|
{Label: "主动拉取", Value: "pull"},
|
|
|
|
|
|
},
|
|
|
|
|
|
Expand: []node.NodeFormField{
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "method",
|
|
|
|
|
|
Label: "请求方式",
|
|
|
|
|
|
Type: "select",
|
|
|
|
|
|
Required: true,
|
|
|
|
|
|
Options: []node.SelectOption{
|
|
|
|
|
|
{Label: "GET", Value: "GET"},
|
|
|
|
|
|
{Label: "POST", Value: "POST"},
|
|
|
|
|
|
{Label: "PUT", Value: "PUT"},
|
|
|
|
|
|
{Label: "DELETE", Value: "DELETE"},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "url",
|
|
|
|
|
|
Label: "请求地址",
|
|
|
|
|
|
Type: "input",
|
|
|
|
|
|
Required: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "headers",
|
|
|
|
|
|
Label: "请求头(支持Authorization鉴权)",
|
|
|
|
|
|
Type: "keyValue",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "bodyType",
|
|
|
|
|
|
Label: "请求体类型",
|
|
|
|
|
|
Type: "select",
|
|
|
|
|
|
Required: true,
|
|
|
|
|
|
Options: []node.SelectOption{
|
|
|
|
|
|
{Label: "无", Value: "None"},
|
|
|
|
|
|
{Label: "JSON", Value: "JSON"},
|
|
|
|
|
|
//{Label: "表单", Value: "FormUrlEncoded"},
|
|
|
|
|
|
//{Label: "文件上传", Value: "FormData"},
|
|
|
|
|
|
//{Label: "原生文本", Value: "Raw"},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "body",
|
|
|
|
|
|
Label: "请求体内容",
|
|
|
|
|
|
Type: "keyValue",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "response",
|
|
|
|
|
|
Label: "结果返回结构",
|
|
|
|
|
|
Type: "keyValue",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "timeout",
|
|
|
|
|
|
Label: "超时时间(秒)",
|
|
|
|
|
|
Type: "inputNumber",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
Default: 30,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "insecureSkipVerify",
|
|
|
|
|
|
Label: "跳过HTTPS证书校验",
|
|
|
|
|
|
Type: "switch",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
Default: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "callbackUrl",
|
|
|
|
|
|
Label: "回调地址(只需要填写字段名称)",
|
|
|
|
|
|
Type: "input",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
Default: "",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "timeout",
|
|
|
|
|
|
Label: "超时时间(秒)",
|
|
|
|
|
|
Type: "inputNumber",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
Default: 30,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
Field: "insecureSkipVerify",
|
|
|
|
|
|
Label: "跳过HTTPS证书校验",
|
|
|
|
|
|
Type: "switch",
|
|
|
|
|
|
Required: false,
|
|
|
|
|
|
Default: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
},
|
2026-05-12 13:34:28 +08:00
|
|
|
|
//{
|
|
|
|
|
|
// NodeCode: node.NodeTypeModel,
|
|
|
|
|
|
// NodeName: node.NodeNameModel,
|
|
|
|
|
|
// SkillOption: true,
|
|
|
|
|
|
// FormConfig: []node.NodeFormField{},
|
|
|
|
|
|
// ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
//},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2026-06-10 14:23:55 +08:00
|
|
|
|
//{
|
|
|
|
|
|
// Group: node.NodeGroupCustom,
|
|
|
|
|
|
// Label: node.NodeGroupNameCustom,
|
|
|
|
|
|
// Items: []node.NodeItem{
|
|
|
|
|
|
// {
|
|
|
|
|
|
// NodeCode: node.NodeTypeCustomNode,
|
|
|
|
|
|
// NodeName: node.NodeNameCustomNode,
|
|
|
|
|
|
// SkillOption: true,
|
|
|
|
|
|
// FormConfig: []node.NodeFormField{
|
|
|
|
|
|
// {Field: "nodeName", Label: node.FormLabelApiKey, Type: "input", Required: true},
|
|
|
|
|
|
// {Field: "nodeType", Label: node.FormLabelModel, Type: "input", Required: true},
|
|
|
|
|
|
// },
|
|
|
|
|
|
// ModelConfig: []node.ModelItem{},
|
|
|
|
|
|
// },
|
|
|
|
|
|
// },
|
|
|
|
|
|
//},
|
2026-05-12 13:34:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
tree := &nodeDto.WorkflowNodeTreeRes{
|
|
|
|
|
|
Groups: WorkflowNodeGroups,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 遍历分组,根据 typeId=1 给【文本模型节点】追加固定表单
|
|
|
|
|
|
for gIdx := range tree.Groups {
|
|
|
|
|
|
group := &tree.Groups[gIdx]
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历分组下的每个节点
|
|
|
|
|
|
for itemIdx := range group.Items {
|
|
|
|
|
|
item := &group.Items[itemIdx]
|
2026-06-10 14:23:55 +08:00
|
|
|
|
if item.NodeCode == node.NodeTypeTextModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeImageModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeVideoModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeAudioModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeBatchModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeDataConversionModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeSenseOptimizeModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeStoryOptimizeModel ||
|
|
|
|
|
|
item.NodeCode == node.NodeTypeScriptOptimizeModel {
|
2026-05-12 13:34:28 +08:00
|
|
|
|
item.ModelConfig = append(item.ModelConfig, node.ModelItem{
|
|
|
|
|
|
ModelName: "自定义",
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return tree, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetUserInfo 设置用户信息
|
|
|
|
|
|
func (s *nodeLibraryService) SetUserInfo(ctx context.Context, creator string, tenantId uint64) (headers map[string]string, err error) {
|
|
|
|
|
|
// 创建完整的用户信息
|
|
|
|
|
|
userInfo := &beans.User{
|
|
|
|
|
|
UserName: creator,
|
|
|
|
|
|
TenantId: tenantId,
|
|
|
|
|
|
}
|
|
|
|
|
|
ctx = context.WithValue(ctx, "user", *userInfo)
|
|
|
|
|
|
// 提取并保存请求头(在连接升级前)
|
|
|
|
|
|
headers = make(map[string]string)
|
|
|
|
|
|
// 提取其他headers
|
|
|
|
|
|
if r := g.RequestFromCtx(ctx); r != nil {
|
|
|
|
|
|
for k, v := range r.Request.Header {
|
|
|
|
|
|
if len(v) > 0 {
|
|
|
|
|
|
headers[k] = v[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 将完整用户信息序列化为JSON,放到X-User-Info请求头
|
|
|
|
|
|
userInfoJson, err := gjson.Encode(userInfo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("用户信息序列化失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
headers["X-User-Info"] = string(userInfoJson)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|