Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f96d3362ac | ||
|
|
89326b30ae | ||
|
|
4c940c6753 | ||
|
|
ad9824aa55 | ||
|
|
8d7e260da0 | ||
|
|
f8a28bf0f3 | ||
|
|
ef8388f66b | ||
|
|
d2eef8d3ca | ||
|
|
031819f634 | ||
|
|
20723f58ce | ||
|
|
08251d9a73 | ||
|
|
cc29dd21e4 | ||
|
|
14c88efe79 | ||
|
|
4ccb81b34a | ||
|
|
e548f20b6e | ||
|
|
f5be9d8a40 | ||
|
|
b83b18a5c8 | ||
|
|
fa2a07600b | ||
|
|
0c50178f0a | ||
|
|
8209219f92 | ||
|
|
48f3b46929 | ||
|
|
c19bd17632 | ||
|
|
bf5d88158e | ||
|
|
8a639d3d09 | ||
|
|
d6e6d0b403 | ||
|
|
ffba7ff726 | ||
|
|
6942e05f1a | ||
|
|
36f9871deb | ||
|
|
91a6436ad7 | ||
|
|
e4bd08458a | ||
|
|
243c9e291f | ||
|
|
84592a09df | ||
|
|
44702d32ad | ||
|
|
29557bdc4d | ||
|
|
4df45069e0 |
@@ -0,0 +1 @@
|
||||
.git
|
||||
@@ -0,0 +1,37 @@
|
||||
# ============================================================
|
||||
# ai-agent dev 分支自动发布
|
||||
# 触发:push dev 分支
|
||||
# 流程:构建镜像 -> push 本地仓库(127.0.0.1:5000) -> 部署到 dev 宿主机本地 k3s
|
||||
# 前置:runner 挂载 dev k3s kubeconfig 到 /root/.kube/k3s-dev.yaml
|
||||
# ============================================================
|
||||
name: Deploy ai-agent to dev k3s
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 2026-06-16 ]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 不依赖 actions/checkout(从 github.com 拉 action 源码,国内网络不稳定),
|
||||
# 直接 clone 本地 Gitea,用 Actions 注入的 GITHUB_TOKEN 认证
|
||||
- name: 拉取代码
|
||||
run: |
|
||||
git clone --branch dev http://x-access-token:${GITHUB_TOKEN}@127.0.0.1:3000/red-future/ai-agent.git .
|
||||
git checkout ${GITHUB_SHA}
|
||||
|
||||
- name: 构建并推送镜像
|
||||
run: |
|
||||
TAG=dev-${GITHUB_SHA::8}
|
||||
docker build -t 127.0.0.1:5000/ai-agent:${TAG} .
|
||||
docker push 127.0.0.1:5000/ai-agent:${TAG}
|
||||
|
||||
- name: 部署到 dev k3s
|
||||
env:
|
||||
KUBECONFIG: /root/.kube/k3s-dev.yaml
|
||||
run: |
|
||||
kubectl create namespace dev --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl apply -f deploy/k8s/ai-agent.yaml
|
||||
kubectl set image deployment/ai-agent ai-agent=127.0.0.1:5000/ai-agent:dev-${GITHUB_SHA::8} -n dev
|
||||
kubectl rollout status deployment/ai-agent -n dev --timeout=5m
|
||||
+2
-26
@@ -1,7 +1,8 @@
|
||||
# 阶段1: 构建
|
||||
FROM golang:alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git ca-certificates tzdata
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
|
||||
apk add --no-cache git ca-certificates tzdata
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
@@ -10,12 +11,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 +19,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
|
||||
|
||||
|
||||
+9
-9
@@ -5,7 +5,7 @@ server:
|
||||
database:
|
||||
default:
|
||||
- type: "pgsql"
|
||||
host: "116.204.74.41"
|
||||
host: "192.168.0.83"
|
||||
port: "15432"
|
||||
user: "postgres"
|
||||
pass: "Bjang09@686^*^"
|
||||
@@ -26,7 +26,7 @@ database:
|
||||
timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效
|
||||
black_deacon:
|
||||
- type: "pgsql"
|
||||
host: "116.204.74.41"
|
||||
host: "192.168.0.83"
|
||||
port: "15432"
|
||||
user: "postgres"
|
||||
pass: "Bjang09@686^*^"
|
||||
@@ -48,7 +48,7 @@ database:
|
||||
|
||||
redis:
|
||||
default:
|
||||
address: 116.204.74.41:6379
|
||||
address: 192.168.0.83:6379
|
||||
db: 0
|
||||
idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/1m/1d
|
||||
maxConnLifetime: "90s" #连接最长存活时间,使用时间字符串例如30s/1m/1d
|
||||
@@ -59,13 +59,13 @@ redis:
|
||||
maxActive: 100
|
||||
|
||||
consul:
|
||||
address: 116.204.74.41:8500
|
||||
address: 192.168.0.83:8500
|
||||
|
||||
jaeger:
|
||||
addr: 116.204.74.41:4318
|
||||
addr: 192.168.0.83:4318
|
||||
|
||||
# 文件上传服务地址,与oss模块minio中的endpoint一致
|
||||
filePrefix: "http://116.204.74.41:9000"
|
||||
# 文件上传服务地址,cdn访问地址
|
||||
filePrefix: "http://cdn.redpowerfuture.com"
|
||||
|
||||
model-asynch:
|
||||
addr: "127.0.0.1:8001"
|
||||
# 文件上传服务地址,minio内网访问地址
|
||||
minioPrefix: "http://192.168.0.83:9000"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ai-agent
|
||||
namespace: dev
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ai-agent
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ai-agent
|
||||
spec:
|
||||
containers:
|
||||
- name: ai-agent
|
||||
image: 127.0.0.1:5000/ai-agent:dev-latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 3005
|
||||
env:
|
||||
- name: TZ
|
||||
value: Asia/Shanghai
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 3005
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 3005
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ai-agent
|
||||
namespace: dev
|
||||
spec:
|
||||
selector:
|
||||
app: ai-agent
|
||||
ports:
|
||||
- port: 3005
|
||||
targetPort: 3005
|
||||
@@ -3,8 +3,8 @@ module ai-agent
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.23
|
||||
github.com/cloudwego/eino v0.9.5
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.29
|
||||
github.com/cloudwego/eino v0.9.12
|
||||
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2
|
||||
@@ -29,7 +29,6 @@ require (
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
@@ -66,7 +65,7 @@ require (
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
@@ -82,16 +81,15 @@ require (
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f // indirect
|
||||
github.com/r3labs/diff/v2 v2.15.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.12.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.17.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tiger1103/gfast-token v1.0.10 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/vcaesar/cedar v0.30.0 // indirect
|
||||
@@ -107,8 +105,8 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
golang.org/x/arch v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/arch v0.19.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
|
||||
@@ -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.21 h1:8w30HmCVmFG/hphH3ODJs1KxDEGmRpq+/PXI0pQjJKc=
|
||||
gitea.com/red-future/common v0.0.21/go.mod h1:6/nqIucVzmjOyqDTIq71feYBXXFNBy0rFwzaQ0/Ueoo=
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.23 h1:xieoA00iKOCDm5SO9iXn+cSyMKBAlZwI0fuEVPWrHLg=
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.23/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY=
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.29 h1:5McaN5pSewvrLUHQzWMX6EaUvD+B5I5bMYoU+clHJk4=
|
||||
gitea.redpowerfuture.com/red-future/common v0.0.29/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,10 +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 v0.9.12 h1:mHAMo5k7GdvnVD8Lc2sLyfpkxEm0S/y3PkEMhsSYt78=
|
||||
github.com/cloudwego/eino v0.9.12/go.mod h1:OBD1mrkfkt/pJa4rkg1P0VnaMeOVl7l8IAdEqY//3IQ=
|
||||
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9 h1:xCz/mp43JeWqupjPR3zLRArmwC6P29/6lTwbwh1yzYM=
|
||||
github.com/cloudwego/eino-ext/components/model/qwen v0.1.9/go.mod h1:slTGTuhzkzhNavf+1UtUg1FvUSA31iNAF+rq1mT4SnI=
|
||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 h1:EeVcR1TslRA2IdNW1h/2LaGbPlffwGhQm99jM3zWZiI=
|
||||
@@ -116,20 +112,14 @@ 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/drivers/pgsql/v2 v2.10.2 h1:u8EpP24GkprogROnJ7htMov9Fc66pTP1eVYrWxiCYOs=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2/go.mod h1:GmvM3r8GVByVMi4RD2+MCs5+CfxVXPMeT8mVDkAaAXE=
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0 h1:N/F9CuDdUZLoM1nVRqrDE/33pDZuhVxpNY4wYdeIaBs=
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0/go.mod h1:x6uoJGfZOtirIRQls8xUlYzC6f7T/eULPUa9er368X0=
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2 h1:iTQegT+lEg/wDKvj2mi3W1wrdrwFarjokf88EXVVgu4=
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2/go.mod h1:ZRw3GNz5cq4uYrW4TPSVyrYWaoqzujKdWro/AOcGBaE=
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88=
|
||||
github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs=
|
||||
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/gogf/gf/v2 v2.10.2 h1:46IO0Uc8e85/FqdftJFskfDejJLBL0JBnGS5qOftUu8=
|
||||
github.com/gogf/gf/v2 v2.10.2/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -243,8 +233,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
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/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -314,12 +304,13 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f h1:lJqhwddJVYAkyp72a4pwzMClI20xTwL7miDdm2W/KBM=
|
||||
github.com/pkg/errors v0.9.2-0.20201214064552-5dd12d0cfe7f/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -339,13 +330,13 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
|
||||
github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
|
||||
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
|
||||
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
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=
|
||||
@@ -384,8 +375,9 @@ github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
|
||||
@@ -411,28 +403,20 @@ go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
@@ -441,8 +425,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
@@ -451,8 +435,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
|
||||
@@ -106,6 +106,7 @@ type NodeItem struct {
|
||||
NodeCode NodeType `json:"nodeCode"`
|
||||
ModelType int `json:"modelType"`
|
||||
NodeName string `json:"nodeName"` // 从常量来
|
||||
PatchLayout bool `json:"patchLayout"`
|
||||
SkillOption bool `json:"skillOption"`
|
||||
PromptOption bool `json:"promptOption"`
|
||||
IsSaveFile bool `json:"isSaveFile"`
|
||||
|
||||
@@ -38,3 +38,17 @@ func (c *flowExecution) Get(ctx context.Context, req *flowDto.GetFlowExecutionRe
|
||||
func (c *flowExecution) List(ctx context.Context, req *flowDto.ListFlowExecutionReq) (res *flowDto.ListFlowExecutionTreeRes, err error) {
|
||||
return flowService.FlowExecutionService.List(ctx, req)
|
||||
}
|
||||
|
||||
func (c *flowExecution) GetSessionList(ctx context.Context, req *flowDto.GetSessionListReq) (res *flowDto.ListFlowExecutionRes, err error) {
|
||||
return flowService.FlowExecutionService.GetSessionList(ctx, req)
|
||||
}
|
||||
|
||||
func (c *flowExecution) DeleteResult(ctx context.Context, req *flowDto.DeleteResultReq) (res *beans.ResponseEmpty, err error) {
|
||||
err = flowService.FlowExecutionService.DeleteResult(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *flowExecution) DeleteSession(ctx context.Context, req *flowDto.DeleteSessionReq) (res *beans.ResponseEmpty, err error) {
|
||||
err = flowService.FlowExecutionService.DeleteSession(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@@ -49,8 +50,17 @@ func (d *flowExecutionDao) Get(ctx context.Context, req *flowDto.GetFlowExecutio
|
||||
}
|
||||
|
||||
func (d *flowExecutionDao) List(ctx context.Context, req *flowDto.ListFlowExecutionReq, fields ...string) (res []*entity.FlowExecution, total int, err error) {
|
||||
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowExecution).Fields(fields).OmitEmpty()
|
||||
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameFlowExecution).Fields(fields)
|
||||
model.Where(entity.FlowExecutionCol.Creator, req.Creator)
|
||||
if !g.IsEmpty(req.ResultDel) {
|
||||
model.Where(entity.FlowExecutionCol.ResultDel, false)
|
||||
}
|
||||
if !g.IsEmpty(req.SessionDel) {
|
||||
model.Where(entity.FlowExecutionCol.SessionDel, false)
|
||||
}
|
||||
if req.IsResult {
|
||||
model.WhereNot(entity.FlowExecutionCol.OutputParams, "[]")
|
||||
}
|
||||
model.OrderDesc(entity.FlowExecutionCol.CreatedAt)
|
||||
if req.Page != nil {
|
||||
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||
|
||||
@@ -86,6 +86,7 @@ func (d *nodeExecutionDao) Get(ctx context.Context, req *nodeDto.GetNodeExecutio
|
||||
func (d *nodeExecutionDao) ListByFlowExecutionId(ctx context.Context, req *nodeDto.ListNodeExecutionByFlowReq, fields ...string) (res []*entity.NodeExecution, total int, err error) {
|
||||
model := gfdb.DB(ctx, public.DbNameBlackDeacon).Model(ctx, public.TableNameNodeExecution).NoTenantId(ctx).Fields(fields).OmitEmpty()
|
||||
model.Where(entity.NodeExecutionCol.FlowExecutionId, req.FlowExecutionId)
|
||||
model.Where(entity.NodeExecutionCol.NodeGroupId, req.NodeGroupId)
|
||||
model.OrderAsc(entity.NodeExecutionCol.CreatedAt)
|
||||
if req.Page != nil {
|
||||
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.com/red-future/common/db/gfdb"
|
||||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ type FlowExecutionInput struct {
|
||||
ExecutionId int64 `json:"executionId"`
|
||||
ConfigMap map[string]*entity.FlowNode `json:"configMap"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
Templates []map[string]any `json:"templates"`
|
||||
Desc string `json:"desc"`
|
||||
SkillName string `json:"skillName"`
|
||||
FileUrl []string `json:"fileUrl"`
|
||||
@@ -49,6 +50,7 @@ type GetModelInfoReq struct {
|
||||
|
||||
type GetModelInfoRes struct {
|
||||
Model struct {
|
||||
FirstFrame string `json:"firstFrame"`
|
||||
LastFrame string `json:"lastFrame"`
|
||||
ResponseTokenField string `json:"responseTokenField"`
|
||||
ResponseMapping map[string]any `json:"responseMapping"`
|
||||
@@ -74,6 +76,7 @@ type ComposeMessagesReq struct {
|
||||
CallbackUrl string `json:"callbackUrl"`
|
||||
Form []map[string]any `json:"form"`
|
||||
UserForm []map[string]any `json:"userForm"`
|
||||
UserPrompt string `json:"userPrompt" dc:"用户提示词"`
|
||||
Consult []Consult `json:"consult"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
NodeId string `json:"nodeId"`
|
||||
@@ -122,18 +125,19 @@ type ComposeCallbackReq struct {
|
||||
TotalRounds int `json:"total_rounds"` // 总轮数
|
||||
Rounds []map[string]any `json:"rounds"` // 每轮详情(动态类型)
|
||||
} `json:"messages,omitempty"`
|
||||
EpicycleId int64 `json:"epicycleId"`
|
||||
ErrorMsg string `json:"errorMsg,omitempty"`
|
||||
EpicycleId int64 `json:"epicycleId"`
|
||||
ErrorMsg string `json:"errorMsg,omitempty"`
|
||||
BillingData []map[string]any `json:"billing_data"`
|
||||
}
|
||||
|
||||
type ModelCallbackReq struct {
|
||||
g.Meta `path:"/modelCallback" method:"post" tags:"提示词处理" summary:"model-gateway 回调" dc:"model-gateway 成功后 GET 回调:callbackUrl/{bizName}"`
|
||||
TaskId string `p:"task_id" json:"task_id" v:"required#task_id不能为空" dc:"网关任务ID"`
|
||||
State int `p:"state" json:"state" dc:"网关任务状态"`
|
||||
OssFile string `p:"oss_file" json:"oss_file" dc:"结果文件地址"`
|
||||
FileType string `p:"file_type" json:"file_type" dc:"结果文件类型"`
|
||||
Messages map[string]any `json:"messages"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
g.Meta `path:"/modelCallback" method:"post" tags:"提示词处理" summary:"model-gateway 回调" dc:"model-gateway 成功后 GET 回调:callbackUrl/{bizName}"`
|
||||
TaskId string `p:"task_id" json:"task_id" v:"required#task_id不能为空" dc:"网关任务ID"`
|
||||
State int `p:"state" json:"state" dc:"网关任务状态"`
|
||||
OssFile string `p:"oss_file" json:"oss_file" dc:"结果文件地址"`
|
||||
FileType string `p:"file_type" json:"file_type" dc:"结果文件类型"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
BillingData []map[string]any `json:"billing_data"`
|
||||
}
|
||||
|
||||
type VideoCallbackReq struct {
|
||||
@@ -181,6 +185,7 @@ type ExecuteReq struct {
|
||||
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
|
||||
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
Templates []map[string]any `json:"templates"`
|
||||
Desc string `json:"desc"`
|
||||
SkillName string `json:"skillName"`
|
||||
FileUrl []string `json:"fileUrl"`
|
||||
@@ -210,6 +215,7 @@ type CreateFlowExecutionReq struct {
|
||||
ErrorMessage string `json:"errorMessage" description:"错误信息"`
|
||||
TraceId string `json:"traceId" description:"跟踪ID"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
Extension map[string]interface{} `json:"extension"`
|
||||
}
|
||||
|
||||
type CreateFlowExecutionRes struct {
|
||||
@@ -217,13 +223,33 @@ type CreateFlowExecutionRes struct {
|
||||
}
|
||||
|
||||
type UpdateFlowExecutionReq struct {
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
NodeGroupId string `json:"nodeGroupId"`
|
||||
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
|
||||
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
||||
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
|
||||
ErrorMessage string `json:"errorMessage" description:"错误信息"`
|
||||
TraceId string `json:"traceId" description:"跟踪ID"`
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
NodeGroupId string `json:"nodeGroupId"`
|
||||
DurationMs int64 `json:"durationMs" description:"执行时长(毫秒)"`
|
||||
Status flow.FlowExecutionStatus `json:"status" description:"状态:1-运行中,2-成功,3-失败"`
|
||||
FlowContent *entity.FlowInfo `json:"flowContent" description:"流程内容"`
|
||||
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
|
||||
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
|
||||
ErrorMessage string `json:"errorMessage" description:"错误信息"`
|
||||
TraceId string `json:"traceId" description:"跟踪ID"`
|
||||
TotalTokens int `json:"totalTokens" description:"总token"`
|
||||
TotalFee float64 `json:"totalFee" description:"总费用"`
|
||||
SessionDel bool `json:"sessionDel" description:"会话是否删除"`
|
||||
ResultDel bool `json:"resultDel" description:"结果是否删除"`
|
||||
Extension map[string]interface{} `json:"extension"`
|
||||
}
|
||||
|
||||
type DeleteResultReq struct {
|
||||
g.Meta `path:"/deleteResult" method:"delete" tags:"任务管理" summary:"删除结果" dc:"删除结果"`
|
||||
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type DeleteSessionReq struct {
|
||||
g.Meta `path:"/deleteSession" method:"delete" tags:"任务管理" summary:"删除会话" dc:"删除会话"`
|
||||
|
||||
Id int64 `json:"id" v:"required#ID不能为空"`
|
||||
}
|
||||
|
||||
type GetFlowExecutionReq struct {
|
||||
@@ -233,11 +259,20 @@ type GetFlowExecutionReq struct {
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
}
|
||||
|
||||
type GetSessionListReq struct {
|
||||
g.Meta `path:"/sessionList" method:"get" tags:"任务管理" summary:"会话列表" dc:"会话列表"`
|
||||
|
||||
*beans.Page `json:"page"`
|
||||
}
|
||||
|
||||
type ListFlowExecutionReq struct {
|
||||
g.Meta `path:"/list" method:"get" tags:"任务管理" summary:"任务列表" dc:"任务列表"`
|
||||
|
||||
Page *beans.Page `json:"page"`
|
||||
Creator string `json:"creator"`
|
||||
*beans.Page `json:"page"`
|
||||
Creator string `json:"creator"`
|
||||
SessionDel *bool `json:"sessionDel"`
|
||||
ResultDel *bool `json:"resultDel"`
|
||||
IsResult bool `json:"isResult"`
|
||||
}
|
||||
|
||||
type ListFlowExecutionRes struct {
|
||||
@@ -256,6 +291,7 @@ type VOFlowExecution struct {
|
||||
NodeInputParams []*entity.FlowNode `json:"nodeInputParams" description:"节点输入参数"`
|
||||
OutputParams []map[string]interface{} `json:"outputParams" description:"输出参数"`
|
||||
ErrorMessage string `json:"errorMessage" description:"错误信息"`
|
||||
Extension map[string]interface{} `json:"extension"`
|
||||
TraceId string `json:"traceId" description:"跟踪ID"`
|
||||
SessionId string `json:"sessionId" dc:"会话ID"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
@@ -264,22 +300,17 @@ type VOFlowExecution struct {
|
||||
}
|
||||
|
||||
// ========== 核心:构建树状结构 ==========
|
||||
// 定义树结构
|
||||
type OutputItem struct {
|
||||
Id int64 `json:"id,string" description:"ID"`
|
||||
Timestamp string `json:"timestamp" description:"时间戳key"`
|
||||
Content string `json:"content" description:"内容值"`
|
||||
Type string `json:"type" description:"类型"`
|
||||
Label string `json:"label" description:"后缀+数字标号"`
|
||||
}
|
||||
type FlowNode struct {
|
||||
FlowName string `json:"flowName" description:"流程名称"`
|
||||
Id int64 `json:"Id,string" description:"任务ID"`
|
||||
SessionId string `json:"sessionId" description:"会话ID"`
|
||||
Items []OutputItem `json:"items" description:"输出项列表"`
|
||||
}
|
||||
|
||||
type DateNode struct {
|
||||
CreateDate string `json:"createDate" description:"创建日期"`
|
||||
Flows []FlowNode `json:"flows" description:"流程列表"`
|
||||
CreateDate string `json:"createDate" description:"创建日期"`
|
||||
Items []OutputItem `json:"items" description:"直接是结果项列表"`
|
||||
}
|
||||
|
||||
type ListFlowExecutionTreeRes struct {
|
||||
|
||||
@@ -38,6 +38,7 @@ type UpdateNodeExecutionReq struct {
|
||||
PromptTokens int `json:"promptTokens"`
|
||||
CompletionTokens int `json:"completionTokens"`
|
||||
TotalTokens int `json:"totalTokens"`
|
||||
TokenInfo []map[string]any `json:"tokenInfo"`
|
||||
Status node.NodeExecutionStatus `json:"status"`
|
||||
DurationMs int64 `json:"durationMs"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
@@ -60,6 +61,7 @@ type ListNodeExecutionByFlowReq struct {
|
||||
g.Meta `path:"/listByFlow" method:"get" tags:"节点执行记录" summary:"查询流程节点执行列表" dc:"查询指定流程执行下的所有节点执行记录"`
|
||||
Page *beans.Page `json:"page"`
|
||||
FlowExecutionId int64 `json:"flowExecutionId" v:"required#流程执行ID不能为空"`
|
||||
NodeGroupId string `json:"nodeGroupId"`
|
||||
}
|
||||
|
||||
// NodeExecutionResp 节点执行记录响应
|
||||
|
||||
@@ -22,6 +22,10 @@ type FlowExecution struct {
|
||||
TraceId string `orm:"trace_id" json:"traceId" description:"跟踪ID"`
|
||||
SessionId string `orm:"session_id" json:"sessionId" description:"会话ID"`
|
||||
TotalTokens int `orm:"total_tokens" json:"totalTokens" description:"总token消耗"`
|
||||
TotalFee int `orm:"total_fee" json:"totalFee" description:"总费用"`
|
||||
SessionDel bool `orm:"session_del" json:"sessionDel" description:"会话是否删除"`
|
||||
ResultDel bool `orm:"result_del" json:"resultDel" description:"结果是否删除"`
|
||||
Extension map[string]interface{} `orm:"extension" json:"extension" description:"扩展字段"`
|
||||
}
|
||||
|
||||
type flowExecutionCol struct {
|
||||
@@ -39,6 +43,10 @@ type flowExecutionCol struct {
|
||||
TraceId string
|
||||
SessionId string
|
||||
TotalTokens string
|
||||
TotalFee string
|
||||
SessionDel string
|
||||
ResultDel string
|
||||
Extension string
|
||||
}
|
||||
|
||||
var FlowExecutionCol = flowExecutionCol{
|
||||
@@ -56,4 +64,8 @@ var FlowExecutionCol = flowExecutionCol{
|
||||
TraceId: "trace_id",
|
||||
SessionId: "session_id",
|
||||
TotalTokens: "total_tokens",
|
||||
TotalFee: "total_fee",
|
||||
SessionDel: "session_del",
|
||||
ResultDel: "result_del",
|
||||
Extension: "extension",
|
||||
}
|
||||
|
||||
@@ -15,18 +15,20 @@ type FlowInfo struct {
|
||||
}
|
||||
|
||||
type FlowNode struct {
|
||||
Id string `json:"id"`
|
||||
NodeCode node.NodeType `json:"nodeCode"`
|
||||
Name string `json:"name"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
SkillName string `json:"skillName"`
|
||||
PromptContent string `json:"promptContent"`
|
||||
IsSaveFile bool `json:"isSaveFile"`
|
||||
InputSource []FlowNodeInputSource `json:"inputSource"` // 前端指定:来源节点ID
|
||||
FormConfig []node.NodeFormField `json:"formConfig"`
|
||||
ModelConfig node.ModelItem `json:"modelConfig"`
|
||||
OutputConfig []node.NodeFormField `json:"outputConfig"`
|
||||
OutputResult []node.NodeFormField `json:"outputResult" ds:"节点输出结果"`
|
||||
Id string `json:"id"`
|
||||
NodeCode node.NodeType `json:"nodeCode"`
|
||||
Name string `json:"name"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
PatchLayout bool `json:"patchLayout"`
|
||||
SkillName string `json:"skillName"`
|
||||
PromptContent string `json:"promptContent"`
|
||||
IsSaveFile bool `json:"isSaveFile"`
|
||||
InputSource []FlowNodeInputSource `json:"inputSource"` // 前端指定:来源节点ID
|
||||
FormConfig []node.NodeFormField `json:"formConfig"`
|
||||
ModelConfig node.ModelItem `json:"modelConfig"`
|
||||
ModelOutputFields []string `json:"modelOutputFields"`
|
||||
OutputConfig []node.NodeFormField `json:"outputConfig"`
|
||||
OutputResult []node.NodeFormField `json:"outputResult" ds:"节点输出结果"`
|
||||
}
|
||||
|
||||
type FlowNodeInputSource struct {
|
||||
|
||||
@@ -22,6 +22,7 @@ type NodeExecution struct {
|
||||
PromptTokens int `orm:"prompt_tokens" json:"promptTokens" description:"提示词token消耗"`
|
||||
CompletionTokens int `orm:"completion_tokens" json:"completionTokens" description:"补全token消耗"`
|
||||
TotalTokens int `orm:"total_tokens" json:"totalTokens" description:"总token消耗"`
|
||||
TokenInfo []map[string]interface{} `orm:"token_info" json:"tokenInfo" description:"token信息"`
|
||||
Status node.NodeExecutionStatus `orm:"status" json:"status" description:"执行状态:1-运行中,2-成功,3-失败,4-暂停,5-等待执行"`
|
||||
DurationMs int64 `orm:"duration_ms" json:"durationMs" description:"执行时长(毫秒)"`
|
||||
ErrorMessage string `orm:"error_message" json:"errorMessage" description:"错误信息"`
|
||||
@@ -40,6 +41,7 @@ type nodeExecutionCol struct {
|
||||
PromptTokens string
|
||||
CompletionTokens string
|
||||
TotalTokens string
|
||||
TokenInfo string
|
||||
Status string
|
||||
DurationMs string
|
||||
ErrorMessage string
|
||||
@@ -58,6 +60,7 @@ var NodeExecutionCol = nodeExecutionCol{
|
||||
PromptTokens: "prompt_tokens",
|
||||
CompletionTokens: "completion_tokens",
|
||||
TotalTokens: "total_tokens",
|
||||
TokenInfo: "token_info",
|
||||
Status: "status",
|
||||
DurationMs: "duration_ms",
|
||||
ErrorMessage: "error_message",
|
||||
|
||||
@@ -16,14 +16,12 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||
"github.com/cloudwego/eino/compose"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/google/uuid"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
@@ -47,54 +45,94 @@ func (s *flowExecutionService) Get(ctx context.Context, req *flowDto.GetFlowExec
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *flowExecutionService) DeleteResult(ctx context.Context, req *flowDto.DeleteResultReq) (err error) {
|
||||
r, err := flowDao.FlowExecutionDao.Get(ctx, &flowDto.GetFlowExecutionReq{Id: req.Id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 遍历并剔除值等于 req.Url 的数据
|
||||
newOutputParams := make([]map[string]any, 0)
|
||||
for _, paramMap := range r.OutputParams {
|
||||
// 单个 paramMap 过滤
|
||||
cleanMap := make(map[string]any)
|
||||
for k, v := range paramMap {
|
||||
// 转为字符串对比
|
||||
if gconv.String(v) != req.Content {
|
||||
cleanMap[k] = v
|
||||
}
|
||||
}
|
||||
// 只保留非空 map,避免出现空层级
|
||||
if len(cleanMap) > 0 {
|
||||
newOutputParams = append(newOutputParams, cleanMap)
|
||||
}
|
||||
}
|
||||
// 赋值回原数据
|
||||
r.OutputParams = newOutputParams
|
||||
// 执行更新:更新 OutputParams + 标记删除
|
||||
flowUpdateReq := new(flowDto.UpdateFlowExecutionReq)
|
||||
flowUpdateReq.Id = req.Id
|
||||
flowUpdateReq.OutputParams = r.OutputParams
|
||||
_, err = flowDao.FlowExecutionDao.Update(ctx, flowUpdateReq)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *flowExecutionService) DeleteSession(ctx context.Context, req *flowDto.DeleteSessionReq) (err error) {
|
||||
flowUpdateReq := new(flowDto.UpdateFlowExecutionReq)
|
||||
flowUpdateReq.Id = req.Id
|
||||
flowUpdateReq.SessionDel = true
|
||||
_, err = flowDao.FlowExecutionDao.Update(ctx, flowUpdateReq)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *flowExecutionService) GetSessionList(ctx context.Context, req *flowDto.GetSessionListReq) (res *flowDto.ListFlowExecutionRes, err error) {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
flowReq := new(flowDto.ListFlowExecutionReq)
|
||||
flowReq.Page = req.Page
|
||||
flowReq.Creator = user.UserName
|
||||
flowReq.SessionDel = gconv.PtrBool(true)
|
||||
list, total, err := flowDao.FlowExecutionDao.List(ctx, flowReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &flowDto.ListFlowExecutionRes{
|
||||
Total: total,
|
||||
}
|
||||
err = gconv.Struct(list, &res.List)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowExecutionReq) (res *flowDto.ListFlowExecutionTreeRes, err error) {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Creator = user.UserName
|
||||
req.ResultDel = gconv.PtrBool(true)
|
||||
req.IsResult = true
|
||||
list, _, err := flowDao.FlowExecutionDao.List(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ===================== 核心修复:只统计【有数据】的执行记录,空的直接跳过 =====================
|
||||
|
||||
executionNumber := make(map[int64]int) // executionId -> 倒序编号(最新=1)
|
||||
|
||||
// 第一次遍历:只处理【有输出参数】的记录,统计并分配编号
|
||||
var validList []*entity.FlowExecution // 只存有效(非空)记录
|
||||
// 过滤出有有效输出的执行记录
|
||||
var validList []*entity.FlowExecution
|
||||
for _, execution := range list {
|
||||
if g.IsEmpty(execution.OutputParams) {
|
||||
continue // 空数据直接过滤,不参与编号、不展示
|
||||
if !g.IsEmpty(execution.OutputParams) {
|
||||
validList = append(validList, execution)
|
||||
}
|
||||
validList = append(validList, execution)
|
||||
}
|
||||
|
||||
// 给有效记录分配【时间倒序编号】(最新=1)
|
||||
totalValid := len(validList)
|
||||
for idx, execution := range validList {
|
||||
executionNumber[execution.Id] = totalValid - idx
|
||||
}
|
||||
|
||||
// 2. 分组映射:日期 -> 流程节点
|
||||
type flowWrap struct {
|
||||
flowNode flowDto.FlowNode
|
||||
createdAt *gtime.Time
|
||||
}
|
||||
dateMap := make(map[string]*[]flowWrap)
|
||||
|
||||
// 遍历【有效数据】构建结构
|
||||
// 1. 按日期归集,严格使用 Y-m-d 格式
|
||||
dateMap := make(map[string][]flowDto.OutputItem)
|
||||
for _, execution := range validList {
|
||||
// 按要求使用 Y-m-d
|
||||
createDate := execution.CreatedAt.Format("Y-m-d")
|
||||
flowName := execution.FlowName
|
||||
execID := execution.Id
|
||||
outputParams := execution.OutputParams
|
||||
|
||||
// 编号只算有效数据,不会把空的算进去
|
||||
num := executionNumber[execution.Id]
|
||||
displayFlowName := fmt.Sprintf("会话-%d(%s)", num, flowName)
|
||||
|
||||
// 3. 解析 outputParams
|
||||
var tempItems []flowDto.OutputItem
|
||||
for _, paramMap := range outputParams {
|
||||
for tsKey, value := range paramMap {
|
||||
@@ -102,98 +140,116 @@ func (s *flowExecutionService) List(ctx context.Context, req *flowDto.ListFlowEx
|
||||
continue
|
||||
}
|
||||
tempItems = append(tempItems, flowDto.OutputItem{
|
||||
Id: execID,
|
||||
Timestamp: tsKey,
|
||||
Content: gconv.String(value),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 修复1:如果解析后依然为空,直接跳过,不生成第二层节点 =====================
|
||||
if len(tempItems) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 时间戳正序
|
||||
// 单条执行内按时间戳正序
|
||||
sort.Slice(tempItems, func(i, j int) bool {
|
||||
t1, _ := strconv.ParseInt(tempItems[i].Timestamp, 10, 64)
|
||||
t2, _ := strconv.ParseInt(tempItems[j].Timestamp, 10, 64)
|
||||
return t1 < t2
|
||||
})
|
||||
|
||||
// 标号:相同类型递增,不同重置
|
||||
suffixCount := make(map[string]int)
|
||||
for idx := range tempItems {
|
||||
item := &tempItems[idx]
|
||||
val := item.Content
|
||||
suffix := "内容"
|
||||
ext := ""
|
||||
ext = GetFileTypeByPath(val)
|
||||
if ext == "image" {
|
||||
suffix = "图片"
|
||||
}
|
||||
if ext == "video" {
|
||||
suffix = "视频"
|
||||
}
|
||||
if ext == "audio" {
|
||||
suffix = "音频"
|
||||
}
|
||||
if ext == "text" {
|
||||
suffix = "文案"
|
||||
}
|
||||
if ext == "html" {
|
||||
suffix = "HTML"
|
||||
}
|
||||
suffixCount[suffix]++
|
||||
item.Type = ext
|
||||
item.Label = fmt.Sprintf("%s_%d", suffix, suffixCount[suffix])
|
||||
}
|
||||
|
||||
// 组装节点
|
||||
flowNode := flowDto.FlowNode{
|
||||
FlowName: displayFlowName,
|
||||
Id: execution.Id,
|
||||
SessionId: gconv.String(execution.SessionId),
|
||||
Items: tempItems,
|
||||
}
|
||||
|
||||
if dateMap[createDate] == nil {
|
||||
dateMap[createDate] = &[]flowWrap{}
|
||||
}
|
||||
*dateMap[createDate] = append(*dateMap[createDate], flowWrap{
|
||||
flowNode: flowNode,
|
||||
createdAt: execution.CreatedAt,
|
||||
})
|
||||
dateMap[createDate] = append(dateMap[createDate], tempItems...)
|
||||
}
|
||||
|
||||
// 6. 构建树 + 排序
|
||||
var tree []flowDto.DateNode
|
||||
for date, wraps := range dateMap {
|
||||
// 第二层按创建时间倒序(最新在前)
|
||||
sort.Slice(*wraps, func(i, j int) bool {
|
||||
return (*wraps)[i].createdAt.After((*wraps)[j].createdAt)
|
||||
})
|
||||
// ========== 修复编号乱序核心逻辑 ==========
|
||||
// 1. 取出所有日期并 倒序排序(和前端展示顺序一致)
|
||||
var sortedDates []string
|
||||
for d := range dateMap {
|
||||
sortedDates = append(sortedDates, d)
|
||||
}
|
||||
// 日期字符串倒序
|
||||
sort.Slice(sortedDates, func(i, j int) bool {
|
||||
return sortedDates[i] > sortedDates[j]
|
||||
})
|
||||
|
||||
var flowNodes []flowDto.FlowNode
|
||||
for _, w := range *wraps {
|
||||
flowNodes = append(flowNodes, w.flowNode)
|
||||
// 2. 按【前端展示顺序】拼接所有条目,用于统计总数量
|
||||
var allItems []flowDto.OutputItem
|
||||
for _, d := range sortedDates {
|
||||
allItems = append(allItems, dateMap[d]...)
|
||||
}
|
||||
|
||||
// 3. 统计各类型总数
|
||||
type totalCnt struct {
|
||||
total int
|
||||
idx int
|
||||
}
|
||||
typeTotal := make(map[string]*totalCnt)
|
||||
for _, item := range allItems {
|
||||
val := item.Content
|
||||
suffix := "内容"
|
||||
ext := GetFileTypeByPath(val)
|
||||
|
||||
switch ext {
|
||||
case "image":
|
||||
suffix = "图片"
|
||||
case "video":
|
||||
suffix = "视频"
|
||||
case "audio":
|
||||
suffix = "音频"
|
||||
case "text":
|
||||
suffix = "文案"
|
||||
case "html":
|
||||
suffix = "HTML"
|
||||
}
|
||||
if _, ok := typeTotal[suffix]; !ok {
|
||||
typeTotal[suffix] = &totalCnt{}
|
||||
}
|
||||
typeTotal[suffix].total++
|
||||
}
|
||||
// 初始序号 = 总数,从最大值开始倒序
|
||||
for _, v := range typeTotal {
|
||||
v.idx = v.total
|
||||
}
|
||||
// ======================================
|
||||
|
||||
// ===================== 修复2:日期下没有流程,也过滤掉 =====================
|
||||
if len(flowNodes) == 0 {
|
||||
var tree []flowDto.DateNode
|
||||
// 按有序日期遍历生成最终数据
|
||||
for _, date := range sortedDates {
|
||||
items := dateMap[date]
|
||||
if len(items) == 0 {
|
||||
continue
|
||||
}
|
||||
// 逐个生成倒序标签
|
||||
for idx := range items {
|
||||
item := &items[idx]
|
||||
val := item.Content
|
||||
suffix := "内容"
|
||||
ext := GetFileTypeByPath(val)
|
||||
|
||||
switch ext {
|
||||
case "image":
|
||||
suffix = "图片"
|
||||
case "video":
|
||||
suffix = "视频"
|
||||
case "audio":
|
||||
suffix = "音频"
|
||||
case "text":
|
||||
suffix = "文案"
|
||||
case "html":
|
||||
suffix = "HTML"
|
||||
}
|
||||
|
||||
cnt := typeTotal[suffix]
|
||||
item.Type = ext
|
||||
item.Label = fmt.Sprintf("%s_%d", suffix, cnt.idx)
|
||||
cnt.idx--
|
||||
}
|
||||
|
||||
tree = append(tree, flowDto.DateNode{
|
||||
CreateDate: date,
|
||||
Flows: flowNodes,
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
|
||||
// 第一层日期倒序
|
||||
sort.Slice(tree, func(i, j int) bool {
|
||||
return tree[i].CreateDate > tree[j].CreateDate
|
||||
})
|
||||
|
||||
imgPrefix, err := utils.GetFileAddressPrefix(ctx)
|
||||
return &flowDto.ListFlowExecutionTreeRes{
|
||||
Tree: tree,
|
||||
@@ -294,17 +350,24 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
var executionId int64
|
||||
var isDialogue bool
|
||||
var nodeGroupId = uuid.NewString()
|
||||
flowName := req.FlowName
|
||||
if !g.IsEmpty(req.Desc) {
|
||||
flowName = req.Desc
|
||||
}
|
||||
isDialogue = false
|
||||
if flowInfo == nil {
|
||||
isDialogue = false
|
||||
var r = new(flowDto.CreateFlowExecutionReq)
|
||||
r.FlowUserId = req.FlowId
|
||||
r.FlowName = req.FlowName
|
||||
r.FlowName = flowName
|
||||
r.NodeGroupId = nodeGroupId
|
||||
r.TriggerType = flow.FlowExecutionTriggerTypeManual.Code()
|
||||
r.FlowContent = req.FlowContent
|
||||
r.NodeInputParams = req.NodeInputParams
|
||||
r.SessionId = req.SessionId
|
||||
r.Status = flow.FlowExecutionStatusRunning.Code()
|
||||
r.Extension = map[string]any{
|
||||
"templates": req.Templates,
|
||||
}
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if span != nil && span.SpanContext().HasTraceID() {
|
||||
r.TraceId = span.SpanContext().TraceID().String()
|
||||
@@ -316,7 +379,6 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
return
|
||||
}
|
||||
} else {
|
||||
isDialogue = true
|
||||
executionId = flowInfo.Id
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if span != nil && span.SpanContext().HasTraceID() {
|
||||
@@ -324,10 +386,15 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
cancelMap.Store(traceId, cancel)
|
||||
}
|
||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||
Id: executionId,
|
||||
NodeGroupId: nodeGroupId,
|
||||
Status: flow.FlowExecutionStatusRunning.Code(),
|
||||
TraceId: traceId,
|
||||
Id: executionId,
|
||||
NodeGroupId: nodeGroupId,
|
||||
FlowContent: req.FlowContent,
|
||||
NodeInputParams: req.NodeInputParams,
|
||||
Extension: map[string]any{
|
||||
"templates": req.Templates,
|
||||
},
|
||||
Status: flow.FlowExecutionStatusRunning.Code(),
|
||||
TraceId: traceId,
|
||||
}
|
||||
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
if err != nil {
|
||||
@@ -349,20 +416,20 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
}
|
||||
}
|
||||
|
||||
if isDialogue && !g.IsEmpty(flowInfo) && !g.IsEmpty(req.ResultUrl) {
|
||||
req.NodeGroupId = nodeGroupId
|
||||
if strings.HasSuffix(gconv.String(req.ResultUrl), ".inc") {
|
||||
err = TextModelSingleLambda(ctx, req, flowInfo)
|
||||
return
|
||||
} else if strings.HasSuffix(gconv.String(req.ResultUrl), ".png") {
|
||||
err = ImgModelSingleLambda(ctx, req, flowInfo)
|
||||
return
|
||||
} else if strings.HasSuffix(gconv.String(req.ResultUrl), ".html") {
|
||||
err = TextImgModelSingleLambda(ctx, req, flowInfo)
|
||||
return
|
||||
}
|
||||
return nil, errors.New("文件格式不支持")
|
||||
}
|
||||
//if isDialogue && !g.IsEmpty(flowInfo) && !g.IsEmpty(req.ResultUrl) {
|
||||
// req.NodeGroupId = nodeGroupId
|
||||
// if strings.HasSuffix(gconv.String(req.ResultUrl), ".inc") {
|
||||
// err = TextModelSingleLambda(ctx, req, flowInfo)
|
||||
// return
|
||||
// } else if strings.HasSuffix(gconv.String(req.ResultUrl), ".png") {
|
||||
// err = ImgModelSingleLambda(ctx, req, flowInfo)
|
||||
// return
|
||||
// } else if strings.HasSuffix(gconv.String(req.ResultUrl), ".html") {
|
||||
// err = TextImgModelSingleLambda(ctx, req, flowInfo)
|
||||
// return
|
||||
// }
|
||||
// return nil, errors.New("文件格式不支持")
|
||||
//}
|
||||
|
||||
// =========================================================================
|
||||
// ✅【第1步】给所有判断节点自动生成意图识别节点
|
||||
@@ -443,6 +510,7 @@ func (s *flowExecutionService) Execute(ctx context.Context, req *flowDto.Execute
|
||||
IsDialogue: isDialogue,
|
||||
ExecutionId: executionId,
|
||||
ConfigMap: configMap,
|
||||
Templates: req.Templates,
|
||||
SessionId: req.SessionId,
|
||||
Desc: req.Desc,
|
||||
SkillName: req.SkillName,
|
||||
@@ -664,18 +732,9 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
||||
// 执行节点
|
||||
_, err = lambda(ctx, realInput)
|
||||
durationMs := time.Since(startTime).Milliseconds()
|
||||
// 上传OSS(每条独立上传)
|
||||
ossResult1, err := Upload(ctx, &dto.UploadFileBytesReq{
|
||||
FileBytes: gconv.Bytes(gconv.String(realInput)),
|
||||
FileName: fmt.Sprintf("nodeInput:%v.txt", time.Now().UnixMilli()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateReq := &nodeDto.UpdateNodeExecutionReq{
|
||||
Id: nodeExecutionId,
|
||||
OutputParamsPath: ossResult1.FileURL,
|
||||
DurationMs: durationMs,
|
||||
Id: nodeExecutionId,
|
||||
DurationMs: durationMs,
|
||||
}
|
||||
if err != nil {
|
||||
// 执行失败,更新状态
|
||||
@@ -689,7 +748,15 @@ func registerNodeToGraph(graph *compose.Graph[any, any], flowNode entity.FlowNod
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 上传OSS(每条独立上传)
|
||||
ossResult1, err := Upload(ctx, &dto.UploadFileBytesReq{
|
||||
FileBytes: gconv.Bytes(gconv.String(realInput)),
|
||||
FileName: fmt.Sprintf("nodeInput:%v.txt", time.Now().UnixMilli()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateReq.OutputParamsPath = ossResult1.FileURL
|
||||
// 执行成功,更新状态
|
||||
updateReq.Status = node.NodeExecutionStatusSuccess.Code()
|
||||
_, _ = nodeDao.NodeExecutionDao.Update(ctx, updateReq)
|
||||
|
||||
@@ -6,9 +6,12 @@ import (
|
||||
"ai-agent/workflow/consts/public"
|
||||
fileDao "ai-agent/workflow/dao/file"
|
||||
flowDao "ai-agent/workflow/dao/flow"
|
||||
nodeDao "ai-agent/workflow/dao/node"
|
||||
"ai-agent/workflow/model/dto"
|
||||
fileDto "ai-agent/workflow/model/dto/file"
|
||||
flowDto "ai-agent/workflow/model/dto/flow"
|
||||
nodeDto "ai-agent/workflow/model/dto/node"
|
||||
"ai-agent/workflow/model/entity"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
@@ -28,6 +31,35 @@ func StartLambda(ctx context.Context, input any) (any, error) {
|
||||
}
|
||||
|
||||
func FormLambda(ctx context.Context, input any) (any, error) {
|
||||
nodeInput, ok := input.(*flowDto.NodeExecutionInput)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("入参类型错误")
|
||||
}
|
||||
if !g.IsEmpty(nodeInput.Config.FormConfig) {
|
||||
for _, v := range nodeInput.Config.FormConfig {
|
||||
if strings.Contains(v.Field, "视频时长") {
|
||||
if g.IsEmpty(v.Value) {
|
||||
return nil, fmt.Errorf("视频时长不能为空")
|
||||
}
|
||||
if gconv.Int(v.Value) >= 16 {
|
||||
return nil, fmt.Errorf("视频时长超过15秒了")
|
||||
}
|
||||
if gconv.Int(v.Value) <= 3 {
|
||||
return nil, fmt.Errorf("视频时长不能小于4秒")
|
||||
}
|
||||
}
|
||||
if strings.Contains(v.Field, "视频分辨率") {
|
||||
if gconv.String(v.Value) != "480p" && gconv.String(v.Value) != "720p" {
|
||||
return nil, fmt.Errorf("视频分辨率不合法, 可选值: 480p, 720p")
|
||||
}
|
||||
}
|
||||
if strings.Contains(v.Field, "宽高比例") {
|
||||
if gconv.String(v.Value) != "21:9" && gconv.String(v.Value) != "1:1" && gconv.String(v.Value) != "16:9" && gconv.String(v.Value) != "4:3" && gconv.String(v.Value) != "9:16" && gconv.String(v.Value) != "3:4" {
|
||||
return nil, fmt.Errorf("宽高比例不合法, 可选值: 21:9, 1:1, 16:9, 4:3, 9:16, 3:4")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
@@ -44,16 +76,18 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
|
||||
// 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 {
|
||||
outputResult = append(outputResult, field)
|
||||
}
|
||||
}
|
||||
outputResult = append(outputResult, inputMap...)
|
||||
outputResult = append(outputResult, outputMap...)
|
||||
//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 {
|
||||
// outputResult = append(outputResult, field)
|
||||
// }
|
||||
//}
|
||||
for _, valueAny := range modelMap {
|
||||
if field, ok := valueAny.(node.NodeFormField); ok {
|
||||
outputResult = append(outputResult, field)
|
||||
@@ -83,7 +117,7 @@ func JudgeLambda(ctx context.Context, input any) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
composeResult, err := GetComposeResult(ctx, 2, getIsChatModel.Model.ModelName, "", "", []map[string]any{{"prompt": strings.Join(branchIdNameLines, "\n")}}, []map[string]any{{"prompt": contextParts}}, nodeInput.Global.FileUrl, nodeInput.Global.SessionId, nodeInput.Config.Id, "判断节点")
|
||||
composeResult, err := GetComposeResult(ctx, nodeInput.NodeExecutionId, 2, getIsChatModel.Model.ModelName, "", "", []map[string]any{{"prompt": strings.Join(branchIdNameLines, "\n")}}, []map[string]any{{"prompt": contextParts}}, nodeInput.Global.FileUrl, nodeInput.Global.SessionId, nodeInput.Config.Id, "判断节点")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -123,62 +157,78 @@ func BatchModelLambda(ctx context.Context, input any) (any, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 结果按索引存放,保证顺序
|
||||
// 结果按索引存放,切片不同下标并发写无竞争,不用锁
|
||||
res := make([][]node.NodeFormField, len(reqMap))
|
||||
var wg sync.WaitGroup
|
||||
// 用一个通道标记是否完成
|
||||
done := make(chan struct{})
|
||||
// 错误只存一个
|
||||
var execErr error
|
||||
|
||||
// 并发执行
|
||||
// 只创建基础上下文,不再主动批量 cancel
|
||||
subCtx := context.WithoutCancel(ctx)
|
||||
// 缓冲1错误通道,仅接收第一个错误
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
// 并发执行任务
|
||||
for idx, item := range reqMap {
|
||||
wg.Add(1)
|
||||
go func(idx int, userItem map[string]any) {
|
||||
defer wg.Done()
|
||||
|
||||
// 基础上下文仅响应上游原始 ctx 取消,内部任务失败不触发这里
|
||||
select {
|
||||
case <-subCtx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
singleUserFrom := []map[string]any{userItem}
|
||||
output, err := TextNode(ctx, nodeInput, skillName, from, singleUserFrom)
|
||||
// 下游调用使用 subCtx,不会因为同批次其他任务报错而取消
|
||||
output, err := TextNode(subCtx, nodeInput, skillName, from, singleUserFrom)
|
||||
if err != nil {
|
||||
// 并发安全赋值错误
|
||||
if execErr == nil {
|
||||
execErr = err
|
||||
// 只往错误通道塞第一个错误,不调用全局 cancel
|
||||
select {
|
||||
case errCh <- err:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 直接按原索引写,顺序绝对正确
|
||||
res[idx] = output
|
||||
}(idx, item)
|
||||
}
|
||||
|
||||
// 后台等待所有协程完成,然后关闭 done 通道
|
||||
// 所有协程跑完再关闭通道
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
close(errCh)
|
||||
}()
|
||||
|
||||
// 等待全部完成
|
||||
<-done
|
||||
// ========== 修复区域 start ==========
|
||||
var execErr error
|
||||
select {
|
||||
case execErr = <-errCh:
|
||||
// 捕获第一个业务错误,等待剩余协程收尾
|
||||
wg.Wait()
|
||||
case <-subCtx.Done():
|
||||
// 上游根上下文被终止,读取已存在的错误
|
||||
execErr = <-errCh
|
||||
wg.Wait()
|
||||
if execErr != nil {
|
||||
execErr = fmt.Errorf("global context canceled: %w", execErr)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有错误,直接返回
|
||||
// 有错误直接返回,不再走结果拼接
|
||||
if execErr != nil {
|
||||
return nil, execErr
|
||||
}
|
||||
|
||||
// 全局自增 i
|
||||
// 拼接输出结果
|
||||
var globalIndex int
|
||||
var outputRes []node.NodeFormField
|
||||
for _, items := range res {
|
||||
for _, item := range items {
|
||||
// 1. 拿到原来的 Field:例如 "text_content:2:0"
|
||||
oldField := item.Field
|
||||
// 2. 找到最后一个 : 的位置
|
||||
if idx := strings.LastIndex(oldField, ":"); idx != -1 {
|
||||
// 3. 截断前面部分,拼接上新的 globalIndex
|
||||
item.Field = oldField[:idx+1] + fmt.Sprint(globalIndex)
|
||||
}
|
||||
// Label 同理
|
||||
oldLabel := item.Label
|
||||
if idx := strings.LastIndex(oldLabel, ":"); idx != -1 {
|
||||
item.Label = oldLabel[:idx+1] + fmt.Sprint(globalIndex)
|
||||
@@ -249,7 +299,7 @@ func VideoModelLambda(ctx context.Context, input any) (any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
videoUrl := ""
|
||||
videoURL := make([]string, 0)
|
||||
for _, v := range res {
|
||||
if strings.Contains(v.Field, "content") {
|
||||
@@ -259,36 +309,58 @@ func VideoModelLambda(ctx context.Context, input any) (any, error) {
|
||||
if g.IsEmpty(videoURL) {
|
||||
return nil, fmt.Errorf("视频合成失败:模型生成视频失败")
|
||||
}
|
||||
waitRes, err := VideoConcat(ctx, videoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg := new(flowDto.VideoCallbackReq)
|
||||
if err = gconv.Struct(waitRes, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urlPrefix, err := utils.GetFileAddressPrefix(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(videoURL) > 1 {
|
||||
var waitRes any
|
||||
waitRes, err = VideoConcat(ctx, videoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg := new(flowDto.VideoCallbackReq)
|
||||
if err = gconv.Struct(waitRes, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var urlPrefix string
|
||||
urlPrefix, err = utils.GetFileAddressPrefix(ctx)
|
||||
newS := strings.ReplaceAll(urlPrefix, g.Cfg().MustGet(ctx, "filePrefix").String(), g.Cfg().MustGet(ctx, "minioPrefix").String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoUrl = newS + msg.FileURL
|
||||
} else {
|
||||
var bytes []byte
|
||||
bytes, err = GetFileBytesFromURL(ctx, videoURL[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("下载图片失败: %w", err)
|
||||
}
|
||||
// 构造文件名
|
||||
fileName := fmt.Sprintf("ai_video_%d%s", time.Now().UnixMilli(), GetUrlSuffix(videoURL[0], true))
|
||||
// 上传到你的OSS(你项目已有的Upload方法)
|
||||
var upResp *dto.UploadFileBytesRes
|
||||
upResp, err = Upload(ctx, &dto.UploadFileBytesReq{
|
||||
FileName: fileName,
|
||||
FileBytes: bytes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("上传OSS失败: %w", err)
|
||||
}
|
||||
videoUrl = upResp.FileURL
|
||||
}
|
||||
|
||||
outputRes := make([]node.NodeFormField, 0)
|
||||
if nodeInput.Config.IsSaveFile {
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("video_oss_url:content:%d", 0),
|
||||
Value: msg.FileURL,
|
||||
Value: videoUrl,
|
||||
Label: fmt.Sprintf("video_oss_url:content:%d", 0),
|
||||
Type: "string",
|
||||
})
|
||||
} else {
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("concat_video_url:content:%d", 0),
|
||||
Value: urlPrefix + msg.FileURL,
|
||||
Label: fmt.Sprintf("concat_video_url:content:%d", 0),
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("concat_video_url:content:%d", 0),
|
||||
Value: videoUrl,
|
||||
Label: fmt.Sprintf("concat_video_url:content:%d", 0),
|
||||
Type: "string",
|
||||
})
|
||||
nodeInput.Config.OutputResult = outputRes
|
||||
|
||||
return nodeInput, nil
|
||||
@@ -437,11 +509,14 @@ func MergeLambda(ctx context.Context, input any) (res any, err error) {
|
||||
// 1. 把所有节点输出拍平成 字段名->内容 的map
|
||||
dataMap := make(map[string]node.NodeFormField)
|
||||
_, outputMap, _ := GetNodeContextContent(nodeInput.Global, nodeInput.Config)
|
||||
for _, valueAny := range outputMap {
|
||||
field := node.NodeFormField{}
|
||||
if field, ok = valueAny.(node.NodeFormField); ok {
|
||||
dataMap[field.Field] = field
|
||||
}
|
||||
//for _, valueAny := range outputMap {
|
||||
// field := node.NodeFormField{}
|
||||
// if field, ok = valueAny.(node.NodeFormField); ok {
|
||||
// dataMap[field.Field] = field
|
||||
// }
|
||||
//}
|
||||
for _, field := range outputMap {
|
||||
dataMap[field.Field] = field
|
||||
}
|
||||
|
||||
// 2. 提取所有文案:text_content_0,1,2...
|
||||
@@ -591,7 +666,7 @@ func SummaryLambda(ctx context.Context, input any) (any, error) {
|
||||
// 生成 毫秒时间戳 作为 KEY
|
||||
timeKey := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||
item := make(map[string]interface{})
|
||||
item[timeKey] = field.Value
|
||||
item[timeKey] = ProcessPath(ctx, gconv.String(field.Value))
|
||||
summaryResult = append(summaryResult, item)
|
||||
}
|
||||
}
|
||||
@@ -609,10 +684,28 @@ func SummaryLambda(ctx context.Context, input any) (any, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
res, _, err := nodeDao.NodeExecutionDao.ListByFlowExecutionId(ctx, &nodeDto.ListNodeExecutionByFlowReq{
|
||||
NodeGroupId: execInput.Global.NodeGroupId,
|
||||
}, entity.NodeExecutionCol.TokenInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var totalTokens int
|
||||
var totalFee float64
|
||||
for _, item := range res {
|
||||
for _, itemToken := range item.TokenInfo {
|
||||
m := gconv.Map(itemToken)
|
||||
totalTokens += gconv.Int(m["total_tokens"])
|
||||
totalFee += gconv.Float64(m["total_fee"])
|
||||
}
|
||||
}
|
||||
|
||||
executionReq := flowDto.UpdateFlowExecutionReq{
|
||||
Id: execInput.Global.ExecutionId,
|
||||
Status: flow.FlowExecutionStatusSuccess.Code(),
|
||||
OutputParams: summaryResult,
|
||||
TotalTokens: totalTokens,
|
||||
TotalFee: totalFee,
|
||||
}
|
||||
_, err = flowDao.FlowExecutionDao.Update(ctx, &executionReq)
|
||||
|
||||
|
||||
@@ -683,24 +683,29 @@ func AudioOptimizeNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInpu
|
||||
return outputRes, nil
|
||||
}
|
||||
|
||||
// splitTextByPunct 按中文标点分割句子,同时保留标点在分段内
|
||||
// 例如:"这个叫高血压调理方,注意是根源调理不是临时缓解,"
|
||||
// 会变成:["这个叫高血压调理方,", "注意是根源调理不是临时缓解,"]
|
||||
func splitTextByPunct(raw string) []string {
|
||||
// 按标点切分+拼接标点
|
||||
slice := regexp.MustCompile(`([,。;!?])`).Split(raw, -1)
|
||||
var res []string
|
||||
var builder strings.Builder
|
||||
for idx, s := range slice {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
builder.WriteString(s)
|
||||
// 偶数位是分隔标点(split后规律:文本、标点、文本、标点...)
|
||||
if idx%2 == 1 {
|
||||
res = append(res, builder.String())
|
||||
builder.Reset()
|
||||
}
|
||||
// 匹配中文标点并保留在文本中,按标点位置切分
|
||||
re := regexp.MustCompile(`[,。;!?]`)
|
||||
// 先找到所有标点的位置
|
||||
indexes := re.FindAllStringIndex(raw, -1)
|
||||
if len(indexes) == 0 {
|
||||
return []string{raw}
|
||||
}
|
||||
if builder.Len() > 0 {
|
||||
res = append(res, builder.String())
|
||||
|
||||
var res []string
|
||||
prev := 0
|
||||
for _, idx := range indexes {
|
||||
end := idx[1] // 标点的结束位置
|
||||
seg := raw[prev:end]
|
||||
res = append(res, seg)
|
||||
prev = end
|
||||
}
|
||||
// 处理最后一段没有标点的文本
|
||||
if prev < len(raw) {
|
||||
res = append(res, raw[prev:])
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -708,46 +713,53 @@ func splitTextByPunct(raw string) []string {
|
||||
// BuildSubtitles 核心工具:单个sentence生成多条subtitle
|
||||
func BuildSubtitles(sents *[]flowDto.Sentence) ([]flowDto.Subtitle, error) {
|
||||
var subtitles []flowDto.Subtitle
|
||||
|
||||
for _, sent := range *sents {
|
||||
// 1. 先按标点把文本拆成多个片段(保留标点)
|
||||
segList := splitTextByPunct(sent.Text)
|
||||
if len(segList) == 0 {
|
||||
return nil, nil
|
||||
continue
|
||||
}
|
||||
|
||||
var subs []flowDto.Subtitle
|
||||
wordIdx := 0
|
||||
allWords := sent.Words
|
||||
|
||||
// 2. 遍历每个文本片段,匹配对应的Words
|
||||
for _, seg := range segList {
|
||||
// 去除文本片段的标点,方便和Word.Word拼接内容匹配
|
||||
segClean := strings.ReplaceAll(seg, ",", "")
|
||||
segClean = strings.ReplaceAll(segClean, "。", "")
|
||||
segClean = strings.ReplaceAll(segClean, ";", "")
|
||||
segClean = strings.ReplaceAll(segClean, "!", "")
|
||||
segClean = strings.ReplaceAll(segClean, "?", "")
|
||||
|
||||
var collectWords []flowDto.Word
|
||||
currentText := ""
|
||||
// 循环取 word,直到拼接内容 包含/匹配 seg
|
||||
for {
|
||||
if wordIdx >= len(allWords) {
|
||||
break
|
||||
}
|
||||
var currentText strings.Builder
|
||||
|
||||
// 收集Word直到拼接内容覆盖当前分段
|
||||
for wordIdx < len(allWords) {
|
||||
word := allWords[wordIdx]
|
||||
currentText += word.Word
|
||||
currentText.WriteString(word.Word)
|
||||
collectWords = append(collectWords, word)
|
||||
wordIdx++
|
||||
|
||||
// 只要包含分段文本,就认为匹配(无视末尾标点差异)
|
||||
if strings.Contains(currentText, seg) {
|
||||
// 当拼接的文本包含当前分段的纯文本时,停止收集
|
||||
if strings.Contains(currentText.String(), segClean) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(collectWords) == 0 {
|
||||
continue
|
||||
}
|
||||
// 生成字幕
|
||||
|
||||
// 3. 生成字幕(时间戳取首尾Word的时间)
|
||||
sub := flowDto.Subtitle{
|
||||
Start: collectWords[0].StartTime,
|
||||
End: collectWords[len(collectWords)-1].EndTime,
|
||||
Text: seg,
|
||||
Text: segClean,
|
||||
}
|
||||
subs = append(subs, sub)
|
||||
subtitles = append(subtitles, sub)
|
||||
}
|
||||
subtitles = append(subtitles, subs...)
|
||||
}
|
||||
|
||||
return subtitles, nil
|
||||
@@ -776,15 +788,52 @@ func VideoOptimizeNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInpu
|
||||
}
|
||||
|
||||
func DataConversionNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput, skillName string, form []map[string]any, userForm []map[string]any) ([]node.NodeFormField, error) {
|
||||
if strings.Contains(nodeInput.Config.Name, "字幕") || strings.Contains(nodeInput.Config.Name, "视频") {
|
||||
jsonStr := ``
|
||||
outputRes := make([]node.NodeFormField, 0)
|
||||
for _, field := range nodeInput.Config.OutputConfig {
|
||||
if strings.Contains(nodeInput.Config.Name, "视频") {
|
||||
for _, item := range nodeInput.Global.ExecutedNodes {
|
||||
refNode, ok := nodeInput.Global.ConfigMap[item.NodeId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, v := range refNode.FormConfig {
|
||||
if v.Field == field.Value {
|
||||
jsonStr, _ = sjson.Set(jsonStr, field.Field, v.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jsonStr, _ = sjson.Set(jsonStr, field.Field, field.Value)
|
||||
}
|
||||
}
|
||||
if strings.Contains(nodeInput.Config.Name, "视频") {
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("data_content"),
|
||||
Value: gconv.Map(jsonStr),
|
||||
Label: fmt.Sprintf("data_content"),
|
||||
Type: "string",
|
||||
})
|
||||
} else {
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("data_conversion"),
|
||||
Value: gconv.Map(jsonStr),
|
||||
Label: fmt.Sprintf("data_conversion"),
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
return outputRes, nil
|
||||
}
|
||||
|
||||
jsonStr := ``
|
||||
jsonVal := "输出字段规范:"
|
||||
jsonVal := ""
|
||||
for _, field := range nodeInput.Config.OutputConfig {
|
||||
jsonStr, _ = sjson.Set(jsonStr, field.Field, "")
|
||||
jsonVal += fmt.Sprintf("%s:%s;", field.Field, field.Value)
|
||||
//jsonVal += fmt.Sprintf("%s:%s;", field.Field, field.Value)
|
||||
}
|
||||
jsonVal += fmt.Sprintf("输出模板结构,仅修改每个字段对应数值:%s", jsonStr)
|
||||
jsonVal += fmt.Sprintf("输出字段规范:%v", jsonStr)
|
||||
nodeInput.Config.PromptContent = fmt.Sprintf("%s;%s", nodeInput.Config.PromptContent, jsonVal)
|
||||
|
||||
mapTaskResult, err := GetModelResult(ctx, "", nodeInput, skillName, form, userForm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -849,7 +898,7 @@ func HttpNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]nod
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
newBody := BuildNestedJson(body, nodeInput.Global.ConfigMap)
|
||||
newBody := BuildNestedJson(body, nodeInput.Global)
|
||||
// 1. 自己生成唯一 taskId(不用前端给)
|
||||
taskId := "my_task_" + uuid.New().String() // 自己生成唯一ID
|
||||
if responseType == "callback" {
|
||||
@@ -875,6 +924,8 @@ func HttpNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]nod
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var e = ""
|
||||
|
||||
finalResult := make(map[string]any)
|
||||
if responseType == "sync" {
|
||||
httpResultJson := gconv.String(rawHttpResult)
|
||||
@@ -884,6 +935,7 @@ func HttpNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]nod
|
||||
finalResult[key] = gjson.Get(httpResultJson, path).Value()
|
||||
}
|
||||
}
|
||||
e = fmt.Sprintf("%v", httpResultJson)
|
||||
}
|
||||
if responseType == "callback" {
|
||||
var waitResult any
|
||||
@@ -910,11 +962,16 @@ func HttpNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]nod
|
||||
}
|
||||
}
|
||||
}
|
||||
e = fmt.Sprintf("%v", bodyStr)
|
||||
}
|
||||
if responseType == "pull" {
|
||||
|
||||
}
|
||||
|
||||
if g.IsEmpty(finalResult) {
|
||||
return nil, fmt.Errorf("http请求异常,返回结果为空:%v", e)
|
||||
}
|
||||
|
||||
outputRes := make([]node.NodeFormField, 0)
|
||||
for i, item := range finalResult {
|
||||
if nodeInput.Config.IsSaveFile {
|
||||
@@ -939,23 +996,17 @@ func HttpNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput) ([]nod
|
||||
func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, resultFrom []map[string]any, resultUserFrom []map[string]any) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
outputResult = append(outputResult, inputMap...)
|
||||
|
||||
resultUserFrom = []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(gconv.String(field.Value))
|
||||
}
|
||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
field.Label: field.Value,
|
||||
})
|
||||
for _, field := range outputMap {
|
||||
if !strings.Contains(field.Field, "text_url") && !strings.Contains(field.Field, "img_url") {
|
||||
if strings.Contains(field.Field, "text_content") {
|
||||
field.Value = StripHtmlTags(gconv.String(field.Value))
|
||||
}
|
||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
field.Label: field.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, valueAny := range modelMap {
|
||||
@@ -963,18 +1014,13 @@ func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, result
|
||||
outputResult = append(outputResult, field)
|
||||
}
|
||||
}
|
||||
//if !nodeInput.Global.IsDialogue {
|
||||
for _, item := range outputResult {
|
||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
item.Label: item.Value,
|
||||
})
|
||||
if !nodeInput.Global.IsDialogue {
|
||||
for _, item := range outputResult {
|
||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
item.Label: item.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, item := range nodeInput.Config.FormConfig {
|
||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
item.Label: item.Value,
|
||||
})
|
||||
}
|
||||
//}
|
||||
if !g.IsEmpty(nodeInput.Global.Desc) {
|
||||
resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
"desc": nodeInput.Global.Desc,
|
||||
@@ -998,48 +1044,37 @@ func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, result
|
||||
return skillName, resultFrom, resultUserFrom
|
||||
}
|
||||
|
||||
func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, nodeEntity *entity.FlowNode) (map[string]any, map[string]any, map[string]any) {
|
||||
input := make(map[string]any)
|
||||
output := make(map[string]any)
|
||||
func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, nodeEntity *entity.FlowNode) ([]node.NodeFormField, []node.NodeFormField, map[string]any) {
|
||||
var input []node.NodeFormField
|
||||
var output []node.NodeFormField
|
||||
model := make(map[string]any)
|
||||
// 1. 有引用 → 取引用节点的字段值
|
||||
if len(nodeEntity.InputSource) > 0 {
|
||||
for _, source := range nodeEntity.InputSource {
|
||||
refNodeID := source.NodeId
|
||||
fields := source.Field
|
||||
|
||||
refNode, ok := execInput.ConfigMap[refNodeID]
|
||||
refNode, ok := execInput.ConfigMap[source.NodeId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
inputMap := buildInputMap(refNode)
|
||||
outputMap := mergeOutput(refNode.OutputResult)
|
||||
modelMap := mergeModel(refNode.ModelConfig)
|
||||
if len(fields) > 0 {
|
||||
if len(source.Field) > 0 {
|
||||
// 取指定字段
|
||||
for _, f := range fields {
|
||||
if v, ok := inputMap[f]; ok {
|
||||
input[f] = v
|
||||
}
|
||||
if v, ok := modelMap[f]; ok {
|
||||
model[f] = v
|
||||
}
|
||||
for k, v := range outputMap {
|
||||
if strings.Contains(k, f) {
|
||||
model[k] = v
|
||||
for _, f := range source.Field {
|
||||
for _, v := range refNode.FormConfig {
|
||||
if v.Label == f {
|
||||
input = append(input, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 取全部
|
||||
if refNode.NodeCode != node.NodeTypeHttp {
|
||||
for k, v := range inputMap {
|
||||
input[k] = v
|
||||
for _, v := range refNode.ModelConfig.ModelForm {
|
||||
if g.IsEmpty(v.Value) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(v.Label, f) {
|
||||
model[f] = v
|
||||
}
|
||||
}
|
||||
for _, v := range refNode.OutputResult {
|
||||
if strings.Contains(v.Label, f) {
|
||||
output = append(output, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range modelMap {
|
||||
model[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1047,34 +1082,145 @@ func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, nodeEntity *en
|
||||
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 _, rawValue := range output.ModelForm {
|
||||
if g.IsEmpty(rawValue.Value) {
|
||||
continue
|
||||
}
|
||||
// 包装成 { "value": 原始值 }
|
||||
m[rawValue.Label] = rawValue.Value
|
||||
}
|
||||
return m
|
||||
}
|
||||
//func BuildParam(nodeInput *flowDto.NodeExecutionInput) (skillName string, resultFrom []map[string]any, resultUserFrom []map[string]any) {
|
||||
// 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 = []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(gconv.String(field.Value))
|
||||
// }
|
||||
// resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
// field.Label: field.Value,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// for _, valueAny := range modelMap {
|
||||
// if field, ok := valueAny.(node.NodeFormField); ok {
|
||||
// outputResult = append(outputResult, field)
|
||||
// }
|
||||
// }
|
||||
// //if !nodeInput.Global.IsDialogue {
|
||||
// for _, item := range outputResult {
|
||||
// resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
// item.Label: item.Value,
|
||||
// })
|
||||
// }
|
||||
// for _, item := range nodeInput.Config.FormConfig {
|
||||
// resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
// item.Label: item.Value,
|
||||
// })
|
||||
// }
|
||||
// //}
|
||||
// if !g.IsEmpty(nodeInput.Global.Desc) {
|
||||
// resultUserFrom = append(resultUserFrom, map[string]any{
|
||||
// "desc": nodeInput.Global.Desc,
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// resultFrom = []map[string]any{}
|
||||
// for _, item := range nodeInput.Config.ModelConfig.ModelForm {
|
||||
// if g.IsEmpty(item.Value) {
|
||||
// continue
|
||||
// }
|
||||
// resultFrom = append(resultFrom, map[string]any{
|
||||
// item.Label: item.Value,
|
||||
// })
|
||||
// }
|
||||
// skillName = nodeInput.Config.SkillName
|
||||
// if g.IsEmpty(nodeInput.Config.SkillName) {
|
||||
// skillName = nodeInput.Global.SkillName
|
||||
// }
|
||||
//
|
||||
// return skillName, resultFrom, resultUserFrom
|
||||
//}
|
||||
//
|
||||
//func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, nodeEntity *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(nodeEntity.InputSource) > 0 {
|
||||
// for _, source := range nodeEntity.InputSource {
|
||||
// refNodeID := source.NodeId
|
||||
// fields := source.Field
|
||||
//
|
||||
// refNode, ok := execInput.ConfigMap[refNodeID]
|
||||
// if !ok {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// inputMap := buildInputMap(refNode)
|
||||
// outputMap := mergeOutput(refNode.OutputResult)
|
||||
// modelMap := mergeModel(refNode.ModelConfig)
|
||||
// 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
|
||||
// }
|
||||
// for k, v := range outputMap {
|
||||
// if strings.Contains(k, f) {
|
||||
// model[k] = v
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // 取全部
|
||||
// if refNode.NodeCode != node.NodeTypeHttp {
|
||||
// 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 _, rawValue := range output.ModelForm {
|
||||
// if g.IsEmpty(rawValue.Value) {
|
||||
// continue
|
||||
// }
|
||||
// // 包装成 { "value": 原始值 }
|
||||
// m[rawValue.Label] = rawValue.Value
|
||||
// }
|
||||
// return m
|
||||
//}
|
||||
|
||||
@@ -9,21 +9,25 @@ import (
|
||||
"ai-agent/workflow/model/entity"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
commonHttp "gitea.redpowerfuture.com/red-future/common/http"
|
||||
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
@@ -97,12 +101,7 @@ func GetModelInfo(ctx context.Context, req *flowDto.GetModelInfoReq) (res *flowD
|
||||
return
|
||||
}
|
||||
|
||||
func GetComposeResult(ctx context.Context, buildType int, modelName, promptContent, skillName string, form []map[string]any, userForm []map[string]any, fileUrl []string, sessionId, nodeId string, cause string) (res *flowDto.ComposeCallbackReq, err error) {
|
||||
if !g.IsEmpty(promptContent) {
|
||||
userForm = append(userForm, map[string]any{
|
||||
"prompt": promptContent,
|
||||
})
|
||||
}
|
||||
func GetComposeResult(ctx context.Context, nodeExecutionId int64, buildType int, modelName, promptContent, skillName string, form []map[string]any, userForm []map[string]any, fileUrl []string, sessionId, nodeId string, cause string) (res *flowDto.ComposeCallbackReq, err error) {
|
||||
var callbackUrl = utils.GetCallbackURL(ctx, "/flow/execution/composeCallBack")
|
||||
var consult = make([]flowDto.Consult, 0)
|
||||
var collectFileUrls func(val any) (fullyConsumed bool)
|
||||
@@ -143,11 +142,27 @@ func GetComposeResult(ctx context.Context, buildType int, modelName, promptConte
|
||||
}
|
||||
var newUserForm []map[string]any
|
||||
for _, m := range userForm {
|
||||
// 先替换字段
|
||||
if val, ok := m["audioDuration"]; ok {
|
||||
delete(m, "audioDuration")
|
||||
m["视频总时长"] = val
|
||||
}
|
||||
if val, ok := m["videoDuration"]; ok {
|
||||
delete(m, "videoDuration")
|
||||
m["视频总时长"] = val
|
||||
}
|
||||
// 收集待删除 key
|
||||
var delKeys []string
|
||||
for k, v := range m {
|
||||
if collectFileUrls(v) {
|
||||
delete(m, k)
|
||||
delKeys = append(delKeys, k)
|
||||
}
|
||||
}
|
||||
// 统一删除
|
||||
for _, k := range delKeys {
|
||||
delete(m, k)
|
||||
}
|
||||
|
||||
if len(m) > 0 {
|
||||
newUserForm = append(newUserForm, m)
|
||||
}
|
||||
@@ -169,23 +184,60 @@ func GetComposeResult(ctx context.Context, buildType int, modelName, promptConte
|
||||
Cause: cause,
|
||||
Form: form,
|
||||
UserForm: newUserForm,
|
||||
UserPrompt: promptContent,
|
||||
Consult: consult,
|
||||
SessionId: sessionId,
|
||||
NodeId: nodeId,
|
||||
}
|
||||
headers := make(map[string]string)
|
||||
msgRes := new(flowDto.ComposeMessagesRes)
|
||||
|
||||
// 1. 隔离上游取消(防止节点执行被中断时下游请求被 cancel)+ 设置独立超时
|
||||
baseCtx := context.WithoutCancel(ctx)
|
||||
postCtx, cancel := context.WithTimeout(baseCtx, 30*time.Minute)
|
||||
defer cancel() // 必须释放,防止上下文泄露
|
||||
|
||||
// 2. 克隆 commonHttp 客户端(保留 Consul 服务发现),显式设置超时和 ResponseHeaderTimeout
|
||||
client := commonHttp.Httpclient.Clone()
|
||||
client.SetTimeout(30 * time.Minute)
|
||||
if tr, ok := client.Transport.(*http.Transport); ok {
|
||||
tr.ResponseHeaderTimeout = 30 * time.Minute
|
||||
}
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
client.SetHeader(k, v[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
msgRes := new(flowDto.ComposeMessagesRes)
|
||||
err = commonHttp.Post(ctx, "prompts-core/prompt/composeMessages", headers, msgRes, &msgReq)
|
||||
resp, err := client.ContentJson().Post(postCtx, "prompts-core/prompt/composeMessages", &msgReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Close()
|
||||
result, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取composeMessages响应失败: %w", err)
|
||||
}
|
||||
// 统一处理内部API响应格式:{code:200,message:"",data:{...}}
|
||||
resultStrut := &ghttp.DefaultHandlerResponse{}
|
||||
|
||||
if err = gconv.Struct(result, &resultStrut); err != nil { // 修复:增加err检查
|
||||
return nil, fmt.Errorf("响应解析失败: " + err.Error())
|
||||
}
|
||||
|
||||
// 添加调试日志:打印解析后的结构
|
||||
g.Log().Debugf(ctx, "[HTTP] 解析后结构: Code=%d, Message=%s, Data类型=%T, Data值=%+v",
|
||||
resultStrut.Code, resultStrut.Message, resultStrut.Data, resultStrut.Data)
|
||||
|
||||
if resultStrut.Code == 200 || resultStrut.Code == 0 {
|
||||
if err = gconv.Struct(resultStrut.Data, &msgRes); err != nil { // 修复:增加err检查
|
||||
return nil, fmt.Errorf("数据解析失败: " + err.Error())
|
||||
}
|
||||
// 添加调试日志:打印最终的target
|
||||
g.Log().Debugf(ctx, "[HTTP] 最终target: %+v", &msgRes)
|
||||
} else {
|
||||
err = errors.New(resultStrut.Message)
|
||||
}
|
||||
if g.IsEmpty(msgRes.TaskId) {
|
||||
return nil, fmt.Errorf("msg is empty")
|
||||
}
|
||||
@@ -197,18 +249,19 @@ func GetComposeResult(ctx context.Context, buildType int, modelName, promptConte
|
||||
if err = gconv.Struct(waitRes, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateTokenCount(ctx, nodeExecutionId, msg.BillingData)
|
||||
if !g.IsEmpty(msg.ErrorMsg) {
|
||||
return nil, fmt.Errorf(msg.ErrorMsg)
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func CreateGatewayTask(ctx context.Context, epicycleId int64, model string, content map[string]any) (map[string]any, error) {
|
||||
func CreateGatewayTask(ctx context.Context, nodeExecutionId int64, epicycleId int64, model string, content map[string]any) (map[string]any, error) {
|
||||
taskId, err := createGatewayTaskOnly(ctx, epicycleId, model, content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return waitGatewayResult(ctx, taskId)
|
||||
return waitGatewayResult(ctx, nodeExecutionId, taskId)
|
||||
}
|
||||
|
||||
// createGatewayTaskOnly creates a gateway task and returns the taskId only
|
||||
@@ -223,29 +276,63 @@ func createGatewayTaskOnly(ctx context.Context, epicycleId int64, model string,
|
||||
EpicycleId: epicycleId,
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
res := new(flowDto.ModelGatewayRes)
|
||||
|
||||
// 1. 隔离上游取消(防止节点执行被中断时下游请求被 cancel)+ 设置独立超时
|
||||
baseCtx := context.WithoutCancel(ctx)
|
||||
postCtx, cancel := context.WithTimeout(baseCtx, 30*time.Minute)
|
||||
defer cancel() // 必须释放,防止上下文泄露
|
||||
|
||||
// 2. 克隆 commonHttp 客户端(保留 Consul 服务发现),显式设置超时和 ResponseHeaderTimeout
|
||||
client := commonHttp.Httpclient.Clone()
|
||||
client.SetTimeout(30 * time.Minute)
|
||||
if tr, ok := client.Transport.(*http.Transport); ok {
|
||||
tr.ResponseHeaderTimeout = 30 * time.Minute
|
||||
}
|
||||
if r := g.RequestFromCtx(ctx); r != nil {
|
||||
for k, v := range r.Request.Header {
|
||||
if len(v) > 0 {
|
||||
headers[k] = v[0]
|
||||
client.SetHeader(k, v[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res := new(flowDto.ModelGatewayRes)
|
||||
err := commonHttp.Post(ctx, "model-gateway/task/createTask", headers, res, &req)
|
||||
rpcResp, err := client.ContentJson().Post(postCtx, "model-gateway/task/createTask", &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rpcResp.Close()
|
||||
result, err := io.ReadAll(rpcResp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取createTask响应失败: %w", err)
|
||||
}
|
||||
// 统一处理内部API响应格式:{code:200,message:"",data:{...}}
|
||||
resultStrut := &ghttp.DefaultHandlerResponse{}
|
||||
|
||||
if err = gconv.Struct(result, &resultStrut); err != nil { // 修复:增加err检查
|
||||
return "", fmt.Errorf("响应解析失败: " + err.Error())
|
||||
}
|
||||
|
||||
// 添加调试日志:打印解析后的结构
|
||||
g.Log().Debugf(ctx, "[HTTP] 解析后结构: Code=%d, Message=%s, Data类型=%T, Data值=%+v",
|
||||
resultStrut.Code, resultStrut.Message, resultStrut.Data, resultStrut.Data)
|
||||
|
||||
if resultStrut.Code == 200 || resultStrut.Code == 0 {
|
||||
if err = gconv.Struct(resultStrut.Data, &res); err != nil { // 修复:增加err检查
|
||||
return "", fmt.Errorf("数据解析失败: " + err.Error())
|
||||
}
|
||||
// 添加调试日志:打印最终的target
|
||||
g.Log().Debugf(ctx, "[HTTP] 最终target: %+v", &res)
|
||||
} else {
|
||||
err = errors.New(resultStrut.Message)
|
||||
}
|
||||
if g.IsEmpty(res.TaskId) {
|
||||
return "", fmt.Errorf("创建模型任务失败,taskId为空")
|
||||
}
|
||||
|
||||
return res.TaskId, nil
|
||||
}
|
||||
|
||||
// waitGatewayResult waits for a created gateway task to complete and returns the result
|
||||
func waitGatewayResult(ctx context.Context, taskId string) (map[string]any, error) {
|
||||
func waitGatewayResult(ctx context.Context, nodeExecutionId int64, taskId string) (map[string]any, error) {
|
||||
waitRes, err := Wait(ctx, taskId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -255,30 +342,39 @@ func waitGatewayResult(ctx context.Context, taskId string) (map[string]any, erro
|
||||
if err = gconv.Struct(waitRes, task); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateTokenCount(ctx, nodeExecutionId, task.BillingData)
|
||||
if task.State == 3 || !g.IsEmpty(task.ErrorMsg) {
|
||||
return nil, fmt.Errorf("模型执行失败:%s", task.ErrorMsg)
|
||||
}
|
||||
if g.IsEmpty(task.Messages) {
|
||||
if g.IsEmpty(task.OssFile) {
|
||||
return nil, fmt.Errorf("模型返回结果为空")
|
||||
}
|
||||
// 获取远程文件内容
|
||||
//file, err := GetFileBytesFromURL(ctx, task.OssFile)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//task.Messages = gconv.Map(file)
|
||||
return task.Messages, nil
|
||||
file, err := GetFileBytesFromURL(ctx, task.OssFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gconv.Map(file), nil
|
||||
}
|
||||
|
||||
// updateTokenCount updates the token count in node execution
|
||||
func updateTokenCount(ctx context.Context, nodeExecutionId int64, responseField string, result map[string]any) {
|
||||
if responseField == "" {
|
||||
func updateTokenCount(ctx context.Context, nodeExecutionId int64, tokenInfo []map[string]any) {
|
||||
res, err := nodeDao.NodeExecutionDao.Get(ctx, &nodeDto.GetNodeExecutionReq{
|
||||
Id: nodeExecutionId,
|
||||
}, entity.NodeExecutionCol.TokenInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var t []map[string]any
|
||||
for _, item := range res.TokenInfo {
|
||||
t = append(t, item)
|
||||
}
|
||||
for _, item := range tokenInfo {
|
||||
t = append(t, item)
|
||||
}
|
||||
_, _ = nodeDao.NodeExecutionDao.Update(ctx, &nodeDto.UpdateNodeExecutionReq{
|
||||
Id: nodeExecutionId,
|
||||
CompletionTokens: gconv.Int(result[responseField]),
|
||||
TotalTokens: gconv.Int(result[responseField]),
|
||||
Id: nodeExecutionId,
|
||||
TokenInfo: t,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -291,11 +387,29 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
if !nodeInput.Global.IsDialogue {
|
||||
sessionId = ""
|
||||
}
|
||||
|
||||
composeResult, err := GetComposeResult(ctx, buildType, nodeInput.Config.ModelConfig.ModelName, nodeInput.Config.PromptContent, skillName, form, userForm, nodeInput.Global.FileUrl, sessionId, nodeInput.Config.Id, nodeInput.Config.Name)
|
||||
needSequential := false
|
||||
for _, item := range userForm {
|
||||
if g.NewVar(item).IsMap() {
|
||||
valMap := gconv.Map(item)
|
||||
for _, v := range valMap {
|
||||
if g.NewVar(v).IsMap() {
|
||||
vv := gconv.Map(v)
|
||||
for kk, vvv := range vv {
|
||||
if kk == "return_last_frame" {
|
||||
needSequential = vvv.(bool)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
composeResult, err := GetComposeResult(ctx, nodeInput.NodeExecutionId, buildType, nodeInput.Config.ModelConfig.ModelName, nodeInput.Config.PromptContent, skillName, form, userForm, nodeInput.Global.FileUrl, sessionId, nodeInput.Config.Id, nodeInput.Config.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if composeResult.Status != "success" {
|
||||
return nil, fmt.Errorf("模型提示词构建错误")
|
||||
}
|
||||
|
||||
modelInfo, err := GetModelInfo(ctx, &flowDto.GetModelInfoReq{ModelName: nodeInput.Config.ModelConfig.ModelName})
|
||||
if err != nil {
|
||||
@@ -305,13 +419,12 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
mapTaskResult = make([]map[string]any, len(composeResult.Messages.Rounds))
|
||||
var taskResultMap map[string]any
|
||||
|
||||
needSequential := false
|
||||
if buildType == 1 {
|
||||
if needSequential {
|
||||
for idx, item := range composeResult.Messages.Rounds {
|
||||
if !g.IsEmpty(taskResultMap) {
|
||||
var set string
|
||||
set, err = sjson.Set(gconv.String(item), modelInfo.Model.LastFrame, gconv.String(taskResultMap[modelInfo.Model.ResponseBody]))
|
||||
set, err = sjson.Set(gconv.String(item), modelInfo.Model.FirstFrame, gconv.String(taskResultMap["content"]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -319,7 +432,7 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
}
|
||||
|
||||
var taskResult map[string]any
|
||||
taskResult, err = CreateGatewayTask(ctx, composeResult.EpicycleId, nodeInput.Config.ModelConfig.ModelName, item)
|
||||
taskResult, err = CreateGatewayTask(ctx, nodeInput.NodeExecutionId, composeResult.EpicycleId, nodeInput.Config.ModelConfig.ModelName, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -328,7 +441,7 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
}
|
||||
|
||||
if nodeInput.Config.NodeCode == node.NodeTypeVideoModel {
|
||||
ext := GetFileTypeByPath(gconv.String(taskResult[modelInfo.Model.ResponseBody]))
|
||||
ext := GetFileTypeByPath(gconv.String(taskResult["content"]))
|
||||
if ext == "image" {
|
||||
taskResultMap = taskResult
|
||||
} else {
|
||||
@@ -339,61 +452,79 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
}
|
||||
|
||||
mapTaskResult[idx] = taskResult
|
||||
updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, taskResult)
|
||||
//updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, taskResult)
|
||||
}
|
||||
} else {
|
||||
taskIdList := make([]string, len(composeResult.Messages.Rounds))
|
||||
|
||||
for idx, item := range composeResult.Messages.Rounds {
|
||||
var taskId string
|
||||
taskId, err = createGatewayTaskOnly(ctx, composeResult.EpicycleId, nodeInput.Config.ModelConfig.ModelName, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
taskIdList[idx] = taskId
|
||||
}
|
||||
// 全局共享子上下文,实现一处报错全部终止
|
||||
subCtx, globalCancel := context.WithCancel(ctx)
|
||||
defer globalCancel() // 函数退出兜底释放
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, len(taskIdList))
|
||||
errChan := make(chan error, len(composeResult.Messages.Rounds))
|
||||
|
||||
// 加互斥锁保护结果map
|
||||
var mu sync.Mutex
|
||||
|
||||
// 每个任务创建后立即启动等待协程:把「回调 vs Wait 注册」的竞争窗口从整个创建循环
|
||||
// 压缩到微秒级,避免创建期间完成的回调被 Notify 静默丢弃导致 Wait 永久阻塞
|
||||
for idx, item := range composeResult.Messages.Rounds {
|
||||
taskId, err := createGatewayTaskOnly(ctx, composeResult.EpicycleId, nodeInput.Config.ModelConfig.ModelName, item)
|
||||
if err != nil {
|
||||
globalCancel() // 取消已启动的等待协程,避免泄漏
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for idx, taskId := range taskIdList {
|
||||
wg.Add(1)
|
||||
|
||||
go func(idx int, taskId string) {
|
||||
defer wg.Done()
|
||||
|
||||
var taskResult map[string]any
|
||||
taskResult, err = waitGatewayResult(ctx, taskId)
|
||||
taskResult, err := waitGatewayResult(subCtx, nodeInput.NodeExecutionId, taskId)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
globalCancel() // 全局取消,所有协程收到ctx取消信号快速退出
|
||||
return
|
||||
}
|
||||
|
||||
// 加锁写入map,解决并发竞态
|
||||
mu.Lock()
|
||||
mapTaskResult[idx] = taskResult
|
||||
updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, taskResult)
|
||||
mu.Unlock()
|
||||
|
||||
//updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, taskResult)
|
||||
}(idx, taskId)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
if len(errChan) > 0 {
|
||||
return nil, <-errChan
|
||||
// 收集全部错误,而非只读一条
|
||||
var errs []error
|
||||
for len(errChan) > 0 {
|
||||
errs = append(errs, <-errChan)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
// 返回第一个错误;如需汇总所有错误可拼接
|
||||
return nil, errs[0]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for idx, item := range composeResult.Messages.Rounds {
|
||||
mapTaskResult[idx] = item
|
||||
updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, item)
|
||||
//updateTokenCount(ctx, nodeInput.NodeExecutionId, modelInfo.Model.ResponseTokenField, item)
|
||||
}
|
||||
}
|
||||
|
||||
return mapTaskResult, nil
|
||||
}
|
||||
|
||||
func BuildNestedJson(body g.Map, mockConfigMap map[string]*entity.FlowNode) g.Map {
|
||||
func BuildNestedJson(body g.Map, global *flowDto.FlowExecutionInput) g.Map {
|
||||
jsonStr := "{}"
|
||||
for originKey, originItem := range body {
|
||||
if originKey == "templates" && !g.IsEmpty(global.Templates) {
|
||||
jsonStr, _ = sjson.Set(jsonStr, originKey, global.Templates)
|
||||
continue
|
||||
}
|
||||
bodyItemMap := gconv.Map(originItem)
|
||||
val := bodyItemMap["value"]
|
||||
if v, ok := bodyItemMap["value"]; ok {
|
||||
@@ -404,7 +535,7 @@ func BuildNestedJson(body g.Map, mockConfigMap map[string]*entity.FlowNode) g.Ma
|
||||
valMap := gconv.Map(val)
|
||||
nodeId := gconv.String(valMap["nodeId"])
|
||||
fieldName := gconv.String(valMap["field"])
|
||||
if configValue, ok := mockConfigMap[nodeId]; ok {
|
||||
if configValue, ok := global.ConfigMap[nodeId]; ok {
|
||||
if !g.IsEmpty(configValue.OutputResult) {
|
||||
for _, v := range configValue.OutputResult {
|
||||
if strings.Contains(v.Field, fieldName) {
|
||||
@@ -490,16 +621,17 @@ func VideoConcat(ctx context.Context, videoUrls []string) (r any, err error) {
|
||||
}
|
||||
|
||||
func GetFileBytesFromURL(ctx context.Context, fileUrl string) ([]byte, error) {
|
||||
newS := strings.ReplaceAll(fileUrl, g.Cfg().MustGet(ctx, "filePrefix").String(), g.Cfg().MustGet(ctx, "minioPrefix").String())
|
||||
// 使用 GoFrame 客户端(自带超时、追踪、日志等能力)
|
||||
resp, err := g.Client().Get(ctx, fileUrl)
|
||||
resp, err := g.Client().Get(ctx, newS)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrapf(err, "failed to request url: %s", fileUrl)
|
||||
return nil, gerror.Wrapf(err, "failed to request url: %s", newS)
|
||||
}
|
||||
defer resp.Close()
|
||||
|
||||
// 校验状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, gerror.Newf("request failed with status code: %d, url: %s", resp.StatusCode, fileUrl)
|
||||
return nil, gerror.Newf("request failed with status code: %d, url: %s", resp.StatusCode, newS)
|
||||
}
|
||||
|
||||
// 读取全部内容
|
||||
@@ -579,6 +711,56 @@ func GetFileTypeByPath(filePath string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// GetUrlSuffix 获取URL文件后缀
|
||||
// rawUrl: 原始链接
|
||||
// withDot: true 返回 .mp4 false 返回 mp4
|
||||
func GetUrlSuffix(rawUrl string, withDot bool) string {
|
||||
// 解析URL,剥离查询参数
|
||||
u, err := url.Parse(rawUrl)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 提取路径部分
|
||||
filePath := u.Path
|
||||
// 获取文件名
|
||||
fileName := path.Base(filePath)
|
||||
if fileName == "" || !strings.Contains(fileName, ".") {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 截取后缀
|
||||
suffix := path.Ext(fileName)
|
||||
if !withDot {
|
||||
suffix = strings.TrimPrefix(suffix, ".")
|
||||
}
|
||||
return suffix
|
||||
}
|
||||
|
||||
// ProcessPath 处理请求路径
|
||||
// 1. 判断是否为合法完整请求路径(以/开头)
|
||||
// 2. 包含 tenantId-1 则截断其及前面所有内容
|
||||
func ProcessPath(ctx context.Context, path string) string {
|
||||
// 判断是否是完整请求路径:以 / 开头
|
||||
if strings.HasPrefix(path, "/") {
|
||||
return path
|
||||
}
|
||||
|
||||
target, err := utils.GetBucketName(ctx)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
idx := strings.Index(path, target)
|
||||
if idx == -1 {
|
||||
// 不包含目标字符串,原样返回
|
||||
return path
|
||||
}
|
||||
|
||||
// 截取 tenantId-1 后面的内容
|
||||
newPath := path[idx+len(target):]
|
||||
return newPath
|
||||
}
|
||||
|
||||
func BuildText(text string) string {
|
||||
// 生成单条HTML
|
||||
var htmlBuilder strings.Builder
|
||||
|
||||
@@ -67,6 +67,7 @@ func (s *nodeLibraryService) GetNodeLibrary(ctx context.Context, req *nodeDto.Wo
|
||||
NodeCode: node.NodeTypeVideoModel,
|
||||
NodeName: node.NodeNameVideoModel,
|
||||
ModelType: node.ModelTypeVideo,
|
||||
PatchLayout: true,
|
||||
SkillOption: false,
|
||||
PromptOption: true,
|
||||
IsSaveFile: true,
|
||||
|
||||
Reference in New Issue
Block a user