7 Commits

61 changed files with 1137 additions and 885 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
@@ -10,12 +10,6 @@ ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
ENV CGO_ENABLED=0
ENV GOTOOLCHAIN=auto
ENV GOPRIVATE=gitea.com/red-future/common
# 配置git使用私有Gitea仓库
RUN git config --global url."http://x-token-auth:9b31146aa8c10a7cb4f2e49dcee0934a223be1076289810e1ad98b968066c2bc@116.204.74.41:3000/red-future/common.git".insteadOf "https://gitea.com/red-future/common.git" && \
git config --global credential.helper store
WORKDIR /build
COPY . .
@@ -24,25 +18,6 @@ RUN go mod download && go mod tidy
RUN go build -ldflags="-s -w" -o main ./main.go
# 阶段2: 运行
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
COPY --from=builder /build/main .
COPY --from=builder /build/config.yml ./
RUN mkdir -p /app/resource/log/run \
/app/resource/log/server \
&& adduser -D -u 1000 appuser \
&& chown -R appuser:appuser /app
USER appuser
EXPOSE 3005

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

33
go.mod
View File

@@ -1,19 +1,17 @@
module ai-agent
go 1.26.0
go 1.26.1
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
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
@@ -64,7 +62,7 @@ require (
github.com/hashicorp/golang-lru v1.0.2 // indirect
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/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.10 // indirect
@@ -94,20 +92,21 @@ require (
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yargevad/filepathx v1.0.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.4.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
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // 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
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.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

87
go.sum
View File

@@ -1,8 +1,6 @@
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.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=
@@ -58,8 +56,8 @@ github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
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=
@@ -114,16 +112,16 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
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/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/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.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 +169,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=
@@ -235,8 +231,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -290,7 +286,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=
@@ -339,8 +334,8 @@ github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6
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=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
@@ -393,26 +388,26 @@ github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5
github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
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/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
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.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=
@@ -426,8 +421,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
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=
@@ -436,8 +431,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -454,8 +449,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -464,8 +459,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -492,17 +487,17 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -512,8 +507,8 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -523,6 +518,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=

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"
)

View File

@@ -12,12 +12,16 @@ const (
const (
NodeNameTextModel = "生成文案"
NodeNameImageModel = "生成图片"
NodeNameVideoModel = "视频"
NodeNameVideoModel = "生成视频"
NodeNameSenseOptimize = "语义优化"
NodeNameStoryOptimize = "分镜优化"
NodeNameScriptOptimize = "剧本优化"
NodeNameAudioModel = "音频"
NodeNameModel = "模型"
NodeNameMerge = "结果合并"
NodeNameJudge = "条件判断"
NodeNameForm = "表单"
NodeNameHttp = "HTTP(S)接口"
NodeNameCustomNode = "自定义节点"
)
@@ -45,6 +49,9 @@ const (
NodeTypeTextModel NodeType = "text_model"
NodeTypeImageModel NodeType = "image_model"
NodeTypeVideoModel NodeType = "video_model"
NodeTypeSenseOptimize NodeType = "sense_optimize"
NodeTypeStoryOptimize NodeType = "story_optimize"
NodeTypeScriptOptimize NodeType = "script_optimize"
NodeTypeAudioModel NodeType = "audio_model"
// 基础
@@ -53,7 +60,7 @@ const (
NodeTypeJudge NodeType = "judge"
NodeTypeForm NodeType = "form"
NodeTypeIntent NodeType = "intent"
NodeTypeHttp NodeType = "http"
// 自定义
NodeTypeCustomNode NodeType = "custom_node"
)
@@ -102,91 +109,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

@@ -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

@@ -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{}

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

@@ -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

@@ -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

@@ -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"
)
@@ -137,6 +137,7 @@ type ExecuteReq struct {
Desc string `json:"desc"`
SkillName string `json:"skillName"`
FileUrl []string `json:"fileUrl"`
ResultUrl string `json:"resultUrl"`
}
type ExecuteRes struct {

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

@@ -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

@@ -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 {

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 {

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

@@ -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

@@ -16,7 +16,7 @@ import (
"strings"
"sync"
"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"
@@ -127,7 +127,7 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
suffix = "图片"
case strings.Contains(val, "html") || strings.Contains(val, "HTML"):
suffix = "HTML"
case strings.Contains(val, "txt") || len(val) > 50:
case strings.Contains(val, "inc") || len(val) > 50:
suffix = "文案"
}
@@ -351,29 +351,19 @@ 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
//}
if isDialogue && !g.IsEmpty(flowInfo) && !g.IsEmpty(req.ResultUrl) {
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步】给所有判断节点自动生成意图识别节点
@@ -415,7 +405,8 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
// =========================================================================
// ✅【第2步】构建执行图
// =========================================================================
runGraph, err := BuildGraphFromFlowContent(execCtx, req.FlowContent, judge2IntentNodeMap, summaryNodeID)
var runGraph compose.Runnable[any, any]
runGraph, err = BuildGraphFromFlowContent(execCtx, req.FlowContent, judge2IntentNodeMap, summaryNodeID)
if err != nil {
executionReq := flowDto.UpdateFlowExecutionReq{
Id: executionId,
@@ -480,9 +471,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
}
return nil, fmt.Errorf("执行工作流失败: %v", err)
}
return
}
// BuildGraphFromFlowContent 根据前端保存的工作流JSON自动构建执行图

View File

@@ -7,7 +7,9 @@ import (
"ai-agent/workflow/model/entity"
"context"
commonHttp "gitea.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/beans"
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -189,7 +191,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

View File

@@ -9,106 +9,19 @@ import (
"ai-agent/workflow/model/dto"
fileDto "ai-agent/workflow/model/dto/file"
flowDto "ai-agent/workflow/model/dto/flow"
"ai-agent/workflow/model/entity"
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"gitea.com/red-future/common/db/gfdb"
"gitea.com/red-future/common/utils"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, node *entity.FlowNode) (map[string]any, map[string]any, map[string]any) {
input := make(map[string]any)
output := make(map[string]any)
model := make(map[string]any)
// 1. 有引用 → 取引用节点的字段值
if len(node.InputSource) > 0 {
for _, source := range node.InputSource {
refNodeID := source.NodeId
isQuoteOutput := source.QuoteOutput
fields := source.Field
refNode, ok := execInput.ConfigMap[refNodeID]
if !ok {
continue
}
inputMap := buildInputMap(refNode)
outputMap := mergeOutput(refNode.OutputResult)
modelMap := mergeModel(refNode.ModelConfig)
if isQuoteOutput {
for k, v := range outputMap {
output[k] = v
}
}
if len(fields) > 0 {
// 取指定字段
for _, f := range fields {
if v, ok := inputMap[f]; ok {
input[f] = v
}
if v, ok := modelMap[f]; ok {
model[f] = v
}
}
} else {
// 取全部
for k, v := range inputMap {
input[k] = v
}
for k, v := range modelMap {
model[k] = v
}
}
}
}
return input, output, model
}
// buildInputMap 从 FormConfig 构造输入map
func buildInputMap(node *entity.FlowNode) map[string]any {
m := make(map[string]any)
for _, item := range node.FormConfig {
m[item.Label] = item
}
return m
}
// mergeOutput 合并节点输出 []map → 单map
func mergeOutput(output []node.NodeFormField) map[string]any {
m := make(map[string]any)
for _, item := range output {
m[item.Label] = item
}
return m
}
// mergeOutput 合并节点输出 []map → 单map
// 合并成你需要的 { key: { value: xxx } } 结构
func mergeModel(output node.ModelItem) map[string]any {
m := make(map[string]any)
// 遍历 output.ModelForm 里的每一个 key 和原始值
for key, rawValue := range output.ModelForm {
// 包装成 { "value": 原始值 }
m[key] = map[string]any{
"value": rawValue,
}
}
return m
}
func StartLambda(ctx context.Context, input any) (any, error) {
return input, nil
}
@@ -118,9 +31,14 @@ func FormLambda(ctx context.Context, input any) (any, error) {
}
func IntentLambda(ctx context.Context, input any) (any, error) {
return input, nil
}
// JudgeLambda 分支判断核心读取IntentLambda的输出 → 返回目标节点ID做路由
func JudgeLambda(ctx context.Context, input any) (string, error) {
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
if !ok {
return nil, fmt.Errorf("入参类型错误,期望 *flowDto.NodeExecutionInput实际 %T", input)
return "", fmt.Errorf("入参类型错误,期望 *flowDto.NodeExecutionInput实际 %T", input)
}
// 1. 直接用你原来的方法(返回两个 map
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
@@ -132,41 +50,21 @@ func IntentLambda(ctx context.Context, input any) (any, error) {
}
for _, valueAny := range outputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
if !strings.Contains(field.Field, "html") && !strings.Contains(field.Field, "img") {
outputResult = append(outputResult, field)
}
}
}
for _, valueAny := range modelMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
nodeInput.Config.OutputResult = outputResult
return nodeInput, nil
}
// JudgeLambda 分支判断核心读取IntentLambda的输出 → 返回目标节点ID做路由
func JudgeLambda(ctx context.Context, input any) (string, error) {
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
if !ok {
return "", fmt.Errorf("入参类型错误,期望 *flowDto.NodeExecutionInput实际 %T", input)
}
out := new([]node.NodeFormField)
err := gconv.Structs(nodeInput.Config.OutputResult, out)
if err != nil {
return "", err
}
contextParts := ""
for _, v := range nodeInput.Config.FormConfig {
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value)
}
if !nodeInput.Global.IsDialogue {
for _, v := range *out {
for _, v := range outputResult {
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value)
}
}
@@ -189,7 +87,6 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
if err != nil {
return "", err
}
req := flowDto.ComposeMessagesReq{
BuildType: 2,
ModelName: getIsChatModel.ModelName,
@@ -224,339 +121,28 @@ func TextModelLambda(ctx context.Context, input any) (any, error) {
if !ok {
return nil, fmt.Errorf("入参类型错误")
}
// 1. 直接用你原来的方法(返回两个 map
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
var outputResult []node.NodeFormField
for _, valueAny := range inputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
resultUserFrom := make(map[string]any)
for _, valueAny := range outputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
if !strings.Contains(field.Field, "text_url") && !strings.Contains(field.Field, "img_url") {
if strings.Contains(field.Field, "text_content") {
field.Value = stripHtmlTags(field.Value, false)
}
resultUserFrom[field.Label] = field
}
}
}
for _, valueAny := range modelMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
if !nodeInput.Global.IsDialogue {
for _, item := range outputResult {
resultUserFrom[item.Label] = item
}
for _, item := range nodeInput.Config.FormConfig {
resultUserFrom[item.Label] = item
}
}
if !g.IsEmpty(nodeInput.Global.Desc) {
resultUserFrom["desc"] = node.NodeFormField{
Value: nodeInput.Global.Desc,
Field: "desc",
Label: "描述",
Type: "text",
}
}
resultFrom := make(map[string]any)
for key, item := range nodeInput.Config.ModelConfig.ModelForm {
resultFrom[key] = map[string]any{
"value": item,
}
}
var skillName = nodeInput.Config.SkillName
if g.IsEmpty(nodeInput.Config.SkillName) {
skillName = nodeInput.Global.SkillName
}
contentStr := "你是专业内容生成助手请严格按以下规则输出内容1、输出标准 HTML 片段,不要 Markdown不要 ``` 符号不要多余解释2、整体用 <div class=\"report-container\"> 包裹3、主标题使用 <h2 class=\"title\">4、章节标题使用 <h3 class=\"section-title\">5、正文段落使用 <p class=\"paragraph\">6、列表使用 <ul class=\"list\"><li>...</li></ul>7、重点内容使用 <strong> 加粗8、段落之间清晰分隔结构规整9、如果生成多条文案每条文案独立用 <div class=\"content-item\" id=\"content-{序号}\"> 包裹序号从1开始10、每条文案内部必须在最上方添加一行固定格式<p class=\"image-count\">需要配图N 张</p> N 是这条文案需要的图片数量只能是数字不能是其他文字11、只输出 HTML 结构,不输出任何额外文字"
resultUserFrom["prompt"] = contentStr
req := flowDto.ComposeMessagesReq{
BuildType: 1,
ModelName: nodeInput.Config.ModelConfig.ModelName,
SkillName: skillName,
Cause: "文案节点",
Form: resultFrom,
UserForm: resultUserFrom,
UserFiles: nodeInput.Global.FileUrl,
SessionId: nodeInput.Global.SessionId,
}
msg, err := ComposeMessages(ctx, &req)
skillName, from, userFrom := BuildParam(nodeInput)
outputRes, err := TextNode(ctx, nodeInput.Global.SessionId, nodeInput.Config.ModelConfig.ModelName, skillName, from, userFrom, nodeInput.Config.ModelConfig.ModelResponse, nodeInput.Global.FileUrl)
if err != nil {
return nil, err
}
if g.IsEmpty(msg.Messages) {
return nil, fmt.Errorf("msg is empty")
}
taskResult, err := GatewayTask(ctx, msg.EpicycleId, nodeInput.Config.ModelConfig.ModelName, msg.Messages)
if err != nil {
return nil, err
}
result, err := GetTaskResult(ctx, taskResult)
if err != nil {
return "", err
}
mapTaskResult := gconv.Map(result.Text)
resultContent := ""
for key, _ := range nodeInput.Config.ModelConfig.ModelResponse {
resultContent = gconv.String(mapTaskResult[key])
}
// 拆分多条文案
contentList := SplitMultiContents(resultContent)
outputRes := make([]node.NodeFormField, 0)
for i, content := range contentList {
// 文案内容content_0, content_1, content_2...
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("text_content_%d", i),
Value: content,
Label: fmt.Sprintf("文案内容_%d", i),
Type: "string",
Expand: extractImageCount(content),
})
// 1. 去掉 HTML 标签,生成纯文本
plainText := stripHtmlTags(content, true)
// 2. 上传纯文本到 OSS
textFileName := fmt.Sprintf("ai_text_%d_%d.txt", time.Now().UnixMilli(), i)
textUrl, err := Upload(ctx, &dto.UploadFileBytesReq{
FileBytes: []byte(plainText),
FileName: textFileName,
})
if err != nil {
return nil, err
}
// 3. 把纯文本地址存入输出
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("%v:text_url:%d", nodeInput.Config.Id, i),
Value: textUrl.FileURL,
Label: fmt.Sprintf("文案纯文本_txt_%d", i),
Type: "string",
Expand: extractImageCount(content),
})
}
nodeInput.Config.OutputResult = outputRes
return nodeInput, nil
}
// 从 HTML 内容里提取图片数量(例如从 <p class="image-count">需要配图3 张</p> 拿到 3
func extractImageCount(content string) int {
re := regexp.MustCompile(`<p class="image-count">[^\d]*(\d+)[^\d]*</p>`)
match := re.FindStringSubmatch(content)
if len(match) >= 2 {
num, _ := strconv.Atoi(match[1])
return num
}
return 0
}
// stripHtmlTags 去掉所有HTML标签保留换行和文本结构并删除配图标记行
func stripHtmlTags(html string, delImageCount bool) string {
if delImageCount {
// 🔥 第一步:直接删除整个 <p class="image-count">...</p> 标签(包含内容)
imageTagRegex := regexp.MustCompile(`<p class="image-count">[\s\S]*?</p>`)
html = imageTagRegex.ReplaceAllString(html, "")
}
// 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
}
// ImageModelLambda 构建图片
func ImageModelLambda(ctx context.Context, input any) (any, error) {
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
if !ok {
return nil, fmt.Errorf("入参类型错误")
}
// 1. 直接用你原来的方法(返回两个 map
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
var outputResult []node.NodeFormField
for _, valueAny := range inputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
resultUserFrom := make(map[string]any)
for _, valueAny := range outputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
if !strings.Contains(field.Field, "text_url") && !strings.Contains(field.Field, "img_url") {
if strings.Contains(field.Field, "text_content") {
field.Value = stripHtmlTags(field.Value, false)
}
resultUserFrom[field.Label] = field
}
}
}
for _, valueAny := range modelMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
if !nodeInput.Global.IsDialogue {
for _, item := range outputResult {
resultUserFrom[item.Label] = item
}
for _, item := range nodeInput.Config.FormConfig {
resultUserFrom[item.Label] = item
}
}
if !g.IsEmpty(nodeInput.Global.Desc) {
resultUserFrom["desc"] = node.NodeFormField{
Value: nodeInput.Global.Desc,
Field: "desc",
Label: "描述",
Type: "text",
}
}
resultFrom := make(map[string]any)
for key, item := range nodeInput.Config.ModelConfig.ModelForm {
resultFrom[key] = map[string]any{
"value": item,
}
}
var skillName = nodeInput.Config.SkillName
if g.IsEmpty(nodeInput.Config.SkillName) {
skillName = nodeInput.Global.SkillName
}
req := flowDto.ComposeMessagesReq{
BuildType: 1,
ModelName: nodeInput.Config.ModelConfig.ModelName,
SkillName: skillName,
Cause: "图片节点",
Form: resultFrom,
UserForm: resultUserFrom,
UserFiles: nodeInput.Global.FileUrl,
SessionId: nodeInput.Global.SessionId,
}
msg, err := ComposeMessages(ctx, &req)
skillName, from, userFrom := BuildParam(nodeInput)
outputRes, err := ImgNode(ctx, nodeInput.Global.SessionId, nodeInput.Config.ModelConfig.ModelName, skillName, from, userFrom, nodeInput.Config.ModelConfig.ModelResponse, nodeInput.Global.FileUrl)
if err != nil {
return nil, err
}
if g.IsEmpty(msg.Messages) {
return nil, fmt.Errorf("msg is empty")
}
taskResult, err := GatewayTask(ctx, msg.EpicycleId, nodeInput.Config.ModelConfig.ModelName, msg.Messages)
if err != nil {
return "", err
}
result, err := GetTaskResult(ctx, taskResult)
if err != nil {
return "", err
}
mapTaskResult := gconv.Map(result.Text)
imgs := []string{}
for key, _ := range nodeInput.Config.ModelConfig.ModelResponse {
imgs = gconv.Strings(mapTaskResult[key])
}
var images []string
for _, item := range imgs {
mapItem := gconv.Map(item)
for _, value := range mapItem {
values := ""
values, ok = value.(string)
if !ok {
return nil, fmt.Errorf("图片地址类型错误")
}
// 下载官方临时图片
imgBytes, _, err := GetImageBytesFromURL(values)
if err != nil {
return nil, fmt.Errorf("下载图片失败: %w", err)
}
// 构造文件名
fileName := fmt.Sprintf("ai_image_%d.png", time.Now().UnixMilli())
// 上传到你的OSS你项目已有的Upload方法
upResp, err := Upload(ctx, &dto.UploadFileBytesReq{
FileName: fileName,
FileBytes: imgBytes,
})
if err != nil {
return nil, fmt.Errorf("上传OSS失败: %w", err)
}
images = append(images, upResp.FileURL)
}
}
url, err := utils.GetFileAddressPrefix(ctx)
if err != nil {
return nil, err
}
outputRes := make([]node.NodeFormField, 0)
for i, item := range images {
// 图片image_0, image_1, image_2...
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("image_%d", i),
Value: fmt.Sprintf("%s%s", url, item),
Label: fmt.Sprintf("图片_%d", i),
Type: "string",
})
// 额外存储关联关系
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("%v:img_url:%d", nodeInput.Config.Id, i),
Value: fmt.Sprintf("%s%s", url, item),
Label: fmt.Sprintf("图片_img_%d关联文案ID", i),
Type: "string",
})
}
nodeInput.Config.OutputResult = outputRes
return input, nil
return nodeInput, nil
}
func MergeLambda(ctx context.Context, input any) (any, error) {
@@ -577,7 +163,7 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
// 2. 提取所有文案text_content_0,1,2...
var contents []node.NodeFormField
for i := 0; ; i++ {
key := fmt.Sprintf("text_content_%d", i)
key := fmt.Sprintf("text_url:%d", i)
val, has := dataMap[key]
if !has || val.Value == "" {
break
@@ -588,7 +174,7 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
// 3. 提取所有图片image_0,1,2...
var images []string
for i := 0; ; i++ {
key := fmt.Sprintf("image_%d", i)
key := fmt.Sprintf("img_url:%d", i)
val, has := dataMap[key]
if !has || val.Value == "" {
break
@@ -638,11 +224,15 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
// 🔥 把现有数据转换成通用 Item 列表(支持:纯文案、纯图片、图文任意组合)
var allItems []Item
url, err := utils.GetFileAddressPrefix(ctx)
if err != nil {
return nil, err
}
// 情况1有文案 → 按文案条目生成 Item每条文案+对应图片)
if len(contents) > 0 {
for i, val := range contents {
item := Item{
Content: val.Value, // 文案
Content: url + val.Value, // 文案
Images: textImgMap[i], // 自动绑定该条目的图片(没有则为空切片)
}
allItems = append(allItems, item)
@@ -668,143 +258,7 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
// 支持任意来源:文生图、图生文、单独文、单独图、文图合并
// 生成单条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 {
margin-bottom: 25px;
}
.image-group img {
width: 100%;
height: auto;
display: block;
margin-bottom: 15px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.image-group img:last-child {
margin-bottom: 0;
}
.text {
padding: 0;
font-size: 15px;
line-height: 1.8;
color: #555;
}
.text h2 {
font-size: 28px;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 15px;
line-height: 1.4;
}
.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: 15px;
text-align: justify;
}
.text strong {
color: #e74c3c;
font-weight: 600;
}
.text ul {
list-style: none;
padding: 0;
margin: 15px 0;
}
.text ul li {
padding: 10px 0 10px 30px;
position: relative;
line-height: 1.6;
}
.text ul li:before {
content: "●";
color: #409eff;
font-size: 12px;
position: absolute;
left: 12px;
top: 12px;
}
@media (max-width: 768px) {
body {
padding: 10px;
}
.item {
padding: 20px;
}
.text h2 {
font-size: 24px;
}
.text h3 {
font-size: 18px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="item">
`)
// 写入图片支持0张、1张、多张
if len(item.Images) > 0 {
htmlBuilder.WriteString(`<div class="image-group">`)
for _, imgUrl := range item.Images {
htmlBuilder.WriteString(fmt.Sprintf(`<img src="%s" alt="图片"/>`, imgUrl))
}
htmlBuilder.WriteString(`</div>`)
}
// 🔥 写入文案前:删除 <p class="image-count">需要配图X 张</p>
if item.Content != "" {
// 正则删除整行
re := regexp.MustCompile(`<p class="image-count">需要配图:\d+ 张</p>`)
cleanContent := re.ReplaceAllString(item.Content, "")
// 写入清理后的文案
htmlBuilder.WriteString(fmt.Sprintf(`<div class="text">%s</div>`, cleanContent))
}
htmlBuilder.WriteString(`</div>
</div>
</body>
</html>`)
htmlContent := htmlBuilder.String()
htmlContent := BuildHtml(item.Content, item.Images)
// 上传OSS每条独立上传
fileName := fmt.Sprintf("item_%d_%d.html", idx, time.Now().UnixMilli())
@@ -832,13 +286,13 @@ func MergeLambda(ctx context.Context, input any) (any, error) {
Type: "text",
},
node.NodeFormField{
Field: fmt.Sprintf("item_text_%d", idx),
Field: fmt.Sprintf("item_txt_url_%d", idx),
Value: item.Content,
Label: fmt.Sprintf("条目%d 文案", idx+1),
Type: "text",
},
node.NodeFormField{
Field: fmt.Sprintf("item_images_%d", idx),
Field: fmt.Sprintf("item_image_url_%d", idx),
Value: strings.Join(item.Images, ","),
Label: fmt.Sprintf("条目%d 图片", idx+1),
Type: "text",
@@ -887,9 +341,9 @@ func SummaryLambda(ctx context.Context, input any) (any, error) {
executionReq := flowDto.UpdateFlowExecutionReq{
Id: execInput.Global.ExecutionId,
Status: flow.FlowExecutionStatusSuccess.Code(),
OutputParams: summaryResult,
}
executionReq.Status = flow.FlowExecutionStatusSuccess.Code()
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
if flowInfo != nil {

View File

@@ -0,0 +1,599 @@
package flow
import (
"ai-agent/workflow/consts/flow"
"ai-agent/workflow/consts/node"
flowDao "ai-agent/workflow/dao/flow"
"ai-agent/workflow/model/dto"
flowDto "ai-agent/workflow/model/dto/flow"
"ai-agent/workflow/model/entity"
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
func getNodeInfo(flowInfo *entity.FlowExecution) (htmlUrl []string, textModelName string, textResultFrom map[string]any, textModelResponse map[string]any, imgModelName string, imgResultFrom map[string]any, imgModelResponse map[string]any) {
textModelName = ""
textResultFrom = make(map[string]any)
textModelResponse = make(map[string]any)
imgModelName = ""
imgResultFrom = make(map[string]any)
imgModelResponse = make(map[string]any)
// 查询节点中是否包含结果合并节点
for _, item := range flowInfo.NodeInputParams {
if item.NodeCode == node.NodeTypeMerge {
for _, outputParamsItem := range flowInfo.OutputParams {
outputParamsMap := gconv.Map(outputParamsItem)
for _, mapItem := range outputParamsMap {
if strings.HasSuffix(gconv.String(mapItem), ".html") {
htmlUrl = append(htmlUrl, gconv.String(mapItem))
}
}
}
}
if item.NodeCode == node.NodeTypeTextModel {
textModelName = item.ModelConfig.ModelName
textModelResponse = item.ModelConfig.ModelResponse
for key, modelFormItem := range item.ModelConfig.ModelForm {
textResultFrom[key] = map[string]any{
"value": modelFormItem,
}
}
}
if item.NodeCode == node.NodeTypeImageModel {
imgModelName = item.ModelConfig.ModelName
imgModelResponse = item.ModelConfig.ModelResponse
for key, modelFormItem := range item.ModelConfig.ModelForm {
imgResultFrom[key] = map[string]any{
"value": modelFormItem,
}
}
}
}
return htmlUrl, textModelName, textResultFrom, textModelResponse, imgModelName, imgResultFrom, imgModelResponse
}
func TextImgModelSingleLambda(ctx context.Context, req *flowDto.ExecuteReq, flowInfo *entity.FlowExecution) (err error) {
_, textModelName, textResultFrom, textModelResponse, imgModelName, imgResultFrom, imgModelResponse := getNodeInfo(flowInfo)
resultUserFrom := make(map[string]any)
resultUserFrom["desc"] = req.Desc
var textNode []node.NodeFormField
textNode, err = TextNode(ctx, req.SessionId, textModelName, req.SkillName, textResultFrom, resultUserFrom, textModelResponse, req.FileUrl)
if err != nil {
return
}
var textContent string
var textUrl string
for _, item := range textNode {
if strings.Contains(item.Field, "text_content") {
textContent = StripHtmlTags(item.Value)
}
if strings.Contains(item.Field, "text_url") {
textUrl = item.Value
}
}
resultUserFrom["text_content"] = textContent
var imgNode []node.NodeFormField
imgNode, err = ImgNode(ctx, req.SessionId, imgModelName, req.SkillName, imgResultFrom, resultUserFrom, imgModelResponse, req.FileUrl)
if err != nil {
return
}
var imgUrl []string
for _, item := range imgNode {
if strings.Contains(item.Field, "img_url") {
imgUrl = append(imgUrl, item.Value)
}
}
// 生成单条HTML
htmlContent := BuildHtml(textUrl, imgUrl)
// 上传OSS每条独立上传
fileName := fmt.Sprintf("item_%d_%d.html", 0, time.Now().UnixMilli())
var ossResult *dto.UploadFileBytesRes
ossResult, err = Upload(ctx, &dto.UploadFileBytesReq{
FileBytes: []byte(htmlContent),
FileName: fileName,
})
if err != nil {
return
}
fmt.Printf("上传OSS成功%s", ossResult.FileURL)
var summaryResult []map[string]interface{}
for _, outputParamsItem := range flowInfo.OutputParams {
mapItem := gconv.Map(outputParamsItem)
for _, mapValue := range mapItem {
if strings.Contains(req.ResultUrl, gconv.String(mapValue)) {
// 生成 毫秒时间戳 作为 KEY
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
item := make(map[string]interface{})
item[timeKey] = ossResult.FileURL
summaryResult = append(summaryResult, item)
continue
}
summaryResult = append(summaryResult, outputParamsItem)
}
}
if !g.IsEmpty(summaryResult) {
executionReq := flowDto.UpdateFlowExecutionReq{
Id: flowInfo.Id,
Status: flow.FlowExecutionStatusSuccess.Code(),
OutputParams: summaryResult,
}
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
}
return
}
func ImgModelSingleLambda(ctx context.Context, req *flowDto.ExecuteReq, flowInfo *entity.FlowExecution) (err error) {
var url string
url, err = utils.GetFileAddressPrefix(ctx)
if err != nil {
return
}
htmlUrl, _, _, _, imgModelName, imgResultFrom, imgModelResponse := getNodeInfo(flowInfo)
resultUserFrom := make(map[string]any)
resultUserFrom["desc"] = req.Desc
var imgNode []node.NodeFormField
imgNode, err = ImgNode(ctx, req.SessionId, imgModelName, req.SkillName, imgResultFrom, resultUserFrom, imgModelResponse, req.FileUrl)
if err != nil {
return
}
var imgUrl string
for _, item := range imgNode {
if strings.Contains(item.Field, "img_url") {
imgUrl = item.Value
}
}
var htmlContentUrl string
var oldHtmlUrl string
if !g.IsEmpty(htmlUrl) {
for i, item := range htmlUrl {
var htmlBytes []byte
htmlBytes, err = GetFileBytesFromURL(url + item)
if err != nil {
return
}
htmlContent := string(htmlBytes)
imgSrcFromHtml := GetAllImgSrcFromHtml(htmlContent)
// 3. 标记是否需要替换
needReplace := false
for _, imgSrc := range imgSrcFromHtml {
if imgSrc == req.ResultUrl {
needReplace = true
break // 找到一个就可以替换
}
}
// 4. 如果匹配到,执行替换(把旧的 req.ResultUrl 替换成 新链接)
if needReplace {
oldHtmlUrl = url + item
htmlContent = ReplaceImgSrc(htmlContent, req.ResultUrl, imgUrl)
// 上传OSS每条独立上传
fileName := fmt.Sprintf("item_%d_%d.html", i, time.Now().UnixMilli())
var ossResult *dto.UploadFileBytesRes
ossResult, err = Upload(ctx, &dto.UploadFileBytesReq{
FileBytes: []byte(htmlContent),
FileName: fileName,
})
if err != nil {
return
}
fmt.Printf("上传OSS成功%s", ossResult.FileURL)
htmlContentUrl = ossResult.FileURL
}
}
}
var summaryResult []map[string]interface{}
if !g.IsEmpty(imgUrl) {
for _, outputParamsItem := range flowInfo.OutputParams {
mapItem := gconv.Map(outputParamsItem)
for _, mapValue := range mapItem {
if strings.Contains(oldHtmlUrl, gconv.String(mapValue)) || strings.Contains(req.ResultUrl, gconv.String(mapValue)) {
if strings.Contains(oldHtmlUrl, gconv.String(mapValue)) {
// 生成 毫秒时间戳 作为 KEY
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
item := make(map[string]interface{})
item[timeKey] = htmlContentUrl
summaryResult = append(summaryResult, item)
}
if strings.Contains(req.ResultUrl, gconv.String(mapValue)) {
// 生成 毫秒时间戳 作为 KEY
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
item := make(map[string]interface{})
item[timeKey] = imgUrl
summaryResult = append(summaryResult, item)
}
continue
}
summaryResult = append(summaryResult, outputParamsItem)
}
}
}
if !g.IsEmpty(summaryResult) {
executionReq := flowDto.UpdateFlowExecutionReq{
Id: flowInfo.Id,
Status: flow.FlowExecutionStatusSuccess.Code(),
OutputParams: summaryResult,
}
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
}
return
}
func TextModelSingleLambda(ctx context.Context, req *flowDto.ExecuteReq, flowInfo *entity.FlowExecution) (err error) {
var url string
url, err = utils.GetFileAddressPrefix(ctx)
if err != nil {
return
}
htmlUrl, textModelName, textResultFrom, textModelResponse, _, _, _ := getNodeInfo(flowInfo)
resultUserFrom := make(map[string]any)
resultUserFrom["desc"] = req.Desc
var textNode []node.NodeFormField
textNode, err = TextNode(ctx, req.SessionId, textModelName, req.SkillName, textResultFrom, resultUserFrom, textModelResponse, req.FileUrl)
if err != nil {
return
}
var textUrl string
for _, item := range textNode {
if strings.Contains(item.Field, "text_url") {
textUrl = item.Value
}
}
var htmlContentUrl string
var oldHtmlUrl string
if !g.IsEmpty(htmlUrl) {
for i, item := range htmlUrl {
var htmlBytes []byte
htmlBytes, err = GetFileBytesFromURL(url + item)
if err != nil {
return
}
htmlContent := string(htmlBytes)
// 1) 匹配出 incUrl 的值
incRegex := regexp.MustCompile(`incUrl\s*=\s*"([^"]+)"`)
match := incRegex.FindStringSubmatch(htmlContent)
// 2) 获取模板里原来的 incUrl
oldIncUrl := ""
if len(match) >= 2 {
oldIncUrl = match[1] // 这是模板里的旧链接
}
// 3) 对比:不一样才替换
if oldIncUrl == req.ResultUrl {
oldHtmlUrl = url + item
// 替换成新的链接
htmlContent = incRegex.ReplaceAllString(htmlContent, fmt.Sprintf(`incUrl = "%s"`, url+textUrl))
// 上传OSS每条独立上传
fileName := fmt.Sprintf("item_%d_%d.html", i, time.Now().UnixMilli())
var ossResult *dto.UploadFileBytesRes
ossResult, err = Upload(ctx, &dto.UploadFileBytesReq{
FileBytes: []byte(htmlContent),
FileName: fileName,
})
if err != nil {
return
}
fmt.Printf("上传OSS成功%s", ossResult.FileURL)
htmlContentUrl = ossResult.FileURL
}
}
}
var summaryResult []map[string]interface{}
if !g.IsEmpty(textUrl) {
for _, outputParamsItem := range flowInfo.OutputParams {
mapItem := gconv.Map(outputParamsItem)
for _, mapValue := range mapItem {
if strings.Contains(oldHtmlUrl, gconv.String(mapValue)) || strings.Contains(req.ResultUrl, gconv.String(mapValue)) {
if strings.Contains(oldHtmlUrl, gconv.String(mapValue)) {
// 生成 毫秒时间戳 作为 KEY
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
item := make(map[string]interface{})
item[timeKey] = htmlContentUrl
summaryResult = append(summaryResult, item)
}
if strings.Contains(req.ResultUrl, gconv.String(mapValue)) {
// 生成 毫秒时间戳 作为 KEY
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
item := make(map[string]interface{})
item[timeKey] = textUrl
summaryResult = append(summaryResult, item)
}
continue
}
summaryResult = append(summaryResult, outputParamsItem)
}
}
}
if !g.IsEmpty(summaryResult) {
executionReq := flowDto.UpdateFlowExecutionReq{
Id: flowInfo.Id,
Status: flow.FlowExecutionStatusSuccess.Code(),
OutputParams: summaryResult,
}
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
}
return
}
func TextNode(ctx context.Context, sessionId, modelName, skillName string, form, userForm, modelResponse map[string]any, fileUrl []string) ([]node.NodeFormField, error) {
contentStr := "你是专业内容生成助手请严格按以下规则输出内容1、输出标准 HTML 片段,不要 Markdown不要 ``` 符号不要多余解释2、整体用 <div class='report-container'> 包裹3、主标题使用 <h2 class='title'>4、章节标题使用 <h3 class='section-title'>5、正文段落使用 <p class='paragraph'>6、列表使用 <ul class='list'><li>...</li></ul>7、重点内容使用 <strong> 加粗8、段落之间清晰分隔结构规整9、如果生成多条文案每条文案独立用 <div class='content-item' id='content-{序号}'> 包裹序号从1开始10、每条文案内部必须在最上方添加一行固定格式<p class='image-count'>需要配图N 张</p> N 是这条文案需要的图片数量只能是数字不能是其他文字11、只输出 HTML 结构,不输出任何额外文字"
userForm["prompt"] = contentStr
mapTaskResult, err := GetModelResult(ctx, modelName, skillName, form, userForm, fileUrl, sessionId, "文案生成")
if err != nil {
return nil, err
}
resultContent := ""
for key, _ := range modelResponse {
resultContent = gconv.String(mapTaskResult[key])
}
// 拆分多条文案
contentList := SplitMultiContents(resultContent)
outputRes := make([]node.NodeFormField, 0)
for i, contentItem := range contentList {
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("text_content_%d", i),
Value: contentItem,
Label: fmt.Sprintf("文案内容_%d", i),
Type: "string",
Expand: ExtractImageCount(contentItem),
})
// 1. 构建html文本
plainText := BuildText(contentItem)
// 2. 上传纯文本到 OSS
textFileName := fmt.Sprintf("ai_text_%d_%d.inc", time.Now().UnixMilli(), i)
var textUrl *dto.UploadFileBytesRes
textUrl, err = Upload(ctx, &dto.UploadFileBytesReq{
FileBytes: []byte(plainText),
FileName: textFileName,
})
if err != nil {
return nil, err
}
// 3. 把纯文本地址存入输出
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("text_url:%d", i),
Value: textUrl.FileURL,
Label: fmt.Sprintf("文案纯文本_txt_%d", i),
Type: "string",
Expand: ExtractImageCount(contentItem),
})
}
return outputRes, nil
}
func ImgNode(ctx context.Context, sessionId, modelName, skillName string, form, userForm, modelResponse map[string]any, fileUrl []string) ([]node.NodeFormField, error) {
mapTaskResult, err := GetModelResult(ctx, modelName, skillName, form, userForm, fileUrl, sessionId, "图片生成")
if err != nil {
return nil, err
}
var resultContent []string
for key, _ := range modelResponse {
resultContent = gconv.Strings(mapTaskResult[key])
}
var images []string
for _, item := range resultContent {
mapItem := gconv.Map(item)
for _, value := range mapItem {
values, ok := value.(string)
if !ok {
return nil, fmt.Errorf("图片地址类型错误")
}
// 下载官方临时图片
var imgBytes []byte
imgBytes, err = GetFileBytesFromURL(values)
if err != nil {
return nil, fmt.Errorf("下载图片失败: %w", err)
}
// 构造文件名
fileName := fmt.Sprintf("ai_image_%d.png", time.Now().UnixMilli())
// 上传到你的OSS你项目已有的Upload方法
var upResp *dto.UploadFileBytesRes
upResp, err = Upload(ctx, &dto.UploadFileBytesReq{
FileName: fileName,
FileBytes: imgBytes,
})
if err != nil {
return nil, fmt.Errorf("上传OSS失败: %w", err)
}
images = append(images, upResp.FileURL)
}
}
var url string
url, err = utils.GetFileAddressPrefix(ctx)
if err != nil {
return nil, err
}
outputRes := make([]node.NodeFormField, 0)
for i, item := range images {
// 图片image_0, image_1, image_2...
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("image_%d", i),
Value: fmt.Sprintf("%s%s", url, item),
Label: fmt.Sprintf("图片_%d", i),
Type: "string",
})
// 额外存储关联关系
outputRes = append(outputRes, node.NodeFormField{
Field: fmt.Sprintf("img_url:%d", i),
Value: fmt.Sprintf("%s%s", url, item),
Label: fmt.Sprintf("图片_img_%d关联文案ID", i),
Type: "string",
})
}
return outputRes, nil
}
func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, resultFrom, resultUserFrom map[string]any) {
// 1. 直接用你原来的方法(返回两个 map
inputMap, outputMap, modelMap := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
var outputResult []node.NodeFormField
for _, valueAny := range inputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
resultUserFrom = make(map[string]any)
for _, valueAny := range outputMap {
if field, ok := valueAny.(node.NodeFormField); ok {
if !strings.Contains(field.Field, "text_url") && !strings.Contains(field.Field, "img_url") {
if strings.Contains(field.Field, "text_content") {
field.Value = StripHtmlTags(field.Value)
}
resultUserFrom[field.Label] = field
}
}
}
for _, valueAny := range modelMap {
if field, ok := valueAny.(node.NodeFormField); ok {
outputResult = append(outputResult, field)
}
}
if !nodeInput.Global.IsDialogue {
for _, item := range outputResult {
resultUserFrom[item.Label] = item
}
for _, item := range nodeInput.Config.FormConfig {
resultUserFrom[item.Label] = item
}
}
if !g.IsEmpty(nodeInput.Global.Desc) {
resultUserFrom["desc"] = node.NodeFormField{
Value: nodeInput.Global.Desc,
Field: "desc",
Label: "描述",
Type: "text",
}
}
resultFrom = make(map[string]any)
for key, item := range nodeInput.Config.ModelConfig.ModelForm {
resultFrom[key] = map[string]any{
"value": item,
}
}
skillName = nodeInput.Config.SkillName
if g.IsEmpty(nodeInput.Config.SkillName) {
skillName = nodeInput.Global.SkillName
}
return skillName, resultFrom, resultUserFrom
}
func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, node *entity.FlowNode) (map[string]any, map[string]any, map[string]any) {
input := make(map[string]any)
output := make(map[string]any)
model := make(map[string]any)
// 1. 有引用 → 取引用节点的字段值
if len(node.InputSource) > 0 {
for _, source := range node.InputSource {
refNodeID := source.NodeId
isQuoteOutput := source.QuoteOutput
fields := source.Field
refNode, ok := execInput.ConfigMap[refNodeID]
if !ok {
continue
}
inputMap := buildInputMap(refNode)
outputMap := mergeOutput(refNode.OutputResult)
modelMap := mergeModel(refNode.ModelConfig)
if isQuoteOutput {
for k, v := range outputMap {
output[k] = v
}
}
if len(fields) > 0 {
// 取指定字段
for _, f := range fields {
if v, ok := inputMap[f]; ok {
input[f] = v
}
if v, ok := modelMap[f]; ok {
model[f] = v
}
}
} else {
// 取全部
for k, v := range inputMap {
input[k] = v
}
for k, v := range modelMap {
model[k] = v
}
}
}
}
return input, output, model
}
// buildInputMap 从 FormConfig 构造输入map
func buildInputMap(node *entity.FlowNode) map[string]any {
m := make(map[string]any)
for _, item := range node.FormConfig {
m[item.Label] = item
}
return m
}
// mergeOutput 合并节点输出 []map → 单map
func mergeOutput(output []node.NodeFormField) map[string]any {
m := make(map[string]any)
for _, item := range output {
m[item.Label] = item
}
return m
}
// mergeOutput 合并节点输出 []map → 单map
func mergeModel(output node.ModelItem) map[string]any {
m := make(map[string]any)
// 遍历 output.ModelForm 里的每一个 key 和原始值
for key, rawValue := range output.ModelForm {
// 包装成 { "value": 原始值 }
m[key] = map[string]any{
"value": rawValue,
}
}
return m
}

View File

@@ -9,9 +9,12 @@ import (
"io"
"mime/multipart"
"net/http"
"regexp"
"strconv"
"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/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -30,6 +33,66 @@ func GetIsChatModel(ctx context.Context) (res *flowDto.GetIsChatModelRes, err er
return
}
func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (res *flowDto.ComposeMessagesRes, 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(flowDto.ComposeMessagesRes)
err = commonHttp.Post(ctx, "prompts-core/prompt/composeMessages", headers, res, &req)
return
}
func GetModelResult(ctx context.Context, modelName, skillName string, form, userFrom map[string]any, fileUrl []string, sessionId string, cause string) (mapTaskResult map[string]any, err error) {
msgReq := flowDto.ComposeMessagesReq{
BuildType: 1,
ModelName: modelName,
SkillName: skillName,
Cause: cause,
Form: form,
UserForm: userFrom,
UserFiles: fileUrl,
SessionId: sessionId,
}
msg, err := ComposeMessages(ctx, &msgReq)
if err != nil {
return
}
if g.IsEmpty(msg.Messages) {
return nil, fmt.Errorf("msg is empty")
}
var taskResult any
taskResult, err = GatewayTask(ctx, msg.EpicycleId, modelName, msg.Messages)
if err != nil {
return
}
var getTaskResult *flowDto.TaskCallback
getTaskResult, err = GetTaskResult(ctx, taskResult)
if err != nil {
return
}
mapTaskResult = gconv.Map(getTaskResult.Text)
return mapTaskResult, nil
}
func GatewayTask(ctx context.Context, epicycleId int64, model string, content map[string]any) (any, error) {
modelTaskId, err := CreateGatewayTask(ctx, &flowDto.CreateTaskReq{
ModelName: model,
BizName: g.Cfg().MustGet(ctx, "server.name").String(),
CallbackUrl: "/flow/execution/modelCallback",
RequestPayload: content,
EpicycleId: epicycleId,
})
if err != nil {
return nil, err
}
return Wait(ctx, modelTaskId)
}
func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string, error) {
headers := make(map[string]string)
if r := g.RequestFromCtx(ctx); r != nil {
@@ -48,34 +111,6 @@ func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string,
return res.TaskId, nil
}
func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (res *flowDto.ComposeMessagesRes, 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(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,
})
if err != nil {
return nil, err
}
return Wait(ctx, modelTaskId)
}
func GetTaskResult(ctx context.Context, result any) (*flowDto.TaskCallback, error) {
task := new(flowDto.TaskCallback)
if err := gconv.Struct(result, task); err != nil {
@@ -113,17 +148,22 @@ func FetchRemoteJsonFile(ctx context.Context, fileUrl string) ([]byte, error) {
return io.ReadAll(resp.Body)
}
func GetImageBytesFromURL(url string) (all []byte, contentType string, err error) {
func GetFileBytesFromURL(url string) (all []byte, err error) {
resp, err := http.Get(url)
if err != nil {
fmt.Printf("请求失败 %s: %v", url, err)
return
}
defer resp.Body.Close()
all, err = io.ReadAll(resp.Body)
if err != nil {
if resp.StatusCode != http.StatusOK {
fmt.Printf("请求失败,状态码: %d\n", resp.StatusCode)
return
}
all, err = io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取内容失败 %s: %v", url, err)
return
}
contentType = resp.Header.Get("Content-Type")
return
}
@@ -160,3 +200,275 @@ func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBy
g.Log().Infof(ctx, "[Upload] success url=%s size=%d", res.FileURL, res.FileSize)
return res, nil
}
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*["']([^"']+)["']`)
matchs := re.FindAllStringSubmatch(html, -1)
for _, match := range matchs {
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,7 @@ import (
"context"
"fmt"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
)

View File

@@ -9,9 +9,9 @@ import (
"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"
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)