feat: 新增主动拉取与多类型回调功能
- 新增 ActivePull 实体、DAO、DTO 及 Service,支持主动拉取任务管理 - 新增 ComposeCallback、VideoCallback、HttpNodeCallback 多类型回调接口 - FlowExecution 增加 NodeGroupId 和 TotalTokens 字段,支持节点组追踪与 Token 统计 - ExecutedNodes 结构由字符串列表改为包含执行状态的节点对象列表 - 重构回调通知机制,统一 Notify 函数调用 - 优化输出项类型判断逻辑,新增文件类型标识
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
commonHttp "gitea.com/red-future/common/http"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
@@ -15,35 +16,122 @@ var NodeLibraryService = &nodeLibraryService{}
|
||||
|
||||
type nodeLibraryService struct{}
|
||||
|
||||
func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.WorkflowNodeTreeReq) (*nodeDto.WorkflowNodeTreeRes, error) {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.WorkflowNodeTreeReq) (*nodeDto.WorkflowNodeTreeRes, error) {
|
||||
WorkflowNodeGroups := []node.NodeGroupItem{
|
||||
{
|
||||
Group: node.NodeGroupComponent,
|
||||
Label: node.NodeGroupNameComponent,
|
||||
Items: []node.NodeItem{
|
||||
{
|
||||
NodeCode: node.NodeTypeTextModel,
|
||||
NodeName: node.NodeNameTextModel,
|
||||
ModelType: node.ModelTypeText,
|
||||
SkillOption: true,
|
||||
FormConfig: []node.NodeFormField{}, // 技能下拉
|
||||
ModelConfig: []node.ModelItem{},
|
||||
NodeCode: node.NodeTypeTextModel,
|
||||
NodeName: node.NodeNameTextModel,
|
||||
ModelType: node.ModelTypeText,
|
||||
SkillOption: false,
|
||||
PromptOption: true,
|
||||
IsSaveFile: true,
|
||||
FormConfig: []node.NodeFormField{},
|
||||
ModelConfig: []node.ModelItem{},
|
||||
},
|
||||
{
|
||||
NodeCode: node.NodeTypeImageModel,
|
||||
NodeName: node.NodeNameImageModel,
|
||||
ModelType: node.ModelTypeImage,
|
||||
SkillOption: true,
|
||||
FormConfig: []node.NodeFormField{}, // 技能下拉
|
||||
ModelConfig: []node.ModelItem{},
|
||||
NodeCode: node.NodeTypeImageModel,
|
||||
NodeName: node.NodeNameImageModel,
|
||||
ModelType: node.ModelTypeImage,
|
||||
SkillOption: false,
|
||||
PromptOption: true,
|
||||
IsSaveFile: true,
|
||||
FormConfig: []node.NodeFormField{},
|
||||
ModelConfig: []node.ModelItem{},
|
||||
},
|
||||
{
|
||||
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{},
|
||||
//},
|
||||
},
|
||||
},
|
||||
{
|
||||
Group: node.NodeGroupBase,
|
||||
Label: node.NodeGroupNameBase,
|
||||
Items: []node.NodeItem{
|
||||
{
|
||||
NodeCode: node.NodeTypeDataConversionModel,
|
||||
NodeName: node.NodeNameDataConversionModel,
|
||||
ModelType: node.ModelTypeText,
|
||||
SkillOption: false,
|
||||
PromptOption: true,
|
||||
FormConfig: []node.NodeFormField{},
|
||||
ModelConfig: []node.ModelItem{},
|
||||
},
|
||||
{
|
||||
NodeCode: node.NodeTypeMerge,
|
||||
NodeName: node.NodeNameMerge,
|
||||
@@ -51,6 +139,13 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
||||
FormConfig: []node.NodeFormField{},
|
||||
ModelConfig: []node.ModelItem{},
|
||||
},
|
||||
{
|
||||
NodeCode: node.NodeTypeDataMerge,
|
||||
NodeName: node.NodeNameDataMerge,
|
||||
SkillOption: false,
|
||||
FormConfig: []node.NodeFormField{},
|
||||
ModelConfig: []node.ModelItem{},
|
||||
},
|
||||
{
|
||||
NodeCode: node.NodeTypeJudge,
|
||||
NodeName: node.NodeNameJudge,
|
||||
@@ -67,6 +162,161 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
||||
FormConfig: []node.NodeFormField{},
|
||||
ModelConfig: []node.ModelItem{},
|
||||
},
|
||||
{
|
||||
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{},
|
||||
},
|
||||
//{
|
||||
// NodeCode: node.NodeTypeModel,
|
||||
// NodeName: node.NodeNameModel,
|
||||
@@ -76,22 +326,22 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
||||
//},
|
||||
},
|
||||
},
|
||||
{
|
||||
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{},
|
||||
},
|
||||
},
|
||||
},
|
||||
//{
|
||||
// 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{},
|
||||
// },
|
||||
// },
|
||||
//},
|
||||
}
|
||||
tree := &nodeDto.WorkflowNodeTreeRes{
|
||||
Groups: WorkflowNodeGroups,
|
||||
@@ -104,17 +354,19 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
||||
// 遍历分组下的每个节点
|
||||
for itemIdx := range group.Items {
|
||||
item := &group.Items[itemIdx]
|
||||
if item.NodeCode == node.NodeTypeTextModel {
|
||||
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 {
|
||||
item.ModelConfig = append(item.ModelConfig, node.ModelItem{
|
||||
ModelName: "自定义",
|
||||
})
|
||||
}
|
||||
if item.NodeCode == node.NodeTypeImageModel {
|
||||
item.ModelConfig = append(item.ModelConfig, node.ModelItem{
|
||||
ModelName: "自定义",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
123
workflow/service/node/node_prompt_service.go
Normal file
123
workflow/service/node/node_prompt_service.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"ai-agent/workflow/consts/node"
|
||||
nodeDao "ai-agent/workflow/dao/node"
|
||||
nodeDto "ai-agent/workflow/model/dto/node"
|
||||
"ai-agent/workflow/service"
|
||||
"context"
|
||||
|
||||
"gitea.com/red-future/common/utils"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
var NodePromptService = &nodePromptService{}
|
||||
|
||||
type nodePromptService struct{}
|
||||
|
||||
// Create 创建节点提示词
|
||||
func (s *nodePromptService) Create(ctx context.Context, req *nodeDto.CreateNodePromptReq) (res *nodeDto.CreateNodePromptRes, err error) {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
get, err := nodeDao.NodePromptDao.Get(ctx, &nodeDto.GetNodePromptReq{Prompt: req.Prompt, Creator: user.UserName})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if g.IsEmpty(get) {
|
||||
var isAdmin bool
|
||||
isAdmin, err = service.UtilService.IsAdmin(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if isAdmin {
|
||||
req.SourceType = node.SourceTypeSystem.Code()
|
||||
} else {
|
||||
req.SourceType = node.SourceTypeUser.Code()
|
||||
}
|
||||
var id int64
|
||||
id, err = nodeDao.NodePromptDao.Insert(ctx, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return &nodeDto.CreateNodePromptRes{Id: id}, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Update 更新节点提示词
|
||||
func (s *nodePromptService) Update(ctx context.Context, req *nodeDto.UpdateNodePromptReq) (err error) {
|
||||
get, err := nodeDao.NodePromptDao.Get(ctx, &nodeDto.GetNodePromptReq{Id: req.Id})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
isAdmin, err := service.UtilService.IsAdmin(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !isAdmin && get.SourceType == node.SourceTypeSystem.Code() {
|
||||
_, err = s.Create(ctx, &nodeDto.CreateNodePromptReq{
|
||||
NodeType: req.NodeType,
|
||||
Prompt: req.Prompt,
|
||||
})
|
||||
} else {
|
||||
_, err = nodeDao.NodePromptDao.Update(ctx, req)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除节点提示词
|
||||
func (s *nodePromptService) Delete(ctx context.Context, req *nodeDto.DeleteNodePromptReq) (err error) {
|
||||
_, err = nodeDao.NodePromptDao.Delete(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
// GetById 根据ID查询节点提示词
|
||||
func (s *nodePromptService) GetById(ctx context.Context, req *nodeDto.GetNodePromptReq) (res *nodeDto.NodePromptResp, err error) {
|
||||
r, err := nodeDao.NodePromptDao.Get(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &nodeDto.NodePromptResp{
|
||||
NodePrompt: r,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ListMy 查询当前创建人自己创建的提示词列表
|
||||
func (s *nodePromptService) ListMy(ctx context.Context, req *nodeDto.ListMyNodePromptReq) (*nodeDto.ListNodePromptResp, error) {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Creator = user.UserName
|
||||
list, total, err := nodeDao.NodePromptDao.ListByOnlyCreator(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nodeDto.ListNodePromptResp{
|
||||
List: list,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListWithSystem 查询当前创建人+系统的提示词列表
|
||||
func (s *nodePromptService) ListWithSystem(ctx context.Context, req *nodeDto.ListNodePromptReq) (*nodeDto.ListNodePromptResp, error) {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 如果请求中没有传creator,使用当前用户
|
||||
if g.IsEmpty(req.Creator) {
|
||||
req.Creator = user.UserName
|
||||
}
|
||||
list, total, err := nodeDao.NodePromptDao.ListByCreator(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nodeDto.ListNodePromptResp{
|
||||
List: list,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user