feat: 新增主动拉取与多类型回调功能
- 新增 ActivePull 实体、DAO、DTO 及 Service,支持主动拉取任务管理 - 新增 ComposeCallback、VideoCallback、HttpNodeCallback 多类型回调接口 - FlowExecution 增加 NodeGroupId 和 TotalTokens 字段,支持节点组追踪与 Token 统计 - ExecutedNodes 结构由字符串列表改为包含执行状态的节点对象列表 - 重构回调通知机制,统一 Notify 函数调用 - 优化输出项类型判断逻辑,新增文件类型标识
This commit is contained in:
9
go.mod
9
go.mod
@@ -9,6 +9,10 @@ require (
|
|||||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
|
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
|
||||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0
|
||||||
github.com/gogf/gf/v2 v2.10.0
|
github.com/gogf/gf/v2 v2.10.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/stretchr/testify v1.11.1
|
||||||
|
github.com/tidwall/gjson v1.19.0
|
||||||
|
github.com/tidwall/sjson v1.2.5
|
||||||
go.opentelemetry.io/otel/trace v1.38.0
|
go.opentelemetry.io/otel/trace v1.38.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +32,7 @@ require (
|
|||||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 // indirect
|
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
|
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
@@ -49,7 +54,6 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/flatbuffers v1.12.1 // indirect
|
github.com/google/flatbuffers v1.12.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/goph/emperror v0.17.2 // indirect
|
github.com/goph/emperror v0.17.2 // indirect
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||||
@@ -83,11 +87,14 @@ require (
|
|||||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/r3labs/diff/v2 v2.15.1 // indirect
|
github.com/r3labs/diff/v2 v2.15.1 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.12.1 // indirect
|
github.com/redis/go-redis/v9 v9.12.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tiger1103/gfast-token v1.0.10 // indirect
|
github.com/tiger1103/gfast-token v1.0.10 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/vcaesar/cedar v0.30.0 // indirect
|
github.com/vcaesar/cedar v0.30.0 // indirect
|
||||||
|
|||||||
9
go.sum
9
go.sum
@@ -369,6 +369,15 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||||
|
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
|
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
|
||||||
github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
|
github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
|
|||||||
7
main.go
7
main.go
@@ -18,6 +18,7 @@ func main() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
defer jaeger.ShutDown(ctx)
|
defer jaeger.ShutDown(ctx)
|
||||||
// 注册路由
|
// 注册路由
|
||||||
|
http.Httpserver.BindHandler("/httpNodeCallback", workflowController.FlowCallBack.HttpNodeCallback)
|
||||||
http.RouteRegister([]interface{}{
|
http.RouteRegister([]interface{}{
|
||||||
//digitalhuman相关接口
|
//digitalhuman相关接口
|
||||||
digitalhumanController.Audio, // 语音相关接口
|
digitalhumanController.Audio, // 语音相关接口
|
||||||
@@ -30,9 +31,15 @@ func main() {
|
|||||||
workflowController.FlowUser,
|
workflowController.FlowUser,
|
||||||
workflowController.FlowTemplate,
|
workflowController.FlowTemplate,
|
||||||
workflowNodeController.NodeLibrary,
|
workflowNodeController.NodeLibrary,
|
||||||
|
workflowNodeController.NodePrompt,
|
||||||
workflowSkillController.SkillTemplate,
|
workflowSkillController.SkillTemplate,
|
||||||
workflowSkillController.SkillUser,
|
workflowSkillController.SkillUser,
|
||||||
})
|
})
|
||||||
|
//workflow.ExternalInterruptDemo()
|
||||||
|
//err := activePullService.ActivePullService.AllList(ctx)
|
||||||
|
//if err != nil {
|
||||||
|
// g.Log().Error(ctx, "ActivePullService err: %v", err)
|
||||||
|
//}
|
||||||
// 保持应用运行
|
// 保持应用运行
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|||||||
139
update.sql
139
update.sql
@@ -406,6 +406,8 @@ CREATE TABLE IF NOT EXISTS black_deacon_flow_execution (
|
|||||||
-- 业务字段
|
-- 业务字段
|
||||||
flow_user_id BIGINT NOT NULL, -- 流程ID
|
flow_user_id BIGINT NOT NULL, -- 流程ID
|
||||||
flow_name VARCHAR(128) NOT NULL DEFAULT '',
|
flow_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
node_group_id varchar(64) NOT NULL DEFAULT '',
|
||||||
|
total_tokens integer NOT NULL DEFAULT 0,
|
||||||
trigger_type VARCHAR(32) NOT NULL DEFAULT '', -- 触发类型
|
trigger_type VARCHAR(32) NOT NULL DEFAULT '', -- 触发类型
|
||||||
duration_ms BIGINT NOT NULL DEFAULT 0, -- 执行时长(毫秒)
|
duration_ms BIGINT NOT NULL DEFAULT 0, -- 执行时长(毫秒)
|
||||||
status SMALLINT NOT NULL DEFAULT 1, -- 状态:1-运行中,2-成功,3-失败
|
status SMALLINT NOT NULL DEFAULT 1, -- 状态:1-运行中,2-成功,3-失败
|
||||||
@@ -435,6 +437,8 @@ COMMENT ON COLUMN black_deacon_flow_execution.updated_at IS '更新时间';
|
|||||||
COMMENT ON COLUMN black_deacon_flow_execution.deleted_at IS '删除时间(软删)';
|
COMMENT ON COLUMN black_deacon_flow_execution.deleted_at IS '删除时间(软删)';
|
||||||
COMMENT ON COLUMN black_deacon_flow_execution.flow_user_id IS '流程ID';
|
COMMENT ON COLUMN black_deacon_flow_execution.flow_user_id IS '流程ID';
|
||||||
COMMENT ON COLUMN black_deacon_flow_execution.flow_name IS '流程名称';
|
COMMENT ON COLUMN black_deacon_flow_execution.flow_name IS '流程名称';
|
||||||
|
COMMENT ON COLUMN black_deacon_flow_execution.total_tokens IS '总token消耗';
|
||||||
|
COMMENT ON COLUMN black_deacon_flow_execution.node_group_id IS '节点组ID';
|
||||||
COMMENT ON COLUMN black_deacon_flow_execution.trigger_type IS '触发类型';
|
COMMENT ON COLUMN black_deacon_flow_execution.trigger_type IS '触发类型';
|
||||||
COMMENT ON COLUMN black_deacon_flow_execution.duration_ms IS '执行时长(毫秒)';
|
COMMENT ON COLUMN black_deacon_flow_execution.duration_ms IS '执行时长(毫秒)';
|
||||||
COMMENT ON COLUMN black_deacon_flow_execution.status IS '状态:1-运行中,2-成功,3-失败';
|
COMMENT ON COLUMN black_deacon_flow_execution.status IS '状态:1-运行中,2-成功,3-失败';
|
||||||
@@ -519,3 +523,138 @@ COMMENT ON COLUMN black_deacon_flow_template.flow_content IS '流程内容';
|
|||||||
COMMENT ON COLUMN black_deacon_flow_template.node_input_params IS '节点输入参数';
|
COMMENT ON COLUMN black_deacon_flow_template.node_input_params IS '节点输入参数';
|
||||||
COMMENT ON COLUMN black_deacon_flow_template.status IS '流程状态:1启用/0停用';
|
COMMENT ON COLUMN black_deacon_flow_template.status IS '流程状态:1启用/0停用';
|
||||||
--------------------pgsql创建black_deacon_flow_template表语句---------------------------
|
--------------------pgsql创建black_deacon_flow_template表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建black_deacon_active_pull表语句---------------------------
|
||||||
|
-- 主动拉取记录表
|
||||||
|
CREATE TABLE IF NOT EXISTS black_deacon_active_pull (
|
||||||
|
-- 基础字段(完全对齐项目规范)
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
creator VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updater VARCHAR(64) NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
|
-- 业务字段
|
||||||
|
type VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
request_parament JSONB DEFAULT '{}',
|
||||||
|
response_parament JSONB DEFAULT '{}',
|
||||||
|
extension JSONB DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
CREATE INDEX idx_active_pull_tenant_id ON black_deacon_active_pull(tenant_id);
|
||||||
|
CREATE INDEX idx_active_pull_type ON black_deacon_active_pull("type");
|
||||||
|
CREATE INDEX idx_active_pull_deleted_at ON black_deacon_active_pull(deleted_at);
|
||||||
|
|
||||||
|
-- 注释
|
||||||
|
COMMENT ON TABLE black_deacon_active_pull IS '主动拉取记录表';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.id IS '主键ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.type IS '类型';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.request_parament IS '请求参数';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.response_parament IS '响应参数';
|
||||||
|
COMMENT ON COLUMN black_deacon_active_pull.extension IS '扩展信息';
|
||||||
|
--------------------pgsql创建black_deacon_active_pull表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建black_deacon_node_prompt表语句---------------------------
|
||||||
|
-- 节点提示词配置表
|
||||||
|
CREATE TABLE IF NOT EXISTS black_deacon_node_prompt (
|
||||||
|
-- 基础字段(完全对齐项目规范)
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||||
|
creator VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updater VARCHAR(64) NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
|
-- 业务字段
|
||||||
|
node_type VARCHAR(64) NOT NULL DEFAULT '', -- 节点类型
|
||||||
|
prompt TEXT NOT NULL DEFAULT '', -- 提示词内容
|
||||||
|
source_type SMALLINT NOT NULL DEFAULT 1 -- 来源:1=系统初始化,2=用户自定义
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
CREATE INDEX idx_node_prompt_tenant_id ON black_deacon_node_prompt(tenant_id);
|
||||||
|
CREATE INDEX idx_node_prompt_node_type ON black_deacon_node_prompt(node_type);
|
||||||
|
CREATE INDEX idx_node_prompt_source_type ON black_deacon_node_prompt(source_type);
|
||||||
|
CREATE INDEX idx_node_prompt_deleted_at ON black_deacon_node_prompt(deleted_at);
|
||||||
|
|
||||||
|
-- 注释
|
||||||
|
COMMENT ON TABLE black_deacon_node_prompt IS '节点提示词配置表';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.id IS '主键ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.node_type IS '节点类型';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.prompt IS '提示词内容';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_prompt.source_type IS '数据来源:1=系统初始化,2=用户自定义';
|
||||||
|
--------------------pgsql创建black_deacon_node_prompt表语句---------------------------
|
||||||
|
|
||||||
|
--------------------pgsql创建black_deacon_node_execution表语句---------------------------
|
||||||
|
-- 节点执行记录表
|
||||||
|
-- 记录每个节点的入参、出参、token消耗、执行状态等详细信息
|
||||||
|
CREATE TABLE IF NOT EXISTS black_deacon_node_execution (
|
||||||
|
-- 基础字段(完全对齐项目规范)
|
||||||
|
id BIGINT PRIMARY KEY, -- 主键ID(非自增)
|
||||||
|
tenant_id BIGINT NOT NULL DEFAULT 0, -- 租户ID int8
|
||||||
|
creator VARCHAR(64) NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updater VARCHAR(64) NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
deleted_at timestamp(6),
|
||||||
|
|
||||||
|
-- 业务字段
|
||||||
|
flow_execution_id BIGINT NOT NULL, -- 流程执行ID
|
||||||
|
node_id VARCHAR(64) NOT NULL DEFAULT '', -- 节点ID
|
||||||
|
node_name VARCHAR(128) NOT NULL DEFAULT '', -- 节点名称
|
||||||
|
node_group_id VARCHAR(64) NOT NULL DEFAULT '', -- 节点分组ID
|
||||||
|
input_params JSONB DEFAULT '{}', -- 节点输入参数
|
||||||
|
output_params JSONB DEFAULT '{}', -- 节点输出参数
|
||||||
|
prompt_tokens INTEGER NOT NULL DEFAULT 0, -- 提示词token消耗
|
||||||
|
completion_tokens INTEGER NOT NULL DEFAULT 0, -- 补全token消耗
|
||||||
|
total_tokens INTEGER NOT NULL DEFAULT 0, -- 总token消耗
|
||||||
|
status SMALLINT NOT NULL DEFAULT 1, -- 执行状态:1-运行中,2-成功,3-失败,4-暂停,5-等待执行
|
||||||
|
duration_ms BIGINT NOT NULL DEFAULT 0, -- 执行时长(毫秒)
|
||||||
|
error_message TEXT DEFAULT '' -- 错误信息
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 索引(高频查询)
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bne_tenant_id ON black_deacon_node_execution(tenant_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bne_flow_execution_id ON black_deacon_node_execution(flow_execution_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bne_node_id ON black_deacon_node_execution(node_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bne_status ON black_deacon_node_execution(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bne_deleted_at ON black_deacon_node_execution(deleted_at);
|
||||||
|
|
||||||
|
-- 表和字段注释
|
||||||
|
COMMENT ON TABLE black_deacon_node_execution IS '节点执行记录表';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.id IS '主键ID(非自增)';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.tenant_id IS '租户ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.creator IS '创建人';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.updater IS '更新人';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.updated_at IS '更新时间';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.deleted_at IS '删除时间(软删)';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.flow_execution_id IS '流程执行ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.node_id IS '节点ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.node_name IS '节点名称';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.node_group_id IS '节点分组ID';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.input_params IS '节点输入参数';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.output_params IS '节点输出参数';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.prompt_tokens IS '提示词token消耗';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.completion_tokens IS '补全token消耗';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.total_tokens IS '总token消耗';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.status IS '执行状态:1-运行中,2-成功,3-失败,4-暂停,5-等待执行';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.duration_ms IS '执行时长(毫秒)';
|
||||||
|
COMMENT ON COLUMN black_deacon_node_execution.error_message IS '错误信息';
|
||||||
|
--------------------pgsql创建black_deacon_node_execution表语句---------------------------
|
||||||
@@ -13,12 +13,15 @@ const (
|
|||||||
NodeNameTextModel = "生成文案"
|
NodeNameTextModel = "生成文案"
|
||||||
NodeNameImageModel = "生成图片"
|
NodeNameImageModel = "生成图片"
|
||||||
NodeNameVideoModel = "生成视频"
|
NodeNameVideoModel = "生成视频"
|
||||||
NodeNameSenseOptimize = "语义优化"
|
NodeNameAudioModel = "生成音频"
|
||||||
NodeNameStoryOptimize = "分镜优化"
|
NodeNameBatchModel = "批量处理一起返回"
|
||||||
NodeNameScriptOptimize = "剧本优化"
|
NodeNameSenseOptimizeModel = "语义优化"
|
||||||
NodeNameAudioModel = "音频"
|
NodeNameStoryOptimizeModel = "分镜优化"
|
||||||
|
NodeNameScriptOptimizeModel = "剧本优化"
|
||||||
|
NodeNameDataConversionModel = "参数转换"
|
||||||
NodeNameModel = "模型"
|
NodeNameModel = "模型"
|
||||||
NodeNameMerge = "结果合并"
|
NodeNameMerge = "结果合并"
|
||||||
|
NodeNameDataMerge = "结果汇集"
|
||||||
NodeNameJudge = "条件判断"
|
NodeNameJudge = "条件判断"
|
||||||
NodeNameForm = "表单"
|
NodeNameForm = "表单"
|
||||||
NodeNameHttp = "HTTP(S)接口"
|
NodeNameHttp = "HTTP(S)接口"
|
||||||
@@ -29,7 +32,6 @@ const (
|
|||||||
const (
|
const (
|
||||||
FormLabelApiKey = "API Key"
|
FormLabelApiKey = "API Key"
|
||||||
FormLabelModel = "模型名称"
|
FormLabelModel = "模型名称"
|
||||||
|
|
||||||
FormLabelCondition = "判断条件"
|
FormLabelCondition = "判断条件"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,14 +51,17 @@ const (
|
|||||||
NodeTypeTextModel NodeType = "text_model"
|
NodeTypeTextModel NodeType = "text_model"
|
||||||
NodeTypeImageModel NodeType = "image_model"
|
NodeTypeImageModel NodeType = "image_model"
|
||||||
NodeTypeVideoModel NodeType = "video_model"
|
NodeTypeVideoModel NodeType = "video_model"
|
||||||
NodeTypeSenseOptimize NodeType = "sense_optimize"
|
|
||||||
NodeTypeStoryOptimize NodeType = "story_optimize"
|
|
||||||
NodeTypeScriptOptimize NodeType = "script_optimize"
|
|
||||||
NodeTypeAudioModel NodeType = "audio_model"
|
NodeTypeAudioModel NodeType = "audio_model"
|
||||||
|
NodeTypeBatchModel NodeType = "batch_model"
|
||||||
|
|
||||||
|
NodeTypeSenseOptimizeModel NodeType = "sense_optimize_model"
|
||||||
|
NodeTypeStoryOptimizeModel NodeType = "story_optimize_model"
|
||||||
|
NodeTypeScriptOptimizeModel NodeType = "script_optimize_model"
|
||||||
// 基础
|
// 基础
|
||||||
|
NodeTypeDataConversionModel NodeType = "data_conversion_model"
|
||||||
NodeTypeModel NodeType = "model"
|
NodeTypeModel NodeType = "model"
|
||||||
NodeTypeMerge NodeType = "merge"
|
NodeTypeMerge NodeType = "merge"
|
||||||
|
NodeTypeDataMerge NodeType = "data_merge"
|
||||||
NodeTypeJudge NodeType = "judge"
|
NodeTypeJudge NodeType = "judge"
|
||||||
NodeTypeForm NodeType = "form"
|
NodeTypeForm NodeType = "form"
|
||||||
NodeTypeIntent NodeType = "intent"
|
NodeTypeIntent NodeType = "intent"
|
||||||
@@ -66,13 +71,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ModelTypeText = 1
|
ModelTypeText = 100
|
||||||
ModelTypeImage = 2
|
ModelTypeImage = 200
|
||||||
|
ModelTypeAudio = 300
|
||||||
|
ModelTypeModality = 500
|
||||||
|
ModelTypeVideo = 600
|
||||||
)
|
)
|
||||||
|
|
||||||
// ======================== 结构定义 ========================
|
// ======================== 结构定义 ========================
|
||||||
type NodeFormField struct {
|
type NodeFormField struct {
|
||||||
Value string `json:"value"`
|
Value any `json:"value"`
|
||||||
Field string `json:"field"`
|
Field string `json:"field"`
|
||||||
Label string `json:"label"` // 从常量来
|
Label string `json:"label"` // 从常量来
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -80,6 +88,7 @@ type NodeFormField struct {
|
|||||||
Default any `json:"default,omitempty"`
|
Default any `json:"default,omitempty"`
|
||||||
Options []SelectOption `json:"options"`
|
Options []SelectOption `json:"options"`
|
||||||
Expand any `json:"expand"`
|
Expand any `json:"expand"`
|
||||||
|
FieldConstraint any `json:"fieldConstraint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectOption struct {
|
type SelectOption struct {
|
||||||
@@ -88,10 +97,8 @@ type SelectOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ModelItem struct {
|
type ModelItem struct {
|
||||||
ModelApiKey string `json:"modelApiKey"`
|
|
||||||
ModelName string `json:"modelName"`
|
ModelName string `json:"modelName"`
|
||||||
ModelForm map[string]any `json:"modelForm"`
|
ModelForm []NodeFormField `json:"modelForm"`
|
||||||
ModelResponse map[string]any `json:"modelResponse"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeItem struct {
|
type NodeItem struct {
|
||||||
@@ -100,6 +107,8 @@ type NodeItem struct {
|
|||||||
ModelType int `json:"modelType"`
|
ModelType int `json:"modelType"`
|
||||||
NodeName string `json:"nodeName"` // 从常量来
|
NodeName string `json:"nodeName"` // 从常量来
|
||||||
SkillOption bool `json:"skillOption"`
|
SkillOption bool `json:"skillOption"`
|
||||||
|
PromptOption bool `json:"promptOption"`
|
||||||
|
IsSaveFile bool `json:"isSaveFile"`
|
||||||
FormConfig []NodeFormField `json:"formConfig"`
|
FormConfig []NodeFormField `json:"formConfig"`
|
||||||
ModelConfig []ModelItem `json:"modelConfig"`
|
ModelConfig []ModelItem `json:"modelConfig"`
|
||||||
}
|
}
|
||||||
|
|||||||
26
workflow/consts/node/source_type.go
Normal file
26
workflow/consts/node/source_type.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
|
var (
|
||||||
|
SourceTypeSystem = newSourceType(gconv.PtrInt8(1), "系统初始化")
|
||||||
|
SourceTypeUser = newSourceType(gconv.PtrInt8(2), "用户自定义")
|
||||||
|
)
|
||||||
|
|
||||||
|
type SourceType *int8
|
||||||
|
|
||||||
|
type sourceType struct {
|
||||||
|
code SourceType
|
||||||
|
desc string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sourceType) Code() SourceType {
|
||||||
|
return s.code
|
||||||
|
}
|
||||||
|
func (s sourceType) Desc() string {
|
||||||
|
return s.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSourceType(code SourceType, desc string) sourceType {
|
||||||
|
return sourceType{code: code, desc: desc}
|
||||||
|
}
|
||||||
@@ -14,4 +14,8 @@ const (
|
|||||||
TableNameSkillTemplate = "skill_template"
|
TableNameSkillTemplate = "skill_template"
|
||||||
TableNameSkillUser = "skill_user"
|
TableNameSkillUser = "skill_user"
|
||||||
TableNameFileTemp = "file_temp"
|
TableNameFileTemp = "file_temp"
|
||||||
|
TableNameActivePull = "active_pull"
|
||||||
|
TableNameWorkflowInterrupt = "workflow_interrupt"
|
||||||
|
TableNameNodePrompt = "node_prompt"
|
||||||
|
TableNameNodeExecution = "node_execution"
|
||||||
)
|
)
|
||||||
|
|||||||
23
workflow/controller/flow/flow_call_back_controller.go
Normal file
23
workflow/controller/flow/flow_call_back_controller.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package flow
|
||||||
|
|
||||||
|
import (
|
||||||
|
flowService "ai-agent/workflow/service/flow"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type flowCallBack struct{}
|
||||||
|
|
||||||
|
var FlowCallBack = new(flowCallBack)
|
||||||
|
|
||||||
|
func (c *flowCallBack) HttpNodeCallback(r *ghttp.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
err := flowService.FlowExecutionService.HttpNodeCallback(ctx)
|
||||||
|
if err != nil {
|
||||||
|
r.Response.WriteJson(g.Map{"code": 500, "message": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Response.WriteJson(g.Map{"code": 0, "message": "success"})
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -16,11 +16,21 @@ func (c *flowExecution) Execute(ctx context.Context, req *flowDto.ExecuteReq) (r
|
|||||||
return flowService.FlowExecutionService.Execute(ctx, req)
|
return flowService.FlowExecutionService.Execute(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *flowExecution) ComposeCallBack(ctx context.Context, req *flowDto.ComposeCallbackReq) (res *beans.ResponseEmpty, err error) {
|
||||||
|
err = flowService.FlowExecutionService.ComposeCallback(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *flowExecution) ModelCallback(ctx context.Context, req *flowDto.ModelCallbackReq) (res *beans.ResponseEmpty, err error) {
|
func (c *flowExecution) ModelCallback(ctx context.Context, req *flowDto.ModelCallbackReq) (res *beans.ResponseEmpty, err error) {
|
||||||
err = flowService.FlowExecutionService.ModelCallback(ctx, req)
|
err = flowService.FlowExecutionService.ModelCallback(ctx, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *flowExecution) VideoCallback(ctx context.Context, req *flowDto.VideoCallbackReq) (res *beans.ResponseEmpty, err error) {
|
||||||
|
err = flowService.FlowExecutionService.VideoCallback(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *flowExecution) Get(ctx context.Context, req *flowDto.GetFlowExecutionReq) (res *flowDto.VOFlowExecution, err error) {
|
func (c *flowExecution) Get(ctx context.Context, req *flowDto.GetFlowExecutionReq) (res *flowDto.VOFlowExecution, err error) {
|
||||||
return flowService.FlowExecutionService.Get(ctx, req)
|
return flowService.FlowExecutionService.Get(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
45
workflow/controller/node/node_prompt_controller.go
Normal file
45
workflow/controller/node/node_prompt_controller.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
nodeDto "ai-agent/workflow/model/dto/node"
|
||||||
|
nodeService "ai-agent/workflow/service/node"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/beans"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nodePrompt struct{}
|
||||||
|
|
||||||
|
var NodePrompt = new(nodePrompt)
|
||||||
|
|
||||||
|
// Create 创建节点提示词
|
||||||
|
func (c *nodePrompt) Create(ctx context.Context, req *nodeDto.CreateNodePromptReq) (res *nodeDto.CreateNodePromptRes, err error) {
|
||||||
|
return nodeService.NodePromptService.Create(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新节点提示词
|
||||||
|
func (c *nodePrompt) Update(ctx context.Context, req *nodeDto.UpdateNodePromptReq) (res *beans.ResponseEmpty, err error) {
|
||||||
|
err = nodeService.NodePromptService.Update(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除节点提示词
|
||||||
|
func (c *nodePrompt) Delete(ctx context.Context, req *nodeDto.DeleteNodePromptReq) (res *beans.ResponseEmpty, err error) {
|
||||||
|
err = nodeService.NodePromptService.Delete(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 根据ID查询节点提示词详情
|
||||||
|
func (c *nodePrompt) Get(ctx context.Context, req *nodeDto.GetNodePromptReq) (res *nodeDto.NodePromptResp, err error) {
|
||||||
|
return nodeService.NodePromptService.GetById(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMy 查询当前用户自己创建的节点提示词列表
|
||||||
|
func (c *nodePrompt) ListMy(ctx context.Context, req *nodeDto.ListMyNodePromptReq) (res *nodeDto.ListNodePromptResp, err error) {
|
||||||
|
return nodeService.NodePromptService.ListMy(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 查询节点提示词列表,包含系统和当前创建人自定义
|
||||||
|
func (c *nodePrompt) List(ctx context.Context, req *nodeDto.ListNodePromptReq) (res *nodeDto.ListNodePromptResp, err error) {
|
||||||
|
return nodeService.NodePromptService.ListWithSystem(ctx, req)
|
||||||
|
}
|
||||||
99
workflow/dao/node/node_execution_dao.go
Normal file
99
workflow/dao/node/node_execution_dao.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/public"
|
||||||
|
nodeDto "ai-agent/workflow/model/dto/node"
|
||||||
|
"ai-agent/workflow/model/entity"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/db/gfdb"
|
||||||
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NodeExecutionDao = &nodeExecutionDao{}
|
||||||
|
|
||||||
|
type nodeExecutionDao struct{}
|
||||||
|
|
||||||
|
// Insert 插入节点执行记录
|
||||||
|
func (d *nodeExecutionDao) Insert(ctx context.Context, req *nodeDto.CreateNodeExecutionReq) (id int64, err error) {
|
||||||
|
nodeExecution := new(entity.NodeExecution)
|
||||||
|
err = gconv.Struct(req, &nodeExecution)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodeExecution).Insert(&nodeExecution)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return r.LastInsertId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新节点执行记录
|
||||||
|
func (d *nodeExecutionDao) Update(ctx context.Context, req *nodeDto.UpdateNodeExecutionReq) (rows int64, err error) {
|
||||||
|
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodeExecution).OmitEmpty()
|
||||||
|
if !g.IsEmpty(req.CompletionTokens) {
|
||||||
|
model.Data(entity.NodeExecutionCol.CompletionTokens, &gdb.Counter{
|
||||||
|
Field: entity.NodeExecutionCol.CompletionTokens,
|
||||||
|
Value: gconv.Float64(req.CompletionTokens),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !g.IsEmpty(req.PromptTokens) {
|
||||||
|
model.Data(entity.NodeExecutionCol.PromptTokens, &gdb.Counter{
|
||||||
|
Field: entity.NodeExecutionCol.PromptTokens,
|
||||||
|
Value: gconv.Float64(req.PromptTokens),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !g.IsEmpty(req.TotalTokens) {
|
||||||
|
model.Data(entity.NodeExecutionCol.TotalTokens, &gdb.Counter{
|
||||||
|
Field: entity.NodeExecutionCol.TotalTokens,
|
||||||
|
Value: gconv.Float64(req.TotalTokens),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
r, err := model.Data(&req).Where(entity.NodeExecutionCol.Id, req.Id).Update()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return r.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除节点执行记录
|
||||||
|
func (d *nodeExecutionDao) Delete(ctx context.Context, req *nodeDto.DeleteNodeExecutionReq) (rows int64, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodeExecution).Where(entity.NodeExecutionCol.Id, req.Id).Delete()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return r.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 根据ID查询节点执行记录
|
||||||
|
func (d *nodeExecutionDao) Get(ctx context.Context, req *nodeDto.GetNodeExecutionReq, fields ...string) (res *entity.NodeExecution, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodeExecution).NoTenantId(ctx).OmitEmpty().
|
||||||
|
Where(entity.NodeExecutionCol.Id, req.Id).
|
||||||
|
Fields(fields).One()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.IsEmpty() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
err = r.Struct(&res)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByFlowExecutionId 查询指定流程执行下的所有节点执行记录
|
||||||
|
func (d *nodeExecutionDao) ListByFlowExecutionId(ctx context.Context, req *nodeDto.ListNodeExecutionByFlowReq, fields ...string) (res []*entity.NodeExecution, total int, err error) {
|
||||||
|
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodeExecution).NoTenantId(ctx).Fields(fields).OmitEmpty()
|
||||||
|
model.Where(entity.NodeExecutionCol.FlowExecutionId, req.FlowExecutionId)
|
||||||
|
model.OrderAsc(entity.NodeExecutionCol.CreatedAt)
|
||||||
|
if req.Page != nil {
|
||||||
|
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||||
|
}
|
||||||
|
r, total, err := model.AllAndCount(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
err = r.Structs(&res)
|
||||||
|
return res, total, err
|
||||||
|
}
|
||||||
95
workflow/dao/node/node_prompt_dao.go
Normal file
95
workflow/dao/node/node_prompt_dao.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/public"
|
||||||
|
nodeDto "ai-agent/workflow/model/dto/node"
|
||||||
|
"ai-agent/workflow/model/entity"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/db/gfdb"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NodePromptDao = &nodePromptDao{}
|
||||||
|
|
||||||
|
type nodePromptDao struct{}
|
||||||
|
|
||||||
|
// Insert 插入节点提示词
|
||||||
|
func (d *nodePromptDao) Insert(ctx context.Context, req *nodeDto.CreateNodePromptReq) (id int64, err error) {
|
||||||
|
nodePrompt := new(entity.NodePrompt)
|
||||||
|
err = gconv.Struct(req, &nodePrompt)
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodePrompt).Insert(&nodePrompt)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return r.LastInsertId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 更新节点提示词
|
||||||
|
func (d *nodePromptDao) Update(ctx context.Context, req *nodeDto.UpdateNodePromptReq) (rows int64, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodePrompt).OmitEmpty().Data(&req).Where(entity.NodePromptCol.Id, req.Id).Update()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return r.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete 删除节点提示词
|
||||||
|
func (d *nodePromptDao) Delete(ctx context.Context, req *nodeDto.DeleteNodePromptReq) (rows int64, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodePrompt).Where(entity.NodePromptCol.Id, req.Id).Delete()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return r.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 根据ID查询节点提示词
|
||||||
|
func (d *nodePromptDao) Get(ctx context.Context, req *nodeDto.GetNodePromptReq, fields ...string) (res *entity.NodePrompt, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodePrompt).NoTenantId(ctx).OmitEmpty().
|
||||||
|
Where(entity.NodePromptCol.Id, req.Id).
|
||||||
|
Where(entity.NodePromptCol.Prompt, req.Prompt).
|
||||||
|
Where(entity.NodePromptCol.Creator, req.Creator).
|
||||||
|
Fields(fields).One()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.IsEmpty() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
err = r.Struct(&res)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByOnlyCreator 查询仅当前创建人自己创建的提示词
|
||||||
|
func (d *nodePromptDao) ListByOnlyCreator(ctx context.Context, req *nodeDto.ListMyNodePromptReq, fields ...string) (res []*entity.NodePrompt, total int, err error) {
|
||||||
|
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodePrompt).NoTenantId(ctx).Fields(fields).OmitEmpty()
|
||||||
|
model.Where(entity.NodePromptCol.Creator, req.Creator)
|
||||||
|
model.Where(entity.NodePromptCol.NodeType, req.NodeType)
|
||||||
|
model.OrderDesc(entity.NodePromptCol.CreatedAt)
|
||||||
|
if req.Page != nil {
|
||||||
|
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||||
|
}
|
||||||
|
r, total, err := model.AllAndCount(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
err = r.Structs(&res)
|
||||||
|
return res, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByCreator 查询当前创建人的所有提示词(包含系统和用户)
|
||||||
|
func (d *nodePromptDao) ListByCreator(ctx context.Context, req *nodeDto.ListNodePromptReq, fields ...string) (res []*entity.NodePrompt, total int, err error) {
|
||||||
|
// 完整 SQL
|
||||||
|
sql := ` SELECT * FROM black_deacon_node_prompt WHERE (creator=? OR source_type=1) AND node_type=? AND "deleted_at" IS NULL ORDER BY created_at DESC `
|
||||||
|
queryParams := []interface{}{req.Creator, req.NodeType}
|
||||||
|
if req.Page != nil {
|
||||||
|
sql += " LIMIT ?,?"
|
||||||
|
queryParams = append(queryParams, req.Page.PageNum, req.Page.PageSize)
|
||||||
|
}
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).GetAll(ctx, sql, queryParams...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
err = r.Structs(&res)
|
||||||
|
return res, total, err
|
||||||
|
}
|
||||||
102
workflow/dao/pull/active_pull_dao.go
Normal file
102
workflow/dao/pull/active_pull_dao.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package pull
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/public"
|
||||||
|
pullDto "ai-agent/workflow/model/dto/pull"
|
||||||
|
"ai-agent/workflow/model/entity"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/db/gfdb"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ActivePullDao = &activePullDao{}
|
||||||
|
|
||||||
|
type activePullDao struct{}
|
||||||
|
|
||||||
|
// Insert 创建执行记录
|
||||||
|
func (d *activePullDao) Insert(ctx context.Context, req *pullDto.CreateActivePullReq) (id int64, err error) {
|
||||||
|
var activePull = new(entity.ActivePull)
|
||||||
|
err = gconv.Struct(req, &activePull)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameActivePull).Insert(activePull)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return r.LastInsertId()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *activePullDao) Update(ctx context.Context, req *pullDto.UpdateActivePullReq) (rows int64, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameActivePull).OmitEmpty().Data(&req).Where(entity.ActivePullCol.Id, req.Id).Update()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return r.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *activePullDao) Delete(ctx context.Context, req *pullDto.DeleteActivePullReq) (rows int64, err error) {
|
||||||
|
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameActivePull).Where(entity.ActivePullCol.Id, req.Id).Delete()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return r.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *activePullDao) List(ctx context.Context, req *pullDto.ListActivePullReq, fields ...string) (res []*entity.ActivePull, total int, err error) {
|
||||||
|
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameActivePull).Fields(fields).OmitEmpty()
|
||||||
|
model.OrderDesc(entity.ActivePullCol.CreatedAt)
|
||||||
|
if req.Page != nil {
|
||||||
|
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||||
|
}
|
||||||
|
r, total, err := model.AllAndCount(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = r.Structs(&res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *activePullDao) ListNative(ctx context.Context, req *pullDto.ListActivePullReq, fields ...string) (res []*entity.ActivePull, total int, err error) {
|
||||||
|
db := gfdb.DB(ctx, public.DbNameBlackDeacon)
|
||||||
|
|
||||||
|
// Select fields
|
||||||
|
selectFields := "*"
|
||||||
|
if len(fields) > 0 {
|
||||||
|
selectFields = strings.Join(fields, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build count query first for total
|
||||||
|
countSql := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE deleted_at is null", "black_deacon_"+public.TableNameActivePull)
|
||||||
|
countResult, err := db.GetAll(ctx, countSql)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if len(countResult) > 0 {
|
||||||
|
total = countResult[0]["COUNT(*)"].Int()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build data query with native SQL
|
||||||
|
sql := fmt.Sprintf("SELECT %s FROM %s WHERE deleted_at is null ORDER BY created_at DESC", selectFields, "black_deacon_"+public.TableNameActivePull)
|
||||||
|
if req.Page != nil && req.Page.PageNum > 0 && req.Page.PageSize > 0 {
|
||||||
|
offset := (req.Page.PageNum - 1) * req.Page.PageSize
|
||||||
|
sql += fmt.Sprintf(" LIMIT %d OFFSET %d", req.Page.PageSize, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute query with GetAll
|
||||||
|
result, err := db.GetAll(ctx, sql)
|
||||||
|
if err != nil {
|
||||||
|
return nil, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan to entity slice
|
||||||
|
var models []*entity.ActivePull
|
||||||
|
if err = result.Structs(&models); err != nil {
|
||||||
|
return nil, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return models, total, nil
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package flow
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"ai-agent/workflow/consts/flow"
|
"ai-agent/workflow/consts/flow"
|
||||||
|
"ai-agent/workflow/consts/node"
|
||||||
"ai-agent/workflow/model/entity"
|
"ai-agent/workflow/model/entity"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.com/red-future/common/beans"
|
||||||
@@ -11,12 +12,20 @@ import (
|
|||||||
|
|
||||||
// NodeExecutionInput 节点执行入参(包含配置+表单架构)
|
// NodeExecutionInput 节点执行入参(包含配置+表单架构)
|
||||||
type NodeExecutionInput struct {
|
type NodeExecutionInput struct {
|
||||||
Config *entity.FlowNode // 节点配置
|
Config *entity.FlowNode `json:"config"` // 节点配置
|
||||||
Global *FlowExecutionInput `json:"-"`
|
Global *FlowExecutionInput `json:"global"`
|
||||||
|
NodeExecutionId int64 `json:"nodeExecutionId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutedNode 已执行节点记录,包含节点ID和执行状态
|
||||||
|
type ExecutedNode struct {
|
||||||
|
NodeId string `json:"nodeId"`
|
||||||
|
Status node.NodeExecutionStatus `json:"status"` // 执行状态:成功/失败
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlowExecutionInput 工作流执行入参(全程不变)
|
// FlowExecutionInput 工作流执行入参(全程不变)
|
||||||
type FlowExecutionInput struct {
|
type FlowExecutionInput struct {
|
||||||
|
NodeGroupId string `json:"nodeGroupId"`
|
||||||
IsDialogue bool `json:"isDialogue"`
|
IsDialogue bool `json:"isDialogue"`
|
||||||
ExecutionId int64 `json:"executionId"`
|
ExecutionId int64 `json:"executionId"`
|
||||||
ConfigMap map[string]*entity.FlowNode `json:"configMap"`
|
ConfigMap map[string]*entity.FlowNode `json:"configMap"`
|
||||||
@@ -24,27 +33,74 @@ type FlowExecutionInput struct {
|
|||||||
Desc string `json:"desc"`
|
Desc string `json:"desc"`
|
||||||
SkillName string `json:"skillName"`
|
SkillName string `json:"skillName"`
|
||||||
FileUrl []string `json:"fileUrl"`
|
FileUrl []string `json:"fileUrl"`
|
||||||
ExecutedNodes []string `json:"executedNodes"`
|
ExecutedNodes []ExecutedNode `json:"executedNodes"` // 已执行节点列表,包含执行状态
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetIsChatModelRes struct {
|
||||||
|
Model struct {
|
||||||
|
ModelName string `json:"modelName"`
|
||||||
|
ResponseBody map[string]any `json:"responseBody"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetModelInfoReq struct {
|
||||||
|
ModelName string `json:"modelName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetModelInfoRes struct {
|
||||||
|
Model struct {
|
||||||
|
LastFrame string `json:"lastFrame"`
|
||||||
|
ResponseTokenField string `json:"responseTokenField"`
|
||||||
|
ResponseMapping map[string]any `json:"responseMapping"`
|
||||||
|
ResponseBody string `json:"responseBody"`
|
||||||
|
//QueryConfig struct {
|
||||||
|
// ResponseType string `json:"responseType"`
|
||||||
|
// CallbackUrl string `json:"callbackUrl"`
|
||||||
|
// Method string `json:"method"`
|
||||||
|
// Url string `json:"url"`
|
||||||
|
// Headers map[string]any `json:"headers"`
|
||||||
|
// Body map[string]any `json:"body"`
|
||||||
|
// Response []map[string]any `json:"response"`
|
||||||
|
// ResponseBody string `json:"responseBody"`
|
||||||
|
// ResponseTokenField string `json:"responseTokenField"`
|
||||||
|
//} `json:"queryConfig"`
|
||||||
|
} `json:"model"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ComposeMessagesReq struct {
|
type ComposeMessagesReq struct {
|
||||||
BuildType int `json:"buildType"`
|
BuildType int `json:"buildType"`
|
||||||
ModelName string `json:"modelName"`
|
ModelName string `json:"modelName"`
|
||||||
SkillName string `json:"skillName"`
|
SkillName string `json:"skillName"`
|
||||||
Form map[string]any `json:"form"`
|
CallbackUrl string `json:"callbackUrl"`
|
||||||
UserForm map[string]any `json:"userForm"`
|
Form []map[string]any `json:"form"`
|
||||||
UserFiles []string `json:"userFiles"`
|
UserForm []map[string]any `json:"userForm"`
|
||||||
|
Consult []Consult `json:"consult"`
|
||||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||||
IsBuild bool `json:"isBuild"`
|
NodeId string `json:"nodeId"`
|
||||||
Cause string `json:"cause"`
|
Cause string `json:"cause"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ComposeMessagesRes struct {
|
type Consult struct {
|
||||||
Messages map[string]any `json:"messages"`
|
Type string `json:"type"`
|
||||||
EpicycleId int64 `json:"epicycleId" dc:"轮次ID"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateTaskReq struct {
|
type ComposeMessagesRes struct {
|
||||||
|
TaskId string `json:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VideoConcatReq struct {
|
||||||
|
VideoUrls []string `json:"video_urls"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Upload bool `json:"upload"`
|
||||||
|
CallbackUrl string `json:"callback_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VideoConcatRes struct {
|
||||||
|
TaskId string `json:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModelGatewayReq struct {
|
||||||
ModelName string `json:"modelName"`
|
ModelName string `json:"modelName"`
|
||||||
ModelKey string `json:"modelKey"`
|
ModelKey string `json:"modelKey"`
|
||||||
BizName string `json:"bizName"`
|
BizName string `json:"bizName"`
|
||||||
@@ -54,13 +110,20 @@ type CreateTaskReq struct {
|
|||||||
EpicycleId int64 `json:"epicycleId" dc:"轮次ID"`
|
EpicycleId int64 `json:"epicycleId" dc:"轮次ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateTaskRes struct {
|
type ModelGatewayRes struct {
|
||||||
TaskId string `json:"taskId"`
|
TaskId string `json:"taskId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetIsChatModelRes struct {
|
type ComposeCallbackReq struct {
|
||||||
ModelName string `json:"modelName"`
|
g.Meta `path:"/composeCallBack" method:"post" tags:"提示词处理" summary:"提示词 回调" dc:"提示词 成功后 GET 回调:callbackUrl/{bizName}"`
|
||||||
ResponseBody map[string]any `json:"responseBody"`
|
TaskId string `json:"taskId"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Messages struct {
|
||||||
|
TotalRounds int `json:"total_rounds"` // 总轮数
|
||||||
|
Rounds []map[string]any `json:"rounds"` // 每轮详情(动态类型)
|
||||||
|
} `json:"messages,omitempty"`
|
||||||
|
EpicycleId int64 `json:"epicycleId"`
|
||||||
|
ErrorMsg string `json:"errorMsg,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModelCallbackReq struct {
|
type ModelCallbackReq struct {
|
||||||
@@ -69,68 +132,52 @@ type ModelCallbackReq struct {
|
|||||||
State int `p:"state" json:"state" dc:"网关任务状态"`
|
State int `p:"state" json:"state" dc:"网关任务状态"`
|
||||||
OssFile string `p:"oss_file" json:"oss_file" dc:"结果文件地址"`
|
OssFile string `p:"oss_file" json:"oss_file" dc:"结果文件地址"`
|
||||||
FileType string `p:"file_type" json:"file_type" dc:"结果文件类型"`
|
FileType string `p:"file_type" json:"file_type" dc:"结果文件类型"`
|
||||||
Text string `p:"text" json:"text" dc:"文本结果(可选,最多约 2000 字符)"`
|
Messages map[string]any `json:"messages"`
|
||||||
|
ErrorMsg string `json:"error_msg"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskCallback struct {
|
type VideoCallbackReq struct {
|
||||||
TaskID string `json:"taskId"`
|
g.Meta `path:"/videoCallback" method:"post" tags:"视频处理" summary:"media 回调" dc:"media 成功后 GET 回调:callbackUrl/{bizName}"`
|
||||||
State int `json:"state"` // 0排队中/1执行中/2成功/3失败/4已下载
|
TaskId string `json:"taskId"`
|
||||||
OssFile string `json:"ossFile"`
|
FileURL string `json:"fileUrl"`
|
||||||
FileType string `json:"fileType"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
//ImgContent *Image `json:"imgContent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Text struct {
|
|
||||||
Choices []struct {
|
|
||||||
FinishReason string `json:"finish_reason"`
|
|
||||||
Index int `json:"index"`
|
|
||||||
Message struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
Role string `json:"role"`
|
|
||||||
} `json:"message"`
|
|
||||||
} `json:"choices"`
|
|
||||||
Created int `json:"created"`
|
|
||||||
Id string `json:"id"`
|
|
||||||
Model string `json:"model"`
|
|
||||||
Object string `json:"object"`
|
|
||||||
Usage struct {
|
|
||||||
CompletionTokens int `json:"completion_tokens"`
|
|
||||||
PromptTokens int `json:"prompt_tokens"`
|
|
||||||
PromptTokensDetails struct {
|
|
||||||
CachedTokens int `json:"cached_tokens"`
|
|
||||||
}
|
|
||||||
TotalTokens int `json:"total_tokens"`
|
|
||||||
} `json:"usage"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Image struct {
|
|
||||||
Output struct {
|
|
||||||
Choices []struct {
|
|
||||||
FinishReason string `json:"finish_reason"`
|
|
||||||
Message struct {
|
|
||||||
Content []struct {
|
|
||||||
Image string `json:"image"`
|
|
||||||
} `json:"content"`
|
|
||||||
Role string `json:"role"`
|
|
||||||
} `json:"message"`
|
|
||||||
} `json:"choices"`
|
|
||||||
} `json:"output"`
|
|
||||||
Usage struct {
|
|
||||||
Height int `json:"height"`
|
|
||||||
ImageCount int `json:"image_count"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
} `json:"usage"`
|
|
||||||
RequestId string `json:"request_id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
|
// 原始入参结构体
|
||||||
|
type Word struct {
|
||||||
|
Confidence float64 `json:"confidence"`
|
||||||
|
StartTime float64 `json:"startTime"`
|
||||||
|
EndTime float64 `json:"endTime"`
|
||||||
|
Word string `json:"word"`
|
||||||
|
}
|
||||||
|
type Sentence struct {
|
||||||
|
EndTime float64 `json:"endTime"`
|
||||||
|
StartTime float64 `json:"startTime"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Words []Word `json:"words"`
|
||||||
|
}
|
||||||
|
type InputData struct {
|
||||||
|
Data struct {
|
||||||
|
Sentences []Sentence `json:"sentences"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出目标结构体(对应截图subtitles格式)
|
||||||
|
type Subtitle struct {
|
||||||
|
Start float64 `json:"start"`
|
||||||
|
End float64 `json:"end"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
type ExecuteReq struct {
|
type ExecuteReq struct {
|
||||||
g.Meta `path:"/execute" method:"post" tags:"任务管理" summary:"执行任务" dc:"执行任务"`
|
g.Meta `path:"/execute" method:"post" tags:"任务管理" summary:"执行任务" dc:"执行任务"`
|
||||||
|
|
||||||
FlowId int64 `json:"flowId" dc:"用户流程ID"`
|
FlowId int64 `json:"flowId" dc:"用户流程ID"`
|
||||||
FlowName string `json:"flowName"`
|
FlowName string `json:"flowName"`
|
||||||
|
NodeGroupId string `json:"nodeGroupId"`
|
||||||
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
|
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
|
||||||
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
|
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
|
||||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||||
@@ -153,6 +200,7 @@ type CancelReq struct {
|
|||||||
type CreateFlowExecutionReq struct {
|
type CreateFlowExecutionReq struct {
|
||||||
FlowUserId int64 `json:"flowUserId" description:"流程ID"`
|
FlowUserId int64 `json:"flowUserId" description:"流程ID"`
|
||||||
FlowName string `json:"flowName"`
|
FlowName string `json:"flowName"`
|
||||||
|
NodeGroupId string `json:"nodeGroupId"`
|
||||||
TriggerType flow.FlowExecutionTriggerType `json:"triggerType" description:"触发类型"`
|
TriggerType flow.FlowExecutionTriggerType `json:"triggerType" description:"触发类型"`
|
||||||
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
|
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
|
||||||
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
||||||
@@ -170,6 +218,7 @@ type CreateFlowExecutionRes struct {
|
|||||||
|
|
||||||
type UpdateFlowExecutionReq struct {
|
type UpdateFlowExecutionReq struct {
|
||||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
NodeGroupId string `json:"nodeGroupId"`
|
||||||
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
|
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
|
||||||
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
||||||
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
|
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
|
||||||
@@ -219,6 +268,7 @@ type VOFlowExecution struct {
|
|||||||
type OutputItem struct {
|
type OutputItem struct {
|
||||||
Timestamp string `json:"timestamp" description:"时间戳key"`
|
Timestamp string `json:"timestamp" description:"时间戳key"`
|
||||||
Content string `json:"content" description:"内容值"`
|
Content string `json:"content" description:"内容值"`
|
||||||
|
Type string `json:"type" description:"类型"`
|
||||||
Label string `json:"label" description:"后缀+数字标号"`
|
Label string `json:"label" description:"后缀+数字标号"`
|
||||||
}
|
}
|
||||||
type FlowNode struct {
|
type FlowNode struct {
|
||||||
@@ -232,7 +282,6 @@ type DateNode struct {
|
|||||||
Flows []FlowNode `json:"flows" description:"流程列表"`
|
Flows []FlowNode `json:"flows" description:"流程列表"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终树结构返回体
|
|
||||||
type ListFlowExecutionTreeRes struct {
|
type ListFlowExecutionTreeRes struct {
|
||||||
Tree []DateNode `json:"tree"`
|
Tree []DateNode `json:"tree"`
|
||||||
ImgAddressPrefix string `json:"imgAddressPrefix"`
|
ImgAddressPrefix string `json:"imgAddressPrefix"`
|
||||||
|
|||||||
68
workflow/model/dto/node/node_execution_dto.go
Normal file
68
workflow/model/dto/node/node_execution_dto.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/node"
|
||||||
|
flowDto "ai-agent/workflow/model/dto/flow"
|
||||||
|
"ai-agent/workflow/model/entity"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/beans"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateNodeExecutionReq 创建节点执行记录请求
|
||||||
|
type CreateNodeExecutionReq struct {
|
||||||
|
g.Meta `path:"/create" method:"post" tags:"节点执行记录" summary:"创建节点执行记录" dc:"创建节点执行记录"`
|
||||||
|
FlowExecutionId int64 `json:"flowExecutionId" v:"required#流程执行ID不能为空"`
|
||||||
|
NodeId string `json:"nodeId" v:"required#节点ID不能为空"`
|
||||||
|
NodeName string `json:"nodeName"`
|
||||||
|
NodeGroupId string `json:"nodeGroupId"`
|
||||||
|
Status node.NodeExecutionStatus `json:"status"`
|
||||||
|
InputParams *flowDto.NodeExecutionInput `json:"inputParams"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateNodeExecutionRes struct {
|
||||||
|
Id int64 `json:"id,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNodeExecutionReq 更新节点执行记录请求
|
||||||
|
type UpdateNodeExecutionReq struct {
|
||||||
|
g.Meta `path:"/update" method:"put" tags:"节点执行记录" summary:"更新节点执行记录" dc:"更新节点执行记录状态和结果"`
|
||||||
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
InputParams *flowDto.NodeExecutionInput `json:"inputParams"`
|
||||||
|
PromptTokens int `json:"promptTokens"`
|
||||||
|
CompletionTokens int `json:"completionTokens"`
|
||||||
|
TotalTokens int `json:"totalTokens"`
|
||||||
|
Status node.NodeExecutionStatus `json:"status"`
|
||||||
|
DurationMs int64 `json:"durationMs"`
|
||||||
|
ErrorMessage string `json:"errorMessage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNodeExecutionReq 删除节点执行记录请求
|
||||||
|
type DeleteNodeExecutionReq struct {
|
||||||
|
g.Meta `path:"/delete" method:"delete" tags:"节点执行记录" summary:"删除节点执行记录" dc:"删除节点执行记录"`
|
||||||
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeExecutionReq 根据ID查询节点执行记录请求
|
||||||
|
type GetNodeExecutionReq struct {
|
||||||
|
g.Meta `path:"/get" method:"get" tags:"节点执行记录" summary:"查询节点执行记录详情" dc:"根据ID查询节点执行记录详情"`
|
||||||
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNodeExecutionByFlowReq 查询流程下所有节点执行记录请求
|
||||||
|
type ListNodeExecutionByFlowReq struct {
|
||||||
|
g.Meta `path:"/listByFlow" method:"get" tags:"节点执行记录" summary:"查询流程节点执行列表" dc:"查询指定流程执行下的所有节点执行记录"`
|
||||||
|
Page *beans.Page `json:"page"`
|
||||||
|
FlowExecutionId int64 `json:"flowExecutionId" v:"required#流程执行ID不能为空"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeExecutionResp 节点执行记录响应
|
||||||
|
type NodeExecutionResp struct {
|
||||||
|
*entity.NodeExecution
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNodeExecutionResp 节点执行记录列表响应
|
||||||
|
type ListNodeExecutionResp struct {
|
||||||
|
List []*entity.NodeExecution `json:"list"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
@@ -27,3 +27,7 @@ type ModelItem struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Form []node.NodeFormField `json:"form"`
|
Form []node.NodeFormField `json:"form"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ModelTypeResponse struct {
|
||||||
|
Type map[int]string `json:"type"` // key 自动解析为整数 100/200/300...
|
||||||
|
}
|
||||||
|
|||||||
70
workflow/model/dto/node/node_prompt_dto.go
Normal file
70
workflow/model/dto/node/node_prompt_dto.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/node"
|
||||||
|
"ai-agent/workflow/model/entity"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/beans"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateNodePromptReq 创建节点提示词请求
|
||||||
|
type CreateNodePromptReq struct {
|
||||||
|
g.Meta `path:"/create" method:"post" tags:"节点提示词管理" summary:"创建节点提示词" dc:"创建用户自定义节点提示词"`
|
||||||
|
NodeType node.NodeType `json:"nodeType" v:"required#节点类型不能为空"`
|
||||||
|
Prompt string `json:"prompt" v:"required#提示词不能为空"`
|
||||||
|
SourceType node.SourceType `json:"sourceType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateNodePromptRes struct {
|
||||||
|
Id int64 `json:"id,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNodePromptReq 更新节点提示词请求
|
||||||
|
type UpdateNodePromptReq struct {
|
||||||
|
g.Meta `path:"/update" method:"put" tags:"节点提示词管理" summary:"更新节点提示词" dc:"更新用户自定义节点提示词"`
|
||||||
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
NodeType node.NodeType `json:"nodeType"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNodePromptReq 删除节点提示词请求
|
||||||
|
type DeleteNodePromptReq struct {
|
||||||
|
g.Meta `path:"/delete" method:"delete" tags:"节点提示词管理" summary:"删除节点提示词" dc:"删除用户自定义节点提示词"`
|
||||||
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodePromptReq 根据ID查询节点提示词请求
|
||||||
|
type GetNodePromptReq struct {
|
||||||
|
g.Meta `path:"/get" method:"get" tags:"节点提示词管理" summary:"查询节点提示词详情" dc:"根据ID查询节点提示词详情"`
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNodePromptReq 查询节点提示词列表请求
|
||||||
|
type ListNodePromptReq struct {
|
||||||
|
g.Meta `path:"/list" method:"get" tags:"节点提示词管理" summary:"查询节点提示词列表" dc:"查询当前创建人的节点提示词,包含系统和用户自定义"`
|
||||||
|
Page *beans.Page `json:"page"`
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
NodeType node.NodeType `json:"nodeType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMyNodePromptReq 查询当前用户节点提示词列表请求
|
||||||
|
type ListMyNodePromptReq struct {
|
||||||
|
g.Meta `path:"/listMy" method:"get" tags:"节点提示词管理" summary:"查询当前用户节点提示词列表" dc:"查询当前创建人自己创建的节点提示词列表"`
|
||||||
|
Page *beans.Page `json:"page"`
|
||||||
|
NodeType node.NodeType `json:"nodeType"`
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodePromptResp 节点提示词响应
|
||||||
|
type NodePromptResp struct {
|
||||||
|
*entity.NodePrompt
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListNodePromptResp 节点提示词列表响应
|
||||||
|
type ListNodePromptResp struct {
|
||||||
|
List []*entity.NodePrompt `json:"list"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
54
workflow/model/dto/pull/active_pull_dto.go
Normal file
54
workflow/model/dto/pull/active_pull_dto.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package pull
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/flow"
|
||||||
|
"ai-agent/workflow/model/entity"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/beans"
|
||||||
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateActivePullReq struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
RequestParament map[string]any `json:"requestParament"`
|
||||||
|
ResponseParament map[string]any `json:"responseParament"`
|
||||||
|
Extension map[string]any `json:"extension"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateActivePullRes struct {
|
||||||
|
Id int64 `json:"id,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateActivePullReq struct {
|
||||||
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
RequestParament map[string]any `json:"requestParament"`
|
||||||
|
ResponseParament map[string]any `json:"responseParament"`
|
||||||
|
Extension map[string]any `json:"extension"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteActivePullReq struct {
|
||||||
|
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListActivePullReq struct {
|
||||||
|
Page *beans.Page `json:"page"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListActivePullRes struct {
|
||||||
|
List []*ActivePullVO `json:"list"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivePullVO struct {
|
||||||
|
Id int64 `json:"id,string" dc:"id"`
|
||||||
|
FlowName string `json:"flowName" description:"流程名称"`
|
||||||
|
Description string `json:"description" description:"流程描述"`
|
||||||
|
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
|
||||||
|
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
|
||||||
|
AccessLevel flow.FlowUserAccessLevel `json:"accessLevel" description:"访问权限:1私有,2团队,3公开"`
|
||||||
|
SourceFlowTemplateId int64 `json:"sourceFlowTemplateId,string" description:"来源流程模板ID"`
|
||||||
|
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||||
|
UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间"`
|
||||||
|
}
|
||||||
28
workflow/model/entity/active_pull.go
Normal file
28
workflow/model/entity/active_pull.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import "gitea.com/red-future/common/beans"
|
||||||
|
|
||||||
|
type ActivePull struct {
|
||||||
|
beans.SQLBaseDO `orm:",inherit"` // 嵌入基础字段:Id, TenantId, Creator, CreatedAt, Updater, UpdatedAt, DeletedAt
|
||||||
|
|
||||||
|
Type string `orm:"type" json:"type"`
|
||||||
|
RequestParament map[string]any `orm:"request_parament" json:"requestParament"`
|
||||||
|
ResponseParament map[string]any `orm:"response_parament" json:"responseParament"`
|
||||||
|
Extension map[string]any `orm:"extension" json:"extension"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type activePullCol struct {
|
||||||
|
beans.SQLBaseCol
|
||||||
|
Type string
|
||||||
|
RequestParament string
|
||||||
|
ResponseParament string
|
||||||
|
Extension string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ActivePullCol = activePullCol{
|
||||||
|
SQLBaseCol: beans.DefSQLBaseCol,
|
||||||
|
Type: "type",
|
||||||
|
RequestParament: "request_parament",
|
||||||
|
ResponseParament: "response_parament",
|
||||||
|
Extension: "extension",
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ type FlowExecution struct {
|
|||||||
// 业务字段
|
// 业务字段
|
||||||
FlowUserId int64 `orm:"flow_user_id" json:"flowUserId" description:"流程ID"`
|
FlowUserId int64 `orm:"flow_user_id" json:"flowUserId" description:"流程ID"`
|
||||||
FlowName string `orm:"flow_name" json:"flowName" description:"流程名称"`
|
FlowName string `orm:"flow_name" json:"flowName" description:"流程名称"`
|
||||||
|
NodeGroupId string `orm:"node_group_id" json:"nodeGroupId" description:"节点组ID"`
|
||||||
TriggerType flow.FlowExecutionTriggerType `orm:"trigger_type" json:"triggerType" description:"触发类型"`
|
TriggerType flow.FlowExecutionTriggerType `orm:"trigger_type" json:"triggerType" description:"触发类型"`
|
||||||
DurationMs int64 `orm:"duration_ms" json:"durationMs" description:"执行时长(毫秒)"`
|
DurationMs int64 `orm:"duration_ms" json:"durationMs" description:"执行时长(毫秒)"`
|
||||||
Status flow.FlowExecutionStatus `orm:"status" json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
Status flow.FlowExecutionStatus `orm:"status" json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
||||||
@@ -20,12 +21,14 @@ type FlowExecution struct {
|
|||||||
ErrorMessage string `orm:"error_message" json:"errorMessage" description:"错误信息"`
|
ErrorMessage string `orm:"error_message" json:"errorMessage" description:"错误信息"`
|
||||||
TraceId string `orm:"trace_id" json:"traceId" description:"跟踪ID"`
|
TraceId string `orm:"trace_id" json:"traceId" description:"跟踪ID"`
|
||||||
SessionId string `orm:"session_id" json:"sessionId" description:"会话ID"`
|
SessionId string `orm:"session_id" json:"sessionId" description:"会话ID"`
|
||||||
|
TotalTokens int `orm:"total_tokens" json:"totalTokens" description:"总token消耗"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type flowExecutionCol struct {
|
type flowExecutionCol struct {
|
||||||
beans.SQLBaseCol
|
beans.SQLBaseCol
|
||||||
FlowUserId string
|
FlowUserId string
|
||||||
FlowName string
|
FlowName string
|
||||||
|
NodeGroupId string
|
||||||
TriggerType string
|
TriggerType string
|
||||||
DurationMs string
|
DurationMs string
|
||||||
Status string
|
Status string
|
||||||
@@ -35,12 +38,14 @@ type flowExecutionCol struct {
|
|||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
TraceId string
|
TraceId string
|
||||||
SessionId string
|
SessionId string
|
||||||
|
TotalTokens string
|
||||||
}
|
}
|
||||||
|
|
||||||
var FlowExecutionCol = flowExecutionCol{
|
var FlowExecutionCol = flowExecutionCol{
|
||||||
SQLBaseCol: beans.DefSQLBaseCol,
|
SQLBaseCol: beans.DefSQLBaseCol,
|
||||||
FlowUserId: "flow_user_id",
|
FlowUserId: "flow_user_id",
|
||||||
FlowName: "flow_name",
|
FlowName: "flow_name",
|
||||||
|
NodeGroupId: "node_group_id",
|
||||||
TriggerType: "trigger_type",
|
TriggerType: "trigger_type",
|
||||||
DurationMs: "duration_ms",
|
DurationMs: "duration_ms",
|
||||||
Status: "status",
|
Status: "status",
|
||||||
@@ -50,4 +55,5 @@ var FlowExecutionCol = flowExecutionCol{
|
|||||||
ErrorMessage: "error_message",
|
ErrorMessage: "error_message",
|
||||||
TraceId: "trace_id",
|
TraceId: "trace_id",
|
||||||
SessionId: "session_id",
|
SessionId: "session_id",
|
||||||
|
TotalTokens: "total_tokens",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,12 @@ type FlowNode struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Config map[string]interface{} `json:"config"`
|
Config map[string]interface{} `json:"config"`
|
||||||
SkillName string `json:"skillName"`
|
SkillName string `json:"skillName"`
|
||||||
|
PromptContent string `json:"promptContent"`
|
||||||
|
IsSaveFile bool `json:"isSaveFile"`
|
||||||
InputSource []FlowNodeInputSource `json:"inputSource"` // 前端指定:来源节点ID
|
InputSource []FlowNodeInputSource `json:"inputSource"` // 前端指定:来源节点ID
|
||||||
FormConfig []node.NodeFormField `json:"formConfig"`
|
FormConfig []node.NodeFormField `json:"formConfig"`
|
||||||
ModelConfig node.ModelItem `json:"modelConfig"`
|
ModelConfig node.ModelItem `json:"modelConfig"`
|
||||||
|
OutputConfig []node.NodeFormField `json:"outputConfig"`
|
||||||
OutputResult []node.NodeFormField `json:"outputResult" ds:"节点输出结果"`
|
OutputResult []node.NodeFormField `json:"outputResult" ds:"节点输出结果"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
58
workflow/model/entity/node_execution.go
Normal file
58
workflow/model/entity/node_execution.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/node"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/beans"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeExecution 节点执行记录
|
||||||
|
// 记录每个节点的入参、出参、token消耗、执行状态等信息
|
||||||
|
type NodeExecution struct {
|
||||||
|
beans.SQLBaseDO `orm:",inherit"` // 嵌入基础字段:Id, TenantId, Creator, CreatedAt, Updater, UpdatedAt, DeletedAt
|
||||||
|
|
||||||
|
FlowExecutionId int64 `orm:"flow_execution_id" json:"flowExecutionId" description:"流程执行ID"`
|
||||||
|
NodeId string `orm:"node_id" json:"nodeId" description:"节点ID"`
|
||||||
|
NodeName string `orm:"node_name" json:"nodeName" description:"节点名称"`
|
||||||
|
NodeGroupId string `orm:"node_group_id" json:"nodeGroupId" description:"节点组ID"`
|
||||||
|
InputParams map[string]interface{} `orm:"input_params" json:"inputParams" description:"节点输入参数"`
|
||||||
|
OutputParams map[string]interface{} `orm:"output_params" json:"outputParams" description:"节点输出参数"`
|
||||||
|
PromptTokens int `orm:"prompt_tokens" json:"promptTokens" description:"提示词token消耗"`
|
||||||
|
CompletionTokens int `orm:"completion_tokens" json:"completionTokens" description:"补全token消耗"`
|
||||||
|
TotalTokens int `orm:"total_tokens" json:"totalTokens" description:"总token消耗"`
|
||||||
|
Status node.NodeExecutionStatus `orm:"status" json:"status" description:"执行状态:1-运行中,2-成功,3-失败,4-暂停,5-等待执行"`
|
||||||
|
DurationMs int64 `orm:"duration_ms" json:"durationMs" description:"执行时长(毫秒)"`
|
||||||
|
ErrorMessage string `orm:"error_message" json:"errorMessage" description:"错误信息"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeExecutionCol struct {
|
||||||
|
beans.SQLBaseCol
|
||||||
|
FlowExecutionId string
|
||||||
|
NodeId string
|
||||||
|
NodeName string
|
||||||
|
NodeGroupId string
|
||||||
|
InputParams string
|
||||||
|
OutputParams string
|
||||||
|
PromptTokens string
|
||||||
|
CompletionTokens string
|
||||||
|
TotalTokens string
|
||||||
|
Status string
|
||||||
|
DurationMs string
|
||||||
|
ErrorMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
var NodeExecutionCol = nodeExecutionCol{
|
||||||
|
SQLBaseCol: beans.DefSQLBaseCol,
|
||||||
|
FlowExecutionId: "flow_execution_id",
|
||||||
|
NodeId: "node_id",
|
||||||
|
NodeName: "node_name",
|
||||||
|
NodeGroupId: "node_group_id",
|
||||||
|
InputParams: "input_params",
|
||||||
|
OutputParams: "output_params",
|
||||||
|
PromptTokens: "prompt_tokens",
|
||||||
|
CompletionTokens: "completion_tokens",
|
||||||
|
TotalTokens: "total_tokens",
|
||||||
|
Status: "status",
|
||||||
|
DurationMs: "duration_ms",
|
||||||
|
ErrorMessage: "error_message",
|
||||||
|
}
|
||||||
29
workflow/model/entity/node_prompt.go
Normal file
29
workflow/model/entity/node_prompt.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ai-agent/workflow/consts/node"
|
||||||
|
|
||||||
|
"gitea.com/red-future/common/beans"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NodePrompt struct {
|
||||||
|
beans.SQLBaseDO `orm:",inherit"` // 嵌入基础字段:Id, TenantId, Creator, CreatedAt, Updater, UpdatedAt, DeletedAt
|
||||||
|
|
||||||
|
NodeType node.NodeType `orm:"node_type" json:"nodeType"`
|
||||||
|
Prompt string `orm:"prompt" json:"prompt"`
|
||||||
|
SourceType node.SourceType `orm:"source_type" json:"sourceType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodePromptCol struct {
|
||||||
|
beans.SQLBaseCol
|
||||||
|
NodeType string
|
||||||
|
Prompt string
|
||||||
|
SourceType string
|
||||||
|
}
|
||||||
|
|
||||||
|
var NodePromptCol = nodePromptCol{
|
||||||
|
SQLBaseCol: beans.DefSQLBaseCol,
|
||||||
|
NodeType: "node_type",
|
||||||
|
Prompt: "prompt",
|
||||||
|
SourceType: "source_type",
|
||||||
|
}
|
||||||
101
workflow/service/active_pull/active_pull_service.go
Normal file
101
workflow/service/active_pull/active_pull_service.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package pull
|
||||||
|
|
||||||
|
import (
|
||||||
|
pullDao "ai-agent/workflow/dao/pull"
|
||||||
|
pullDto "ai-agent/workflow/model/dto/pull"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ActivePullService = &activePullService{}
|
||||||
|
|
||||||
|
type activePullService struct{}
|
||||||
|
|
||||||
|
func (s *activePullService) Create(ctx context.Context, req *pullDto.CreateActivePullReq) (res *pullDto.CreateActivePullRes, err error) {
|
||||||
|
id, err := pullDao.ActivePullDao.Insert(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return &pullDto.CreateActivePullRes{Id: id}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *activePullService) Update(ctx context.Context, req *pullDto.UpdateActivePullReq) (err error) {
|
||||||
|
_, err = pullDao.ActivePullDao.Update(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *activePullService) Delete(ctx context.Context, req *pullDto.DeleteActivePullReq) (err error) {
|
||||||
|
_, err = pullDao.ActivePullDao.Delete(ctx, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (s *activePullService) AllList(ctx context.Context) (err error) {
|
||||||
|
// ctx = context.WithValue(ctx, "user", &beans.User{
|
||||||
|
// UserName: "admin",
|
||||||
|
// })
|
||||||
|
// for {
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return ctx.Err()
|
||||||
|
// default:
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var list []*entity.ActivePull
|
||||||
|
// list, _, err = pullDao.ActivePullDao.ListNative(ctx, &pullDto.ListActivePullReq{})
|
||||||
|
// if err != nil {
|
||||||
|
// g.Log().Error(ctx, "AllList query failed: %v", err)
|
||||||
|
// time.Sleep(time.Second * 3)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Get all active pull tasks and check each one for results
|
||||||
|
// for _, item := range list {
|
||||||
|
// var result map[string]any
|
||||||
|
// result, err = flow.PullTaskResult(ctx, item.RequestParament, item.Extension)
|
||||||
|
// if err != nil {
|
||||||
|
// g.Log().Error(ctx, "PullTaskResult failed for item %d: %v", item.Id, err)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// if !g.IsEmpty(result) {
|
||||||
|
// // Find the task ID that matches the creation pattern
|
||||||
|
// // When created in CreateGatewayTask (flow/lambda_node_util.go),
|
||||||
|
// // the last parameter value extracted from the response becomes the waiting task ID
|
||||||
|
// var id string
|
||||||
|
// if taskId, ok := item.RequestParament["task_id"]; ok {
|
||||||
|
// id = gconv.String(taskId)
|
||||||
|
// } else if requestId, ok := item.RequestParament["id"]; ok {
|
||||||
|
// id = gconv.String(requestId)
|
||||||
|
// } else if jobId, ok := item.RequestParament["job_id"]; ok {
|
||||||
|
// id = gconv.String(jobId)
|
||||||
|
// } else {
|
||||||
|
// // Fallback to original behavior: use last value (matches creation logic)
|
||||||
|
// for _, v := range item.RequestParament {
|
||||||
|
// id = gconv.String(v)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if id != "" {
|
||||||
|
// flow.Notify(id, result)
|
||||||
|
// // Delete after successful notification
|
||||||
|
// _, _ = pullDao.ActivePullDao.Delete(ctx, &pullDto.DeleteActivePullReq{Id: item.Id})
|
||||||
|
// } else {
|
||||||
|
// g.Log().Warning(ctx, "AllList: could not extract task ID for item %d", item.Id)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// time.Sleep(time.Second * 10)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (s *activePullService) List(ctx context.Context, req *pullDto.ListActivePullReq) (res *pullDto.ListActivePullRes, err error) {
|
||||||
|
list, total, err := pullDao.ActivePullDao.List(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = &pullDto.ListActivePullRes{
|
||||||
|
Total: total,
|
||||||
|
}
|
||||||
|
err = gconv.Struct(list, &res.List)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -5,8 +5,10 @@ import (
|
|||||||
"ai-agent/workflow/consts/node"
|
"ai-agent/workflow/consts/node"
|
||||||
fileDao "ai-agent/workflow/dao/file"
|
fileDao "ai-agent/workflow/dao/file"
|
||||||
flowDao "ai-agent/workflow/dao/flow"
|
flowDao "ai-agent/workflow/dao/flow"
|
||||||
|
nodeDao "ai-agent/workflow/dao/node"
|
||||||
fileDto "ai-agent/workflow/model/dto/file"
|
fileDto "ai-agent/workflow/model/dto/file"
|
||||||
flowDto "ai-agent/workflow/model/dto/flow"
|
flowDto "ai-agent/workflow/model/dto/flow"
|
||||||
|
nodeDto "ai-agent/workflow/model/dto/node"
|
||||||
"ai-agent/workflow/model/entity"
|
"ai-agent/workflow/model/entity"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -15,12 +17,14 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.com/red-future/common/utils"
|
"gitea.com/red-future/common/utils"
|
||||||
"github.com/cloudwego/eino/compose"
|
"github.com/cloudwego/eino/compose"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"github.com/google/uuid"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,22 +125,30 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
|
|||||||
item := &tempItems[idx]
|
item := &tempItems[idx]
|
||||||
val := item.Content
|
val := item.Content
|
||||||
suffix := "内容"
|
suffix := "内容"
|
||||||
|
ext := ""
|
||||||
switch {
|
ext = GetFileTypeByPath(val)
|
||||||
case strings.Contains(val, "img") || strings.Contains(val, "png") || strings.Contains(val, "jpg"):
|
if ext == "image" {
|
||||||
suffix = "图片"
|
suffix = "图片"
|
||||||
case strings.Contains(val, "html") || strings.Contains(val, "HTML"):
|
}
|
||||||
suffix = "HTML"
|
if ext == "video" {
|
||||||
case strings.Contains(val, "inc") || len(val) > 50:
|
suffix = "视频"
|
||||||
|
}
|
||||||
|
if ext == "audio" {
|
||||||
|
suffix = "音频"
|
||||||
|
}
|
||||||
|
if ext == "text" {
|
||||||
suffix = "文案"
|
suffix = "文案"
|
||||||
}
|
}
|
||||||
|
if ext == "html" {
|
||||||
|
suffix = "HTML"
|
||||||
|
}
|
||||||
suffixCount[suffix]++
|
suffixCount[suffix]++
|
||||||
|
item.Type = ext
|
||||||
item.Label = fmt.Sprintf("%s_%d", suffix, suffixCount[suffix])
|
item.Label = fmt.Sprintf("%s_%d", suffix, suffixCount[suffix])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组装节点
|
// 组装节点
|
||||||
node := flowDto.FlowNode{
|
flowNode := flowDto.FlowNode{
|
||||||
FlowName: displayFlowName,
|
FlowName: displayFlowName,
|
||||||
Id: execution.Id,
|
Id: execution.Id,
|
||||||
SessionId: gconv.String(execution.SessionId),
|
SessionId: gconv.String(execution.SessionId),
|
||||||
@@ -147,7 +159,7 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
|
|||||||
dateMap[createDate] = &[]flowWrap{}
|
dateMap[createDate] = &[]flowWrap{}
|
||||||
}
|
}
|
||||||
*dateMap[createDate] = append(*dateMap[createDate], flowWrap{
|
*dateMap[createDate] = append(*dateMap[createDate], flowWrap{
|
||||||
flowNode: node,
|
flowNode: flowNode,
|
||||||
createdAt: execution.CreatedAt,
|
createdAt: execution.CreatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -188,6 +200,12 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ComposeCallback 提示词回调接口
|
||||||
|
func (s *flowExecutionService) ComposeCallback(ctx context.Context, req *flowDto.ComposeCallbackReq) (err error) {
|
||||||
|
Notify(req.TaskId, req)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ModelCallback 模型回调接口
|
// ModelCallback 模型回调接口
|
||||||
func (s *flowExecutionService) ModelCallback(ctx context.Context, req *flowDto.ModelCallbackReq) (err error) {
|
func (s *flowExecutionService) ModelCallback(ctx context.Context, req *flowDto.ModelCallbackReq) (err error) {
|
||||||
// 唤醒等待的任务
|
// 唤醒等待的任务
|
||||||
@@ -195,43 +213,19 @@ func (s *flowExecutionService) ModelCallback(ctx context.Context, req *flowDto.M
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局等待任务回调的工具
|
// VideoCallback 视频拼接回调接口
|
||||||
var (
|
func (s *flowExecutionService) VideoCallback(ctx context.Context, req *flowDto.VideoCallbackReq) (err error) {
|
||||||
asyncMu sync.Mutex
|
// 唤醒等待的任务
|
||||||
asyncTasks = make(map[string]chan any)
|
Notify(req.TaskId, req)
|
||||||
)
|
return nil
|
||||||
|
|
||||||
// Wait 阻塞等待回调结果
|
|
||||||
// 调用后会一直卡住,直到 Notify 唤醒 或 超时/取消
|
|
||||||
func Wait(ctx context.Context, taskId string) (any, error) {
|
|
||||||
asyncMu.Lock()
|
|
||||||
ch := make(chan any, 1)
|
|
||||||
asyncTasks[taskId] = ch
|
|
||||||
asyncMu.Unlock()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case result := <-ch:
|
|
||||||
return result, nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
asyncMu.Lock()
|
|
||||||
delete(asyncTasks, taskId)
|
|
||||||
asyncMu.Unlock()
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify 回调时调用,唤醒等待的任务
|
// HttpNodeCallback http节点回调接口
|
||||||
func Notify(taskId string, result any) {
|
func (s *flowExecutionService) HttpNodeCallback(ctx context.Context) (err error) {
|
||||||
asyncMu.Lock()
|
r := g.RequestFromCtx(ctx)
|
||||||
defer asyncMu.Unlock()
|
taskId := r.Get("task_id").String()
|
||||||
|
Notify(taskId, r)
|
||||||
ch, exist := asyncTasks[taskId]
|
return nil
|
||||||
if !exist {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ch <- result
|
|
||||||
delete(asyncTasks, taskId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 核心改造:替换为 sync.Map 存储取消上下文 =====================
|
// ===================== 核心改造:替换为 sync.Map 存储取消上下文 =====================
|
||||||
@@ -298,11 +292,13 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
}
|
}
|
||||||
var executionId int64
|
var executionId int64
|
||||||
var isDialogue bool
|
var isDialogue bool
|
||||||
|
var nodeGroupId = uuid.NewString()
|
||||||
if flowInfo == nil {
|
if flowInfo == nil {
|
||||||
isDialogue = false
|
isDialogue = false
|
||||||
var r = new(flowDto.CreateFlowExecutionReq)
|
var r = new(flowDto.CreateFlowExecutionReq)
|
||||||
r.FlowUserId = req.FlowId
|
r.FlowUserId = req.FlowId
|
||||||
r.FlowName = req.FlowName
|
r.FlowName = req.FlowName
|
||||||
|
r.NodeGroupId = nodeGroupId
|
||||||
r.TriggerType = flow.FlowExecutionTriggerTypeManual.Code()
|
r.TriggerType = flow.FlowExecutionTriggerTypeManual.Code()
|
||||||
r.FlowContent = req.FlowContent
|
r.FlowContent = req.FlowContent
|
||||||
r.NodeInputParams = req.NodeInputParams
|
r.NodeInputParams = req.NodeInputParams
|
||||||
@@ -328,6 +324,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
}
|
}
|
||||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||||
Id: executionId,
|
Id: executionId,
|
||||||
|
NodeGroupId: nodeGroupId,
|
||||||
Status: flow.FlowExecutionStatusRunning.Code(),
|
Status: flow.FlowExecutionStatusRunning.Code(),
|
||||||
TraceId: traceId,
|
TraceId: traceId,
|
||||||
}
|
}
|
||||||
@@ -352,6 +349,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isDialogue && !g.IsEmpty(flowInfo) && !g.IsEmpty(req.ResultUrl) {
|
if isDialogue && !g.IsEmpty(flowInfo) && !g.IsEmpty(req.ResultUrl) {
|
||||||
|
req.NodeGroupId = nodeGroupId
|
||||||
if strings.HasSuffix(gconv.String(req.ResultUrl), ".inc") {
|
if strings.HasSuffix(gconv.String(req.ResultUrl), ".inc") {
|
||||||
err = TextModelSingleLambda(ctx, req, flowInfo)
|
err = TextModelSingleLambda(ctx, req, flowInfo)
|
||||||
return
|
return
|
||||||
@@ -440,6 +438,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
// ✅【第4步】构建全局执行入参(现在 schemaMap 是有值的!)
|
// ✅【第4步】构建全局执行入参(现在 schemaMap 是有值的!)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
execInput := &flowDto.FlowExecutionInput{
|
execInput := &flowDto.FlowExecutionInput{
|
||||||
|
NodeGroupId: nodeGroupId,
|
||||||
IsDialogue: isDialogue,
|
IsDialogue: isDialogue,
|
||||||
ExecutionId: executionId,
|
ExecutionId: executionId,
|
||||||
ConfigMap: configMap,
|
ConfigMap: configMap,
|
||||||
@@ -476,6 +475,17 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
|||||||
|
|
||||||
// BuildGraphFromFlowContent 根据前端保存的工作流JSON,自动构建执行图
|
// BuildGraphFromFlowContent 根据前端保存的工作流JSON,自动构建执行图
|
||||||
func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo, judge2IntentNodeMap map[string]string, summaryNodeID string) (compose.Runnable[any, any], error) {
|
func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo, judge2IntentNodeMap map[string]string, summaryNodeID string) (compose.Runnable[any, any], error) {
|
||||||
|
// 注册自定义合并函数:处理 *flowDto.FlowExecutionInput 类型合并
|
||||||
|
// 由于 ConfigMap 是 map 引用类型,所有并行分支修改已经写入共享内存
|
||||||
|
// 直接返回第一个实例即可,所有修改都已经可见
|
||||||
|
compose.RegisterValuesMergeFunc(func(values []*flowDto.FlowExecutionInput) (*flowDto.FlowExecutionInput, error) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// 返回第一个实例,ConfigMap 是指针,所有修改都已经写入共享数据结构
|
||||||
|
return values[0], nil
|
||||||
|
})
|
||||||
|
|
||||||
graph := compose.NewGraph[any, any]()
|
graph := compose.NewGraph[any, any]()
|
||||||
nodeMap := make(map[string]entity.FlowNode)
|
nodeMap := make(map[string]entity.FlowNode)
|
||||||
|
|
||||||
@@ -582,7 +592,7 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
|
|||||||
}
|
}
|
||||||
_ = graph.AddEdge(summaryNodeID, compose.END)
|
_ = graph.AddEdge(summaryNodeID, compose.END)
|
||||||
|
|
||||||
return graph.Compile(ctx, compose.WithGraphName("auto_build_workflow"))
|
return graph.Compile(ctx, compose.WithGraphName("auto_build_workflow"), compose.WithNodeTriggerMode(compose.AllPredecessor))
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------- 节点自动注册器(核心分发) --------------------------
|
// -------------------------- 节点自动注册器(核心分发) --------------------------
|
||||||
@@ -606,7 +616,7 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取入参 - 适配切片类型:遍历所有来源节点
|
// 获取入参 - 适配切片类型:遍历所有来源节点
|
||||||
var realInput any
|
realInput := new(flowDto.NodeExecutionInput)
|
||||||
if len(flowNode.InputSource) > 0 { // 改为判断切片长度
|
if len(flowNode.InputSource) > 0 { // 改为判断切片长度
|
||||||
// 遍历所有指定的来源节点,聚合输出结果
|
// 遍历所有指定的来源节点,聚合输出结果
|
||||||
for _, inputSource := range flowNode.InputSource { // 遍历切片
|
for _, inputSource := range flowNode.InputSource { // 遍历切片
|
||||||
@@ -621,19 +631,54 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
|||||||
Config: currentConfig,
|
Config: currentConfig,
|
||||||
Global: execInput, // ✅ 把【全部节点】的对象直接塞进来
|
Global: execInput, // ✅ 把【全部节点】的对象直接塞进来
|
||||||
}
|
}
|
||||||
|
// ✅ 插入节点执行记录,初始状态为运行中
|
||||||
// 执行节点
|
startTime := time.Now()
|
||||||
output, err := lambda(ctx, realInput)
|
nodeExecutionId, err := nodeDao.NodeExecutionDao.Insert(ctx, &nodeDto.CreateNodeExecutionReq{
|
||||||
|
FlowExecutionId: execInput.ExecutionId,
|
||||||
|
NodeId: nodeID,
|
||||||
|
NodeName: flowNode.Name,
|
||||||
|
NodeGroupId: execInput.NodeGroupId,
|
||||||
|
InputParams: realInput,
|
||||||
|
Status: node.NodeExecutionStatusRunning.Code(),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// 记录失败到已执行列表
|
||||||
|
execInput.ExecutedNodes = append(execInput.ExecutedNodes, flowDto.ExecutedNode{
|
||||||
|
NodeId: nodeID,
|
||||||
|
Status: node.NodeExecutionStatusFailed.Code(),
|
||||||
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// ✅ 自动把当前节点ID 加入已执行列表
|
realInput.NodeExecutionId = nodeExecutionId
|
||||||
execInput.ExecutedNodes = append(execInput.ExecutedNodes, nodeID)
|
// 执行节点
|
||||||
|
_, err = lambda(ctx, realInput)
|
||||||
// 输出存入 FlowNodeConfig
|
durationMs := time.Since(startTime).Milliseconds()
|
||||||
if outConfig, ok := output.(*entity.FlowNode); ok {
|
updateReq := &nodeDto.UpdateNodeExecutionReq{
|
||||||
currentConfig.OutputResult = outConfig.OutputResult
|
Id: nodeExecutionId,
|
||||||
|
InputParams: realInput,
|
||||||
|
DurationMs: durationMs,
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
// 执行失败,更新状态
|
||||||
|
updateReq.Status = node.NodeExecutionStatusFailed.Code()
|
||||||
|
updateReq.ErrorMessage = err.Error()
|
||||||
|
_, _ = nodeDao.NodeExecutionDao.Update(ctx, updateReq)
|
||||||
|
// 记录失败到已执行列表
|
||||||
|
execInput.ExecutedNodes = append(execInput.ExecutedNodes, flowDto.ExecutedNode{
|
||||||
|
NodeId: nodeID,
|
||||||
|
Status: node.NodeExecutionStatusFailed.Code(),
|
||||||
|
})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行成功,更新状态
|
||||||
|
updateReq.Status = node.NodeExecutionStatusSuccess.Code()
|
||||||
|
_, _ = nodeDao.NodeExecutionDao.Update(ctx, updateReq)
|
||||||
|
// 记录成功到已执行列表
|
||||||
|
execInput.ExecutedNodes = append(execInput.ExecutedNodes, flowDto.ExecutedNode{
|
||||||
|
NodeId: nodeID,
|
||||||
|
Status: node.NodeExecutionStatusSuccess.Code(),
|
||||||
|
})
|
||||||
|
|
||||||
// ✅ 关键:返回整个 execInput,让下一个节点继续用!
|
// ✅ 关键:返回整个 execInput,让下一个节点继续用!
|
||||||
return execInput, nil
|
return execInput, nil
|
||||||
@@ -654,6 +699,16 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
|||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(VideoModelLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(VideoModelLambda)))
|
||||||
case node.NodeTypeAudioModel:
|
case node.NodeTypeAudioModel:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(AudioModelLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(AudioModelLambda)))
|
||||||
|
case node.NodeTypeBatchModel:
|
||||||
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(BatchModelLambda)))
|
||||||
|
case node.NodeTypeDataConversionModel:
|
||||||
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(DataConversionLambda)))
|
||||||
|
//case node.NodeTypeSenseOptimizeModel:
|
||||||
|
// _ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(SenseOptimizeModelLambda)))
|
||||||
|
//case node.NodeTypeStoryOptimizeModel:
|
||||||
|
// _ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(StoryOptimizeModelLambda)))
|
||||||
|
//case node.NodeTypeScriptOptimizeModel:
|
||||||
|
// _ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(ScriptOptimizeModelLambda)))
|
||||||
case node.NodeTypeCustomNode:
|
case node.NodeTypeCustomNode:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(CustomLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(CustomLambda)))
|
||||||
case node.NodeTypeForm:
|
case node.NodeTypeForm:
|
||||||
@@ -662,6 +717,10 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
|||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(IntentLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(IntentLambda)))
|
||||||
case node.NodeTypeMerge:
|
case node.NodeTypeMerge:
|
||||||
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(MergeLambda)))
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(MergeLambda)))
|
||||||
|
case node.NodeTypeDataMerge:
|
||||||
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(DataMergeLambda)))
|
||||||
|
case node.NodeTypeHttp:
|
||||||
|
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(HttpLambda)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package flow
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"ai-agent/workflow/consts/flow"
|
"ai-agent/workflow/consts/flow"
|
||||||
|
"ai-agent/workflow/consts/node"
|
||||||
flowDao "ai-agent/workflow/dao/flow"
|
flowDao "ai-agent/workflow/dao/flow"
|
||||||
flowDto "ai-agent/workflow/model/dto/flow"
|
flowDto "ai-agent/workflow/model/dto/flow"
|
||||||
"ai-agent/workflow/model/entity"
|
"ai-agent/workflow/model/entity"
|
||||||
|
"ai-agent/workflow/service"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.com/red-future/common/beans"
|
||||||
commonHttp "gitea.com/red-future/common/http"
|
|
||||||
"gitea.com/red-future/common/utils"
|
"gitea.com/red-future/common/utils"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
@@ -18,25 +19,8 @@ var FlowUserService = &flowUserService{}
|
|||||||
|
|
||||||
type flowUserService struct{}
|
type flowUserService struct{}
|
||||||
|
|
||||||
// IsAdmin 调用admin-go服务检查是否是管理员
|
|
||||||
func IsAdmin(ctx context.Context) (res bool, 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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var r = make(map[string]bool)
|
|
||||||
if err = commonHttp.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return r["isSuperAdmin"], err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *flowUserService) Create(ctx context.Context, req *flowDto.CreateFlowUserReq) (res *flowDto.CreateFlowUserRes, err error) {
|
func (s *flowUserService) Create(ctx context.Context, req *flowDto.CreateFlowUserReq) (res *flowDto.CreateFlowUserRes, err error) {
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -57,7 +41,7 @@ func (s *flowUserService) Create(ctx context.Context, req *flowDto.CreateFlowUse
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *flowUserService) Update(ctx context.Context, req *flowDto.UpdateFlowUserReq) (err error) {
|
func (s *flowUserService) Update(ctx context.Context, req *flowDto.UpdateFlowUserReq) (err error) {
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,6 +81,26 @@ func (s *flowUserService) Update(ctx context.Context, req *flowDto.UpdateFlowUse
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExtractFlowNodeFrom(flowContent *entity.FlowInfo) []*entity.FlowNode {
|
func ExtractFlowNodeFrom(flowContent *entity.FlowInfo) []*entity.FlowNode {
|
||||||
|
// 构建每个节点的上游节点映射
|
||||||
|
upstreamMap := make(map[string][]string)
|
||||||
|
for _, edge := range flowContent.Edges {
|
||||||
|
upstreamMap[edge.To] = append(upstreamMap[edge.To], edge.From)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时更新 flowContent.Nodes 中的 DataMerge 节点
|
||||||
|
for i := range flowContent.Nodes {
|
||||||
|
n := &flowContent.Nodes[i]
|
||||||
|
// 对于 DataMerge 节点,自动根据边关系填充 InputSource
|
||||||
|
if n.NodeCode == node.NodeTypeDataMerge {
|
||||||
|
n.InputSource = nil
|
||||||
|
for _, fromId := range upstreamMap[n.Id] {
|
||||||
|
n.InputSource = append(n.InputSource, entity.FlowNodeInputSource{
|
||||||
|
NodeId: fromId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var flowNodes []*entity.FlowNode
|
var flowNodes []*entity.FlowNode
|
||||||
for _, item := range flowContent.Nodes {
|
for _, item := range flowContent.Nodes {
|
||||||
flowNodes = append(flowNodes, &item)
|
flowNodes = append(flowNodes, &item)
|
||||||
@@ -105,7 +109,7 @@ func ExtractFlowNodeFrom(flowContent *entity.FlowInfo) []*entity.FlowNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *flowUserService) Delete(ctx context.Context, req *flowDto.DeleteFlowUserReq) (err error) {
|
func (s *flowUserService) Delete(ctx context.Context, req *flowDto.DeleteFlowUserReq) (err error) {
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -146,7 +150,7 @@ func (s *flowUserService) Get(ctx context.Context, req *flowDto.GetFlowUserReq)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *flowUserService) List(ctx context.Context, req *flowDto.ListFlowUserReq) (res *flowDto.ListFlowRes, err error) {
|
func (s *flowUserService) List(ctx context.Context, req *flowDto.ListFlowUserReq) (res *flowDto.ListFlowRes, err error) {
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.com/red-future/common/db/gfdb"
|
"gitea.com/red-future/common/db/gfdb"
|
||||||
@@ -58,7 +59,6 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
|
|||||||
outputResult = append(outputResult, field)
|
outputResult = append(outputResult, field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contextParts := ""
|
contextParts := ""
|
||||||
for _, v := range nodeInput.Config.FormConfig {
|
for _, v := range nodeInput.Config.FormConfig {
|
||||||
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value)
|
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value)
|
||||||
@@ -68,51 +68,128 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
|
|||||||
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value)
|
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !g.IsEmpty(nodeInput.Global.Desc) {
|
if !g.IsEmpty(nodeInput.Global.Desc) {
|
||||||
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, "描述", nodeInput.Global.Desc)
|
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, "描述", nodeInput.Global.Desc)
|
||||||
}
|
}
|
||||||
configMap := gconv.Map(nodeInput.Config.Config)
|
configMap := gconv.Map(nodeInput.Config.Config)
|
||||||
ids := gconv.Strings(configMap["branch_ids"])
|
ids := gconv.Strings(configMap["branch_ids"])
|
||||||
branchIdNameMap := gconv.Map(configMap["branch_id_name_map"])
|
branchIdNameMap := gconv.Map(configMap["branch_id_name_map"])
|
||||||
|
|
||||||
// 【重构】构建提示词:展示ID和对应的名称
|
|
||||||
var branchIdNameLines []string
|
var branchIdNameLines []string
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
name := gconv.String(branchIdNameMap[id])
|
name := gconv.String(branchIdNameMap[id])
|
||||||
branchIdNameLines = append(branchIdNameLines, fmt.Sprintf("%s: %s", id, name))
|
branchIdNameLines = append(branchIdNameLines, fmt.Sprintf("%s: %s", id, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
getIsChatModel, err := GetIsChatModel(ctx)
|
getIsChatModel, err := GetIsChatModel(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
req := flowDto.ComposeMessagesReq{
|
composeResult, err := GetComposeResult(ctx, 2, getIsChatModel.Model.ModelName, "", "", []map[string]any{{"prompt": strings.Join(branchIdNameLines, "\n")}}, []map[string]any{{"prompt": contextParts}}, nodeInput.Global.FileUrl, nodeInput.Global.SessionId, nodeInput.Config.Id, "判断节点")
|
||||||
BuildType: 2,
|
|
||||||
ModelName: getIsChatModel.ModelName,
|
|
||||||
SkillName: "",
|
|
||||||
Cause: "判断节点",
|
|
||||||
Form: map[string]any{"prompt": strings.Join(branchIdNameLines, "\n")},
|
|
||||||
UserForm: map[string]any{"prompt": contextParts},
|
|
||||||
UserFiles: nodeInput.Global.FileUrl,
|
|
||||||
SessionId: nodeInput.Global.SessionId,
|
|
||||||
}
|
|
||||||
msg, err := ComposeMessages(ctx, &req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if g.IsEmpty(msg.Messages) {
|
if g.IsEmpty(composeResult.TaskId) {
|
||||||
return "", fmt.Errorf("msg is empty")
|
return "", fmt.Errorf("msg is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
content := ""
|
content := ""
|
||||||
for key, _ := range getIsChatModel.ResponseBody {
|
for key, _ := range getIsChatModel.Model.ResponseBody {
|
||||||
content = gconv.String(msg.Messages[key])
|
content = gconv.String(composeResult.Messages.Rounds[0][key])
|
||||||
|
}
|
||||||
|
fmt.Printf("JudgeLambda路由:目标节点ID=%s\n", gconv.String(content))
|
||||||
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("JudgeLambda路由:目标节点ID=%s\n", gconv.String(content))
|
func BatchModelLambda(ctx context.Context, input any) (any, error) {
|
||||||
|
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("入参类型错误")
|
||||||
|
}
|
||||||
|
skillName, from, userFrom := BuildParam(nodeInput)
|
||||||
|
reqMap := make([]map[string]any, 0)
|
||||||
|
for _, userItem := range userFrom {
|
||||||
|
m := gconv.Map(userItem)
|
||||||
|
for _, i := range nodeInput.Config.InputSource {
|
||||||
|
for _, f := range i.Field {
|
||||||
|
val := m[f]
|
||||||
|
if !g.IsEmpty(val) {
|
||||||
|
if g.NewVar(val).IsSlice() {
|
||||||
|
slice := gconv.SliceAny(val)
|
||||||
|
for _, item := range slice {
|
||||||
|
reqMap = append(reqMap, map[string]any{f: item})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reqMap = append(reqMap, map[string]any{f: val})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 结果按索引存放,保证顺序
|
||||||
|
res := make([][]node.NodeFormField, len(reqMap))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
// 用一个通道标记是否完成
|
||||||
|
done := make(chan struct{})
|
||||||
|
// 错误只存一个
|
||||||
|
var execErr error
|
||||||
|
|
||||||
return content, nil
|
// 并发执行
|
||||||
|
for idx, item := range reqMap {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, userItem map[string]any) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
singleUserFrom := []map[string]any{userItem}
|
||||||
|
output, err := TextNode(ctx, nodeInput, skillName, from, singleUserFrom)
|
||||||
|
if err != nil {
|
||||||
|
// 并发安全赋值错误
|
||||||
|
if execErr == nil {
|
||||||
|
execErr = err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接按原索引写,顺序绝对正确
|
||||||
|
res[idx] = output
|
||||||
|
}(idx, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后台等待所有协程完成,然后关闭 done 通道
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 等待全部完成
|
||||||
|
<-done
|
||||||
|
|
||||||
|
// 如果有错误,直接返回
|
||||||
|
if execErr != nil {
|
||||||
|
return nil, execErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局自增 i
|
||||||
|
var globalIndex int
|
||||||
|
var outputRes []node.NodeFormField
|
||||||
|
for _, items := range res {
|
||||||
|
for _, item := range items {
|
||||||
|
// 1. 拿到原来的 Field:例如 "text_content:2:0"
|
||||||
|
oldField := item.Field
|
||||||
|
// 2. 找到最后一个 : 的位置
|
||||||
|
if idx := strings.LastIndex(oldField, ":"); idx != -1 {
|
||||||
|
// 3. 截断前面部分,拼接上新的 globalIndex
|
||||||
|
item.Field = oldField[:idx+1] + fmt.Sprint(globalIndex)
|
||||||
|
}
|
||||||
|
// Label 同理
|
||||||
|
oldLabel := item.Label
|
||||||
|
if idx := strings.LastIndex(oldLabel, ":"); idx != -1 {
|
||||||
|
item.Label = oldLabel[:idx+1] + fmt.Sprint(globalIndex)
|
||||||
|
}
|
||||||
|
outputRes = append(outputRes, item)
|
||||||
|
}
|
||||||
|
globalIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeInput.Config.OutputResult = outputRes
|
||||||
|
return nodeInput, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextModelLambda 构建文案
|
// TextModelLambda 构建文案
|
||||||
@@ -122,7 +199,7 @@ func TextModelLambda(ctx context.Context, input any) (any, error) {
|
|||||||
return nil, fmt.Errorf("入参类型错误")
|
return nil, fmt.Errorf("入参类型错误")
|
||||||
}
|
}
|
||||||
skillName, from, userFrom := BuildParam(nodeInput)
|
skillName, from, userFrom := BuildParam(nodeInput)
|
||||||
outputRes, err := TextNode(ctx, nodeInput.Global.SessionId, nodeInput.Config.ModelConfig.ModelName, skillName, from, userFrom, nodeInput.Config.ModelConfig.ModelResponse, nodeInput.Global.FileUrl)
|
outputRes, err := TextNode(ctx, nodeInput, skillName, from, userFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -137,7 +214,7 @@ func ImageModelLambda(ctx context.Context, input any) (any, error) {
|
|||||||
return nil, fmt.Errorf("入参类型错误")
|
return nil, fmt.Errorf("入参类型错误")
|
||||||
}
|
}
|
||||||
skillName, from, userFrom := BuildParam(nodeInput)
|
skillName, from, userFrom := BuildParam(nodeInput)
|
||||||
outputRes, err := ImgNode(ctx, nodeInput.Global.SessionId, nodeInput.Config.ModelConfig.ModelName, skillName, from, userFrom, nodeInput.Config.ModelConfig.ModelResponse, nodeInput.Global.FileUrl)
|
outputRes, err := ImgNode(ctx, nodeInput, skillName, from, userFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -145,7 +222,213 @@ func ImageModelLambda(ctx context.Context, input any) (any, error) {
|
|||||||
return nodeInput, nil
|
return nodeInput, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeLambda(ctx context.Context, input any) (any, error) {
|
// AudioModelLambda 构建音频
|
||||||
|
func AudioModelLambda(ctx context.Context, input any) (any, error) {
|
||||||
|
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("入参类型错误")
|
||||||
|
}
|
||||||
|
skillName, from, userFrom := BuildParam(nodeInput)
|
||||||
|
outputRes, err := AudioOptimizeNode(ctx, nodeInput, skillName, from, userFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodeInput.Config.OutputResult = outputRes
|
||||||
|
return nodeInput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoModelLambda 构建视频
|
||||||
|
func VideoModelLambda(ctx context.Context, input any) (any, error) {
|
||||||
|
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("入参类型错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
skillName, from, userFrom := BuildParam(nodeInput)
|
||||||
|
res, err := VideoOptimizeNode(ctx, nodeInput, skillName, from, userFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
videoURL := make([]string, 0)
|
||||||
|
for _, v := range res {
|
||||||
|
if strings.Contains(v.Field, "content") {
|
||||||
|
videoURL = append(videoURL, gconv.String(v.Value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if g.IsEmpty(videoURL) {
|
||||||
|
return nil, fmt.Errorf("视频合成失败:模型生成视频失败")
|
||||||
|
}
|
||||||
|
waitRes, err := VideoConcat(ctx, videoURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg := new(flowDto.VideoCallbackReq)
|
||||||
|
if err = gconv.Struct(waitRes, msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
urlPrefix, err := utils.GetFileAddressPrefix(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputRes := make([]node.NodeFormField, 0)
|
||||||
|
if nodeInput.Config.IsSaveFile {
|
||||||
|
outputRes = append(outputRes, node.NodeFormField{
|
||||||
|
Field: fmt.Sprintf("video_oss_url:content:%d", 0),
|
||||||
|
Value: msg.FileURL,
|
||||||
|
Label: fmt.Sprintf("video_oss_url:content:%d", 0),
|
||||||
|
Type: "string",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
outputRes = append(outputRes, node.NodeFormField{
|
||||||
|
Field: fmt.Sprintf("concat_video_url:content:%d", 0),
|
||||||
|
Value: urlPrefix + msg.FileURL,
|
||||||
|
Label: fmt.Sprintf("concat_video_url:content:%d", 0),
|
||||||
|
Type: "string",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
nodeInput.Config.OutputResult = outputRes
|
||||||
|
|
||||||
|
return nodeInput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpLambda 构建HTTP(S)接口
|
||||||
|
func HttpLambda(ctx context.Context, input any) (any, error) {
|
||||||
|
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("入参类型错误")
|
||||||
|
}
|
||||||
|
outputRes := make([]node.NodeFormField, 0)
|
||||||
|
var err error
|
||||||
|
outputRes, err = HttpNode(ctx, nodeInput)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodeInput.Config.OutputResult = outputRes
|
||||||
|
return nodeInput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataConversionLambda 构建数据转换
|
||||||
|
func DataConversionLambda(ctx context.Context, input any) (any, error) {
|
||||||
|
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("入参类型错误")
|
||||||
|
}
|
||||||
|
skillName, from, userFrom := BuildParam(nodeInput)
|
||||||
|
outputRes, err := DataConversionNode(ctx, nodeInput, skillName, from, userFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodeInput.Config.OutputResult = outputRes
|
||||||
|
return nodeInput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DataMergeLambda(ctx context.Context, input any) (res any, err error) {
|
||||||
|
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("参数合并入参类型错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// var nodeIds []string
|
||||||
|
// for _, item := range nodeInput.Config.InputSource {
|
||||||
|
// nodeIds = append(nodeIds, item.NodeId)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 检查是否所有输入节点都执行完成,并且检查是否有节点失败
|
||||||
|
// checkAllExecuted := func() (allExecuted bool, hasFailed bool, failedNode string) {
|
||||||
|
// executedCount := 0
|
||||||
|
// for _, executedNode := range nodeInput.Global.ExecutedNodes {
|
||||||
|
// // 检查是否是我们需要的输入节点,并且它失败了
|
||||||
|
// for _, targetId := range nodeIds {
|
||||||
|
// if executedNode.NodeId == targetId {
|
||||||
|
// if executedNode.Status == node.NodeExecutionStatusFailed.Code() {
|
||||||
|
// return false, true, targetId
|
||||||
|
// }
|
||||||
|
// executedCount++
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return executedCount == len(nodeIds), false, ""
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 初次检查
|
||||||
|
// allExecuted, hasFailed, failedNode := checkAllExecuted()
|
||||||
|
// if hasFailed {
|
||||||
|
// return nil, fmt.Errorf("输入节点[%s]执行失败", failedNode)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 如果不是全部都已执行,阻塞等待直到全部完成、上下文取消或有节点失败
|
||||||
|
// if !allExecuted {
|
||||||
|
// // 轮询检查,每500ms检查一次,依赖ctx超时控制
|
||||||
|
// ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
// defer ticker.Stop()
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// // 如果上下文已经取消,说明已有节点报错,直接退出
|
||||||
|
// return nil, ctx.Err()
|
||||||
|
// case <-ticker.C:
|
||||||
|
// // 重新检查所有节点
|
||||||
|
// allExecuted, hasFailed, failedNode := checkAllExecuted()
|
||||||
|
// if hasFailed {
|
||||||
|
// // 有一个输入节点失败,直接退出
|
||||||
|
// return nil, fmt.Errorf("输入节点[%s]执行失败", failedNode)
|
||||||
|
// }
|
||||||
|
// if allExecuted {
|
||||||
|
// // 全部执行完成,退出循环继续执行
|
||||||
|
// goto allDone
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 再次检查上下文是否已经取消,如果已经取消则立即退出
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return nil, ctx.Err()
|
||||||
|
// default:
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//allDone:
|
||||||
|
//
|
||||||
|
// // 最终检查:所有输入节点都成功了吗
|
||||||
|
// _, hasFailed, failedNode = checkAllExecuted()
|
||||||
|
// if hasFailed {
|
||||||
|
// // 有一个输入节点失败,直接退出
|
||||||
|
// return nil, fmt.Errorf("输入节点[%s]执行失败", failedNode)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 构建已执行节点ID的map,方便合并时查找
|
||||||
|
// executedMap := make(map[string]*flowDto.ExecutedNode, len(nodeInput.Global.ExecutedNodes))
|
||||||
|
// for _, en := range nodeInput.Global.ExecutedNodes {
|
||||||
|
// executedMap[en.NodeId] = &en
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 合并所有输入源节点的输出结果
|
||||||
|
// for _, inputSource := range nodeInput.Config.InputSource {
|
||||||
|
// // 每次循环都检查上下文是否已取消,提前退出
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return nil, ctx.Err()
|
||||||
|
// default:
|
||||||
|
// }
|
||||||
|
// // 再次检查该节点是否失败
|
||||||
|
// if en, ok := executedMap[inputSource.NodeId]; ok && en.Status == node.NodeExecutionStatusFailed.Code() {
|
||||||
|
// return nil, fmt.Errorf("输入节点[%s]执行失败", inputSource.NodeId)
|
||||||
|
// }
|
||||||
|
// sourceNodeConfig := nodeInput.Global.ConfigMap[inputSource.NodeId]
|
||||||
|
// if sourceNodeConfig != nil && len(sourceNodeConfig.OutputResult) > 0 {
|
||||||
|
// nodeInput.Config.OutputResult = append(nodeInput.Config.OutputResult, sourceNodeConfig.OutputResult...)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return nodeInput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeLambda(ctx context.Context, input any) (res any, err error) {
|
||||||
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("汇总节点入参类型错误")
|
return nil, fmt.Errorf("汇总节点入参类型错误")
|
||||||
@@ -155,7 +438,8 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
|
|||||||
dataMap := make(map[string]node.NodeFormField)
|
dataMap := make(map[string]node.NodeFormField)
|
||||||
_, outputMap, _ := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
_, outputMap, _ := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
||||||
for _, valueAny := range outputMap {
|
for _, valueAny := range outputMap {
|
||||||
if field, ok := valueAny.(node.NodeFormField); ok {
|
field := node.NodeFormField{}
|
||||||
|
if field, ok = valueAny.(node.NodeFormField); ok {
|
||||||
dataMap[field.Field] = field
|
dataMap[field.Field] = field
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +447,7 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
|
|||||||
// 2. 提取所有文案:text_content_0,1,2...
|
// 2. 提取所有文案:text_content_0,1,2...
|
||||||
var contents []node.NodeFormField
|
var contents []node.NodeFormField
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
key := fmt.Sprintf("text_url:%d", i)
|
key := fmt.Sprintf("text_content:%d", i)
|
||||||
val, has := dataMap[key]
|
val, has := dataMap[key]
|
||||||
if !has || val.Value == "" {
|
if !has || val.Value == "" {
|
||||||
break
|
break
|
||||||
@@ -179,7 +463,7 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
|
|||||||
if !has || val.Value == "" {
|
if !has || val.Value == "" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
images = append(images, val.Value)
|
images = append(images, gconv.String(val.Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 🔥 核心算法:图片按顺序连续归属给每条文案
|
// 4. 🔥 核心算法:图片按顺序连续归属给每条文案
|
||||||
@@ -232,7 +516,7 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
|
|||||||
if len(contents) > 0 {
|
if len(contents) > 0 {
|
||||||
for i, val := range contents {
|
for i, val := range contents {
|
||||||
item := Item{
|
item := Item{
|
||||||
Content: url + val.Value, // 文案
|
Content: url + gconv.String(val.Value), // 文案
|
||||||
Images: textImgMap[i], // 自动绑定该条目的图片(没有则为空切片)
|
Images: textImgMap[i], // 自动绑定该条目的图片(没有则为空切片)
|
||||||
}
|
}
|
||||||
allItems = append(allItems, item)
|
allItems = append(allItems, item)
|
||||||
@@ -254,12 +538,17 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
|
|||||||
|
|
||||||
// 遍历所有【独立图文条目】 → 每条生成独立HTML、独立上传OSS、独立输出记录
|
// 遍历所有【独立图文条目】 → 每条生成独立HTML、独立上传OSS、独立输出记录
|
||||||
for idx, item := range allItems {
|
for idx, item := range allItems {
|
||||||
// item 结构包含:Content(string) + Images([]string)
|
|
||||||
// 支持任意来源:文生图、图生文、单独文、单独图、文图合并
|
|
||||||
|
|
||||||
// 生成单条HTML
|
// 生成单条HTML
|
||||||
htmlContent := BuildHtml(item.Content, item.Images)
|
htmlContent := BuildHtml(item.Content, item.Images)
|
||||||
|
outputRecords = append(outputRecords,
|
||||||
|
node.NodeFormField{
|
||||||
|
Field: fmt.Sprintf("item_html_%d", idx),
|
||||||
|
Value: htmlContent,
|
||||||
|
Label: fmt.Sprintf("条目%d HTML", idx+1),
|
||||||
|
Type: "textarea",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if nodeInput.Config.IsSaveFile {
|
||||||
// 上传OSS(每条独立上传)
|
// 上传OSS(每条独立上传)
|
||||||
fileName := fmt.Sprintf("item_%d_%d.html", idx, time.Now().UnixMilli())
|
fileName := fmt.Sprintf("item_%d_%d.html", idx, time.Now().UnixMilli())
|
||||||
ossResult, err := Upload(ctx, &dto.UploadFileBytesReq{
|
ossResult, err := Upload(ctx, &dto.UploadFileBytesReq{
|
||||||
@@ -269,36 +558,16 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拼接成一条输出记录
|
|
||||||
// 每条记录包含:HTML内容 + 访问URL + 文案 + 图片列表
|
|
||||||
outputRecords = append(outputRecords,
|
outputRecords = append(outputRecords,
|
||||||
node.NodeFormField{
|
|
||||||
Field: fmt.Sprintf("item_html_%d", idx),
|
|
||||||
Value: htmlContent,
|
|
||||||
Label: fmt.Sprintf("条目%d HTML", idx+1),
|
|
||||||
Type: "textarea",
|
|
||||||
},
|
|
||||||
node.NodeFormField{
|
node.NodeFormField{
|
||||||
Field: fmt.Sprintf("item_html_url_%d", idx),
|
Field: fmt.Sprintf("item_html_url_%d", idx),
|
||||||
Value: ossResult.FileURL,
|
Value: ossResult.FileURL,
|
||||||
Label: fmt.Sprintf("条目%d 地址", idx+1),
|
Label: fmt.Sprintf("条目%d 地址", idx+1),
|
||||||
Type: "text",
|
Type: "text",
|
||||||
},
|
},
|
||||||
node.NodeFormField{
|
|
||||||
Field: fmt.Sprintf("item_txt_url_%d", idx),
|
|
||||||
Value: item.Content,
|
|
||||||
Label: fmt.Sprintf("条目%d 文案", idx+1),
|
|
||||||
Type: "text",
|
|
||||||
},
|
|
||||||
node.NodeFormField{
|
|
||||||
Field: fmt.Sprintf("item_image_url_%d", idx),
|
|
||||||
Value: strings.Join(item.Images, ","),
|
|
||||||
Label: fmt.Sprintf("条目%d 图片", idx+1),
|
|
||||||
Type: "text",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 最终输出多条记录
|
// 最终输出多条记录
|
||||||
nodeInput.Config.OutputResult = outputRecords
|
nodeInput.Config.OutputResult = outputRecords
|
||||||
@@ -313,11 +582,12 @@ func SummaryLambda(ctx context.Context, input any) (any, error) {
|
|||||||
|
|
||||||
// 聚合所有已执行节点的输出结果
|
// 聚合所有已执行节点的输出结果
|
||||||
var summaryResult []map[string]interface{}
|
var summaryResult []map[string]interface{}
|
||||||
for _, nodeID := range execInput.Global.ExecutedNodes {
|
for _, executedNode := range execInput.Global.ExecutedNodes {
|
||||||
|
nodeID := executedNode.NodeId
|
||||||
nodeConfig := execInput.Global.ConfigMap[nodeID]
|
nodeConfig := execInput.Global.ConfigMap[nodeID]
|
||||||
if nodeConfig != nil && len(nodeConfig.OutputResult) > 0 {
|
if nodeConfig != nil && len(nodeConfig.OutputResult) > 0 {
|
||||||
for _, field := range nodeConfig.OutputResult {
|
for _, field := range nodeConfig.OutputResult {
|
||||||
if strings.Contains(field.Field, "item_html_url") || strings.Contains(field.Field, "img_url") || strings.Contains(field.Field, "text_url") {
|
if strings.Contains(field.Field, "http_file_url") || strings.Contains(field.Field, "audio_oss_url") || strings.Contains(field.Field, "video_oss_url") || strings.Contains(field.Field, "item_html_url") || strings.Contains(field.Field, "img_oss_url") || strings.Contains(field.Field, "text_url") {
|
||||||
// 生成 毫秒时间戳 作为 KEY
|
// 生成 毫秒时间戳 作为 KEY
|
||||||
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||||
item := make(map[string]interface{})
|
item := make(map[string]interface{})
|
||||||
@@ -376,18 +646,6 @@ func SummaryLambda(ctx context.Context, input any) (any, error) {
|
|||||||
return execInput, err
|
return execInput, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoModelLambda 构建视频
|
|
||||||
func VideoModelLambda(ctx context.Context, input any) (any, error) {
|
|
||||||
fmt.Println("VideoModelLambda:", input)
|
|
||||||
return input, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AudioModelLambda 构建音频
|
|
||||||
func AudioModelLambda(ctx context.Context, input any) (any, error) {
|
|
||||||
fmt.Println("AudioModelLambda:", input)
|
|
||||||
return input, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomLambda 构建自定义
|
// CustomLambda 构建自定义
|
||||||
func CustomLambda(ctx context.Context, input any) (any, error) {
|
func CustomLambda(ctx context.Context, input any) (any, error) {
|
||||||
fmt.Println("CustomLambda:", input)
|
fmt.Println("CustomLambda:", input)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,74 @@
|
|||||||
package flow
|
package flow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai-agent/workflow/consts/node"
|
||||||
|
nodeDao "ai-agent/workflow/dao/node"
|
||||||
"ai-agent/workflow/model/dto"
|
"ai-agent/workflow/model/dto"
|
||||||
flowDto "ai-agent/workflow/model/dto/flow"
|
flowDto "ai-agent/workflow/model/dto/flow"
|
||||||
|
nodeDto "ai-agent/workflow/model/dto/node"
|
||||||
|
"ai-agent/workflow/model/entity"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
commonHttp "gitea.com/red-future/common/http"
|
commonHttp "gitea.com/red-future/common/http"
|
||||||
"gitea.com/red-future/common/utils"
|
"gitea.com/red-future/common/utils"
|
||||||
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 全局等待任务回调的工具
|
||||||
|
var (
|
||||||
|
asyncMu sync.Mutex
|
||||||
|
asyncTasks = make(map[string]chan any)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wait 阻塞等待回调结果
|
||||||
|
// 调用后会一直卡住,直到 Notify 唤醒 或 超时/取消
|
||||||
|
func Wait(ctx context.Context, taskId string) (any, error) {
|
||||||
|
asyncMu.Lock()
|
||||||
|
ch := make(chan any, 1)
|
||||||
|
asyncTasks[taskId] = ch
|
||||||
|
asyncMu.Unlock()
|
||||||
|
|
||||||
|
defer close(ch)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case result := <-ch:
|
||||||
|
return result, nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
asyncMu.Lock()
|
||||||
|
delete(asyncTasks, taskId)
|
||||||
|
asyncMu.Unlock()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify 回调时调用,唤醒等待的任务
|
||||||
|
func Notify(taskId string, result any) {
|
||||||
|
asyncMu.Lock()
|
||||||
|
defer asyncMu.Unlock()
|
||||||
|
|
||||||
|
ch, exist := asyncTasks[taskId]
|
||||||
|
if !exist {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- result
|
||||||
|
delete(asyncTasks, taskId)
|
||||||
|
}
|
||||||
|
|
||||||
func GetIsChatModel(ctx context.Context) (res *flowDto.GetIsChatModelRes, err error) {
|
func GetIsChatModel(ctx context.Context) (res *flowDto.GetIsChatModelRes, err error) {
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
if r := g.RequestFromCtx(ctx); r != nil {
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||||||
@@ -33,7 +83,7 @@ func GetIsChatModel(ctx context.Context) (res *flowDto.GetIsChatModelRes, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (res *flowDto.ComposeMessagesRes, err error) {
|
func GetModelInfo(ctx context.Context, req *flowDto.GetModelInfoReq) (res *flowDto.GetModelInfoRes, err error) {
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
if r := g.RequestFromCtx(ctx); r != nil {
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||||||
for k, v := range r.Request.Header {
|
for k, v := range r.Request.Header {
|
||||||
@@ -42,58 +92,121 @@ func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res = new(flowDto.ComposeMessagesRes)
|
res = new(flowDto.GetModelInfoRes)
|
||||||
err = commonHttp.Post(ctx, "prompts-core/prompt/composeMessages", headers, res, &req)
|
err = commonHttp.Get(ctx, "model-gateway/model/getModel", headers, res, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetModelResult(ctx context.Context, modelName, skillName string, form, userFrom map[string]any, fileUrl []string, sessionId string, cause string) (mapTaskResult map[string]any, err error) {
|
func GetComposeResult(ctx context.Context, buildType int, modelName, promptContent, skillName string, form []map[string]any, userForm []map[string]any, fileUrl []string, sessionId, nodeId string, cause string) (res *flowDto.ComposeCallbackReq, err error) {
|
||||||
|
if !g.IsEmpty(promptContent) {
|
||||||
|
userForm = append(userForm, map[string]any{
|
||||||
|
"prompt": promptContent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var callbackUrl = utils.GetCallbackURL(ctx, "/flow/execution/composeCallBack")
|
||||||
|
var consult = make([]flowDto.Consult, 0)
|
||||||
|
var collectFileUrls func(val any)
|
||||||
|
collectFileUrls = func(val any) {
|
||||||
|
switch {
|
||||||
|
case g.NewVar(val).IsSlice():
|
||||||
|
slice := gconv.SliceAny(val)
|
||||||
|
for _, item := range slice {
|
||||||
|
collectFileUrls(item)
|
||||||
|
}
|
||||||
|
case g.NewVar(val).IsMap():
|
||||||
|
m := gconv.Map(val)
|
||||||
|
for _, item := range m {
|
||||||
|
collectFileUrls(item)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s := gconv.String(val)
|
||||||
|
if s != "" {
|
||||||
|
getFileTypeByPath := GetFileTypeByPath(s)
|
||||||
|
if getFileTypeByPath != "" {
|
||||||
|
consult = append(consult, flowDto.Consult{
|
||||||
|
Type: getFileTypeByPath,
|
||||||
|
Url: s,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range userForm {
|
||||||
|
for _, v := range gconv.Map(m) {
|
||||||
|
collectFileUrls(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range fileUrl {
|
||||||
|
getFileTypeByPath := GetFileTypeByPath(gconv.String(v))
|
||||||
|
if getFileTypeByPath != "" {
|
||||||
|
consult = append(consult, flowDto.Consult{
|
||||||
|
Type: getFileTypeByPath,
|
||||||
|
Url: gconv.String(v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
msgReq := flowDto.ComposeMessagesReq{
|
msgReq := flowDto.ComposeMessagesReq{
|
||||||
BuildType: 1,
|
BuildType: buildType,
|
||||||
ModelName: modelName,
|
ModelName: modelName,
|
||||||
SkillName: skillName,
|
SkillName: skillName,
|
||||||
|
CallbackUrl: callbackUrl,
|
||||||
Cause: cause,
|
Cause: cause,
|
||||||
Form: form,
|
Form: form,
|
||||||
UserForm: userFrom,
|
UserForm: userForm,
|
||||||
UserFiles: fileUrl,
|
Consult: consult,
|
||||||
SessionId: sessionId,
|
SessionId: sessionId,
|
||||||
|
NodeId: nodeId,
|
||||||
}
|
}
|
||||||
msg, err := ComposeMessages(ctx, &msgReq)
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msgRes := new(flowDto.ComposeMessagesRes)
|
||||||
|
err = commonHttp.Post(ctx, "prompts-core/prompt/composeMessages", headers, msgRes, &msgReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if g.IsEmpty(msg.Messages) {
|
if g.IsEmpty(msgRes.TaskId) {
|
||||||
return nil, fmt.Errorf("msg is empty")
|
return nil, fmt.Errorf("msg is empty")
|
||||||
}
|
}
|
||||||
var taskResult any
|
waitRes, err := Wait(ctx, msgRes.TaskId)
|
||||||
taskResult, err = GatewayTask(ctx, msg.EpicycleId, modelName, msg.Messages)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var getTaskResult *flowDto.TaskCallback
|
|
||||||
getTaskResult, err = GetTaskResult(ctx, taskResult)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mapTaskResult = gconv.Map(getTaskResult.Text)
|
|
||||||
return mapTaskResult, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GatewayTask(ctx context.Context, epicycleId int64, model string, content map[string]any) (any, error) {
|
|
||||||
modelTaskId, err := CreateGatewayTask(ctx, &flowDto.CreateTaskReq{
|
|
||||||
ModelName: model,
|
|
||||||
BizName: g.Cfg().MustGet(ctx, "server.name").String(),
|
|
||||||
CallbackUrl: "/flow/execution/modelCallback",
|
|
||||||
RequestPayload: content,
|
|
||||||
EpicycleId: epicycleId,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return Wait(ctx, modelTaskId)
|
msg := new(flowDto.ComposeCallbackReq)
|
||||||
|
if err = gconv.Struct(waitRes, msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !g.IsEmpty(msg.ErrorMsg) {
|
||||||
|
return nil, fmt.Errorf(msg.ErrorMsg)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateGatewayTask(ctx context.Context, epicycleId int64, model string, content map[string]any) (map[string]any, error) {
|
||||||
|
taskId, err := createGatewayTaskOnly(ctx, epicycleId, model, content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return waitGatewayResult(ctx, taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createGatewayTaskOnly creates a gateway task and returns the taskId only
|
||||||
|
// doesn't wait for completion
|
||||||
|
func createGatewayTaskOnly(ctx context.Context, epicycleId int64, model string, content map[string]any) (string, error) {
|
||||||
|
callbackUrl := utils.GetCallbackURL(ctx, "/flow/execution/modelCallback")
|
||||||
|
req := flowDto.ModelGatewayReq{
|
||||||
|
ModelName: model,
|
||||||
|
BizName: g.Cfg().MustGet(ctx, "server.name").String(),
|
||||||
|
CallbackUrl: callbackUrl,
|
||||||
|
RequestPayload: content,
|
||||||
|
EpicycleId: epicycleId,
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string, error) {
|
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
if r := g.RequestFromCtx(ctx); r != nil {
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||||||
for k, v := range r.Request.Header {
|
for k, v := range r.Request.Header {
|
||||||
@@ -102,69 +215,280 @@ func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res := new(flowDto.CreateTaskRes)
|
|
||||||
|
res := new(flowDto.ModelGatewayRes)
|
||||||
err := commonHttp.Post(ctx, "model-gateway/task/createTask", headers, res, &req)
|
err := commonHttp.Post(ctx, "model-gateway/task/createTask", headers, res, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if g.IsEmpty(res.TaskId) {
|
||||||
|
return "", fmt.Errorf("创建模型任务失败,taskId为空")
|
||||||
|
}
|
||||||
|
|
||||||
return res.TaskId, nil
|
return res.TaskId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTaskResult(ctx context.Context, result any) (*flowDto.TaskCallback, error) {
|
// waitGatewayResult waits for a created gateway task to complete and returns the result
|
||||||
task := new(flowDto.TaskCallback)
|
func waitGatewayResult(ctx context.Context, taskId string) (map[string]any, error) {
|
||||||
if err := gconv.Struct(result, task); err != nil {
|
waitRes, err := Wait(ctx, taskId)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := utils.GetFileAddressPrefix(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取远程文件内容
|
task := new(flowDto.ModelCallbackReq)
|
||||||
file, err := FetchRemoteJsonFile(ctx, url+task.OssFile)
|
if err := gconv.Struct(waitRes, task); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if task.State == 3 || !g.IsEmpty(task.ErrorMsg) {
|
||||||
|
return nil, fmt.Errorf("模型执行失败:%s", task.ErrorMsg)
|
||||||
|
}
|
||||||
|
if g.IsEmpty(task.Messages) {
|
||||||
|
return nil, fmt.Errorf("模型返回结果为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
return task.Messages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateTokenCount updates the token count in node execution
|
||||||
|
func updateTokenCount(ctx context.Context, nodeExecutionId int64, responseField string, result map[string]any) {
|
||||||
|
if responseField == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = nodeDao.NodeExecutionDao.Update(ctx, &nodeDto.UpdateNodeExecutionReq{
|
||||||
|
Id: nodeExecutionId,
|
||||||
|
CompletionTokens: gconv.Int(result[responseField]),
|
||||||
|
TotalTokens: gconv.Int(result[responseField]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.NodeExecutionInput, skillName string, form []map[string]any, userForm []map[string]any) (mapTaskResult []map[string]any, err error) {
|
||||||
|
buildType := 1
|
||||||
|
if nodeInput.Config.NodeCode == node.NodeTypeDataConversionModel {
|
||||||
|
buildType = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
composeResult, err := GetComposeResult(ctx, buildType, nodeInput.Config.ModelConfig.ModelName, nodeInput.Config.PromptContent, skillName, form, userForm, nodeInput.Global.FileUrl, sessionId, nodeInput.Config.Id, nodeInput.Config.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
task.Text = gconv.String(file)
|
modelInfo, err := GetModelInfo(ctx, &flowDto.GetModelInfoReq{ModelName: nodeInput.Config.ModelConfig.ModelName})
|
||||||
|
if err != nil {
|
||||||
return task, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchRemoteJsonFile(ctx context.Context, fileUrl string) ([]byte, error) {
|
mapTaskResult = make([]map[string]any, len(composeResult.Messages.Rounds))
|
||||||
// 1. 下载文件
|
var taskResultMap map[string]any
|
||||||
|
|
||||||
|
needSequential := false
|
||||||
|
if buildType == 1 {
|
||||||
|
if needSequential {
|
||||||
|
for idx, item := range composeResult.Messages.Rounds {
|
||||||
|
if !g.IsEmpty(taskResultMap) {
|
||||||
|
var set string
|
||||||
|
set, err = sjson.Set(gconv.String(item), modelInfo.Model.LastFrame, gconv.String(taskResultMap[modelInfo.Model.ResponseBody]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item = gconv.Map(set)
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskResult map[string]any
|
||||||
|
taskResult, err = CreateGatewayTask(ctx, composeResult.EpicycleId, nodeInput.Config.ModelConfig.ModelName, item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if g.IsEmpty(taskResult) {
|
||||||
|
return nil, fmt.Errorf("模型返回结果为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update taskResultMap for next round (used by VideoModel)
|
||||||
|
if nodeInput.Config.NodeCode == node.NodeTypeVideoModel {
|
||||||
|
ext := GetFileTypeByPath(gconv.String(taskResult[modelInfo.Model.ResponseBody]))
|
||||||
|
if ext == "image" {
|
||||||
|
taskResultMap = taskResult
|
||||||
|
} else {
|
||||||
|
taskResultMap = make(map[string]any)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
taskResultMap = make(map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapTaskResult[idx] = taskResult
|
||||||
|
updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, taskResult)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
taskIdList := make([]string, len(composeResult.Messages.Rounds))
|
||||||
|
|
||||||
|
for idx, item := range composeResult.Messages.Rounds {
|
||||||
|
var taskId string
|
||||||
|
taskId, err = createGatewayTaskOnly(ctx, composeResult.EpicycleId, nodeInput.Config.ModelConfig.ModelName, item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
taskIdList[idx] = taskId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Wait for all tasks in parallel
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errChan := make(chan error, len(taskIdList))
|
||||||
|
|
||||||
|
for idx, taskId := range taskIdList {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
// Pass idx and taskId as parameters to avoid loop variable capture bug
|
||||||
|
// This guarantees results are stored in the correct order matching original requests
|
||||||
|
go func(idx int, taskId string) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var taskResult map[string]any
|
||||||
|
taskResult, err = waitGatewayResult(ctx, taskId)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mapTaskResult[idx] = taskResult
|
||||||
|
updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, taskResult)
|
||||||
|
}(idx, taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(errChan)
|
||||||
|
|
||||||
|
if len(errChan) > 0 {
|
||||||
|
return nil, <-errChan
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for idx, item := range composeResult.Messages.Rounds {
|
||||||
|
mapTaskResult[idx] = item
|
||||||
|
updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapTaskResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildNestedJson(body g.Map, mockConfigMap map[string]*entity.FlowNode) g.Map {
|
||||||
|
jsonStr := "{}"
|
||||||
|
for originKey, originItem := range body {
|
||||||
|
bodyItemMap := gconv.Map(originItem)
|
||||||
|
val := bodyItemMap["value"]
|
||||||
|
if v, ok := bodyItemMap["value"]; ok {
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, originKey, v)
|
||||||
|
}
|
||||||
|
// 判断 value 是不是引用结构(map)
|
||||||
|
if g.NewVar(val).IsMap() {
|
||||||
|
valMap := gconv.Map(val)
|
||||||
|
nodeId := gconv.String(valMap["nodeId"])
|
||||||
|
fieldName := gconv.String(valMap["field"])
|
||||||
|
if configValue, ok := mockConfigMap[nodeId]; ok {
|
||||||
|
if !g.IsEmpty(configValue.OutputResult) {
|
||||||
|
for _, v := range configValue.OutputResult {
|
||||||
|
if strings.Contains(v.Field, fieldName) {
|
||||||
|
if configValue.NodeCode == node.NodeTypeDataConversionModel {
|
||||||
|
switch {
|
||||||
|
case g.NewVar(v.Value).IsSlice() || g.NewVar(v.Value).IsMap():
|
||||||
|
// 核心:自动判断两种结构,精准赋值
|
||||||
|
vm := gconv.Map(v.Value)
|
||||||
|
// 先判断是否是 单个key包裹的对象(如 {"subtitle_style": {...}})
|
||||||
|
if len(vm) == 1 {
|
||||||
|
// 遍历取出唯一的 key 和 真实值
|
||||||
|
for innerKey, innerVal := range vm {
|
||||||
|
// 直接用 innerKey(subtitle_style)赋值
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, innerKey, innerVal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 直接是对象,用 originKey 赋值
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, originKey, v.Value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, originKey, v.Value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, originKey, v.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !g.IsEmpty(configValue.FormConfig) {
|
||||||
|
for _, v := range configValue.FormConfig {
|
||||||
|
if v.Field == fieldName {
|
||||||
|
if v.Type == "uploadMultiple" {
|
||||||
|
if g.NewVar(v.FieldConstraint).IsMap() {
|
||||||
|
mapFieldConstraint := gconv.Map(v.FieldConstraint)
|
||||||
|
for key, value := range mapFieldConstraint {
|
||||||
|
if key == "maxFileCount" {
|
||||||
|
if gconv.Int(value) == 1 {
|
||||||
|
// 如果是单文件上传,则替换成字符串重新赋值给v.Value
|
||||||
|
if g.NewVar(v.Value).IsSlice() {
|
||||||
|
sliceVal := gconv.SliceAny(v.Value)
|
||||||
|
if len(sliceVal) > 0 {
|
||||||
|
v.Value = sliceVal[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonStr, _ = sjson.Set(jsonStr, originKey, v.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gconv.Map(jsonStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func VideoConcat(ctx context.Context, videoUrls []string) (r any, err error) {
|
||||||
|
var httpUrl = "media/video/concat/async"
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var callbackUrl = utils.GetCallbackURL(ctx, "/flow/execution/videoCallback")
|
||||||
|
var newBody = flowDto.VideoConcatReq{
|
||||||
|
VideoUrls: videoUrls,
|
||||||
|
Method: "auto",
|
||||||
|
Upload: true,
|
||||||
|
CallbackUrl: callbackUrl,
|
||||||
|
}
|
||||||
|
res := new(flowDto.VideoConcatRes)
|
||||||
|
err = commonHttp.Post(ctx, httpUrl, headers, &res, newBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return Wait(ctx, res.TaskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileBytesFromURL(ctx context.Context, fileUrl string) ([]byte, error) {
|
||||||
|
// 使用 GoFrame 客户端(自带超时、追踪、日志等能力)
|
||||||
resp, err := g.Client().Get(ctx, fileUrl)
|
resp, err := g.Client().Get(ctx, fileUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get file failed: %w", err)
|
return nil, gerror.Wrapf(err, "failed to request url: %s", fileUrl)
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
|
|
||||||
|
// 校验状态码
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("http status error: %d", resp.StatusCode)
|
return nil, gerror.Newf("request failed with status code: %d, url: %s", resp.StatusCode, fileUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
// 读取全部内容
|
||||||
|
allBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, gerror.Wrapf(err, "failed to read response body, url: %s", fileUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFileBytesFromURL(url string) (all []byte, err error) {
|
return allBytes, nil
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("请求失败 %s: %v", url, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
fmt.Printf("请求失败,状态码: %d\n", resp.StatusCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
all, err = io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("读取内容失败 %s: %v", url, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBytesRes, error) {
|
func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBytesRes, error) {
|
||||||
@@ -192,8 +516,8 @@ func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBy
|
|||||||
|
|
||||||
// 发起上传请求
|
// 发起上传请求
|
||||||
res := &dto.UploadFileBytesRes{}
|
res := &dto.UploadFileBytesRes{}
|
||||||
url := "oss/file/uploadFile"
|
httpUrl := "oss/file/uploadFile"
|
||||||
if err = commonHttp.Post(ctx, url, headers, res, body.Bytes()); err != nil {
|
if err = commonHttp.Post(ctx, httpUrl, headers, res, body.Bytes()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +525,40 @@ func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBy
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetFileTypeByPath(filePath string) string {
|
||||||
|
if filePath == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 URL,获取真实路径(兼容 http 链接)
|
||||||
|
u, err := url.Parse(filePath)
|
||||||
|
if err == nil {
|
||||||
|
filePath = u.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取后缀(小写)
|
||||||
|
ext := filepath.Ext(filePath)
|
||||||
|
ext = strings.ToLower(ext)
|
||||||
|
|
||||||
|
// 判断类型
|
||||||
|
switch ext {
|
||||||
|
case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp":
|
||||||
|
return "image"
|
||||||
|
case ".mp4", ".mov", ".avi", ".flv", ".wmv", ".mkv":
|
||||||
|
return "video"
|
||||||
|
case ".mp3", ".wav", ".m4a", ".flac", ".aac", ".ogg":
|
||||||
|
return "audio"
|
||||||
|
case ".txt", ".md", ".log", ".json", ".xml", ".inc":
|
||||||
|
return "text"
|
||||||
|
case ".html":
|
||||||
|
return "html"
|
||||||
|
case ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx":
|
||||||
|
return "document"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BuildText(text string) string {
|
func BuildText(text string) string {
|
||||||
// 生成单条HTML
|
// 生成单条HTML
|
||||||
var htmlBuilder strings.Builder
|
var htmlBuilder strings.Builder
|
||||||
@@ -457,8 +815,8 @@ func SplitMultiContents(htmlContent string) []string {
|
|||||||
func GetAllImgSrcFromHtml(html string) []string {
|
func GetAllImgSrcFromHtml(html string) []string {
|
||||||
var imgSrcList []string
|
var imgSrcList []string
|
||||||
re := regexp.MustCompile(`<img[^>]*src\s*=\s*["']([^"']+)["']`)
|
re := regexp.MustCompile(`<img[^>]*src\s*=\s*["']([^"']+)["']`)
|
||||||
matchs := re.FindAllStringSubmatch(html, -1)
|
submatch := re.FindAllStringSubmatch(html, -1)
|
||||||
for _, match := range matchs {
|
for _, match := range submatch {
|
||||||
if len(match) >= 2 {
|
if len(match) >= 2 {
|
||||||
imgSrcList = append(imgSrcList, match[1])
|
imgSrcList = append(imgSrcList, match[1])
|
||||||
}
|
}
|
||||||
@@ -468,7 +826,7 @@ func GetAllImgSrcFromHtml(html string) []string {
|
|||||||
|
|
||||||
// ReplaceImgSrc 替换img src的方法
|
// ReplaceImgSrc 替换img src的方法
|
||||||
func ReplaceImgSrc(html string, oldSrc string, newSrc string) string {
|
func ReplaceImgSrc(html string, oldSrc string, newSrc string) string {
|
||||||
// 精准替换:找到 <img xxx src="oldSrc" xxx> 并替换
|
// 精准替换:找到 <img xxx src="oldSrc" xxx>
|
||||||
re := regexp.MustCompile(`(<img[^>]*src\s*=\s*["'])` + regexp.QuoteMeta(oldSrc) + `(["'])`)
|
re := regexp.MustCompile(`(<img[^>]*src\s*=\s*["'])` + regexp.QuoteMeta(oldSrc) + `(["'])`)
|
||||||
return re.ReplaceAllString(html, `${1}`+newSrc+`${2}`)
|
return re.ReplaceAllString(html, `${1}`+newSrc+`${2}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"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/encoding/gjson"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
)
|
)
|
||||||
@@ -15,8 +16,28 @@ var NodeLibraryService = &nodeLibraryService{}
|
|||||||
|
|
||||||
type nodeLibraryService struct{}
|
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{
|
WorkflowNodeGroups := []node.NodeGroupItem{
|
||||||
{
|
{
|
||||||
Group: node.NodeGroupComponent,
|
Group: node.NodeGroupComponent,
|
||||||
@@ -26,24 +47,91 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
|||||||
NodeCode: node.NodeTypeTextModel,
|
NodeCode: node.NodeTypeTextModel,
|
||||||
NodeName: node.NodeNameTextModel,
|
NodeName: node.NodeNameTextModel,
|
||||||
ModelType: node.ModelTypeText,
|
ModelType: node.ModelTypeText,
|
||||||
SkillOption: true,
|
SkillOption: false,
|
||||||
FormConfig: []node.NodeFormField{}, // 技能下拉
|
PromptOption: true,
|
||||||
|
IsSaveFile: true,
|
||||||
|
FormConfig: []node.NodeFormField{},
|
||||||
ModelConfig: []node.ModelItem{},
|
ModelConfig: []node.ModelItem{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NodeCode: node.NodeTypeImageModel,
|
NodeCode: node.NodeTypeImageModel,
|
||||||
NodeName: node.NodeNameImageModel,
|
NodeName: node.NodeNameImageModel,
|
||||||
ModelType: node.ModelTypeImage,
|
ModelType: node.ModelTypeImage,
|
||||||
SkillOption: true,
|
SkillOption: false,
|
||||||
FormConfig: []node.NodeFormField{}, // 技能下拉
|
PromptOption: true,
|
||||||
|
IsSaveFile: true,
|
||||||
|
FormConfig: []node.NodeFormField{},
|
||||||
ModelConfig: []node.ModelItem{},
|
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,
|
Group: node.NodeGroupBase,
|
||||||
Label: node.NodeGroupNameBase,
|
Label: node.NodeGroupNameBase,
|
||||||
Items: []node.NodeItem{
|
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,
|
NodeCode: node.NodeTypeMerge,
|
||||||
NodeName: node.NodeNameMerge,
|
NodeName: node.NodeNameMerge,
|
||||||
@@ -51,6 +139,13 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
|||||||
FormConfig: []node.NodeFormField{},
|
FormConfig: []node.NodeFormField{},
|
||||||
ModelConfig: []node.ModelItem{},
|
ModelConfig: []node.ModelItem{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
NodeCode: node.NodeTypeDataMerge,
|
||||||
|
NodeName: node.NodeNameDataMerge,
|
||||||
|
SkillOption: false,
|
||||||
|
FormConfig: []node.NodeFormField{},
|
||||||
|
ModelConfig: []node.ModelItem{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
NodeCode: node.NodeTypeJudge,
|
NodeCode: node.NodeTypeJudge,
|
||||||
NodeName: node.NodeNameJudge,
|
NodeName: node.NodeNameJudge,
|
||||||
@@ -67,6 +162,161 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
|||||||
FormConfig: []node.NodeFormField{},
|
FormConfig: []node.NodeFormField{},
|
||||||
ModelConfig: []node.ModelItem{},
|
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,
|
// NodeCode: node.NodeTypeModel,
|
||||||
// NodeName: node.NodeNameModel,
|
// NodeName: node.NodeNameModel,
|
||||||
@@ -76,22 +326,22 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
|||||||
//},
|
//},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
//{
|
||||||
Group: node.NodeGroupCustom,
|
// Group: node.NodeGroupCustom,
|
||||||
Label: node.NodeGroupNameCustom,
|
// Label: node.NodeGroupNameCustom,
|
||||||
Items: []node.NodeItem{
|
// Items: []node.NodeItem{
|
||||||
{
|
// {
|
||||||
NodeCode: node.NodeTypeCustomNode,
|
// NodeCode: node.NodeTypeCustomNode,
|
||||||
NodeName: node.NodeNameCustomNode,
|
// NodeName: node.NodeNameCustomNode,
|
||||||
SkillOption: true,
|
// SkillOption: true,
|
||||||
FormConfig: []node.NodeFormField{
|
// FormConfig: []node.NodeFormField{
|
||||||
{Field: "nodeName", Label: node.FormLabelApiKey, Type: "input", Required: true},
|
// {Field: "nodeName", Label: node.FormLabelApiKey, Type: "input", Required: true},
|
||||||
{Field: "nodeType", Label: node.FormLabelModel, Type: "input", Required: true},
|
// {Field: "nodeType", Label: node.FormLabelModel, Type: "input", Required: true},
|
||||||
},
|
// },
|
||||||
ModelConfig: []node.ModelItem{},
|
// ModelConfig: []node.ModelItem{},
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
},
|
//},
|
||||||
}
|
}
|
||||||
tree := &nodeDto.WorkflowNodeTreeRes{
|
tree := &nodeDto.WorkflowNodeTreeRes{
|
||||||
Groups: WorkflowNodeGroups,
|
Groups: WorkflowNodeGroups,
|
||||||
@@ -104,17 +354,19 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
|||||||
// 遍历分组下的每个节点
|
// 遍历分组下的每个节点
|
||||||
for itemIdx := range group.Items {
|
for itemIdx := range group.Items {
|
||||||
item := &group.Items[itemIdx]
|
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{
|
item.ModelConfig = append(item.ModelConfig, node.ModelItem{
|
||||||
ModelName: "自定义",
|
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
|
||||||
|
}
|
||||||
@@ -4,13 +4,13 @@ import (
|
|||||||
skillDao "ai-agent/workflow/dao/skill"
|
skillDao "ai-agent/workflow/dao/skill"
|
||||||
skillDto "ai-agent/workflow/model/dto/skill"
|
skillDto "ai-agent/workflow/model/dto/skill"
|
||||||
"ai-agent/workflow/model/entity"
|
"ai-agent/workflow/model/entity"
|
||||||
|
"ai-agent/workflow/service"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.com/red-future/common/beans"
|
"gitea.com/red-future/common/beans"
|
||||||
commonHttp "gitea.com/red-future/common/http"
|
|
||||||
"gitea.com/red-future/common/utils"
|
"gitea.com/red-future/common/utils"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
@@ -20,29 +20,12 @@ var SkillUserService = &skillUserService{}
|
|||||||
|
|
||||||
type skillUserService struct{}
|
type skillUserService struct{}
|
||||||
|
|
||||||
// IsAdmin 调用admin-go服务检查是否是管理员
|
|
||||||
func IsAdmin(ctx context.Context) (res bool, 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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var r = make(map[string]bool)
|
|
||||||
if err = commonHttp.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return r["isSuperAdmin"], err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *skillUserService) Create(ctx context.Context, req *skillDto.CreateSkillUserReq) (res *skillDto.CreateSkillUserRes, err error) {
|
func (s *skillUserService) Create(ctx context.Context, req *skillDto.CreateSkillUserReq) (res *skillDto.CreateSkillUserRes, err error) {
|
||||||
ext := strings.TrimPrefix(filepath.Ext(req.FileUrl), ".")
|
ext := strings.TrimPrefix(filepath.Ext(req.FileUrl), ".")
|
||||||
if ext != "zip" {
|
if ext != "zip" {
|
||||||
return nil, fmt.Errorf("文件格式不支持,请上传zip格式文件")
|
return nil, fmt.Errorf("文件格式不支持,请上传zip格式文件")
|
||||||
}
|
}
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -91,7 +74,7 @@ func (s *skillUserService) Update(ctx context.Context, req *skillDto.UpdateSkill
|
|||||||
if ext != "zip" {
|
if ext != "zip" {
|
||||||
return fmt.Errorf("文件格式不支持,请上传zip格式文件")
|
return fmt.Errorf("文件格式不支持,请上传zip格式文件")
|
||||||
}
|
}
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -138,7 +121,7 @@ func (s *skillUserService) Update(ctx context.Context, req *skillDto.UpdateSkill
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *skillUserService) Delete(ctx context.Context, req *skillDto.DeleteSkillUserReq) (err error) {
|
func (s *skillUserService) Delete(ctx context.Context, req *skillDto.DeleteSkillUserReq) (err error) {
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -153,7 +136,7 @@ func (s *skillUserService) Delete(ctx context.Context, req *skillDto.DeleteSkill
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *skillUserService) Get(ctx context.Context, req *skillDto.GetSkillUserReq) (res *skillDto.SkillUserVO, err error) {
|
func (s *skillUserService) Get(ctx context.Context, req *skillDto.GetSkillUserReq) (res *skillDto.SkillUserVO, err error) {
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -196,7 +179,7 @@ func (s *skillUserService) Get(ctx context.Context, req *skillDto.GetSkillUserRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *skillUserService) List(ctx context.Context, req *skillDto.ListSkillReq) (res *skillDto.ListSkillUserRes, err error) {
|
func (s *skillUserService) List(ctx context.Context, req *skillDto.ListSkillReq) (res *skillDto.ListSkillUserRes, err error) {
|
||||||
admin, err := IsAdmin(ctx)
|
admin, err := service.UtilService.IsAdmin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -280,7 +263,7 @@ func (s *skillUserService) GetUserOrTemplate(ctx context.Context, req *skillDto.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Creator = user.UserName
|
req.Creator = user.UserName
|
||||||
var userList *entity.SkillUser
|
userList := new(entity.SkillUser)
|
||||||
userList, err = skillDao.SkillUserDao.Get(ctx, &skillDto.GetSkillUserReq{
|
userList, err = skillDao.SkillUserDao.Get(ctx, &skillDto.GetSkillUserReq{
|
||||||
Id: req.Id,
|
Id: req.Id,
|
||||||
Creator: user.UserName,
|
Creator: user.UserName,
|
||||||
|
|||||||
29
workflow/service/util_service.go
Normal file
29
workflow/service/util_service.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
commonHttp "gitea.com/red-future/common/http"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
var UtilService = &utilService{}
|
||||||
|
|
||||||
|
type utilService struct{}
|
||||||
|
|
||||||
|
// IsAdmin 调用admin-go服务检查是否是管理员
|
||||||
|
func (s *utilService) IsAdmin(ctx context.Context) (res bool, 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var r = make(map[string]bool)
|
||||||
|
if err = commonHttp.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return r["isSuperAdmin"], err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user