feat: 新增会话与结果管理功能及扩展字段

重构执行列表树状结构,扁平化日期节点下的输出项,新增会话列表查询、结果删除与软删除标记,引入扩展字段与模板配置,升级依赖版本。
This commit is contained in:
2026-07-08 09:22:54 +08:00
parent 4ccb81b34a
commit 14c88efe79
13 changed files with 257 additions and 122 deletions
+12 -8
View File
@@ -4,12 +4,14 @@ go 1.26.0
require (
gitea.redpowerfuture.com/red-future/common v0.0.29
github.com/cloudwego/eino v0.9.5
github.com/cloudwego/eino v0.9.12
github.com/cloudwego/eino-examples v0.0.0-20260630021147-171220631fb7
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9
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/stretchr/testify v1.11.1
github.com/tidwall/gjson v1.19.0
github.com/tidwall/sjson v1.2.5
go.opentelemetry.io/otel/trace v1.44.0
@@ -29,6 +31,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
@@ -65,7 +68,7 @@ require (
github.com/hashicorp/serf v0.10.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
@@ -81,15 +84,16 @@ require (
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
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/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f // 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/redis/go-redis/v9 v9.17.2 // 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/tidwall/pretty v1.2.1 // 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
@@ -105,8 +109,8 @@ require (
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
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/arch v0.19.0 // indirect
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
+18
View File
@@ -58,6 +58,10 @@ 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.9.5 h1:0Nftjx9gPek/2S/hzm38LVxSjk5/6mqRr3I9VKrKvm4=
github.com/cloudwego/eino v0.9.5/go.mod h1:OBD1mrkfkt/pJa4rkg1P0VnaMeOVl7l8IAdEqY//3IQ=
github.com/cloudwego/eino v0.9.12 h1:mHAMo5k7GdvnVD8Lc2sLyfpkxEm0S/y3PkEMhsSYt78=
github.com/cloudwego/eino v0.9.12/go.mod h1:OBD1mrkfkt/pJa4rkg1P0VnaMeOVl7l8IAdEqY//3IQ=
github.com/cloudwego/eino-examples v0.0.0-20260630021147-171220631fb7 h1:HU60xk+OI3g7b/C73vl8fyEnD1rLqOGHiVQAPBiDDG8=
github.com/cloudwego/eino-examples v0.0.0-20260630021147-171220631fb7/go.mod h1:pSszHeBqB4AuF0EY05eh6eUpVcJtDtsCTwbrSh8LnH0=
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=
@@ -235,6 +239,8 @@ github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uq
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -306,10 +312,14 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f h1:lJqhwddJVYAkyp72a4pwzMClI20xTwL7miDdm2W/KBM=
github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -331,6 +341,8 @@ github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg
github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -376,6 +388,8 @@ 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/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/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=
@@ -425,6 +439,8 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
@@ -435,6 +451,8 @@ golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+1
View File
@@ -106,6 +106,7 @@ type NodeItem struct {
NodeCode NodeType `json:"nodeCode"`
ModelType int `json:"modelType"`
NodeName string `json:"nodeName"` // 从常量来
PatchLayout bool `json:"patchLayout"`
SkillOption bool `json:"skillOption"`
PromptOption bool `json:"promptOption"`
IsSaveFile bool `json:"isSaveFile"`
@@ -38,3 +38,17 @@ func (c *flowExecution) Get(ctx context.Context, req *flowDto.GetFlowExecutionRe
func (c *flowExecution) List(ctx context.Context, req *flowDto.ListFlowExecutionReq) (res *flowDto.ListFlowExecutionTreeRes, err error) {
return flowService.FlowExecutionService.List(ctx, req)
}
func (c *flowExecution) GetSessionList(ctx context.Context, req *flowDto.GetSessionListReq) (res *flowDto.ListFlowExecutionRes, err error) {
return flowService.FlowExecutionService.GetSessionList(ctx, req)
}
func (c *flowExecution) DeleteResult(ctx context.Context, req *flowDto.DeleteResultReq) (res *beans.ResponseEmpty, err error) {
err = flowService.FlowExecutionService.DeleteResult(ctx, req)
return
}
func (c *flowExecution) DeleteSession(ctx context.Context, req *flowDto.DeleteSessionReq) (res *beans.ResponseEmpty, err error) {
err = flowService.FlowExecutionService.DeleteSession(ctx, req)
return
}
+5
View File
@@ -51,6 +51,11 @@ func (d *flowExecutionDao) Get(ctx context.Context, req *flowDto.GetFlowExecutio
func (d *flowExecutionDao) List(ctx context.Context, req *flowDto.ListFlowExecutionReq, fields ...string) (res []*entity.FlowExecution, total int, err error) {
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowExecution).Fields(fields).OmitEmpty()
model.Where(entity.FlowExecutionCol.Creator, req.Creator)
model.Where(entity.FlowExecutionCol.ResultDel, req.ResultDel)
model.Where(entity.FlowExecutionCol.SessionDel, req.SessionDel)
if req.IsResult {
model.WhereNot(entity.FlowExecutionCol.OutputParams, "[]")
}
model.OrderDesc(entity.FlowExecutionCol.CreatedAt)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
+46 -20
View File
@@ -30,6 +30,7 @@ type FlowExecutionInput struct {
ExecutionId int64 `json:"executionId"`
ConfigMap map[string]*entity.FlowNode `json:"configMap"`
SessionId string `json:"sessionId" dc:"会话ID"`
Templates []map[string]any `json:"templates"`
Desc string `json:"desc"`
SkillName string `json:"skillName"`
FileUrl []string `json:"fileUrl"`
@@ -184,6 +185,7 @@ type ExecuteReq struct {
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
SessionId string `json:"sessionId" dc:"会话ID"`
Templates []map[string]any `json:"templates"`
Desc string `json:"desc"`
SkillName string `json:"skillName"`
FileUrl []string `json:"fileUrl"`
@@ -213,6 +215,7 @@ type CreateFlowExecutionReq struct {
ErrorMessage string `json:"errorMessage" description:"错误信息"`
TraceId string `json:"traceId" description:"跟踪ID"`
SessionId string `json:"sessionId" dc:"会话ID"`
Extension map[string]interface{} `json:"extension"`
}
type CreateFlowExecutionRes struct {
@@ -220,15 +223,33 @@ 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:"输出参数"`
ErrorMessage string `json:"errorMessage" description:"错误信息"`
TraceId string `json:"traceId" description:"跟踪ID"`
TotalTokens int `json:"totalTokens" description:"总token"`
TotalFee float64 `json:"totalFee" description:"总费用"`
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-失败"`
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
ErrorMessage string `json:"errorMessage" description:"错误信息"`
TraceId string `json:"traceId" description:"跟踪ID"`
TotalTokens int `json:"totalTokens" description:"总token"`
TotalFee float64 `json:"totalFee" description:"总费用"`
SessionDel bool `json:"sessionDel" description:"会话是否删除"`
ResultDel bool `json:"resultDel" description:"结果是否删除"`
Extension map[string]interface{} `json:"extension"`
}
type DeleteResultReq struct {
g.Meta `path:"/deleteResult" method:"delete" tags:"任务管理" summary:"删除结果" dc:"删除结果"`
Id int64 `json:"id" v:"required#ID不能为空"`
Content string `json:"content"`
}
type DeleteSessionReq struct {
g.Meta `path:"/deleteSession" method:"delete" tags:"任务管理" summary:"删除会话" dc:"删除会话"`
Id int64 `json:"id" v:"required#ID不能为空"`
}
type GetFlowExecutionReq struct {
@@ -238,11 +259,20 @@ type GetFlowExecutionReq struct {
SessionId string `json:"sessionId" dc:"会话ID"`
}
type GetSessionListReq struct {
g.Meta `path:"/sessionList" method:"get" tags:"任务管理" summary:"会话列表" dc:"会话列表"`
*beans.Page `json:"page"`
}
type ListFlowExecutionReq struct {
g.Meta `path:"/list" method:"get" tags:"任务管理" summary:"任务列表" dc:"任务列表"`
Page *beans.Page `json:"page"`
Creator string `json:"creator"`
*beans.Page `json:"page"`
Creator string `json:"creator"`
SessionDel bool `json:"sessionDel"`
ResultDel bool `json:"resultDel"`
IsResult bool `json:"isResult"`
}
type ListFlowExecutionRes struct {
@@ -261,6 +291,7 @@ type VOFlowExecution struct {
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
ErrorMessage string `json:"errorMessage" description:"错误信息"`
Extension map[string]interface{} `json:"extension"`
TraceId string `json:"traceId" description:"跟踪ID"`
SessionId string `json:"sessionId" dc:"会话ID"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
@@ -269,22 +300,17 @@ type VOFlowExecution struct {
}
// ========== 核心:构建树状结构 ==========
// 定义树结构
type OutputItem struct {
Id int64 `json:"id,string" description:"ID"`
Timestamp string `json:"timestamp" description:"时间戳key"`
Content string `json:"content" description:"内容值"`
Type string `json:"type" description:"类型"`
Label string `json:"label" description:"后缀+数字标号"`
}
type FlowNode struct {
FlowName string `json:"flowName" description:"流程名称"`
Id int64 `json:"Id,string" description:"任务ID"`
SessionId string `json:"sessionId" description:"会话ID"`
Items []OutputItem `json:"items" description:"输出项列表"`
}
type DateNode struct {
CreateDate string `json:"createDate" description:"创建日期"`
Flows []FlowNode `json:"flows" description:"流程列表"`
CreateDate string `json:"createDate" description:"创建日期"`
Items []OutputItem `json:"items" description:"直接是结果项列表"`
}
type ListFlowExecutionTreeRes struct {
+9
View File
@@ -23,6 +23,9 @@ type FlowExecution struct {
SessionId string `orm:"session_id" json:"sessionId" description:"会话ID"`
TotalTokens int `orm:"total_tokens" json:"totalTokens" description:"总token消耗"`
TotalFee int `orm:"total_fee" json:"totalFee" description:"总费用"`
SessionDel bool `orm:"session_del" json:"sessionDel" description:"会话是否删除"`
ResultDel bool `orm:"result_del" json:"resultDel" description:"结果是否删除"`
Extension map[string]interface{} `orm:"extension" json:"extension" description:"扩展字段"`
}
type flowExecutionCol struct {
@@ -41,6 +44,9 @@ type flowExecutionCol struct {
SessionId string
TotalTokens string
TotalFee string
SessionDel string
ResultDel string
Extension string
}
var FlowExecutionCol = flowExecutionCol{
@@ -59,4 +65,7 @@ var FlowExecutionCol = flowExecutionCol{
SessionId: "session_id",
TotalTokens: "total_tokens",
TotalFee: "total_fee",
SessionDel: "session_del",
ResultDel: "result_del",
Extension: "extension",
}
+1
View File
@@ -19,6 +19,7 @@ type FlowNode struct {
NodeCode node.NodeType `json:"nodeCode"`
Name string `json:"name"`
Config map[string]interface{} `json:"config"`
PatchLayout bool `json:"patchLayout"`
SkillName string `json:"skillName"`
PromptContent string `json:"promptContent"`
IsSaveFile bool `json:"isSaveFile"`
+115 -89
View File
@@ -16,14 +16,12 @@ import (
"fmt"
"sort"
"strconv"
"strings"
"sync"
"time"
"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"
@@ -47,54 +45,100 @@ func (s *flowExecutionService) Get(ctx context.Context, req *flowDto.GetFlowExec
return res, err
}
func (s *flowExecutionService) DeleteResult(ctx context.Context, req *flowDto.DeleteResultReq) (err error) {
r, err := flowDao.FlowExecutionDao.Get(ctx, &flowDto.GetFlowExecutionReq{Id: req.Id})
if err != nil {
return err
}
// 遍历并剔除值等于 req.Url 的数据
newOutputParams := make([]map[string]any, 0)
for _, paramMap := range r.OutputParams {
// 单个 paramMap 过滤
cleanMap := make(map[string]any)
for k, v := range paramMap {
// 转为字符串对比
if gconv.String(v) != req.Content {
cleanMap[k] = v
}
}
// 只保留非空 map,避免出现空层级
if len(cleanMap) > 0 {
newOutputParams = append(newOutputParams, cleanMap)
}
}
// 赋值回原数据
r.OutputParams = newOutputParams
// 执行更新:更新 OutputParams + 标记删除
flowUpdateReq := new(flowDto.UpdateFlowExecutionReq)
flowUpdateReq.Id = req.Id
flowUpdateReq.OutputParams = r.OutputParams
_, err = flowDao.FlowExecutionDao.Update(ctx, flowUpdateReq)
return
}
func (s *flowExecutionService) DeleteSession(ctx context.Context, req *flowDto.DeleteSessionReq) (err error) {
flowUpdateReq := new(flowDto.UpdateFlowExecutionReq)
flowUpdateReq.Id = req.Id
flowUpdateReq.SessionDel = true
_, err = flowDao.FlowExecutionDao.Update(ctx, flowUpdateReq)
return
}
func (s *flowExecutionService) GetSessionList(ctx context.Context, req *flowDto.GetSessionListReq) (res *flowDto.ListFlowExecutionRes, err error) {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return
}
flowReq := new(flowDto.ListFlowExecutionReq)
flowReq.Page = req.Page
flowReq.Creator = user.UserName
flowReq.SessionDel = false
list, total, err := flowDao.FlowExecutionDao.List(ctx, flowReq)
if err != nil {
return nil, err
}
res = &flowDto.ListFlowExecutionRes{
Total: total,
}
err = gconv.Struct(list, &res.List)
return res, err
}
func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowExecutionReq) (res *flowDto.ListFlowExecutionTreeRes, err error) {
user, err := utils.GetUserInfo(ctx)
if err != nil {
return
}
req.Creator = user.UserName
req.IsResult = true
list, _, err := flowDao.FlowExecutionDao.List(ctx, req)
if err != nil {
return nil, err
}
// ===================== 核心修复:只统计【有数据】的执行记录,空的直接跳过 =====================
// ========== 只统计【有数据】的执行记录,空的直接跳过 ==========
executionNumber := make(map[int64]int) // executionId -> 倒序编号(最新=1
// 第一次遍历:只处理【有输出参数】的记录,统计并分配编号
var validList []*entity.FlowExecution // 只存有效(非空)记录
var validList []*entity.FlowExecution
for _, execution := range list {
if g.IsEmpty(execution.OutputParams) {
continue // 空数据直接过滤,不参与编号、不展示
continue
}
validList = append(validList, execution)
}
// 给有效记录分配【时间倒序编号】(最新=1)
totalValid := len(validList)
for idx, execution := range validList {
executionNumber[execution.Id] = totalValid - idx
}
// 2. 分组映射:日期 -> 流程节点
type flowWrap struct {
flowNode flowDto.FlowNode
createdAt *gtime.Time
}
dateMap := make(map[string]*[]flowWrap)
// ========== 分组:日期 -> 合并所有 OutputItem ==========
dateMap := make(map[string][]flowDto.OutputItem)
// 遍历【有效数据】构建结构
for _, execution := range validList {
createDate := execution.CreatedAt.Format("Y-m-d")
flowName := execution.FlowName
execID := execution.Id // 拿到执行ID
outputParams := execution.OutputParams
// 编号只算有效数据,不会把空的算进去
num := executionNumber[execution.Id]
displayFlowName := fmt.Sprintf("会话-%d(%s)", num, flowName)
// 3. 解析 outputParams
// 解析 outputParams
var tempItems []flowDto.OutputItem
for _, paramMap := range outputParams {
for tsKey, value := range paramMap {
@@ -102,13 +146,12 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
continue
}
tempItems = append(tempItems, flowDto.OutputItem{
Id: execID,
Timestamp: tsKey,
Content: gconv.String(value),
})
}
}
// ===================== 修复1:如果解析后依然为空,直接跳过,不生成第二层节点 =====================
if len(tempItems) == 0 {
continue
}
@@ -126,21 +169,17 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
item := &tempItems[idx]
val := item.Content
suffix := "内容"
ext := ""
ext = GetFileTypeByPath(val)
if ext == "image" {
ext := GetFileTypeByPath(val)
switch ext {
case "image":
suffix = "图片"
}
if ext == "video" {
case "video":
suffix = "视频"
}
if ext == "audio" {
case "audio":
suffix = "音频"
}
if ext == "text" {
case "text":
suffix = "文案"
}
if ext == "html" {
case "html":
suffix = "HTML"
}
suffixCount[suffix]++
@@ -148,48 +187,23 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
item.Label = fmt.Sprintf("%s_%d", suffix, suffixCount[suffix])
}
// 组装节点
flowNode := flowDto.FlowNode{
FlowName: displayFlowName,
Id: execution.Id,
SessionId: gconv.String(execution.SessionId),
Items: tempItems,
}
if dateMap[createDate] == nil {
dateMap[createDate] = &[]flowWrap{}
}
*dateMap[createDate] = append(*dateMap[createDate], flowWrap{
flowNode: flowNode,
createdAt: execution.CreatedAt,
})
// 直接追加到对应日期下(不再包一层 FlowNode)
dateMap[createDate] = append(dateMap[createDate], tempItems...)
}
// 6. 构建树 + 排序
// ========== 构建树并排序 ==========
var tree []flowDto.DateNode
for date, wraps := range dateMap {
// 第二层按创建时间倒序(最新在前)
sort.Slice(*wraps, func(i, j int) bool {
return (*wraps)[i].createdAt.After((*wraps)[j].createdAt)
})
var flowNodes []flowDto.FlowNode
for _, w := range *wraps {
flowNodes = append(flowNodes, w.flowNode)
}
// ===================== 修复2:日期下没有流程,也过滤掉 =====================
if len(flowNodes) == 0 {
for date, items := range dateMap {
if len(items) == 0 {
continue
}
tree = append(tree, flowDto.DateNode{
CreateDate: date,
Flows: flowNodes,
Items: items,
})
}
// 第一层日期倒序
// 日期倒序
sort.Slice(tree, func(i, j int) bool {
return tree[i].CreateDate > tree[j].CreateDate
})
@@ -294,17 +308,24 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
var executionId int64
var isDialogue bool
var nodeGroupId = uuid.NewString()
flowName := req.FlowName
if !g.IsEmpty(req.Desc) {
flowName = req.Desc
}
isDialogue = false
if flowInfo == nil {
isDialogue = false
var r = new(flowDto.CreateFlowExecutionReq)
r.FlowUserId = req.FlowId
r.FlowName = req.FlowName
r.FlowName = flowName
r.NodeGroupId = nodeGroupId
r.TriggerType = flow.FlowExecutionTriggerTypeManual.Code()
r.FlowContent = req.FlowContent
r.NodeInputParams = req.NodeInputParams
r.SessionId = req.SessionId
r.Status = flow.FlowExecutionStatusRunning.Code()
r.Extension = map[string]any{
"templates": req.Templates,
}
span := trace.SpanFromContext(ctx)
if span != nil && span.SpanContext().HasTraceID() {
r.TraceId = span.SpanContext().TraceID().String()
@@ -316,7 +337,6 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
return
}
} else {
isDialogue = true
executionId = flowInfo.Id
span := trace.SpanFromContext(ctx)
if span != nil && span.SpanContext().HasTraceID() {
@@ -324,10 +344,15 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
cancelMap.Store(traceId, cancel)
}
executionReq := flowDto.UpdateFlowExecutionReq{
Id: executionId,
NodeGroupId: nodeGroupId,
Status: flow.FlowExecutionStatusRunning.Code(),
TraceId: traceId,
Id: executionId,
NodeGroupId: nodeGroupId,
FlowContent: req.FlowContent,
NodeInputParams: req.NodeInputParams,
Extension: map[string]any{
"templates": req.Templates,
},
Status: flow.FlowExecutionStatusRunning.Code(),
TraceId: traceId,
}
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
if err != nil {
@@ -349,20 +374,20 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
}
}
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("文件格式不支持")
}
//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("文件格式不支持")
//}
// =========================================================================
// ✅【第1步】给所有判断节点自动生成意图识别节点
@@ -443,6 +468,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
IsDialogue: isDialogue,
ExecutionId: executionId,
ConfigMap: configMap,
Templates: req.Templates,
SessionId: req.SessionId,
Desc: req.Desc,
SkillName: req.SkillName,
+1 -1
View File
@@ -663,7 +663,7 @@ func SummaryLambda(ctx context.Context, input any) (any, error) {
// 生成 毫秒时间戳 作为 KEY
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
item := make(map[string]interface{})
item[timeKey] = field.Value
item[timeKey] = ProcessPath(ctx, gconv.String(field.Value))
summaryResult = append(summaryResult, item)
}
}
+1 -1
View File
@@ -898,7 +898,7 @@ func HttpNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]nod
}
// 构建请求参数
newBody := BuildNestedJson(body, nodeInput.Global.ConfigMap)
newBody := BuildNestedJson(body, nodeInput.Global)
// 1. 自己生成唯一 taskId(不用前端给)
taskId := "my_task_" + uuid.New().String() // 自己生成唯一ID
if responseType == "callback" {
+33 -3
View File
@@ -413,7 +413,9 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
// 加锁写入map,解决并发竞态
mu.Lock()
fmt.Println("taskResult======================", idx, taskResult)
mapTaskResult[idx] = taskResult
fmt.Println("mapTaskResult======================", mapTaskResult)
mu.Unlock()
//updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, taskResult)
@@ -440,13 +442,17 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
//updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, item)
}
}
fmt.Println("mapTaskResult--------------------------------------", mapTaskResult)
return mapTaskResult, nil
}
func BuildNestedJson(body g.Map, mockConfigMap map[string]*entity.FlowNode) g.Map {
func BuildNestedJson(body g.Map, global *flowDto.FlowExecutionInput) g.Map {
jsonStr := "{}"
for originKey, originItem := range body {
if originKey == "templates" && !g.IsEmpty(global.Templates) {
jsonStr, _ = sjson.Set(jsonStr, originKey, global.Templates)
continue
}
bodyItemMap := gconv.Map(originItem)
val := bodyItemMap["value"]
if v, ok := bodyItemMap["value"]; ok {
@@ -457,7 +463,7 @@ func BuildNestedJson(body g.Map, mockConfigMap map[string]*entity.FlowNode) g.Ma
valMap := gconv.Map(val)
nodeId := gconv.String(valMap["nodeId"])
fieldName := gconv.String(valMap["field"])
if configValue, ok := mockConfigMap[nodeId]; ok {
if configValue, ok := global.ConfigMap[nodeId]; ok {
if !g.IsEmpty(configValue.OutputResult) {
for _, v := range configValue.OutputResult {
if strings.Contains(v.Field, fieldName) {
@@ -659,6 +665,30 @@ func GetUrlSuffix(rawUrl string, withDot bool) string {
return suffix
}
// ProcessPath 处理请求路径
// 1. 判断是否为合法完整请求路径(以/开头)
// 2. 包含 tenantId-1 则截断其及前面所有内容
func ProcessPath(ctx context.Context, path string) string {
// 判断是否是完整请求路径:以 / 开头
if strings.HasPrefix(path, "/") {
return path
}
target, err := utils.GetBucketName(ctx)
if err != nil {
return path
}
idx := strings.Index(path, target)
if idx == -1 {
// 不包含目标字符串,原样返回
return path
}
// 截取 tenantId-1 后面的内容
newPath := path[idx+len(target):]
return newPath
}
func BuildText(text string) string {
// 生成单条HTML
var htmlBuilder strings.Builder
@@ -67,6 +67,7 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
NodeCode: node.NodeTypeVideoModel,
NodeName: node.NodeNameVideoModel,
ModelType: node.ModelTypeVideo,
PatchLayout: true,
SkillOption: false,
PromptOption: true,
IsSaveFile: true,