12 Commits

Author SHA1 Message Date
qhd
695c00aed5 refactor: 重构工作流执行图构建与节点上下文处理 2026-06-16 13:26:27 +08:00
qhd
6a249af7fa fix: 移除优化类节点多余的自定义模型配置 2026-06-16 13:08:41 +08:00
qhd
a6e98914e8 chore: 更新依赖包导入路径 2026-06-16 13:05:40 +08:00
qhd
fba7d032ae feat: 将节点输入输出参数上传至OSS存储 2026-06-12 11:02:36 +08:00
qhd
ddc4e0be63 fix: 移除已消费的文件URL并优化递归收集逻辑 2026-06-11 16:49:11 +08:00
qhd
73dd18baf4 build: 更新common模块依赖仓库地址 2026-06-11 09:16:53 +08:00
qhd
03c95c3601 feat: 新增主动拉取与多类型回调功能
- 新增 ActivePull 实体、DAO、DTO 及 Service,支持主动拉取任务管理
- 新增 ComposeCallback、VideoCallback、HttpNodeCallback 多类型回调接口
- FlowExecution 增加 NodeGroupId 和 TotalTokens 字段,支持节点组追踪与 Token 统计
- ExecutedNodes 结构由字符串列表改为包含执行状态的节点对象列表
- 重构回调通知机制,统一 Notify 函数调用
- 优化输出项类型判断逻辑,新增文件类型标识
2026-06-10 14:23:55 +08:00
ab3a2d967e ci/cd调整 2026-06-08 13:39:20 +08:00
qhd
34c5eeaf63 feat: 查询流程用户列表时自动填充创建人 2026-05-20 13:11:59 +08:00
qhd
de55c16734 refactor: 重构工作流执行逻辑并提取单模型调用 2026-05-18 18:58:46 +08:00
qhd
1fbed2febd refactor: 重构工作流执行逻辑并提取单模型调用 2026-05-18 18:58:04 +08:00
qhd
d5206df131 feat: 添加对话式工作流节点执行与结果合并逻辑 2026-05-15 18:30:44 +08:00
79 changed files with 4190 additions and 1287 deletions

View File

@@ -1,5 +1,5 @@
# 阶段1: 构建
FROM golang:1.26-alpine AS builder
FROM golang:alpine AS builder
RUN apk add --no-cache git ca-certificates tzdata

View File

@@ -5,7 +5,7 @@ import (
"ai-agent/digital-human/service"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -5,7 +5,7 @@ import (
"ai-agent/digital-human/service"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -5,7 +5,7 @@ import (
"ai-agent/digital-human/service"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type digitalhuman struct{}

View File

@@ -5,7 +5,7 @@ import (
"ai-agent/digital-human/service"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type video struct{}

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/digital-human/consts/public"
"ai-agent/digital-human/model/entity"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
)

View File

@@ -7,7 +7,7 @@ import (
"ai-agent/digital-human/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.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"

View File

@@ -6,8 +6,8 @@ import (
"ai-agent/digital-human/model/entity"
"context"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -7,7 +7,7 @@ import (
"ai-agent/digital-human/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.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"

View File

@@ -7,7 +7,7 @@ import (
"ai-agent/digital-human/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.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"

View File

@@ -3,7 +3,7 @@ package dto
import (
"ai-agent/digital-human/consts"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -1,7 +1,7 @@
package dto
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -3,7 +3,7 @@ package dto
import (
"ai-agent/digital-human/consts"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -3,7 +3,7 @@ package dto
import (
"ai-agent/digital-human/consts"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -1,7 +1,7 @@
package entity
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type asyncTaskRefCol struct {

View File

@@ -3,7 +3,7 @@ package entity
import (
"ai-agent/digital-human/consts"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type audioCol struct {

View File

@@ -1,7 +1,7 @@
package entity
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type customVoiceCol struct {

View File

@@ -3,7 +3,7 @@ package entity
import (
"ai-agent/digital-human/consts"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type digitalHumanCol struct {

View File

@@ -3,7 +3,7 @@ package entity
import (
"ai-agent/digital-human/consts"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type videoCol struct {

View File

@@ -13,8 +13,8 @@ import (
"sync"
"time"
commonHttp "gitea.com/red-future/common/http"
"gitea.com/red-future/common/utils"
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)

View File

@@ -175,7 +175,7 @@ EOF
#### 创建 Dockerfile
```bash
cat > Dockerfile << 'EOF'
cat > Dockerfile.bak << 'EOF'
FROM python:3.12-slim
WORKDIR /app

29
go.mod
View File

@@ -3,17 +3,18 @@ module ai-agent
go 1.26.0
require (
gitea.com/red-future/common v0.0.19
github.com/cloudwego/eino v0.8.13
gitea.redpowerfuture.com/red-future/common v0.0.23
github.com/cloudwego/eino v0.9.5
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9
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/v2 v2.10.0
go.opentelemetry.io/otel/trace v1.38.0
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2
github.com/gogf/gf/v2 v2.10.2
github.com/google/uuid v1.6.0
github.com/tidwall/gjson v1.19.0
github.com/tidwall/sjson v1.2.5
go.opentelemetry.io/otel/trace v1.44.0
)
//replace gitea.com/red-future/common => ../common
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
@@ -28,6 +29,7 @@ require (
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudwego/base64x v0.1.6 // 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/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@@ -49,7 +51,6 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // 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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
@@ -83,11 +84,14 @@ require (
github.com/olekukonko/tablewriter v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.9 // 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/redis/go-redis/v9 v9.12.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sirupsen/logrus v1.9.3 // 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/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/vcaesar/cedar v0.30.0 // indirect
@@ -96,11 +100,11 @@ require (
github.com/yargevad/filepathx v1.0.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
golang.org/x/arch v0.11.0 // indirect
@@ -108,6 +112,7 @@ require (
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect

38
go.sum
View File

@@ -1,8 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
gitea.com/red-future/common v0.0.12 h1:whaCAiH33orl0P+oDpxzC4VoNluHKNYKGZ+FcUWw85Q=
gitea.com/red-future/common v0.0.12/go.mod h1:3a7cwZNvgpKw5FzE8x5MZImd7NBePGXRGFSMjt90158=
gitea.com/red-future/common v0.0.19 h1:9/WrfCFUCeFUYwuhBYF+JOQi5F5xuOy+gVnf2ZvHZu4=
gitea.com/red-future/common v0.0.19/go.mod h1:6/nqIucVzmjOyqDTIq71feYBXXFNBy0rFwzaQ0/Ueoo=
gitea.com/red-future/common v0.0.21 h1:8w30HmCVmFG/hphH3ODJs1KxDEGmRpq+/PXI0pQjJKc=
gitea.com/red-future/common v0.0.21/go.mod h1:6/nqIucVzmjOyqDTIq71feYBXXFNBy0rFwzaQ0/Ueoo=
gitea.redpowerfuture.com/red-future/common v0.0.23 h1:xieoA00iKOCDm5SO9iXn+cSyMKBAlZwI0fuEVPWrHLg=
gitea.redpowerfuture.com/red-future/common v0.0.23/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
@@ -60,6 +60,8 @@ github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/eino v0.8.13 h1:z5dhaZNN8TWZbP/lgKxGmF26Ii8fPeUlQCGV/NTtms0=
github.com/cloudwego/eino v0.8.13/go.mod h1:+2N4nsMPxA6kGBHpH+75JuTfEcGprAMTdsZESrShKpU=
github.com/cloudwego/eino v0.9.5 h1:0Nftjx9gPek/2S/hzm38LVxSjk5/6mqRr3I9VKrKvm4=
github.com/cloudwego/eino v0.9.5/go.mod h1:OBD1mrkfkt/pJa4rkg1P0VnaMeOVl7l8IAdEqY//3IQ=
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9 h1:xCz/mp43JeWqupjPR3zLRArmwC6P29/6lTwbwh1yzYM=
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9/go.mod h1:slTGTuhzkzhNavf+1UtUg1FvUSA31iNAF+rq1mT4SnI=
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 h1:EeVcR1TslRA2IdNW1h/2LaGbPlffwGhQm99jM3zWZiI=
@@ -116,14 +118,20 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0 h1:39+jbTenm7KBj4hO2C8ANAxVHpX/7OuRDs1VcGC9ylA=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0/go.mod h1:B0s0fVzn0W220E8UTpSGzrrGKsop5KcB90twBeLCiz0=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2 h1:u8EpP24GkprogROnJ7htMov9Fc66pTP1eVYrWxiCYOs=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2/go.mod h1:GmvM3r8GVByVMi4RD2+MCs5+CfxVXPMeT8mVDkAaAXE=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0 h1:N/F9CuDdUZLoM1nVRqrDE/33pDZuhVxpNY4wYdeIaBs=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0/go.mod h1:x6uoJGfZOtirIRQls8xUlYzC6f7T/eULPUa9er368X0=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2 h1:iTQegT+lEg/wDKvj2mi3W1wrdrwFarjokf88EXVVgu4=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2/go.mod h1:ZRw3GNz5cq4uYrW4TPSVyrYWaoqzujKdWro/AOcGBaE=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88=
github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs=
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
github.com/gogf/gf/v2 v2.10.2 h1:46IO0Uc8e85/FqdftJFskfDejJLBL0JBnGS5qOftUu8=
github.com/gogf/gf/v2 v2.10.2/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -171,8 +179,6 @@ github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
@@ -290,7 +296,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
@@ -374,6 +379,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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
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/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -399,20 +413,28 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -523,6 +545,8 @@ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=

11
main.go
View File

@@ -8,8 +8,8 @@ import (
workflowSkillController "ai-agent/workflow/controller/skill"
"context"
"gitea.com/red-future/common/http"
"gitea.com/red-future/common/jaeger"
"gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/jaeger"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
)
@@ -18,6 +18,7 @@ func main() {
ctx := context.Background()
defer jaeger.ShutDown(ctx)
// 注册路由
http.Httpserver.BindHandler("/httpNodeCallback", workflowController.FlowCallBack.HttpNodeCallback)
http.RouteRegister([]interface{}{
//digitalhuman相关接口
digitalhumanController.Audio, // 语音相关接口
@@ -30,9 +31,15 @@ func main() {
workflowController.FlowUser,
workflowController.FlowTemplate,
workflowNodeController.NodeLibrary,
workflowNodeController.NodePrompt,
workflowSkillController.SkillTemplate,
workflowSkillController.SkillUser,
})
//workflow.ExternalInterruptDemo()
//err := activePullService.ActivePullService.AllList(ctx)
//if err != nil {
// g.Log().Error(ctx, "ActivePullService err: %v", err)
//}
// 保持应用运行
select {}
}

View File

@@ -406,6 +406,8 @@ CREATE TABLE IF NOT EXISTS black_deacon_flow_execution (
-- 业务字段
flow_user_id BIGINT NOT NULL, -- 流程ID
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 '', -- 触发类型
duration_ms BIGINT NOT NULL DEFAULT 0, -- 执行时长(毫秒)
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.flow_user_id IS '流程ID';
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.duration_ms IS '执行时长(毫秒)';
COMMENT ON COLUMN black_deacon_flow_execution.status IS '状态:1-运行中,2-成功,3-失败';
@@ -518,4 +522,143 @@ COMMENT ON COLUMN black_deacon_flow_template.category_name IS '流程分类名
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.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 '{}', -- 节点输入参数
input_params_path VARCHAR(256) DEFAULT '', -- 节点输入参数路径
output_params JSONB DEFAULT '{}', -- 节点输出参数
output_params_path VARCHAR(256) 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.input_params_path IS '节点输入参数路径';
COMMENT ON COLUMN black_deacon_node_execution.output_params IS '节点输出参数';
COMMENT ON COLUMN black_deacon_node_execution.output_params_path 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表语句---------------------------

View File

@@ -10,22 +10,30 @@ const (
// 节点名称
const (
NodeNameTextModel = "生成文案"
NodeNameImageModel = "生成图片"
NodeNameVideoModel = "视频"
NodeNameAudioModel = "音频"
NodeNameModel = "模型"
NodeNameMerge = "结果合并"
NodeNameJudge = "条件判断"
NodeNameForm = "表单"
NodeNameCustomNode = "自定义节点"
NodeNameTextModel = "生成文案"
NodeNameImageModel = "生成图片"
NodeNameVideoModel = "生成视频"
NodeNameAudioModel = "生成音频"
NodeNameBatchModel = "批量处理一起返回"
NodeNameDataConversionModel = "参数转换"
NodeNameSenseOptimizeModel = "语义优化"
NodeNameStoryOptimizeModel = "分镜优化"
NodeNameScriptOptimizeModel = "剧本优化"
NodeNameModel = "模型"
NodeNameMerge = "结果合并"
NodeNameDataMerge = "结果汇集"
NodeNameJudge = "条件判断"
NodeNameLoop = "循环"
NodeNameForm = "表单"
NodeNameHttp = "HTTP(S)接口"
NodeNameCustomNode = "自定义节点"
NodeNameSystemSum = "系统-结果汇总"
)
// 表单字段 Label
const (
FormLabelApiKey = "API Key"
FormLabelModel = "模型名称"
FormLabelApiKey = "API Key"
FormLabelModel = "模型名称"
FormLabelCondition = "判断条件"
)
@@ -46,33 +54,45 @@ const (
NodeTypeImageModel NodeType = "image_model"
NodeTypeVideoModel NodeType = "video_model"
NodeTypeAudioModel NodeType = "audio_model"
NodeTypeBatchModel NodeType = "batch_model"
NodeTypeDataConversionModel NodeType = "data_conversion_model"
NodeTypeSenseOptimizeModel NodeType = "sense_optimize_model"
NodeTypeStoryOptimizeModel NodeType = "story_optimize_model"
NodeTypeScriptOptimizeModel NodeType = "script_optimize_model"
// 基础
NodeTypeModel NodeType = "model"
NodeTypeMerge NodeType = "merge"
NodeTypeJudge NodeType = "judge"
NodeTypeForm NodeType = "form"
NodeTypeIntent NodeType = "intent"
NodeTypeModel NodeType = "model"
NodeTypeMerge NodeType = "merge"
NodeTypeDataMerge NodeType = "data_merge"
NodeTypeJudge NodeType = "judge"
NodeTypeForm NodeType = "form"
NodeTypeIntent NodeType = "intent"
NodeTypeHttp NodeType = "http"
// 自定义
NodeTypeCustomNode NodeType = "custom_node"
// 系统
NodeTypeSystemSum NodeType = "system_sum"
)
const (
ModelTypeText = 1
ModelTypeImage = 2
ModelTypeText = 100
ModelTypeImage = 200
ModelTypeAudio = 300
ModelTypeModality = 500
ModelTypeVideo = 600
)
// ======================== 结构定义 ========================
type NodeFormField struct {
Value string `json:"value"`
Field string `json:"field"`
Label string `json:"label"` // 从常量来
Type string `json:"type"`
Required bool `json:"required"`
Default any `json:"default,omitempty"`
Options []SelectOption `json:"options"`
Expand any `json:"expand"`
Value any `json:"value"`
Field string `json:"field"`
Label string `json:"label"` // 从常量来
Type string `json:"type"`
Required bool `json:"required"`
Default any `json:"default,omitempty"`
Options []SelectOption `json:"options"`
Expand any `json:"expand"`
FieldConstraint any `json:"fieldConstraint"`
}
type SelectOption struct {
@@ -81,20 +101,20 @@ type SelectOption struct {
}
type ModelItem struct {
ModelApiKey string `json:"modelApiKey"`
ModelName string `json:"modelName"`
ModelForm map[string]any `json:"modelForm"`
ModelResponse map[string]any `json:"modelResponse"`
ModelName string `json:"modelName"`
ModelForm []NodeFormField `json:"modelForm"`
}
type NodeItem struct {
NodeId string `json:"nodeId"`
NodeCode NodeType `json:"nodeCode"`
ModelType int `json:"modelType"`
NodeName string `json:"nodeName"` // 从常量来
SkillOption bool `json:"skillOption"`
FormConfig []NodeFormField `json:"formConfig"`
ModelConfig []ModelItem `json:"modelConfig"`
NodeId string `json:"nodeId"`
NodeCode NodeType `json:"nodeCode"`
ModelType int `json:"modelType"`
NodeName string `json:"nodeName"` // 从常量来
SkillOption bool `json:"skillOption"`
PromptOption bool `json:"promptOption"`
IsSaveFile bool `json:"isSaveFile"`
FormConfig []NodeFormField `json:"formConfig"`
ModelConfig []ModelItem `json:"modelConfig"`
}
type NodeGroupItem struct {
@@ -102,91 +122,3 @@ type NodeGroupItem struct {
Label string `json:"label"` // 从常量来
Items []NodeItem `json:"items"`
}
//
//// 文案模型节点定义
//func NewTextModelNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeTextModel,
// NodeName: NodeNameTextModel,
// FormConfig: []ModelItem{},
// }
//}
//
//// 图片模型节点
//func NewImageModelNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeImageModel,
// NodeName: NodeNameImageModel,
// FormConfig: []ModelItem{},
// }
//}
//
//// 音频模型节点
//func NewAudioModelNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeAudioModel,
// NodeName: NodeNameAudioModel,
// FormConfig: []ModelItem{},
// }
//}
//
//// 视频模型节点
//func NewVideoModelNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeVideoModel,
// NodeName: NodeNameVideoModel,
// FormConfig: []ModelItem{},
// }
//}
//
//// 基础模型节点
//func NewModelNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeModel,
// NodeName: NodeNameModel,
// FormConfig: []ModelItem{
// {
// ModelName: "模型名称",
// ModelForm: []NodeFormField{
// {Field: "apiKey", Label: FormLabelApiKey, Type: "input", Required: true},
// {Field: "model", Label: FormLabelModel, Type: "input", Required: true},
// },
// },
// },
// }
//}
//
//// 判断节点
//func NewJudgeNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeJudge,
// NodeName: NodeNameJudge,
// FormConfig: []ModelItem{
// {
// ModelName: "判断条件",
// ModelForm: []NodeFormField{
// {Field: "condition", Label: FormLabelCondition, Type: "input", Required: true},
// },
// },
// },
// }
//}
//
//// 表单参数节点
//func NewFormNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeForm,
// NodeName: NodeNameForm,
// FormConfig: []ModelItem{},
// }
//}
//
//// 自定义节点
//func NewCustomNode() NodeItem {
// return NodeItem{
// NodeCode: NodeTypeCustomNode,
// NodeName: NodeNameCustomNode,
// FormConfig: []ModelItem{},
// }
//}

View 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}
}

View File

@@ -7,11 +7,15 @@ const (
// 数据库表名
const (
TableNameCreationInfo = "creation_info"
TableNameFlowExecution = "flow_execution"
TableNameFlowTemplate = "flow_template"
TableNameFlowUser = "flow_user"
TableNameSkillTemplate = "skill_template"
TableNameSkillUser = "skill_user"
TableNameFileTemp = "file_temp"
TableNameCreationInfo = "creation_info"
TableNameFlowExecution = "flow_execution"
TableNameFlowTemplate = "flow_template"
TableNameFlowUser = "flow_user"
TableNameSkillTemplate = "skill_template"
TableNameSkillUser = "skill_user"
TableNameFileTemp = "file_temp"
TableNameActivePull = "active_pull"
TableNameWorkflowInterrupt = "workflow_interrupt"
TableNameNodePrompt = "node_prompt"
TableNameNodeExecution = "node_execution"
)

View File

@@ -5,7 +5,7 @@ import (
"ai-agent/workflow/service"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type creationInfo struct{}

View 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
}

View File

@@ -5,7 +5,7 @@ import (
flowService "ai-agent/workflow/service/flow"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type flowExecution struct{}
@@ -16,11 +16,21 @@ func (c *flowExecution) Execute(ctx context.Context, req *flowDto.ExecuteReq) (r
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) {
err = flowService.FlowExecutionService.ModelCallback(ctx, req)
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) {
return flowService.FlowExecutionService.Get(ctx, req)
}

View File

@@ -5,7 +5,7 @@ import (
flowService "ai-agent/workflow/service/flow"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)

View File

@@ -5,7 +5,7 @@ import (
flowService "ai-agent/workflow/service/flow"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)

View File

@@ -0,0 +1,45 @@
package node
import (
nodeDto "ai-agent/workflow/model/dto/node"
nodeService "ai-agent/workflow/service/node"
"context"
"gitea.redpowerfuture.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)
}

View File

@@ -5,7 +5,7 @@ import (
skillService "ai-agent/workflow/service/skill"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type skillTemplate struct{}

View File

@@ -5,7 +5,7 @@ import (
skillService "ai-agent/workflow/service/skill"
"context"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type skillUser struct{}

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/workflow/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/workflow/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/workflow/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/workflow/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/workflow/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)

View 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.redpowerfuture.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
}

View 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.redpowerfuture.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
}

View 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.redpowerfuture.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
}

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/workflow/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -6,7 +6,7 @@ import (
"ai-agent/workflow/model/entity"
"context"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -1,7 +1,7 @@
package dto
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -1,7 +1,7 @@
package file
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -2,21 +2,30 @@ package flow
import (
"ai-agent/workflow/consts/flow"
"ai-agent/workflow/consts/node"
"ai-agent/workflow/model/entity"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// NodeExecutionInput 节点执行入参(包含配置+表单架构)
type NodeExecutionInput struct {
Config *entity.FlowNode // 节点配置
Global *FlowExecutionInput `json:"-"`
Config *entity.FlowNode `json:"config"` // 节点配置
Global *FlowExecutionInput `json:"global"`
NodeExecutionId int64 `json:"nodeExecutionId"`
}
// ExecutedNode 已执行节点记录包含节点ID和执行状态
type ExecutedNode struct {
NodeId string `json:"nodeId"`
Status node.NodeExecutionStatus `json:"status"` // 执行状态:成功/失败
}
// FlowExecutionInput 工作流执行入参(全程不变)
type FlowExecutionInput struct {
NodeGroupId string `json:"nodeGroupId"`
IsDialogue bool `json:"isDialogue"`
ExecutionId int64 `json:"executionId"`
ConfigMap map[string]*entity.FlowNode `json:"configMap"`
@@ -24,27 +33,74 @@ type FlowExecutionInput struct {
Desc string `json:"desc"`
SkillName string `json:"skillName"`
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 {
BuildType int `json:"buildType"`
ModelName string `json:"modelName"`
SkillName string `json:"skillName"`
Form map[string]any `json:"form"`
UserForm map[string]any `json:"userForm"`
UserFiles []string `json:"userFiles"`
SessionId string `json:"sessionId" dc:"会话ID"`
IsBuild bool `json:"isBuild"`
Cause string `json:"cause"`
BuildType int `json:"buildType"`
ModelName string `json:"modelName"`
SkillName string `json:"skillName"`
CallbackUrl string `json:"callbackUrl"`
Form []map[string]any `json:"form"`
UserForm []map[string]any `json:"userForm"`
Consult []Consult `json:"consult"`
SessionId string `json:"sessionId" dc:"会话ID"`
NodeId string `json:"nodeId"`
Cause string `json:"cause"`
}
type Consult struct {
Type string `json:"type"`
Url string `json:"url"`
}
type ComposeMessagesRes struct {
Messages map[string]any `json:"messages"`
EpicycleId int64 `json:"epicycleId" dc:"轮次ID"`
TaskId string `json:"taskId"`
}
type CreateTaskReq struct {
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"`
ModelKey string `json:"modelKey"`
BizName string `json:"bizName"`
@@ -54,89 +110,81 @@ type CreateTaskReq struct {
EpicycleId int64 `json:"epicycleId" dc:"轮次ID"`
}
type CreateTaskRes struct {
type ModelGatewayRes struct {
TaskId string `json:"taskId"`
}
type GetIsChatModelRes struct {
ModelName string `json:"modelName"`
ResponseBody map[string]any `json:"responseBody"`
type ComposeCallbackReq struct {
g.Meta `path:"/composeCallBack" method:"post" tags:"提示词处理" summary:"提示词 回调" dc:"提示词 成功后 GET 回调callbackUrl/{bizName}"`
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 {
g.Meta `path:"/modelCallback" method:"post" tags:"提示词处理" summary:"model-gateway 回调" dc:"model-gateway 成功后 GET 回调callbackUrl/{bizName}"`
TaskId string `p:"task_id" json:"task_id" v:"required#task_id不能为空" dc:"网关任务ID"`
State int `p:"state" json:"state" dc:"网关任务状态"`
OssFile string `p:"oss_file" json:"oss_file" dc:"结果文件地址"`
FileType string `p:"file_type" json:"file_type" dc:"结果文件类型"`
Text string `p:"text" json:"text" dc:"文本结果(可选,最多约 2000 字符)"`
TaskId string `p:"task_id" json:"task_id" v:"required#task_id不能为空" dc:"网关任务ID"`
State int `p:"state" json:"state" dc:"网关任务状态"`
OssFile string `p:"oss_file" json:"oss_file" dc:"结果文件地址"`
FileType string `p:"file_type" json:"file_type" dc:"结果文件类型"`
Messages map[string]any `json:"messages"`
ErrorMsg string `json:"error_msg"`
}
type TaskCallback struct {
TaskID string `json:"taskId"`
State int `json:"state"` // 0排队中/1执行中/2成功/3失败/4已下载
OssFile string `json:"ossFile"`
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 VideoCallbackReq struct {
g.Meta `path:"/videoCallback" method:"post" tags:"视频处理" summary:"media 回调" dc:"media 成功后 GET 回调callbackUrl/{bizName}"`
TaskId string `json:"taskId"`
FileURL string `json:"fileUrl"`
}
//=============================================================================
// 原始入参结构体
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 {
g.Meta `path:"/execute" method:"post" tags:"任务管理" summary:"执行任务" dc:"执行任务"`
FlowId int64 `json:"flowId" dc:"用户流程ID"`
FlowName string `json:"flowName"`
NodeGroupId string `json:"nodeGroupId"`
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
SessionId string `json:"sessionId" dc:"会话ID"`
Desc string `json:"desc"`
SkillName string `json:"skillName"`
FileUrl []string `json:"fileUrl"`
ResultUrl string `json:"resultUrl"`
}
type ExecuteRes struct {
@@ -152,6 +200,7 @@ type CancelReq struct {
type CreateFlowExecutionReq struct {
FlowUserId int64 `json:"flowUserId" description:"流程ID"`
FlowName string `json:"flowName"`
NodeGroupId string `json:"nodeGroupId"`
TriggerType flow.FlowExecutionTriggerType `json:"triggerType" description:"触发类型"`
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
@@ -169,6 +218,7 @@ type CreateFlowExecutionRes struct {
type UpdateFlowExecutionReq struct {
Id int64 `json:"id" v:"required#ID不能为空"`
NodeGroupId string `json:"nodeGroupId"`
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
@@ -218,6 +268,7 @@ type VOFlowExecution struct {
type OutputItem struct {
Timestamp string `json:"timestamp" description:"时间戳key"`
Content string `json:"content" description:"内容值"`
Type string `json:"type" description:"类型"`
Label string `json:"label" description:"后缀+数字标号"`
}
type FlowNode struct {
@@ -231,7 +282,6 @@ type DateNode struct {
Flows []FlowNode `json:"flows" description:"流程列表"`
}
// 最终树结构返回体
type ListFlowExecutionTreeRes struct {
Tree []DateNode `json:"tree"`
ImgAddressPrefix string `json:"imgAddressPrefix"`

View File

@@ -4,7 +4,7 @@ import (
"ai-agent/workflow/consts/flow"
"ai-agent/workflow/model/entity"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -4,7 +4,7 @@ import (
"ai-agent/workflow/consts/flow"
"ai-agent/workflow/model/entity"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -0,0 +1,74 @@
package node
import (
"ai-agent/workflow/consts/node"
flowDto "ai-agent/workflow/model/dto/flow"
"ai-agent/workflow/model/entity"
"gitea.redpowerfuture.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"`
InputParamsPath string
OutputParams *flowDto.NodeExecutionInput `json:"outputParams"`
OutputParamsPath string
}
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"`
InputParamsPath string
OutputParams *flowDto.NodeExecutionInput `json:"outputParams"`
OutputParamsPath string
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"`
}

View File

@@ -27,3 +27,7 @@ type ModelItem struct {
Name string `json:"name"`
Form []node.NodeFormField `json:"form"`
}
type ModelTypeResponse struct {
Type map[int]string `json:"type"` // key 自动解析为整数 100/200/300...
}

View File

@@ -0,0 +1,70 @@
package node
import (
"ai-agent/workflow/consts/node"
"ai-agent/workflow/model/entity"
"gitea.redpowerfuture.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"`
}

View File

@@ -0,0 +1,54 @@
package pull
import (
"ai-agent/workflow/consts/flow"
"ai-agent/workflow/model/entity"
"gitea.redpowerfuture.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:"更新时间"`
}

View File

@@ -1,7 +1,7 @@
package skill
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -1,7 +1,7 @@
package skill
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)

View File

@@ -0,0 +1,28 @@
package entity
import "gitea.redpowerfuture.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",
}

View File

@@ -1,6 +1,6 @@
package entity
import "gitea.com/red-future/common/beans"
import "gitea.redpowerfuture.com/red-future/common/beans"
type CreationInfo struct {
beans.SQLBaseDO `orm:",inherit"` // 嵌入基础字段Id, TenantId, Creator, CreatedAt, Updater, UpdatedAt, DeletedAt

View File

@@ -1,7 +1,7 @@
package entity
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type FileTemp struct {

View File

@@ -3,7 +3,7 @@ package entity
import (
"ai-agent/workflow/consts/flow"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type FlowExecution struct {
@@ -11,6 +11,7 @@ type FlowExecution struct {
// 业务字段
FlowUserId int64 `orm:"flow_user_id" json:"flowUserId" description:"流程ID"`
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:"触发类型"`
DurationMs int64 `orm:"duration_ms" json:"durationMs" description:"执行时长(毫秒)"`
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:"错误信息"`
TraceId string `orm:"trace_id" json:"traceId" description:"跟踪ID"`
SessionId string `orm:"session_id" json:"sessionId" description:"会话ID"`
TotalTokens int `orm:"total_tokens" json:"totalTokens" description:"总token消耗"`
}
type flowExecutionCol struct {
beans.SQLBaseCol
FlowUserId string
FlowName string
NodeGroupId string
TriggerType string
DurationMs string
Status string
@@ -35,12 +38,14 @@ type flowExecutionCol struct {
ErrorMessage string
TraceId string
SessionId string
TotalTokens string
}
var FlowExecutionCol = flowExecutionCol{
SQLBaseCol: beans.DefSQLBaseCol,
FlowUserId: "flow_user_id",
FlowName: "flow_name",
NodeGroupId: "node_group_id",
TriggerType: "trigger_type",
DurationMs: "duration_ms",
Status: "status",
@@ -50,4 +55,5 @@ var FlowExecutionCol = flowExecutionCol{
ErrorMessage: "error_message",
TraceId: "trace_id",
SessionId: "session_id",
TotalTokens: "total_tokens",
}

View File

@@ -3,7 +3,7 @@ package entity
import (
"ai-agent/workflow/consts/flow"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type FlowTemplate struct {

View File

@@ -4,7 +4,7 @@ import (
"ai-agent/workflow/consts/flow"
"ai-agent/workflow/consts/node"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type FlowInfo struct {
@@ -15,15 +15,18 @@ type FlowInfo struct {
}
type FlowNode struct {
Id string `json:"id"`
NodeCode node.NodeType `json:"nodeCode"`
Name string `json:"name"`
Config map[string]interface{} `json:"config"`
SkillName string `json:"skillName"`
InputSource []FlowNodeInputSource `json:"inputSource"` // 前端指定来源节点ID
FormConfig []node.NodeFormField `json:"formConfig"`
ModelConfig node.ModelItem `json:"modelConfig"`
OutputResult []node.NodeFormField `json:"outputResult" ds:"节点输出结果"`
Id string `json:"id"`
NodeCode node.NodeType `json:"nodeCode"`
Name string `json:"name"`
Config map[string]interface{} `json:"config"`
SkillName string `json:"skillName"`
PromptContent string `json:"promptContent"`
IsSaveFile bool `json:"isSaveFile"`
InputSource []FlowNodeInputSource `json:"inputSource"` // 前端指定来源节点ID
FormConfig []node.NodeFormField `json:"formConfig"`
ModelConfig node.ModelItem `json:"modelConfig"`
OutputConfig []node.NodeFormField `json:"outputConfig"`
OutputResult []node.NodeFormField `json:"outputResult" ds:"节点输出结果"`
}
type FlowNodeInputSource struct {

View File

@@ -0,0 +1,64 @@
package entity
import (
"ai-agent/workflow/consts/node"
"gitea.redpowerfuture.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:"节点输入参数"`
InputParamsPath string `orm:"input_params_path" json:"inputParamsPath" description:"节点输入参数路径"`
OutputParams map[string]interface{} `orm:"output_params" json:"outputParams" description:"节点输出参数"`
OutputParamsPath string `orm:"output_params_path" json:"outputParamsPath" 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
InputParamsPath string
OutputParams string
OutputParamsPath 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",
InputParamsPath: "input_params_path",
OutputParams: "output_params",
OutputParamsPath: "output_params_path",
PromptTokens: "prompt_tokens",
CompletionTokens: "completion_tokens",
TotalTokens: "total_tokens",
Status: "status",
DurationMs: "duration_ms",
ErrorMessage: "error_message",
}

View File

@@ -0,0 +1,29 @@
package entity
import (
"ai-agent/workflow/consts/node"
"gitea.redpowerfuture.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",
}

View File

@@ -1,7 +1,7 @@
package entity
import (
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type SkillTemplate struct {

View File

@@ -1,6 +1,6 @@
package entity
import "gitea.com/red-future/common/beans"
import "gitea.redpowerfuture.com/red-future/common/beans"
type SkillUser struct {
beans.SQLBaseDO `orm:",inherit"` // 嵌入基础字段Id, TenantId, Creator, CreatedAt, Updater, UpdatedAt, DeletedAt

View 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
}

View File

@@ -9,7 +9,7 @@ import (
"fmt"
"sort"
"gitea.com/red-future/common/utils"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/util/gconv"
)

View File

@@ -9,8 +9,8 @@ import (
"net/http"
"strings"
commonHttp "gitea.com/red-future/common/http"
"gitea.com/red-future/common/utils"
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/cloudwego/eino/schema"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"

View File

@@ -5,8 +5,11 @@ import (
"ai-agent/workflow/consts/node"
fileDao "ai-agent/workflow/dao/file"
flowDao "ai-agent/workflow/dao/flow"
nodeDao "ai-agent/workflow/dao/node"
"ai-agent/workflow/model/dto"
fileDto "ai-agent/workflow/model/dto/file"
flowDto "ai-agent/workflow/model/dto/flow"
nodeDto "ai-agent/workflow/model/dto/node"
"ai-agent/workflow/model/entity"
"context"
"errors"
@@ -15,12 +18,14 @@ import (
"strconv"
"strings"
"sync"
"time"
"gitea.com/red-future/common/utils"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/cloudwego/eino/compose"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/google/uuid"
"go.opentelemetry.io/otel/trace"
)
@@ -121,22 +126,30 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
item := &tempItems[idx]
val := item.Content
suffix := "内容"
switch {
case strings.Contains(val, "img") || strings.Contains(val, "png") || strings.Contains(val, "jpg"):
ext := ""
ext = GetFileTypeByPath(val)
if ext == "image" {
suffix = "图片"
case strings.Contains(val, "html") || strings.Contains(val, "HTML"):
suffix = "HTML"
case strings.Contains(val, "txt") || len(val) > 50:
}
if ext == "video" {
suffix = "视频"
}
if ext == "audio" {
suffix = "音频"
}
if ext == "text" {
suffix = "文案"
}
if ext == "html" {
suffix = "HTML"
}
suffixCount[suffix]++
item.Type = ext
item.Label = fmt.Sprintf("%s_%d", suffix, suffixCount[suffix])
}
// 组装节点
node := flowDto.FlowNode{
flowNode := flowDto.FlowNode{
FlowName: displayFlowName,
Id: execution.Id,
SessionId: gconv.String(execution.SessionId),
@@ -147,7 +160,7 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
dateMap[createDate] = &[]flowWrap{}
}
*dateMap[createDate] = append(*dateMap[createDate], flowWrap{
flowNode: node,
flowNode: flowNode,
createdAt: execution.CreatedAt,
})
}
@@ -188,6 +201,12 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
}, nil
}
// ComposeCallback 提示词回调接口
func (s *flowExecutionService) ComposeCallback(ctx context.Context, req *flowDto.ComposeCallbackReq) (err error) {
Notify(req.TaskId, req)
return nil
}
// ModelCallback 模型回调接口
func (s *flowExecutionService) ModelCallback(ctx context.Context, req *flowDto.ModelCallbackReq) (err error) {
// 唤醒等待的任务
@@ -195,43 +214,19 @@ func (s *flowExecutionService) ModelCallback(ctx context.Context, req *flowDto.M
return nil
}
// 全局等待任务回调的工具
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()
select {
case result := <-ch:
return result, nil
case <-ctx.Done():
asyncMu.Lock()
delete(asyncTasks, taskId)
asyncMu.Unlock()
return nil, ctx.Err()
}
// VideoCallback 视频拼接回调接口
func (s *flowExecutionService) VideoCallback(ctx context.Context, req *flowDto.VideoCallbackReq) (err error) {
// 唤醒等待的任务
Notify(req.TaskId, req)
return nil
}
// Notify 回调时调用,唤醒等待的任务
func Notify(taskId string, result any) {
asyncMu.Lock()
defer asyncMu.Unlock()
ch, exist := asyncTasks[taskId]
if !exist {
return
}
ch <- result
delete(asyncTasks, taskId)
// HttpNodeCallback http节点回调接口
func (s *flowExecutionService) HttpNodeCallback(ctx context.Context) (err error) {
r := g.RequestFromCtx(ctx)
taskId := r.Get("task_id").String()
Notify(taskId, r)
return nil
}
// ===================== 核心改造:替换为 sync.Map 存储取消上下文 =====================
@@ -298,11 +293,13 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
}
var executionId int64
var isDialogue bool
var nodeGroupId = uuid.NewString()
if flowInfo == nil {
isDialogue = false
var r = new(flowDto.CreateFlowExecutionReq)
r.FlowUserId = req.FlowId
r.FlowName = req.FlowName
r.NodeGroupId = nodeGroupId
r.TriggerType = flow.FlowExecutionTriggerTypeManual.Code()
r.FlowContent = req.FlowContent
r.NodeInputParams = req.NodeInputParams
@@ -327,9 +324,10 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
cancelMap.Store(traceId, cancel)
}
executionReq := flowDto.UpdateFlowExecutionReq{
Id: executionId,
Status: flow.FlowExecutionStatusRunning.Code(),
TraceId: traceId,
Id: executionId,
NodeGroupId: nodeGroupId,
Status: flow.FlowExecutionStatusRunning.Code(),
TraceId: traceId,
}
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
if err != nil {
@@ -351,71 +349,26 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
}
}
//_, err = flowDao.FlowUserDao.Update(ctx, &flowDto.UpdateFlowUserReq{
// Id: req.FlowId,
// FlowContent: req.FlowContent,
// NodeInputParams: req.NodeInputParams,
//})
//if err != nil {
// return nil, err
//}
//nodeInsert := make([]*nodeDto.CreateNodeExecutionReq, 0, len(flowInfo.NodeInputParams))
//for _, flowNode := range flowInfo.NodeInputParams {
// nodeInsert = append(nodeInsert, &nodeDto.CreateNodeExecutionReq{
// FlowExecutionId: executionId,
// NodeId: flowNode.Id,
// Status: node.NodeExecutionStatusWait.Code(),
// NodeInputParams: flowNode,
// TraceId: r.TraceId,
// })
//}
//_, err = nodeDao.NodeExecutionDao.BatchInsert(ctx, nodeInsert)
//if err != nil {
// return
//}
// =========================================================================
// ✅【第1步】给所有判断节点自动生成意图识别节点
// =========================================================================
judge2IntentNodeMap := make(map[string]string)
finalNodes := make([]entity.FlowNode, 0, len(req.FlowContent.Nodes)*2)
for _, item := range req.FlowContent.Nodes {
finalNodes = append(finalNodes, item)
// 判断节点自动加 intent 节点
if item.NodeCode == node.NodeTypeJudge {
intentNodeID := fmt.Sprintf("intent_%s", item.Id)
intentNode := entity.FlowNode{
Id: intentNodeID,
NodeCode: node.NodeTypeIntent,
Name: fmt.Sprintf("意图识别-%s", item.Name),
InputSource: item.InputSource, // ✅ 正确赋值
FormConfig: item.FormConfig, // ✅ 用户配置
ModelConfig: item.ModelConfig, // ✅ 系统配置
}
finalNodes = append(finalNodes, intentNode)
judge2IntentNodeMap[item.Id] = intentNodeID
if isDialogue && !g.IsEmpty(flowInfo) && !g.IsEmpty(req.ResultUrl) {
req.NodeGroupId = nodeGroupId
if strings.HasSuffix(gconv.String(req.ResultUrl), ".inc") {
err = TextModelSingleLambda(ctx, req, flowInfo)
return
} else if strings.HasSuffix(gconv.String(req.ResultUrl), ".png") {
err = ImgModelSingleLambda(ctx, req, flowInfo)
return
} else if strings.HasSuffix(gconv.String(req.ResultUrl), ".html") {
err = TextImgModelSingleLambda(ctx, req, flowInfo)
return
}
return nil, errors.New("文件格式不支持")
}
summaryNodeID := "summary_node"
summaryNode := entity.FlowNode{
Id: summaryNodeID,
NodeCode: node.NodeTypeCustomNode, // 复用自定义节点类型,也可新增专属类型
Name: "结果汇总节点",
InputSource: []entity.FlowNodeInputSource{}, // 后续自动聚合所有节点输出
FormConfig: nil,
ModelConfig: node.ModelItem{},
}
finalNodes = append(finalNodes, summaryNode)
// 替换节点列表
req.FlowContent.Nodes = finalNodes
// =========================================================================
// ✅【第2步】构建执行图
// =========================================================================
runGraph, err := BuildGraphFromFlowContent(execCtx, req.FlowContent, judge2IntentNodeMap, summaryNodeID)
var nodeList []entity.FlowNode
var runGraph compose.Runnable[any, any]
nodeList, runGraph, err = BuildGraphFromFlowContent(execCtx, req.FlowContent)
if err != nil {
executionReq := flowDto.UpdateFlowExecutionReq{
Id: executionId,
@@ -428,7 +381,6 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
}
return nil, fmt.Errorf("执行工作流失败: %v", err)
}
// =========================================================================
// ✅【第3步】构建 ConfigMap
// =========================================================================
@@ -436,19 +388,14 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
for _, cfg := range req.NodeInputParams {
configMap[cfg.Id] = cfg
}
// 自动给意图节点复制配置
for judgeID, intentID := range judge2IntentNodeMap {
if cfg, ok := configMap[judgeID]; ok {
configMap[intentID] = cfg
}
for _, i := range nodeList {
configMap[i.Id] = &i
}
// 初始化汇总节点配置
configMap[summaryNodeID] = &summaryNode
// =========================================================================
// ✅【第4步】构建全局执行入参现在 schemaMap 是有值的!)
// =========================================================================
execInput := &flowDto.FlowExecutionInput{
NodeGroupId: nodeGroupId,
IsDialogue: isDialogue,
ExecutionId: executionId,
ConfigMap: configMap,
@@ -480,17 +427,43 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
}
return nil, fmt.Errorf("执行工作流失败: %v", err)
}
return
}
// 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) ([]entity.FlowNode, 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]()
nodeMap := make(map[string]entity.FlowNode)
var nodeList []entity.FlowNode
nodeId := uuid.NewString()
originalEndNodes := findEndNodes(flowContent.StartNodeId, flowContent.Edges)
for i := range originalEndNodes {
sprintf := fmt.Sprintf("%v_%d", nodeId, i)
summaryNode := entity.FlowNode{
Id: sprintf,
NodeCode: node.NodeTypeSystemSum,
Name: node.NodeNameSystemSum,
InputSource: []entity.FlowNodeInputSource{}, // 后续自动聚合所有节点输出
FormConfig: nil,
ModelConfig: node.ModelItem{},
}
nodeList = append(nodeList, summaryNode)
flowContent.Nodes = append(flowContent.Nodes, summaryNode)
}
// 注册所有节点
nodeMap := make(map[string]entity.FlowNode)
for _, item := range flowContent.Nodes {
nodeMap[item.Id] = item
if item.NodeCode != node.NodeTypeJudge {
@@ -498,6 +471,16 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
}
}
// 注册所有边
if flowContent.StartNodeId != "" {
_ = graph.AddEdge(compose.START, flowContent.StartNodeId)
}
for i, endID := range originalEndNodes {
sprintf := fmt.Sprintf("%v_%d", nodeId, i)
_ = graph.AddEdge(endID, sprintf)
_ = graph.AddEdge(sprintf, compose.END)
}
// 构建边关系
upstreamMap := make(map[string][]string)
edgeMap := make(map[string][]entity.FlowEdge)
@@ -510,15 +493,8 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
for fromNodeID, edges := range edgeMap {
fromNode := nodeMap[fromNodeID]
// --------------------------
// 判断节点 → 分支处理
// --------------------------
if fromNode.NodeCode == node.NodeTypeJudge {
intentNodeID, ok := judge2IntentNodeMap[fromNodeID]
if !ok {
return nil, fmt.Errorf("判断节点[%s]未生成意图节点", fromNodeID)
}
branchMap := make(map[string]bool)
for _, e := range edges {
branchMap[e.To] = true
@@ -553,11 +529,6 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
m["branch_id_name_map"] = branchIdNameMap // 传递ID-名称映射
currentConfig.Config = m
// 从意图节点取输出
if intentCfg, ok := execInput.ConfigMap[intentNodeID]; ok {
currentConfig.OutputResult = intentCfg.OutputResult
}
// 关键修改:构造 NodeExecutionInput 传入 JudgeLambda
nodeExecInput := &flowDto.NodeExecutionInput{
Config: currentConfig, // 当前判断节点配置
@@ -566,34 +537,21 @@ func BuildGraphFromFlowContent(ctx context.Context, flowContent *entity.FlowInfo
return JudgeLambda(ctx, nodeExecInput) // 传入 NodeExecutionInput 类型
}
_ = graph.AddBranch(intentNodeID, compose.NewGraphBranch(judgeLambda, branchMap))
_ = graph.AddBranch(upstreamMap[fromNodeID][0], compose.NewGraphBranch(judgeLambda, branchMap))
continue
}
// --------------------------
// 普通节点连线
// --------------------------
for _, e := range edges {
toNode := nodeMap[e.To]
if toNode.NodeCode == node.NodeTypeJudge {
_ = graph.AddEdge(e.From, fmt.Sprintf("intent_%s", toNode.Id))
continue
}
_ = graph.AddEdge(e.From, e.To)
}
}
// ==================== 第四步:处理开始/结束节点 ====================
if flowContent.StartNodeId != "" {
_ = graph.AddEdge(compose.START, flowContent.StartNodeId)
}
originalEndNodes := findEndNodes(flowContent.StartNodeId, flowContent.Edges)
for _, endID := range originalEndNodes {
_ = graph.AddEdge(endID, summaryNodeID)
}
_ = graph.AddEdge(summaryNodeID, compose.END)
return graph.Compile(ctx, compose.WithGraphName("auto_build_workflow"))
compile, err := graph.Compile(ctx, compose.WithGraphName("auto_build_workflow"))
return nodeList, compile, err
}
// -------------------------- 节点自动注册器(核心分发) --------------------------
@@ -617,7 +575,7 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
}
// 获取入参 - 适配切片类型:遍历所有来源节点
var realInput any
realInput := new(flowDto.NodeExecutionInput)
if len(flowNode.InputSource) > 0 { // 改为判断切片长度
// 遍历所有指定的来源节点,聚合输出结果
for _, inputSource := range flowNode.InputSource { // 遍历切片
@@ -632,31 +590,82 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
Config: currentConfig,
Global: execInput, // ✅ 把【全部节点】的对象直接塞进来
}
// ✅ 插入节点执行记录,初始状态为运行中
startTime := time.Now()
// 执行节点
output, err := lambda(ctx, realInput)
// 上传OSS每条独立上传
ossResult, err := Upload(ctx, &dto.UploadFileBytesReq{
FileBytes: gconv.Bytes(gconv.String(realInput)),
FileName: fmt.Sprintf("nodeInput:%v.txt", time.Now().UnixMilli()),
})
if err != nil {
return nil, err
}
// ✅ 自动把当前节点ID 加入已执行列表
execInput.ExecutedNodes = append(execInput.ExecutedNodes, nodeID)
// 输出存入 FlowNodeConfig
if outConfig, ok := output.(*entity.FlowNode); ok {
currentConfig.OutputResult = outConfig.OutputResult
nodeExecutionId, err := nodeDao.NodeExecutionDao.Insert(ctx, &nodeDto.CreateNodeExecutionReq{
FlowExecutionId: execInput.ExecutionId,
NodeId: nodeID,
NodeName: flowNode.Name,
NodeGroupId: execInput.NodeGroupId,
InputParamsPath: ossResult.FileURL,
Status: node.NodeExecutionStatusRunning.Code(),
})
if err != nil {
// 记录失败到已执行列表
execInput.ExecutedNodes = append(execInput.ExecutedNodes, flowDto.ExecutedNode{
NodeId: nodeID,
Status: node.NodeExecutionStatusFailed.Code(),
})
return nil, err
}
realInput.NodeExecutionId = nodeExecutionId
// 执行节点
_, err = lambda(ctx, realInput)
durationMs := time.Since(startTime).Milliseconds()
// 上传OSS每条独立上传
ossResult1, err := Upload(ctx, &dto.UploadFileBytesReq{
FileBytes: gconv.Bytes(gconv.String(realInput)),
FileName: fmt.Sprintf("nodeInput:%v.txt", time.Now().UnixMilli()),
})
if err != nil {
return nil, err
}
updateReq := &nodeDto.UpdateNodeExecutionReq{
Id: nodeExecutionId,
OutputParamsPath: ossResult1.FileURL,
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让下一个节点继续用
return execInput, nil
}
}
if nodeID == "summary_node" {
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(SummaryLambda)))
return
}
switch code {
case "__start__":
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(StartLambda)))
case node.NodeTypeSystemSum:
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(SummaryLambda)))
case node.NodeTypeTextModel:
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(TextModelLambda)))
case node.NodeTypeImageModel:
@@ -665,6 +674,10 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(VideoModelLambda)))
case node.NodeTypeAudioModel:
_ = 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.NodeTypeCustomNode:
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(CustomLambda)))
case node.NodeTypeForm:
@@ -673,46 +686,43 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(IntentLambda)))
case node.NodeTypeMerge:
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(MergeLambda)))
case node.NodeTypeDataMerge:
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(DataMergeLambda)), compose.WithGraphCompileOptions(compose.WithNodeTriggerMode(compose.AllPredecessor)))
case node.NodeTypeHttp:
_ = graph.AddLambdaNode(nodeID, compose.InvokableLambda(wrapLambda(HttpLambda)))
}
}
// --------------------------------------------------------------------
// ✅【工具方法】找出所有没有出边的节点 → 作为结束节点连接 END
// --------------------------------------------------------------------
func findEndNodes(startNodeId string, edges []entity.FlowEdge) []string {
// 构建 节点 → 后续节点 的映射
nextMap := make(map[string][]string)
for _, e := range edges {
nextMap[e.From] = append(nextMap[e.From], e.To)
}
endNodeSet := make(map[string]struct{})
visited := make(map[string]struct{})
queue := []string{startNodeId}
// 🚀 只从【开始节点】递归遍历(关键修复)
findLeafNodes(startNodeId, nextMap, endNodeSet)
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
// 转成数组返回
endNodes := make([]string, 0, len(endNodeSet))
for id := range endNodeSet {
endNodes = append(endNodes, id)
}
return endNodes
}
// --------------------------------------------------------------------
// ✅ 递归:查找以 nodeId 开头的所有叶子节点
// --------------------------------------------------------------------
func findLeafNodes(nodeId string, nextMap map[string][]string, endNodeSet map[string]struct{}) {
nextNodes := nextMap[nodeId]
// 🚩 没有下一个节点 = 真实结束节点
if len(nextNodes) == 0 {
endNodeSet[nodeId] = struct{}{}
return
}
// 递归继续找下一个
for _, nextId := range nextNodes {
findLeafNodes(nextId, nextMap, endNodeSet)
if _, exist := visited[node]; exist {
continue
}
visited[node] = struct{}{}
nextList := nextMap[node]
if len(nextList) == 0 {
endNodeSet[node] = struct{}{}
continue
}
queue = append(queue, nextList...)
}
res := make([]string, 0, len(endNodeSet))
for k := range endNodeSet {
res = append(res, k)
}
return res
}

View File

@@ -2,12 +2,15 @@ package flow
import (
"ai-agent/workflow/consts/flow"
"ai-agent/workflow/consts/node"
flowDao "ai-agent/workflow/dao/flow"
flowDto "ai-agent/workflow/model/dto/flow"
"ai-agent/workflow/model/entity"
"ai-agent/workflow/service"
"context"
commonHttp "gitea.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -16,25 +19,8 @@ var FlowUserService = &flowUserService{}
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) {
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
return
}
@@ -55,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) {
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
return
}
@@ -95,6 +81,26 @@ func (s *flowUserService) Update(ctx context.Context, req *flowDto.UpdateFlowUse
}
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
for _, item := range flowContent.Nodes {
flowNodes = append(flowNodes, &item)
@@ -103,7 +109,7 @@ func ExtractFlowNodeFrom(flowContent *entity.FlowInfo) []*entity.FlowNode {
}
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 {
return
}
@@ -144,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) {
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
return
}
@@ -189,7 +195,12 @@ func (s *flowUserService) List(ctx context.Context, req *flowDto.ListFlowUserReq
if err != nil {
return
}
var user *beans.User
user, err = utils.GetUserInfo(ctx)
if err != nil {
return nil, err
}
req.Creator = user.UserName
list, total, err := flowDao.FlowUserDao.List(ctx, req)
if err != nil {
return

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,74 @@
package flow
import (
"ai-agent/workflow/consts/node"
nodeDao "ai-agent/workflow/dao/node"
"ai-agent/workflow/model/dto"
flowDto "ai-agent/workflow/model/dto/flow"
nodeDto "ai-agent/workflow/model/dto/node"
"ai-agent/workflow/model/entity"
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
commonHttp "gitea.com/red-future/common/http"
"gitea.com/red-future/common/utils"
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"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) {
headers := make(map[string]string)
if r := g.RequestFromCtx(ctx); r != nil {
@@ -30,7 +83,7 @@ func GetIsChatModel(ctx context.Context) (res *flowDto.GetIsChatModelRes, err er
return
}
func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string, error) {
func GetModelInfo(ctx context.Context, req *flowDto.GetModelInfoReq) (res *flowDto.GetModelInfoRes, err error) {
headers := make(map[string]string)
if r := g.RequestFromCtx(ctx); r != nil {
for k, v := range r.Request.Header {
@@ -39,16 +92,380 @@ func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string,
}
}
}
res := new(flowDto.CreateTaskRes)
res = new(flowDto.GetModelInfoRes)
err = commonHttp.Get(ctx, "model-gateway/model/getModel", headers, res, req)
return
}
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) (fullyConsumed bool)
collectFileUrls = func(val any) (fullyConsumed bool) {
switch {
case g.NewVar(val).IsSlice():
slice := gconv.SliceAny(val)
allConsumed := false
for _, item := range slice {
if collectFileUrls(item) {
allConsumed = true
}
}
return allConsumed
case g.NewVar(val).IsMap():
m := gconv.Map(val)
allConsumed := false
for _, item := range m {
if collectFileUrls(item) {
allConsumed = true
}
}
return allConsumed
default:
s := gconv.String(val)
if s != "" {
getFileTypeByPath := GetFileTypeByPath(s)
if getFileTypeByPath != "" {
consult = append(consult, flowDto.Consult{
Type: getFileTypeByPath,
Url: s,
})
return true
}
}
return false
}
}
var newUserForm []map[string]any
for _, m := range userForm {
for k, v := range m {
if collectFileUrls(v) {
delete(m, k)
}
}
if len(m) > 0 {
newUserForm = append(newUserForm, m)
}
}
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{
BuildType: buildType,
ModelName: modelName,
SkillName: skillName,
CallbackUrl: callbackUrl,
Cause: cause,
Form: form,
UserForm: newUserForm,
Consult: consult,
SessionId: sessionId,
NodeId: nodeId,
}
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 {
return
}
if g.IsEmpty(msgRes.TaskId) {
return nil, fmt.Errorf("msg is empty")
}
waitRes, err := Wait(ctx, msgRes.TaskId)
if err != nil {
return nil, err
}
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,
}
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(flowDto.ModelGatewayRes)
err := commonHttp.Post(ctx, "model-gateway/task/createTask", headers, res, &req)
if err != nil {
return "", err
}
if g.IsEmpty(res.TaskId) {
return "", fmt.Errorf("创建模型任务失败taskId为空")
}
return res.TaskId, nil
}
func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (res *flowDto.ComposeMessagesRes, err error) {
// waitGatewayResult waits for a created gateway task to complete and returns the result
func waitGatewayResult(ctx context.Context, taskId string) (map[string]any, error) {
waitRes, err := Wait(ctx, taskId)
if err != nil {
return nil, err
}
task := new(flowDto.ModelCallbackReq)
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("模型返回结果为空")
}
// 获取远程文件内容
//file, err := GetFileBytesFromURL(ctx, task.OssFile)
//if err != nil {
// return nil, err
//}
//task.Messages = gconv.Map(file)
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
}
if !nodeInput.Global.IsDialogue {
sessionId = ""
}
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 {
return nil, err
}
modelInfo, err := GetModelInfo(ctx, &flowDto.GetModelInfoReq{ModelName: nodeInput.Config.ModelConfig.ModelName})
if err != nil {
return nil, err
}
mapTaskResult = make([]map[string]any, len(composeResult.Messages.Rounds))
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("模型返回结果为空")
}
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
}
var wg sync.WaitGroup
errChan := make(chan error, len(taskIdList))
for idx, taskId := range taskIdList {
wg.Add(1)
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 {
// 直接用 innerKeysubtitle_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 {
@@ -57,74 +474,41 @@ func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (res
}
}
}
res = new(flowDto.ComposeMessagesRes)
err = commonHttp.Post(ctx, "prompts-core/prompt/composeMessages", headers, res, &req)
return
}
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,
})
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, modelTaskId)
return Wait(ctx, res.TaskId)
}
func GetTaskResult(ctx context.Context, result any) (*flowDto.TaskCallback, error) {
task := new(flowDto.TaskCallback)
if err := gconv.Struct(result, task); err != nil {
return nil, err
}
url, err := utils.GetFileAddressPrefix(ctx)
if err != nil {
return nil, err
}
// 获取远程文件内容
file, err := FetchRemoteJsonFile(ctx, url+task.OssFile)
if err != nil {
return nil, err
}
task.Text = gconv.String(file)
return task, nil
}
func FetchRemoteJsonFile(ctx context.Context, fileUrl string) ([]byte, error) {
// 1. 下载文件
func GetFileBytesFromURL(ctx context.Context, fileUrl string) ([]byte, error) {
// 使用 GoFrame 客户端(自带超时、追踪、日志等能力)
resp, err := g.Client().Get(ctx, fileUrl)
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()
// 校验状态码
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 GetImageBytesFromURL(url string) (all []byte, contentType string, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
all, err = io.ReadAll(resp.Body)
if err != nil {
return
}
contentType = resp.Header.Get("Content-Type")
return
return allBytes, nil
}
func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBytesRes, error) {
@@ -152,11 +536,317 @@ func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBy
// 发起上传请求
res := &dto.UploadFileBytesRes{}
url := "oss/file/uploadFile"
if err = commonHttp.Post(ctx, url, headers, res, body.Bytes()); err != nil {
httpUrl := "oss/file/uploadFile"
if err = commonHttp.Post(ctx, httpUrl, headers, res, body.Bytes()); err != nil {
return nil, err
}
g.Log().Infof(ctx, "[Upload] success url=%s size=%d", res.FileURL, res.FileSize)
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 {
// 生成单条HTML
var htmlBuilder strings.Builder
htmlBuilder.WriteString(`
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
background: #f5f5f5;
color: #333;
line-height: 1.8;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.item {
padding: 30px;
}
.image-group img {
width: 100%;
height: auto;
display: block;
margin-bottom: 6px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.image-group img:last-child {
margin-bottom: 0;
}
.image-group {
margin-bottom: 25px;
}
.text {
padding: 0;
font-size: 15px;
line-height: 1.4;
color: #555;
}
.text h2 {
font-size: 28px;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 15px;
line-height: 1.2;
}
.text h3 {
font-size: 20px;
font-weight: 600;
color: #2c3e50;
margin: 20px 0 12px;
padding-left: 12px;
border-left: 4px solid #409eff;
}
.text p {
margin-bottom: 12px;
text-align: justify;
}
.text strong {
color: #e74c3c;
font-weight: 600;
}
.text ul {
list-style: none;
padding: 0;
margin: 8px 0;
}
.text ul li {
padding: 10px 0 10px 30px;
position: relative;
line-height: 1.2;
}
.text ul li:before {
content: "●";
color: #409eff;
font-size: 12px;
position: absolute;
left: 12px;
top: 12px;
}
@media (max-width: 768px) {
body {
padding: 10px;
}
.text h2 {
font-size: 24px;
}
.text h3 {
font-size: 18px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="item">
`)
// 🔥 写入文案前:删除 <p class="image-count">需要配图X 张</p>
if text != "" {
// 写入清理后的文案
htmlBuilder.WriteString(fmt.Sprintf(`<div class="text">%s</div>`, ImageTagRegex(text)))
}
htmlBuilder.WriteString(`</div>
</div>
</body>
</html>`)
return htmlBuilder.String()
}
func BuildHtml(text string, images []string) string {
var htmlBuilder strings.Builder
htmlBuilder.WriteString(`<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", sans-serif;
padding: 20px;
background-color: #f6f6f6;
line-height: 1.7;
font-size: 16px;
color: #333;
}
.container {
max-width: 750px;
margin: 0 auto;
background: #fff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
}
</style>
</head>
<body>
<div class="container">
`)
// 写入图片支持0张、1张、多张
if len(images) > 0 {
htmlBuilder.WriteString(`<div class="image-group">`)
for _, imgUrl := range images {
htmlBuilder.WriteString(fmt.Sprintf(`<img src="%s" alt="图片"/>`, imgUrl))
}
htmlBuilder.WriteString(`</div>`)
}
htmlBuilder.WriteString(`
<div id="content">加载中...</div>
</div>
<script>
const incUrl = "` + text + `";
fetch(incUrl)
.then(res => {
if (!res.ok) throw new Error("加载失败");
return res.text();
})
.then(text => {
document.getElementById("content").innerHTML = text;
})
.catch(err => {
document.getElementById("content").innerHTML = "加载失败:" + err.message;
});
</script>
</body>
</html>`)
return htmlBuilder.String()
}
// ExtractImageCount 修复:支持单引号/双引号 + 换行 + 空格
func ExtractImageCount(content string) int {
// 🔥 关键:支持 class='image-count' (单引号)
re := regexp.MustCompile(`<p class=['"]image-count['"][^>]*>.*?(\d+).*?</p>`)
match := re.FindStringSubmatch(content)
if len(match) >= 2 {
num, err := strconv.Atoi(match[1])
if err == nil {
return num
}
}
return 0
}
func ImageTagRegex(html string) string {
// 🔥 修复支持单引号、双引号、空格、换行100% 删除 <p class='image-count'>
imageTagRegex := regexp.MustCompile(`<p class=['"]image-count['"][^>]*>[\s\S]*?</p>`)
return imageTagRegex.ReplaceAllString(html, "")
}
// StripHtmlTags 去掉所有HTML标签保留换行和文本结构并删除配图标记行
func StripHtmlTags(html string) string {
// 1. 替换块级标签为换行,保证排版
blockTags := regexp.MustCompile(`</?(div|p|h1|h2|h3|h4|h5|h6|li|ul|ol|br|tr|td|th)[^>]*>`)
text := blockTags.ReplaceAllString(html, "\n")
// 2. 去掉所有剩余的 HTML 标签
allTags := regexp.MustCompile(`<[^>]+>`)
text = allTags.ReplaceAllString(text, "")
// 4. 清理多余空行(多个换行只保留一个)
text = regexp.MustCompile(`\n\s*\n`).ReplaceAllString(text, "\n")
// 5. 只去掉首尾空白,中间换行保留
text = strings.TrimSpace(text)
return text
}
// SplitMultiContents 拆分模型返回的多条文案基于HTML标签分隔
func SplitMultiContents(htmlContent string) []string {
var contents []string
// 正则匹配<div class="content-item" id="content-{序号}">包裹的内容
re := regexp.MustCompile(`<div class="content-item" id="content-\d+">([\s\S]*?)</div>`)
matches := re.FindAllStringSubmatch(htmlContent, -1)
for _, match := range matches {
if len(match) > 1 {
// 清理空内容
trimmed := strings.TrimSpace(match[1])
if trimmed != "" {
contents = append(contents, trimmed)
}
}
}
// 兜底:如果没有匹配到结构化内容,按换行/分隔符拆分
if len(contents) == 0 {
contents = strings.Split(htmlContent, "===分隔符===") // 提示词中可新增此兜底规则
}
return contents
}
// GetAllImgSrcFromHtml 先把提取img src的工具方法放在外面
func GetAllImgSrcFromHtml(html string) []string {
var imgSrcList []string
re := regexp.MustCompile(`<img[^>]*src\s*=\s*["']([^"']+)["']`)
submatch := re.FindAllStringSubmatch(html, -1)
for _, match := range submatch {
if len(match) >= 2 {
imgSrcList = append(imgSrcList, match[1])
}
}
return imgSrcList
}
// ReplaceImgSrc 替换img src的方法
func ReplaceImgSrc(html string, oldSrc string, newSrc string) string {
// 精准替换:找到 <img xxx src="oldSrc" xxx>
re := regexp.MustCompile(`(<img[^>]*src\s*=\s*["'])` + regexp.QuoteMeta(oldSrc) + `(["'])`)
return re.ReplaceAllString(html, `${1}`+newSrc+`${2}`)
}

View File

@@ -6,7 +6,8 @@ import (
"context"
"fmt"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
)
@@ -15,35 +16,122 @@ var NodeLibraryService = &nodeLibraryService{}
type nodeLibraryService struct{}
func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.WorkflowNodeTreeReq) (*nodeDto.WorkflowNodeTreeRes, error) {
func GetModelType(ctx context.Context) (mainTypeMap map[int]string, err error) {
headers := make(map[string]string)
if r := g.RequestFromCtx(ctx); r != nil {
for k, v := range r.Request.Header {
if len(v) > 0 {
headers[k] = v[0]
}
}
}
res := new(nodeDto.ModelTypeResponse)
err = commonHttp.Get(ctx, "model-gateway/model/listType", headers, res, nil)
// 通用过滤:只保留 能被 100 整除的主类型100/200/300...
mainTypeMap = make(map[int]string)
for typ, name := range res.Type {
if typ%100 == 0 {
mainTypeMap[typ] = name
}
}
return
}
func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.WorkflowNodeTreeReq) (*nodeDto.WorkflowNodeTreeRes, error) {
WorkflowNodeGroups := []node.NodeGroupItem{
{
Group: node.NodeGroupComponent,
Label: node.NodeGroupNameComponent,
Items: []node.NodeItem{
{
NodeCode: node.NodeTypeTextModel,
NodeName: node.NodeNameTextModel,
ModelType: node.ModelTypeText,
SkillOption: true,
FormConfig: []node.NodeFormField{}, // 技能下拉
ModelConfig: []node.ModelItem{},
NodeCode: node.NodeTypeTextModel,
NodeName: node.NodeNameTextModel,
ModelType: node.ModelTypeText,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeImageModel,
NodeName: node.NodeNameImageModel,
ModelType: node.ModelTypeImage,
SkillOption: true,
FormConfig: []node.NodeFormField{}, // 技能下拉
ModelConfig: []node.ModelItem{},
NodeCode: node.NodeTypeImageModel,
NodeName: node.NodeNameImageModel,
ModelType: node.ModelTypeImage,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeVideoModel,
NodeName: node.NodeNameVideoModel,
ModelType: node.ModelTypeVideo,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeAudioModel,
NodeName: node.NodeNameAudioModel,
ModelType: node.ModelTypeAudio,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeBatchModel,
NodeName: node.NodeNameBatchModel,
ModelType: node.ModelTypeText,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
//{
// NodeCode: node.NodeTypeSenseOptimizeModel,
// NodeName: node.NodeNameSenseOptimizeModel,
// ModelType: node.ModelTypeText,
// SkillOption: false,
// FormConfig: []node.NodeFormField{},
// ModelConfig: []node.ModelItem{},
//},
//{
// NodeCode: node.NodeTypeStoryOptimizeModel,
// NodeName: node.NodeNameStoryOptimizeModel,
// ModelType: node.ModelTypeText,
// SkillOption: false,
// FormConfig: []node.NodeFormField{},
// ModelConfig: []node.ModelItem{},
//},
//{
// NodeCode: node.NodeTypeScriptOptimizeModel,
// NodeName: node.NodeNameScriptOptimizeModel,
// ModelType: node.ModelTypeText,
// SkillOption: false,
// FormConfig: []node.NodeFormField{},
// ModelConfig: []node.ModelItem{},
//},
},
},
{
Group: node.NodeGroupBase,
Label: node.NodeGroupNameBase,
Items: []node.NodeItem{
{
NodeCode: node.NodeTypeDataConversionModel,
NodeName: node.NodeNameDataConversionModel,
ModelType: node.ModelTypeText,
SkillOption: false,
PromptOption: true,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeMerge,
NodeName: node.NodeNameMerge,
@@ -51,6 +139,13 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeDataMerge,
NodeName: node.NodeNameDataMerge,
SkillOption: false,
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeJudge,
NodeName: node.NodeNameJudge,
@@ -67,6 +162,161 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
FormConfig: []node.NodeFormField{},
ModelConfig: []node.ModelItem{},
},
{
NodeCode: node.NodeTypeHttp,
NodeName: node.NodeNameHttp,
SkillOption: false,
IsSaveFile: true,
FormConfig: []node.NodeFormField{
{
Field: "method",
Label: "请求方式",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "GET", Value: "GET"},
{Label: "POST", Value: "POST"},
{Label: "PUT", Value: "PUT"},
{Label: "DELETE", Value: "DELETE"},
},
},
{
Field: "url",
Label: "请求地址",
Type: "input",
Required: true,
},
{
Field: "headers",
Label: "请求头支持Authorization鉴权",
Type: "keyValue",
Required: false,
},
{
Field: "bodyType",
Label: "请求体类型",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "无", Value: "None"},
{Label: "JSON", Value: "JSON"},
//{Label: "表单", Value: "FormUrlEncoded"},
//{Label: "文件上传", Value: "FormData"},
//{Label: "原生文本", Value: "Raw"},
},
},
{
Field: "body",
Label: "请求体内容",
Type: "keyValue",
Required: false,
},
{
Field: "response",
Label: "结果返回结构",
Type: "keyValue",
Required: false,
},
{
Field: "responseType",
Label: "结果返回方式",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "同步返回", Value: "sync"},
{Label: "等候回调", Value: "callback"},
{Label: "主动拉取", Value: "pull"},
},
Expand: []node.NodeFormField{
{
Field: "method",
Label: "请求方式",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "GET", Value: "GET"},
{Label: "POST", Value: "POST"},
{Label: "PUT", Value: "PUT"},
{Label: "DELETE", Value: "DELETE"},
},
},
{
Field: "url",
Label: "请求地址",
Type: "input",
Required: true,
},
{
Field: "headers",
Label: "请求头支持Authorization鉴权",
Type: "keyValue",
Required: false,
},
{
Field: "bodyType",
Label: "请求体类型",
Type: "select",
Required: true,
Options: []node.SelectOption{
{Label: "无", Value: "None"},
{Label: "JSON", Value: "JSON"},
//{Label: "表单", Value: "FormUrlEncoded"},
//{Label: "文件上传", Value: "FormData"},
//{Label: "原生文本", Value: "Raw"},
},
},
{
Field: "body",
Label: "请求体内容",
Type: "keyValue",
Required: false,
},
{
Field: "response",
Label: "结果返回结构",
Type: "keyValue",
Required: false,
},
{
Field: "timeout",
Label: "超时时间(秒)",
Type: "inputNumber",
Required: false,
Default: 30,
},
{
Field: "insecureSkipVerify",
Label: "跳过HTTPS证书校验",
Type: "switch",
Required: false,
Default: false,
},
},
},
{
Field: "callbackUrl",
Label: "回调地址(只需要填写字段名称)",
Type: "input",
Required: false,
Default: "",
},
{
Field: "timeout",
Label: "超时时间(秒)",
Type: "inputNumber",
Required: false,
Default: 30,
},
{
Field: "insecureSkipVerify",
Label: "跳过HTTPS证书校验",
Type: "switch",
Required: false,
Default: false,
},
},
ModelConfig: []node.ModelItem{},
},
//{
// NodeCode: node.NodeTypeModel,
// NodeName: node.NodeNameModel,
@@ -76,22 +326,22 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
//},
},
},
{
Group: node.NodeGroupCustom,
Label: node.NodeGroupNameCustom,
Items: []node.NodeItem{
{
NodeCode: node.NodeTypeCustomNode,
NodeName: node.NodeNameCustomNode,
SkillOption: true,
FormConfig: []node.NodeFormField{
{Field: "nodeName", Label: node.FormLabelApiKey, Type: "input", Required: true},
{Field: "nodeType", Label: node.FormLabelModel, Type: "input", Required: true},
},
ModelConfig: []node.ModelItem{},
},
},
},
//{
// Group: node.NodeGroupCustom,
// Label: node.NodeGroupNameCustom,
// Items: []node.NodeItem{
// {
// NodeCode: node.NodeTypeCustomNode,
// NodeName: node.NodeNameCustomNode,
// SkillOption: true,
// FormConfig: []node.NodeFormField{
// {Field: "nodeName", Label: node.FormLabelApiKey, Type: "input", Required: true},
// {Field: "nodeType", Label: node.FormLabelModel, Type: "input", Required: true},
// },
// ModelConfig: []node.ModelItem{},
// },
// },
//},
}
tree := &nodeDto.WorkflowNodeTreeRes{
Groups: WorkflowNodeGroups,
@@ -104,17 +354,16 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
// 遍历分组下的每个节点
for itemIdx := range group.Items {
item := &group.Items[itemIdx]
if item.NodeCode == node.NodeTypeTextModel {
if item.NodeCode == node.NodeTypeTextModel ||
item.NodeCode == node.NodeTypeImageModel ||
item.NodeCode == node.NodeTypeVideoModel ||
item.NodeCode == node.NodeTypeAudioModel ||
item.NodeCode == node.NodeTypeBatchModel ||
item.NodeCode == node.NodeTypeDataConversionModel {
item.ModelConfig = append(item.ModelConfig, node.ModelItem{
ModelName: "自定义",
})
}
if item.NodeCode == node.NodeTypeImageModel {
item.ModelConfig = append(item.ModelConfig, node.ModelItem{
ModelName: "自定义",
})
}
}
}

View 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.redpowerfuture.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
}

View File

@@ -4,14 +4,14 @@ import (
skillDao "ai-agent/workflow/dao/skill"
skillDto "ai-agent/workflow/model/dto/skill"
"ai-agent/workflow/model/entity"
"ai-agent/workflow/service"
"context"
"fmt"
"path/filepath"
"strings"
"gitea.com/red-future/common/beans"
commonHttp "gitea.com/red-future/common/http"
"gitea.com/red-future/common/utils"
"gitea.redpowerfuture.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -20,29 +20,12 @@ var SkillUserService = &skillUserService{}
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) {
ext := strings.TrimPrefix(filepath.Ext(req.FileUrl), ".")
if ext != "zip" {
return nil, fmt.Errorf("文件格式不支持,请上传zip格式文件")
}
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
return
}
@@ -91,7 +74,7 @@ func (s *skillUserService) Update(ctx context.Context, req *skillDto.UpdateSkill
if ext != "zip" {
return fmt.Errorf("文件格式不支持,请上传zip格式文件")
}
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
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) {
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
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) {
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
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) {
admin, err := IsAdmin(ctx)
admin, err := service.UtilService.IsAdmin(ctx)
if err != nil {
return
}
@@ -280,7 +263,7 @@ func (s *skillUserService) GetUserOrTemplate(ctx context.Context, req *skillDto.
return
}
req.Creator = user.UserName
var userList *entity.SkillUser
userList := new(entity.SkillUser)
userList, err = skillDao.SkillUserDao.Get(ctx, &skillDto.GetSkillUserReq{
Id: req.Id,
Creator: user.UserName,

View File

@@ -0,0 +1,29 @@
package service
import (
"context"
commonHttp "gitea.redpowerfuture.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
}