Compare commits
9 Commits
d4614e3cc9
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 59d4ef557b | |||
| b7b1ee51a1 | |||
| ab3a2d967e | |||
| 34c5eeaf63 | |||
| de55c16734 | |||
| 1fbed2febd | |||
| d5206df131 | |||
| ffba1f30ec | |||
| b1ee117f6c |
27
Dockerfile
27
Dockerfile
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
type asyncTaskRefCol struct {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
type customVoiceCol struct {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -175,7 +175,7 @@ EOF
|
||||
#### 创建 Dockerfile
|
||||
|
||||
```bash
|
||||
cat > Dockerfile << 'EOF'
|
||||
cat > Dockerfile.bak << 'EOF'
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
33
go.mod
33
go.mod
@@ -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
87
go.sum
@@ -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=
|
||||
|
||||
4
main.go
4
main.go
@@ -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"
|
||||
)
|
||||
|
||||
42
update.sql
42
update.sql
@@ -279,6 +279,42 @@ COMMENT ON COLUMN black_deacon_creation_info.title IS '标题';
|
||||
|
||||
--------------------pgsql创建creation_info表语句---------------------------
|
||||
|
||||
--------------------pgsql创建black_deacon_file_temp表语句---------------------------
|
||||
-- 临时文件表
|
||||
CREATE TABLE IF NOT EXISTS black_deacon_file_temp (
|
||||
-- 基础字段(完全对齐项目规范)
|
||||
id BIGINT PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at timestamp(6),
|
||||
|
||||
-- 业务字段
|
||||
business_id VARCHAR(255) NOT NULL DEFAULT '',
|
||||
file_url VARCHAR(512) NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX idx_file_temp_tenant_id ON black_deacon_file_temp(tenant_id);
|
||||
CREATE INDEX idx_file_temp_business_id ON black_deacon_file_temp(business_id);
|
||||
CREATE INDEX idx_file_temp_file_url ON black_deacon_file_temp(file_url);
|
||||
CREATE INDEX idx_file_temp_deleted_at ON black_deacon_file_temp(deleted_at);
|
||||
|
||||
-- 注释
|
||||
COMMENT ON TABLE black_deacon_file_temp IS '临时文件表';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.id IS '主键ID';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.creator IS '创建人';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.updater IS '更新人';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.business_id IS '业务ID';
|
||||
COMMENT ON COLUMN black_deacon_file_temp.file_url IS '文件地址';
|
||||
--------------------pgsql创建black_deacon_file_temp表语句---------------------------
|
||||
|
||||
--------------------pgsql创建black_deacon_skill_template表语句---------------------------
|
||||
-- 技能模板表
|
||||
CREATE TABLE IF NOT EXISTS black_deacon_skill_template (
|
||||
@@ -294,14 +330,12 @@ CREATE TABLE IF NOT EXISTS black_deacon_skill_template (
|
||||
-- 业务字段
|
||||
name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
description TEXT DEFAULT '',
|
||||
category VARCHAR(64) NOT NULL DEFAULT '',
|
||||
file_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
file_url VARCHAR(512) NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX idx_skill_template_tenant_id ON black_deacon_skill_template(tenant_id);
|
||||
CREATE INDEX idx_skill_template_category ON black_deacon_skill_template(category);
|
||||
CREATE INDEX idx_skill_template_deleted_at ON black_deacon_skill_template(deleted_at);
|
||||
|
||||
-- 注释
|
||||
@@ -315,7 +349,6 @@ COMMENT ON COLUMN black_deacon_skill_template.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN black_deacon_skill_template.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN black_deacon_skill_template.name IS '技能模板名称';
|
||||
COMMENT ON COLUMN black_deacon_skill_template.description IS '描述';
|
||||
COMMENT ON COLUMN black_deacon_skill_template.category IS '分类';
|
||||
COMMENT ON COLUMN black_deacon_skill_template.file_name IS '文件名称';
|
||||
COMMENT ON COLUMN black_deacon_skill_template.file_url IS '文件地址';
|
||||
--------------------pgsql创建black_deacon_skill_template表语句---------------------------
|
||||
@@ -335,14 +368,12 @@ CREATE TABLE IF NOT EXISTS black_deacon_skill_user (
|
||||
-- 业务字段
|
||||
name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
description TEXT DEFAULT '',
|
||||
category VARCHAR(64) NOT NULL DEFAULT '',
|
||||
file_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
file_url VARCHAR(512) NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX idx_skill_user_tenant_id ON black_deacon_skill_user(tenant_id);
|
||||
CREATE INDEX idx_skill_user_category ON black_deacon_skill_user(category);
|
||||
CREATE INDEX idx_skill_user_deleted_at ON black_deacon_skill_user(deleted_at);
|
||||
|
||||
-- 注释
|
||||
@@ -356,7 +387,6 @@ COMMENT ON COLUMN black_deacon_skill_user.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN black_deacon_skill_user.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN black_deacon_skill_user.name IS '技能名称';
|
||||
COMMENT ON COLUMN black_deacon_skill_user.description IS '描述';
|
||||
COMMENT ON COLUMN black_deacon_skill_user.category IS '分类';
|
||||
COMMENT ON COLUMN black_deacon_skill_user.file_name IS '文件名称';
|
||||
COMMENT ON COLUMN black_deacon_skill_user.file_url IS '文件地址';
|
||||
--------------------pgsql创建black_deacon_skill_user表语句---------------------------
|
||||
|
||||
@@ -6,7 +6,7 @@ var (
|
||||
FlowExecutionStatusRunning = newFlowExecutionStatus(gconv.PtrInt8(1), "running") // 运行中
|
||||
FlowExecutionStatusSuccess = newFlowExecutionStatus(gconv.PtrInt8(2), "success") // 成功
|
||||
FlowExecutionStatusFailed = newFlowExecutionStatus(gconv.PtrInt8(3), "failed") // 失败
|
||||
FlowExecutionStatusPaused = newFlowExecutionStatus(gconv.PtrInt8(4), "paused") // 暂停
|
||||
FlowExecutionStatusCancel = newFlowExecutionStatus(gconv.PtrInt8(4), "cancel") // 取消
|
||||
)
|
||||
|
||||
type FlowExecutionStatus *int8
|
||||
|
||||
@@ -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{},
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -13,4 +13,5 @@ const (
|
||||
TableNameFlowUser = "flow_user"
|
||||
TableNameSkillTemplate = "skill_template"
|
||||
TableNameSkillUser = "skill_user"
|
||||
TableNameFileTemp = "file_temp"
|
||||
)
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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{}
|
||||
@@ -16,11 +16,24 @@ func (c *skillUser) Create(ctx context.Context, req *skillDto.CreateSkillUserReq
|
||||
return skillService.SkillUserService.Create(ctx, req)
|
||||
}
|
||||
|
||||
func (c *skillUser) Update(ctx context.Context, req *skillDto.UpdateSkillUserReq) (res *beans.ResponseEmpty, err error) {
|
||||
err = skillService.SkillUserService.Update(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *skillUser) Delete(ctx context.Context, req *skillDto.DeleteSkillUserReq) (res *beans.ResponseEmpty, err error) {
|
||||
err = skillService.SkillUserService.Delete(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *skillUser) Get(ctx context.Context, req *skillDto.GetSkillUserReq) (res *skillDto.SkillUserVO, err error) {
|
||||
return skillService.SkillUserService.Get(ctx, req)
|
||||
}
|
||||
|
||||
func (c *skillUser) GetUserOrTemplate(ctx context.Context, req *skillDto.GetSkillReq) (res *skillDto.SkillUserVO, err error) {
|
||||
return skillService.SkillUserService.GetUserOrTemplate(ctx, req)
|
||||
}
|
||||
|
||||
func (c *skillUser) List(ctx context.Context, req *skillDto.ListSkillReq) (res *skillDto.ListSkillUserRes, err error) {
|
||||
return skillService.SkillUserService.List(ctx, req)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
59
workflow/dao/file/file_temp_dao.go
Normal file
59
workflow/dao/file/file_temp_dao.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"ai-agent/workflow/consts/public"
|
||||
fileDto "ai-agent/workflow/model/dto/file"
|
||||
"ai-agent/workflow/model/entity"
|
||||
"context"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
var FileTempDao = &fileTempDao{}
|
||||
|
||||
type fileTempDao struct{}
|
||||
|
||||
func (d *fileTempDao) Insert(ctx context.Context, req *fileDto.CreateFileTempReq) (id int64, err error) {
|
||||
fileTemp := new(entity.FileTemp)
|
||||
err = gconv.Struct(req, &fileTemp)
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFileTemp).Insert(&fileTemp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *fileTempDao) BatchInsert(ctx context.Context, req []*fileDto.CreateFileTempReq) (rows int64, err error) {
|
||||
var res []*entity.FileTemp
|
||||
if err = gconv.Structs(req, &res); err != nil {
|
||||
return
|
||||
}
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFileTemp).Data(res).Save()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *fileTempDao) Delete(ctx context.Context, req *fileDto.DeleteFileTempReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFileTemp).Where(entity.FileTempCol.Id, req.Id).Delete()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *fileTempDao) List(ctx context.Context, req *fileDto.ListFileTempReq, fields ...string) (res []*entity.FileTemp, total int, err error) {
|
||||
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFileTemp).NoTenantId(ctx).Fields(fields).OmitEmpty()
|
||||
model.OrderDesc(entity.FileTempCol.CreatedAt)
|
||||
if req.Page != nil {
|
||||
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||
}
|
||||
r, total, err := model.AllAndCount(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = r.Structs(&res)
|
||||
return
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -25,6 +25,14 @@ func (d *skillTemplateDao) Insert(ctx context.Context, req *skillDto.CreateSkill
|
||||
return r.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *skillTemplateDao) Update(ctx context.Context, req *skillDto.UpdateSkillTemplateReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillTemplate).OmitEmpty().Data(&req).Where(entity.SkillTemplateCol.Id, req.Id).Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *skillTemplateDao) Delete(ctx context.Context, req *skillDto.DeleteSkillTemplateReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillTemplate).Where(entity.SkillTemplateCol.Id, req.Id).Delete()
|
||||
if err != nil {
|
||||
@@ -33,6 +41,26 @@ func (d *skillTemplateDao) Delete(ctx context.Context, req *skillDto.DeleteSkill
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *skillTemplateDao) Count(ctx context.Context, req *skillDto.GetSkillTemplateReq) (count int, err error) {
|
||||
count, err = gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillTemplate).NoTenantId(ctx).OmitEmpty().
|
||||
WhereNot(entity.SkillTemplateCol.Id, req.NotInId).
|
||||
Where(entity.SkillTemplateCol.Name, req.Name).
|
||||
Where(entity.SkillTemplateCol.Id, req.Id).Count()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *skillTemplateDao) Get(ctx context.Context, req *skillDto.GetSkillTemplateReq, fields ...string) (res *entity.SkillTemplate, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillTemplate).NoTenantId(ctx).OmitEmpty().
|
||||
Where(entity.SkillTemplateCol.Id, req.Id).
|
||||
Where(entity.SkillTemplateCol.Name, req.Name).
|
||||
Fields(fields).One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = r.Struct(&res)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *skillTemplateDao) List(ctx context.Context, req *skillDto.ListSkillTemplateReq, fields ...string) (res []*entity.SkillTemplate, total int, err error) {
|
||||
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillTemplate).NoTenantId(ctx).Fields(fields).OmitEmpty()
|
||||
if !g.IsEmpty(req.Keyword) {
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -25,6 +25,14 @@ func (d *skillUserDao) Insert(ctx context.Context, req *skillDto.CreateSkillUser
|
||||
return r.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *skillUserDao) Update(ctx context.Context, req *skillDto.UpdateSkillUserReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillUser).OmitEmpty().Data(&req).Where(entity.SkillUserCol.Id, req.Id).Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *skillUserDao) Delete(ctx context.Context, req *skillDto.DeleteSkillUserReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillUser).Where(entity.SkillUserCol.Id, req.Id).Delete()
|
||||
if err != nil {
|
||||
@@ -33,6 +41,28 @@ func (d *skillUserDao) Delete(ctx context.Context, req *skillDto.DeleteSkillUser
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *skillUserDao) Count(ctx context.Context, req *skillDto.GetSkillUserReq) (count int, err error) {
|
||||
count, err = gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillUser).NoTenantId(ctx).OmitEmpty().
|
||||
Where(entity.SkillUserCol.Name, req.Name).
|
||||
Where(entity.SkillUserCol.Creator, req.Creator).
|
||||
WhereNot(entity.SkillUserCol.Id, req.NotInId).
|
||||
Where(entity.SkillUserCol.Id, req.Id).Count()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *skillUserDao) Get(ctx context.Context, req *skillDto.GetSkillUserReq, fields ...string) (res *entity.SkillUser, err error) {
|
||||
r, err := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillUser).NoTenantId(ctx).OmitEmpty().
|
||||
Where(entity.SkillUserCol.Id, req.Id).
|
||||
Where(entity.SkillUserCol.Name, req.Name).
|
||||
Where(entity.SkillUserCol.Creator, req.Creator).
|
||||
Fields(fields).One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = r.Struct(&res)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *skillUserDao) List(ctx context.Context, req *skillDto.ListSkillUserReq, fields ...string) (res []*entity.SkillUser, total int, err error) {
|
||||
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameSkillUser).NoTenantId(ctx).Fields(fields).OmitEmpty()
|
||||
model.Where(entity.SkillUserCol.Creator, req.Creator)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
45
workflow/model/dto/file/file_temp_dto.go
Normal file
45
workflow/model/dto/file/file_temp_dto.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type CreateFileTempReq struct {
|
||||
g.Meta `path:"/create" method:"post" tags:"临时文件管理" summary:"创建临时文件" dc:"创建临时文件"`
|
||||
|
||||
BusinessId string `json:"businessId"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
}
|
||||
|
||||
type CreateFileTempRes struct {
|
||||
Id int64 `json:"id,string"`
|
||||
}
|
||||
|
||||
type DeleteFileTempReq struct {
|
||||
g.Meta `path:"/delete" method:"delete" tags:"临时文件管理" summary:"删除临时文件" dc:"删除临时文件"`
|
||||
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
}
|
||||
|
||||
type ListFileTempReq struct {
|
||||
g.Meta `path:"/list" method:"get" tags:"临时文件管理" summary:"临时文件列表" dc:"临时文件列表"`
|
||||
|
||||
Page *beans.Page `json:"page"`
|
||||
BusinessId string `json:"businessId"`
|
||||
CreatedAt *gtime.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ListFileTempRes struct {
|
||||
List []*FileTempVO `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type FileTempVO struct {
|
||||
Id int64 `json:"id,string" dc:"id"`
|
||||
BusinessId string `json:"businessId"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间"`
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
@@ -17,6 +17,7 @@ type NodeExecutionInput struct {
|
||||
|
||||
// FlowExecutionInput 工作流执行入参(全程不变)
|
||||
type FlowExecutionInput struct {
|
||||
IsDialogue bool `json:"isDialogue"`
|
||||
ExecutionId int64 `json:"executionId"`
|
||||
ConfigMap map[string]*entity.FlowNode `json:"configMap"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
@@ -27,7 +28,7 @@ type FlowExecutionInput struct {
|
||||
}
|
||||
|
||||
type ComposeMessagesReq struct {
|
||||
ModelTypeId int `json:"modelTypeId"`
|
||||
BuildType int `json:"buildType"`
|
||||
ModelName string `json:"modelName"`
|
||||
SkillName string `json:"skillName"`
|
||||
Form map[string]any `json:"form"`
|
||||
@@ -136,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 {
|
||||
@@ -143,9 +145,9 @@ type ExecuteRes struct {
|
||||
}
|
||||
|
||||
type CancelReq struct {
|
||||
g.Meta `path:"/cancel" method:"get" tags:"任务管理" summary:"取消任务" dc:"取消任务"`
|
||||
g.Meta `path:"/cancel" method:"post" tags:"任务管理" summary:"取消任务" dc:"取消任务"`
|
||||
|
||||
FlowId int64 `json:"flowId" dc:"用户流程ID"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
}
|
||||
|
||||
type CreateFlowExecutionReq struct {
|
||||
@@ -206,8 +208,10 @@ type VOFlowExecution struct {
|
||||
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
|
||||
ErrorMessage string `json:"errorMessage" description:"错误信息"`
|
||||
TraceId string `json:"traceId" description:"跟踪ID"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间"`
|
||||
ImgAddressPrefix string `json:"imgAddressPrefix"`
|
||||
}
|
||||
|
||||
// ========== 核心:构建树状结构 ==========
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -11,7 +11,6 @@ type CreateSkillTemplateReq struct {
|
||||
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
}
|
||||
@@ -20,12 +19,30 @@ type CreateSkillTemplateRes struct {
|
||||
Id int64 `json:"id,string"`
|
||||
}
|
||||
|
||||
type UpdateSkillTemplateReq struct {
|
||||
g.Meta `path:"/update" method:"put" tags:"Skill技能管理" summary:"修改Skill用户技能" dc:"修改Skill用户技能"`
|
||||
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
}
|
||||
|
||||
type DeleteSkillTemplateReq struct {
|
||||
g.Meta `path:"/delete" method:"delete" tags:"Skill技能管理" summary:"删除Skill技能" dc:"删除Skill技能"`
|
||||
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
}
|
||||
|
||||
type GetSkillTemplateReq struct {
|
||||
g.Meta `path:"/get" method:"get" tags:"Skill技能管理" summary:"Skill技能详情" dc:"Skill技能详情"`
|
||||
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
NotInId int64 `json:"notInId"`
|
||||
}
|
||||
|
||||
type ListSkillTemplateReq struct {
|
||||
g.Meta `path:"/list" method:"get" tags:"Skill技能管理" summary:"Skill技能列表" dc:"Skill技能列表"`
|
||||
|
||||
@@ -42,7 +59,6 @@ type SkillTemplateVO struct {
|
||||
Id int64 `json:"id,string" dc:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -11,7 +11,6 @@ type CreateSkillUserReq struct {
|
||||
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
}
|
||||
@@ -20,12 +19,40 @@ type CreateSkillUserRes struct {
|
||||
Id int64 `json:"id,string"`
|
||||
}
|
||||
|
||||
type UpdateSkillUserReq struct {
|
||||
g.Meta `path:"/update" method:"put" tags:"Skill用户技能管理" summary:"修改Skill用户技能" dc:"修改Skill用户技能"`
|
||||
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
}
|
||||
|
||||
type DeleteSkillUserReq struct {
|
||||
g.Meta `path:"/delete" method:"delete" tags:"Skill用户技能管理" summary:"删除Skill用户技能" dc:"删除Skill用户技能"`
|
||||
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
}
|
||||
|
||||
type GetSkillUserReq struct {
|
||||
g.Meta `path:"/get" method:"get" tags:"Skill用户技能管理" summary:"Skill用户技能详情" dc:"Skill用户技能详情"`
|
||||
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Creator string `json:"creator"`
|
||||
NotInId int64 `json:"notInId"`
|
||||
}
|
||||
|
||||
type GetSkillReq struct {
|
||||
g.Meta `path:"/getUserOrTemplate" method:"get" tags:"Skill用户技能管理" summary:"Skill技能详情" dc:"Skill技能详情"`
|
||||
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Creator string `json:"creator"`
|
||||
NotInId int64 `json:"notInId"`
|
||||
}
|
||||
|
||||
type ListSkillReq struct {
|
||||
g.Meta `path:"/list" method:"get" tags:"Skill用户技能管理" summary:"Skill用户技能列表" dc:"Skill用户技能列表"`
|
||||
|
||||
@@ -51,9 +78,9 @@ type SkillUserVO struct {
|
||||
Id int64 `json:"id,string" dc:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUrl string `json:"fileUrl"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间"`
|
||||
ImgAddressPrefix string `json:"imgAddressPrefix"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
24
workflow/model/entity/file_temp.go
Normal file
24
workflow/model/entity/file_temp.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
type FileTemp struct {
|
||||
beans.SQLBaseDO `orm:",inherit"` // 嵌入基础字段:Id, TenantId, Creator, CreatedAt, Updater, UpdatedAt, DeletedAt
|
||||
|
||||
BusinessId string `orm:"business_id" json:"businessId"`
|
||||
FileUrl string `orm:"file_url" json:"fileUrl"`
|
||||
}
|
||||
|
||||
type fileTempCol struct {
|
||||
beans.SQLBaseCol
|
||||
BusinessId string
|
||||
FileUrl string
|
||||
}
|
||||
|
||||
var FileTempCol = fileTempCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
BusinessId: "business_id",
|
||||
FileUrl: "file_url",
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
type SkillTemplate struct {
|
||||
@@ -9,7 +9,6 @@ type SkillTemplate struct {
|
||||
|
||||
Name string `orm:"name" json:"name"`
|
||||
Description string `orm:"description" json:"description"`
|
||||
Category string `orm:"category" json:"category"`
|
||||
FileName string `orm:"file_name" json:"fileName"`
|
||||
FileUrl string `orm:"file_url" json:"fileUrl"`
|
||||
}
|
||||
@@ -18,7 +17,6 @@ type skillTemplateCol struct {
|
||||
beans.SQLBaseCol
|
||||
Name string
|
||||
Description string
|
||||
Category string
|
||||
FileName string
|
||||
FileUrl string
|
||||
}
|
||||
@@ -27,7 +25,6 @@ var SkillTemplateCol = skillTemplateCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
Category: "category",
|
||||
FileName: "file_name",
|
||||
FileUrl: "file_url",
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
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
|
||||
|
||||
Name string `orm:"name" json:"name"`
|
||||
Description string `orm:"description" json:"description"`
|
||||
Category string `orm:"category" json:"category"`
|
||||
FileName string `orm:"file_name" json:"fileName"`
|
||||
FileUrl string `orm:"file_url" json:"fileUrl"`
|
||||
}
|
||||
@@ -16,7 +15,6 @@ type skillUserCol struct {
|
||||
beans.SQLBaseCol
|
||||
Name string
|
||||
Description string
|
||||
Category string
|
||||
FileName string
|
||||
FileUrl string
|
||||
}
|
||||
@@ -25,7 +23,6 @@ var SkillUserCol = skillUserCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
Category: "category",
|
||||
FileName: "file_name",
|
||||
FileUrl: "file_url",
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -3,17 +3,20 @@ package flow
|
||||
import (
|
||||
"ai-agent/workflow/consts/flow"
|
||||
"ai-agent/workflow/consts/node"
|
||||
fileDao "ai-agent/workflow/dao/file"
|
||||
flowDao "ai-agent/workflow/dao/flow"
|
||||
fileDto "ai-agent/workflow/model/dto/file"
|
||||
flowDto "ai-agent/workflow/model/dto/flow"
|
||||
"ai-agent/workflow/model/entity"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"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"
|
||||
@@ -31,6 +34,10 @@ func (s *flowExecutionService) Get(ctx context.Context, req *flowDto.GetFlowExec
|
||||
return nil, err
|
||||
}
|
||||
res = new(flowDto.VOFlowExecution)
|
||||
res.ImgAddressPrefix, err = utils.GetFileAddressPrefix(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = gconv.Struct(r, &res)
|
||||
return res, err
|
||||
}
|
||||
@@ -120,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 = "文案"
|
||||
}
|
||||
|
||||
@@ -227,15 +234,61 @@ func Notify(taskId string, result any) {
|
||||
delete(asyncTasks, taskId)
|
||||
}
|
||||
|
||||
//func (s *flowExecutionService) Cancel(ctx context.Context, req *flowDto.CancelReq) (err error) {
|
||||
// res, err := flowDao.FlowExecutionDao.Get(ctx, &flowDto.GetFlowExecutionReq{
|
||||
// Id: req.FlowId,
|
||||
// })
|
||||
// res.TraceId
|
||||
// return
|
||||
//}
|
||||
// ===================== 核心改造:替换为 sync.Map 存储取消上下文 =====================
|
||||
var (
|
||||
// cancelMap: traceID -> context.CancelFunc
|
||||
cancelMap sync.Map
|
||||
)
|
||||
|
||||
func (s *flowExecutionService) Cancel(ctx context.Context, req *flowDto.CancelReq) (err error) {
|
||||
getRes, err := flowDao.FlowExecutionDao.Get(ctx, &flowDto.GetFlowExecutionReq{
|
||||
SessionId: req.SessionId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if g.IsEmpty(getRes) {
|
||||
return fmt.Errorf("会话[%s] 不存在", req.SessionId)
|
||||
}
|
||||
// 从 sync.Map 获取取消函数
|
||||
cancelVal, exist := cancelMap.Load(getRes.TraceId)
|
||||
if !exist {
|
||||
return fmt.Errorf("traceID[%s] 不存在或已执行完成", getRes.TraceId)
|
||||
}
|
||||
|
||||
// 执行取消
|
||||
cancel, ok := cancelVal.(context.CancelFunc)
|
||||
if !ok {
|
||||
return fmt.Errorf("traceID[%s] 对应的取消函数类型错误", getRes.TraceId)
|
||||
}
|
||||
cancel()
|
||||
|
||||
// 取消后清理(可选:也可以在流程结束时统一清理)
|
||||
cancelMap.Delete(getRes.TraceId)
|
||||
|
||||
// 同步更新流程执行状态为已取消
|
||||
_, err = flowDao.FlowExecutionDao.Update(ctx, &flowDto.UpdateFlowExecutionReq{
|
||||
Id: getRes.Id,
|
||||
Status: flow.FlowExecutionStatusCancel.Code(),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新取消状态失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.ExecuteReq) (res *flowDto.ExecuteRes, err error) {
|
||||
// ===================== 核心改造1:创建可取消的上下文 =====================
|
||||
execCtx, cancel := context.WithCancel(ctx)
|
||||
traceId := ""
|
||||
defer func() {
|
||||
// 流程结束(成功/失败)时清理 cancelMap
|
||||
if traceId != "" {
|
||||
cancelMap.Delete(traceId)
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
flowInfo, err := flowDao.FlowExecutionDao.Get(ctx, &flowDto.GetFlowExecutionReq{
|
||||
SessionId: req.SessionId,
|
||||
@@ -244,7 +297,9 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
return
|
||||
}
|
||||
var executionId int64
|
||||
var isDialogue bool
|
||||
if flowInfo == nil {
|
||||
isDialogue = false
|
||||
var r = new(flowDto.CreateFlowExecutionReq)
|
||||
r.FlowUserId = req.FlowId
|
||||
r.FlowName = req.FlowName
|
||||
@@ -256,38 +311,59 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if span != nil && span.SpanContext().HasTraceID() {
|
||||
r.TraceId = span.SpanContext().TraceID().String()
|
||||
traceId = r.TraceId
|
||||
cancelMap.Store(traceId, cancel)
|
||||
}
|
||||
executionId, err = flowDao.FlowExecutionDao.Insert(ctx, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
isDialogue = true
|
||||
executionId = flowInfo.Id
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if span != nil && span.SpanContext().HasTraceID() {
|
||||
traceId = span.SpanContext().TraceID().String()
|
||||
cancelMap.Store(traceId, cancel)
|
||||
}
|
||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||
Id: executionId,
|
||||
Status: flow.FlowExecutionStatusRunning.Code(),
|
||||
TraceId: traceId,
|
||||
}
|
||||
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = flowDao.FlowUserDao.Update(ctx, &flowDto.UpdateFlowUserReq{
|
||||
Id: req.FlowId,
|
||||
FlowContent: req.FlowContent,
|
||||
NodeInputParams: req.NodeInputParams,
|
||||
})
|
||||
if !g.IsEmpty(req.FileUrl) {
|
||||
createFileTempReq := make([]*fileDto.CreateFileTempReq, 0, len(req.FileUrl))
|
||||
for _, fileUrl := range req.FileUrl {
|
||||
var createReq = new(fileDto.CreateFileTempReq)
|
||||
createReq.BusinessId = req.SessionId
|
||||
createReq.FileUrl = fileUrl
|
||||
createFileTempReq = append(createFileTempReq, createReq)
|
||||
}
|
||||
_, err = fileDao.FileTempDao.BatchInsert(ctx, createFileTempReq)
|
||||
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步】给所有判断节点自动生成意图识别节点
|
||||
@@ -329,18 +405,19 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
// =========================================================================
|
||||
// ✅【第2步】构建执行图
|
||||
// =========================================================================
|
||||
runGraph, err := BuildGraphFromFlowContent(ctx, 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,
|
||||
Status: flow.FlowExecutionStatusFailed.Code(),
|
||||
ErrorMessage: err.Error(),
|
||||
}
|
||||
executionReq.Status = flow.FlowExecutionStatusFailed.Code()
|
||||
executionReq.ErrorMessage = err.Error()
|
||||
_, err1 := flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("执行工作流失败: %v", err)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -363,6 +440,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
// ✅【第4步】构建全局执行入参(现在 schemaMap 是有值的!)
|
||||
// =========================================================================
|
||||
execInput := &flowDto.FlowExecutionInput{
|
||||
IsDialogue: isDialogue,
|
||||
ExecutionId: executionId,
|
||||
ConfigMap: configMap,
|
||||
SessionId: req.SessionId,
|
||||
@@ -371,22 +449,29 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
FileUrl: req.FileUrl,
|
||||
}
|
||||
// 执行工作流
|
||||
_, err = runGraph.Invoke(ctx, execInput)
|
||||
_, err = runGraph.Invoke(execCtx, execInput)
|
||||
if err != nil {
|
||||
// 检测是否是取消导致的错误
|
||||
if errors.Is(execCtx.Err(), context.Canceled) {
|
||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||
Id: executionId,
|
||||
Status: flow.FlowExecutionStatusCancel.Code(),
|
||||
}
|
||||
_, _ = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
return nil, fmt.Errorf("工作流已被取消: %v", err)
|
||||
}
|
||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||
Id: executionId,
|
||||
Status: flow.FlowExecutionStatusFailed.Code(),
|
||||
ErrorMessage: err.Error(),
|
||||
}
|
||||
executionReq.Status = flow.FlowExecutionStatusFailed.Code()
|
||||
executionReq.ErrorMessage = err.Error()
|
||||
_, err1 := flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("执行工作流失败: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// BuildGraphFromFlowContent 根据前端保存的工作流JSON,自动构建执行图
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,107 +3,25 @@ package flow
|
||||
import (
|
||||
"ai-agent/workflow/consts/flow"
|
||||
"ai-agent/workflow/consts/node"
|
||||
"ai-agent/workflow/consts/public"
|
||||
fileDao "ai-agent/workflow/dao/file"
|
||||
flowDao "ai-agent/workflow/dao/flow"
|
||||
"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/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
|
||||
}
|
||||
@@ -113,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)
|
||||
@@ -127,43 +50,28 @@ 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)
|
||||
}
|
||||
for _, v := range *out {
|
||||
if !nodeInput.Global.IsDialogue {
|
||||
for _, v := range outputResult {
|
||||
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, v.Label, v.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if !g.IsEmpty(nodeInput.Global.Desc) {
|
||||
contextParts = fmt.Sprintf("%s,%s:%s", contextParts, "描述", nodeInput.Global.Desc)
|
||||
}
|
||||
configMap := gconv.Map(nodeInput.Config.Config)
|
||||
ids := gconv.Strings(configMap["branch_ids"])
|
||||
branchIdNameMap := gconv.Map(configMap["branch_id_name_map"])
|
||||
@@ -175,33 +83,17 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
|
||||
branchIdNameLines = append(branchIdNameLines, fmt.Sprintf("%s: %s", id, name))
|
||||
}
|
||||
|
||||
prompt := fmt.Sprintf(`
|
||||
你是流程路由助手,你的任务是根据上下文,选择一个正确的节点ID返回。
|
||||
|
||||
规则:
|
||||
1. 只允许从下面的可选节点ID列表中选择一个返回
|
||||
2. 不要返回任何多余文字、标点、解释、标题
|
||||
3. 只返回纯节点ID
|
||||
|
||||
可选节点ID(ID: 节点描述):
|
||||
%s
|
||||
|
||||
上下文内容:
|
||||
%s
|
||||
`, strings.Join(branchIdNameLines, "\n"), contextParts)
|
||||
|
||||
getIsChatModel, err := GetIsChatModel(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req := flowDto.ComposeMessagesReq{
|
||||
BuildType: 2,
|
||||
ModelName: getIsChatModel.ModelName,
|
||||
SkillName: "",
|
||||
IsBuild: true,
|
||||
Cause: "判断节点",
|
||||
Form: map[string]any{},
|
||||
UserForm: map[string]any{"prompt": prompt},
|
||||
Form: map[string]any{"prompt": strings.Join(branchIdNameLines, "\n")},
|
||||
UserForm: map[string]any{"prompt": contextParts},
|
||||
UserFiles: nodeInput.Global.FileUrl,
|
||||
SessionId: nodeInput.Global.SessionId,
|
||||
}
|
||||
@@ -209,19 +101,13 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
taskResult, err := GatewayTask(ctx, msg.EpicycleId, getIsChatModel.ModelName, msg.Messages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if g.IsEmpty(msg.Messages) {
|
||||
return "", fmt.Errorf("msg is empty")
|
||||
}
|
||||
result, err := GetTaskResult(ctx, taskResult)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
mapTaskResult := gconv.Map(result.Text)
|
||||
|
||||
content := ""
|
||||
for key, _ := range getIsChatModel.ResponseBody {
|
||||
content = gconv.String(mapTaskResult[key])
|
||||
content = gconv.String(msg.Messages[key])
|
||||
}
|
||||
|
||||
fmt.Printf("JudgeLambda路由:目标节点ID=%s\n", gconv.String(content))
|
||||
@@ -235,325 +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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
resultUserFrom := make(map[string]any)
|
||||
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 := "你是专业内容生成助手,请严格按以下规则输出内容:\n1. 输出标准 HTML 片段,不要 Markdown,不要 ``` 符号,不要多余解释\n2. 整体用 <div class=\"report-container\"> 包裹\n3. 主标题使用 <h2 class=\"title\">\n4. 章节标题使用 <h3 class=\"section-title\">\n5. 正文段落使用 <p class=\"paragraph\">\n6. 列表使用 <ul class=\"list\"><li>...</li></ul>\n7. 重点内容使用 <strong> 加粗\n8. 段落之间清晰分隔,结构规整\n9. 如果生成多条文案,每条文案独立用 <div class=\"content-item\" id=\"content-{序号}\"> 包裹(序号从1开始)\n10. 每条文案内部必须在最上方添加一行固定格式:<p class=\"image-count\">需要配图:N 张</p> N 是这条文案需要的图片数量,只能是数字,不能是其他文字\n11. 只输出 HTML 结构,不输出任何额外文字"
|
||||
resultUserFrom["prompt"] = contentStr
|
||||
|
||||
req := flowDto.ComposeMessagesReq{
|
||||
ModelName: nodeInput.Config.ModelConfig.ModelName,
|
||||
SkillName: skillName,
|
||||
IsBuild: true,
|
||||
Cause: "文案节点",
|
||||
Form: resultFrom,
|
||||
UserForm: resultUserFrom,
|
||||
UserFiles: nodeInput.Global.FileUrl,
|
||||
SessionId: nodeInput.Global.SessionId,
|
||||
}
|
||||
//contentStr := "你是专业内容生成助手,请按以下通用规则输出内容:\n1. 输出标准 HTML 片段,不要 Markdown,不要 ``` 符号,不要多余解释\n2. 整体用 <div class=\"report-container\"> 包裹\n3. 主标题使用 <h2 class=\"title\">\n4. 章节标题使用 <h3 class=\"section-title\">\n5. 正文段落使用 <p class=\"paragraph\">\n6. 列表使用 <ul class=\"list\"><li>...</li></ul>\n7. 重点内容使用 <strong> 加粗\n8. 段落之间清晰分隔,结构规整\n9. 只输出 HTML 结构,不输出任何额外文字"
|
||||
|
||||
//contentStr := "你是专业内容生成助手,请按以下通用规则输出内容:\n1. 输出标准 HTML 片段,不要 Markdown,不要 ``` 符号,不要多余解释\n2. 整体用 <div class=\"report-container\"> 包裹\n3. 主标题使用 <h2 class=\"title\">\n4. 章节标题使用 <h3 class=\"section-title\">\n5. 正文段落使用 <p class=\"paragraph\">\n6. 列表使用 <ul class=\"list\"><li>...</li></ul>\n7. 重点内容使用 <strong> 加粗\n8. 段落之间清晰分隔,结构规整\n9. 如果生成多条文案,每条文案独立用 <div class=\"content-item\" id=\"content-{序号}\"> 包裹(序号从1开始)\n10. 只输出 HTML 结构,不输出任何额外文字"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
// 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("文案纯文本_%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+) 张</p>`)
|
||||
match := re.FindStringSubmatch(content)
|
||||
if len(match) >= 2 {
|
||||
num, _ := strconv.Atoi(match[1])
|
||||
return num
|
||||
}
|
||||
return 0 // 没找到默认 0
|
||||
}
|
||||
|
||||
// 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, "")
|
||||
|
||||
// 3. 🔥 新增:删除 "需要配图:X 张" 这一行(含前后可能的空格/换行)
|
||||
imageCountLine := regexp.MustCompile(`(?m)^\s*需要配图:\d+\s*张\s*$`)
|
||||
text = imageCountLine.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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
resultUserFrom := make(map[string]any)
|
||||
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{
|
||||
ModelName: nodeInput.Config.ModelConfig.ModelName,
|
||||
SkillName: skillName,
|
||||
IsBuild: true,
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
//result := new(flowDto.TaskCallback)
|
||||
//result.Text = "{\n \"content\": [\n {\n \"image\": \"https://dashscope-7c2c.oss-accelerate.aliyuncs.com/7d/8d/20260512/76483b06/306aac7b-915e-479d-94d4-adc3cf1d6f22.png?Expires=1779159512&OSSAccessKeyId=LTAI5tPxpiCM2hjmWrFXrym1&Signature=3a3KDmPNeO%2BVjHJbAV8t0R7UF6Q%3D\"\n },\n {\n \"image\": \"https://dashscope-7c2c.oss-accelerate.aliyuncs.com/7d/c9/20260512/76483b06/f8f3e9be-2920-48b8-93f5-acbf26e52b0c.png?Expires=1779159512&OSSAccessKeyId=LTAI5tPxpiCM2hjmWrFXrym1&Signature=li%2FpcoX5i7FJrk3PCpw5jrbWy2k%3D\"\n },\n {\n \"image\": \"https://dashscope-7c2c.oss-accelerate.aliyuncs.com/7d/89/20260512/76483b06/38d55abe-8230-4837-85d3-426265139be0.png?Expires=1779159512&OSSAccessKeyId=LTAI5tPxpiCM2hjmWrFXrym1&Signature=uNRV9RQY2O60frAtIg6JvCcVhDw%3D\"\n },\n {\n \"image\": \"https://dashscope-7c2c.oss-accelerate.aliyuncs.com/7d/82/20260512/76483b06/e100070d-2a79-4ec8-be72-105226854bab.png?Expires=1779159512&OSSAccessKeyId=LTAI5tPxpiCM2hjmWrFXrym1&Signature=7UCh7FmYt0%2FYxyItNoLELp7zPF0%3D\"\n }\n ]\n}"
|
||||
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("图片_%d关联文案ID", i),
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
nodeInput.Config.OutputResult = outputRes
|
||||
return input, nil
|
||||
return nodeInput, nil
|
||||
}
|
||||
|
||||
func MergeLambda(ctx context.Context, input any) (any, error) {
|
||||
@@ -574,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
|
||||
@@ -585,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
|
||||
@@ -635,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)
|
||||
@@ -665,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())
|
||||
@@ -829,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",
|
||||
@@ -874,113 +331,51 @@ func SummaryLambda(ctx context.Context, input any) (any, error) {
|
||||
// 把汇总结果存入当前节点的输出
|
||||
g.Log().Info(ctx, fmt.Sprintf("结果汇总完成,汇总数据:%+v", summaryResult))
|
||||
|
||||
err := gfdb.DB(ctx, public.DbNameBlackDeacon).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
flowInfo, err := flowDao.FlowExecutionDao.Get(ctx, &flowDto.GetFlowExecutionReq{
|
||||
SessionId: execInput.Global.SessionId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||
Id: execInput.Global.ExecutionId,
|
||||
Status: flow.FlowExecutionStatusSuccess.Code(),
|
||||
OutputParams: summaryResult,
|
||||
}
|
||||
executionReq.Status = flow.FlowExecutionStatusSuccess.Code()
|
||||
_, err := flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
|
||||
if flowInfo != nil {
|
||||
var url string
|
||||
url, err = utils.GetFileAddressPrefix(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createFileTempReq := make([]*fileDto.CreateFileTempReq, 0, len(flowInfo.OutputParams))
|
||||
for _, fileUrl := range flowInfo.OutputParams {
|
||||
m := gconv.Map(fileUrl)
|
||||
for _, v := range m {
|
||||
var createReq = new(fileDto.CreateFileTempReq)
|
||||
createReq.BusinessId = flowInfo.SessionId
|
||||
createReq.FileUrl = url + gconv.String(v)
|
||||
createFileTempReq = append(createFileTempReq, createReq)
|
||||
}
|
||||
}
|
||||
if len(createFileTempReq) > 0 {
|
||||
_, err = fileDao.FileTempDao.BatchInsert(ctx, createFileTempReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return execInput, err
|
||||
}
|
||||
|
||||
//func SummaryLambda(ctx context.Context, input any) (any, error) {
|
||||
// execInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||
// if !ok {
|
||||
// return nil, fmt.Errorf("汇总节点入参类型错误,实际是 %T", input)
|
||||
// }
|
||||
//
|
||||
// // 1. 定义临时映射:按条目序号(如item_0)聚合html/img/text
|
||||
// // key: 条目序号(如0/1/2), value: {html:"", img:"", text:""}
|
||||
// itemMap := make(map[int]map[string]string)
|
||||
// // 存储每个条目对应的时间戳(一个条目一个唯一时间戳)
|
||||
// itemTimeMap := make(map[int]int64)
|
||||
//
|
||||
// // 2. 遍历已执行节点,解析输出字段并分组
|
||||
// for _, nodeID := range execInput.Global.ExecutedNodes {
|
||||
// nodeConfig := execInput.Global.ConfigMap[nodeID]
|
||||
// if nodeConfig == nil || len(nodeConfig.OutputResult) == 0 {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // 遍历节点的输出字段
|
||||
// for _, field := range nodeConfig.OutputResult {
|
||||
// var itemIndex int
|
||||
// var fieldType string
|
||||
// var fieldValue string
|
||||
//
|
||||
// // 匹配「条目HTML地址」字段(如item_html_url_0)
|
||||
// if match := regexp.MustCompile(`item_html_url_(\d+)`).FindStringSubmatch(field.Field); len(match) == 2 {
|
||||
// itemIndex, _ = strconv.Atoi(match[1])
|
||||
// fieldType = "html"
|
||||
// fieldValue = gconv.String(field.Value)
|
||||
// } else if match := regexp.MustCompile(`img_url:(\d+)`).FindStringSubmatch(field.Field); len(match) == 2 {
|
||||
// itemIndex, _ = strconv.Atoi(match[1])
|
||||
// fieldType = "img"
|
||||
// fieldValue = gconv.String(field.Value)
|
||||
// } else if match := regexp.MustCompile(`text_url:(\d+)`).FindStringSubmatch(field.Field); len(match) == 2 {
|
||||
// itemIndex, _ = strconv.Atoi(match[1])
|
||||
// fieldType = "text"
|
||||
// fieldValue = gconv.String(field.Value)
|
||||
// } else {
|
||||
// // 非目标字段,跳过
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // 初始化条目映射(首次遇到该条目时)
|
||||
// if _, exists := itemMap[itemIndex]; !exists {
|
||||
// itemMap[itemIndex] = map[string]string{
|
||||
// "html": "",
|
||||
// "img": "",
|
||||
// "text": "",
|
||||
// }
|
||||
// // 为该条目生成唯一时间戳(毫秒级)
|
||||
// itemTimeMap[itemIndex] = time.Now().UnixMilli()
|
||||
// }
|
||||
//
|
||||
// // 填充该条目对应的字段值
|
||||
// itemMap[itemIndex][fieldType] = fieldValue
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 3. 组装最终的汇总结构:[{内容N:{html:"",img:"",text:""},时间戳:xxx}, ...]
|
||||
// var summaryResult []map[string]interface{}
|
||||
// // 按条目序号排序(保证顺序一致)
|
||||
// itemIndexes := make([]int, 0, len(itemMap))
|
||||
// for idx := range itemMap {
|
||||
// itemIndexes = append(itemIndexes, idx)
|
||||
// }
|
||||
// sort.Ints(itemIndexes)
|
||||
//
|
||||
// // 遍历排序后的条目,组装结构
|
||||
// for _, idx := range itemIndexes {
|
||||
// itemData := itemMap[idx]
|
||||
// timeStamp := itemTimeMap[idx]
|
||||
//
|
||||
// // 单条目结构:{"内容X": {html:"",img:"",text:""}, "时间戳": xxx}
|
||||
// itemResult := make(map[string]interface{})
|
||||
// itemResult[fmt.Sprintf("内容%d", idx+1)] = map[string]string{
|
||||
// "html": itemData["html"],
|
||||
// "img": itemData["img"],
|
||||
// "text": itemData["text"],
|
||||
// }
|
||||
// itemResult["时间戳"] = timeStamp
|
||||
//
|
||||
// summaryResult = append(summaryResult, itemResult)
|
||||
// }
|
||||
//
|
||||
// // 4. 打印调试&更新数据库
|
||||
// g.Log().Info(ctx, fmt.Sprintf("结果汇总完成,汇总数据:%+v", summaryResult))
|
||||
// executionReq := flowDto.UpdateFlowExecutionReq{
|
||||
// Id: execInput.Global.ExecutionId,
|
||||
// OutputParams: summaryResult,
|
||||
// Status: flow.FlowExecutionStatusSuccess.Code(),
|
||||
// }
|
||||
// _, err := flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
//
|
||||
// return execInput, err
|
||||
//}
|
||||
|
||||
// VideoModelLambda 构建视频
|
||||
func VideoModelLambda(ctx context.Context, input any) (any, error) {
|
||||
fmt.Println("VideoModelLambda:", input)
|
||||
|
||||
599
workflow/service/flow/lambda_node_imp.go
Normal file
599
workflow/service/flow/lambda_node_imp.go
Normal 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
|
||||
}
|
||||
@@ -9,15 +9,17 @@ 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"
|
||||
)
|
||||
|
||||
func GetIsChatModel(ctx context.Context) (*flowDto.GetIsChatModelRes, error) {
|
||||
func GetIsChatModel(ctx context.Context) (res *flowDto.GetIsChatModelRes, err error) {
|
||||
headers := make(map[string]string)
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
@@ -26,13 +28,69 @@ func GetIsChatModel(ctx context.Context) (*flowDto.GetIsChatModelRes, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
res := new(flowDto.GetIsChatModelRes)
|
||||
err := commonHttp.Get(ctx, "model-gateway/model/getIsChatModel", headers, res, nil)
|
||||
res = new(flowDto.GetIsChatModelRes)
|
||||
err = commonHttp.Get(ctx, "model-gateway/model/getIsChatModel", headers, res, nil)
|
||||
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 res, nil
|
||||
return Wait(ctx, modelTaskId)
|
||||
}
|
||||
|
||||
func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string, error) {
|
||||
@@ -53,38 +111,6 @@ func CreateGatewayTask(ctx context.Context, req *flowDto.CreateTaskReq) (string,
|
||||
return res.TaskId, nil
|
||||
}
|
||||
|
||||
func ComposeMessages(ctx context.Context, req *flowDto.ComposeMessagesReq) (*flowDto.ComposeMessagesRes, 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, 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 GetTaskResult(ctx context.Context, result any) (*flowDto.TaskCallback, error) {
|
||||
task := new(flowDto.TaskCallback)
|
||||
if err := gconv.Struct(result, task); err != nil {
|
||||
@@ -122,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
|
||||
}
|
||||
|
||||
@@ -170,76 +201,274 @@ func Upload(ctx context.Context, req *dto.UploadFileBytesReq) (*dto.UploadFileBy
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func buildMergeHtml(texts []string, images []string) string {
|
||||
html := strings.Builder{}
|
||||
|
||||
html.WriteString(`
|
||||
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", Arial, sans-serif;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
/* 图片:完全贴边,无额外间距 */
|
||||
.image-block {
|
||||
width: 100%;
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.image-block img {
|
||||
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;
|
||||
border-radius: 0;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
/* 文案:极致紧凑 */
|
||||
.text-block {
|
||||
.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: 16px; /* 仅保留内边距,不设外边距 */
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
color: #444;
|
||||
white-space: pre-wrap;
|
||||
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">
|
||||
`)
|
||||
|
||||
// 1. 先渲染图片(无任何上下边距,占满宽度)
|
||||
// 写入图片(支持0张、1张、多张)
|
||||
if len(images) > 0 {
|
||||
html.WriteString(`<div class="image-block">`)
|
||||
for _, img := range images {
|
||||
html.WriteString(fmt.Sprintf(`<img src="%s" alt="" />`, img))
|
||||
htmlBuilder.WriteString(`<div class="image-group">`)
|
||||
for _, imgUrl := range images {
|
||||
htmlBuilder.WriteString(fmt.Sprintf(`<img src="%s" alt="图片"/>`, imgUrl))
|
||||
}
|
||||
html.WriteString(`</div>`)
|
||||
htmlBuilder.WriteString(`</div>`)
|
||||
}
|
||||
|
||||
// 2. 渲染文案(紧贴图片下方,仅用内边距留白)
|
||||
if len(texts) > 0 {
|
||||
html.WriteString(`<div class="text-block">`)
|
||||
// 段落之间用 <br> 而不是 <br><br>,减少空行
|
||||
html.WriteString(strings.Join(texts, "<br>"))
|
||||
html.WriteString(`</div>`)
|
||||
}
|
||||
|
||||
html.WriteString(`
|
||||
htmlBuilder.WriteString(`
|
||||
<div id="content">加载中...</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
return html.String()
|
||||
<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}`)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -5,9 +5,13 @@ import (
|
||||
skillDto "ai-agent/workflow/model/dto/skill"
|
||||
"ai-agent/workflow/model/entity"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
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"
|
||||
)
|
||||
@@ -34,25 +38,105 @@ func IsAdmin(ctx context.Context) (res bool, err error) {
|
||||
}
|
||||
|
||||
func (s *skillUserService) Create(ctx context.Context, req *skillDto.CreateSkillUserReq) (res *skillDto.CreateSkillUserRes, err error) {
|
||||
ext := strings.TrimPrefix(filepath.Ext(req.FileUrl), ".")
|
||||
if ext != "zip" {
|
||||
return nil, fmt.Errorf("文件格式不支持,请上传zip格式文件")
|
||||
}
|
||||
admin, err := IsAdmin(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var id int64
|
||||
if admin {
|
||||
var count int
|
||||
count, err = skillDao.SkillTemplateDao.Count(ctx, &skillDto.GetSkillTemplateReq{
|
||||
Name: req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil, fmt.Errorf("技能名称 %s 已存在", req.Name)
|
||||
}
|
||||
id, err = skillDao.SkillTemplateDao.Insert(ctx, &skillDto.CreateSkillTemplateReq{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Category: req.Category,
|
||||
FileName: req.FileName,
|
||||
FileUrl: req.FileUrl,
|
||||
})
|
||||
} else {
|
||||
var user *beans.User
|
||||
user, err = utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var count int
|
||||
count, err = skillDao.SkillUserDao.Count(ctx, &skillDto.GetSkillUserReq{
|
||||
Name: req.Name,
|
||||
Creator: user.UserName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil, fmt.Errorf("技能名称 %s 已存在", req.Name)
|
||||
}
|
||||
id, err = skillDao.SkillUserDao.Insert(ctx, req)
|
||||
}
|
||||
return &skillDto.CreateSkillUserRes{Id: id}, err
|
||||
}
|
||||
|
||||
func (s *skillUserService) Update(ctx context.Context, req *skillDto.UpdateSkillUserReq) (err error) {
|
||||
ext := strings.TrimPrefix(filepath.Ext(req.FileUrl), ".")
|
||||
if ext != "zip" {
|
||||
return fmt.Errorf("文件格式不支持,请上传zip格式文件")
|
||||
}
|
||||
admin, err := IsAdmin(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if admin {
|
||||
var count int
|
||||
count, err = skillDao.SkillTemplateDao.Count(ctx, &skillDto.GetSkillTemplateReq{
|
||||
NotInId: req.Id,
|
||||
Name: req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return fmt.Errorf("技能名称 %s 已存在", req.Name)
|
||||
}
|
||||
_, err = skillDao.SkillTemplateDao.Update(ctx, &skillDto.UpdateSkillTemplateReq{
|
||||
Id: req.Id,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
FileName: req.FileName,
|
||||
FileUrl: req.FileUrl,
|
||||
})
|
||||
} else {
|
||||
var user *beans.User
|
||||
user, err = utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var count int
|
||||
count, err = skillDao.SkillUserDao.Count(ctx, &skillDto.GetSkillUserReq{
|
||||
NotInId: req.Id,
|
||||
Name: req.Name,
|
||||
Creator: user.UserName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return fmt.Errorf("技能名称 %s 已存在", req.Name)
|
||||
}
|
||||
_, err = skillDao.SkillUserDao.Update(ctx, req)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *skillUserService) Delete(ctx context.Context, req *skillDto.DeleteSkillUserReq) (err error) {
|
||||
admin, err := IsAdmin(ctx)
|
||||
if err != nil {
|
||||
@@ -68,6 +152,49 @@ func (s *skillUserService) Delete(ctx context.Context, req *skillDto.DeleteSkill
|
||||
return
|
||||
}
|
||||
|
||||
func (s *skillUserService) Get(ctx context.Context, req *skillDto.GetSkillUserReq) (res *skillDto.SkillUserVO, err error) {
|
||||
admin, err := IsAdmin(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if admin {
|
||||
var list *entity.SkillTemplate
|
||||
list, err = skillDao.SkillTemplateDao.Get(ctx, &skillDto.GetSkillTemplateReq{
|
||||
Id: req.Id,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &skillDto.SkillUserVO{}
|
||||
res.ImgAddressPrefix, err = utils.GetFileAddressPrefix(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = gconv.Struct(list, &res)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Creator = user.UserName
|
||||
list, err := skillDao.SkillUserDao.Get(ctx, &skillDto.GetSkillUserReq{
|
||||
Id: req.Id,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &skillDto.SkillUserVO{}
|
||||
res.ImgAddressPrefix, err = utils.GetFileAddressPrefix(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = gconv.Struct(list, &res)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *skillUserService) List(ctx context.Context, req *skillDto.ListSkillReq) (res *skillDto.ListSkillUserRes, err error) {
|
||||
admin, err := IsAdmin(ctx)
|
||||
if err != nil {
|
||||
@@ -128,3 +255,46 @@ func (s *skillUserService) ListUser(ctx context.Context, req *skillDto.ListSkill
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *skillUserService) GetUserOrTemplate(ctx context.Context, req *skillDto.GetSkillReq) (res *skillDto.SkillUserVO, err error) {
|
||||
var list *entity.SkillTemplate
|
||||
list, err = skillDao.SkillTemplateDao.Get(ctx, &skillDto.GetSkillTemplateReq{
|
||||
Id: req.Id,
|
||||
Name: req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !g.IsEmpty(list) {
|
||||
res = &skillDto.SkillUserVO{}
|
||||
res.ImgAddressPrefix, err = utils.GetFileAddressPrefix(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = gconv.Struct(list, &res)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Creator = user.UserName
|
||||
var userList *entity.SkillUser
|
||||
userList, err = skillDao.SkillUserDao.Get(ctx, &skillDto.GetSkillUserReq{
|
||||
Id: req.Id,
|
||||
Creator: user.UserName,
|
||||
Name: req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &skillDto.SkillUserVO{}
|
||||
res.ImgAddressPrefix, err = utils.GetFileAddressPrefix(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = gconv.Struct(userList, &res)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user