mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-21 18:44:24 +08:00
完成即梦配置功能页面
This commit is contained in:
195
JIMENG_CONFIG_README.md
Normal file
195
JIMENG_CONFIG_README.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# 即梦 AI 配置功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
即梦 AI 配置功能允许管理员通过 Web 界面配置即梦 AI 的 API 密钥和算力消耗设置,支持动态配置更新,无需重启服务。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 秘钥配置
|
||||
|
||||
- AccessKey 和 SecretKey 配置
|
||||
- 支持密码显示/隐藏
|
||||
- 连接测试功能
|
||||
|
||||
### 2. 算力配置
|
||||
|
||||
- 文生图算力消耗
|
||||
- 图生图算力消耗
|
||||
- 图片编辑算力消耗
|
||||
- 图片特效算力消耗
|
||||
- 文生视频算力消耗
|
||||
- 图生视频算力消耗
|
||||
|
||||
### 3. 动态配置
|
||||
|
||||
- 配置实时生效
|
||||
- 无需重启服务
|
||||
- 支持配置验证
|
||||
|
||||
## API 接口
|
||||
|
||||
### 获取配置
|
||||
|
||||
```
|
||||
GET /api/admin/jimeng/config
|
||||
```
|
||||
|
||||
### 更新配置
|
||||
|
||||
```
|
||||
POST /api/admin/jimeng/config
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"config": {
|
||||
"access_key": "your_access_key",
|
||||
"secret_key": "your_secret_key",
|
||||
"power": {
|
||||
"text_to_image": 10,
|
||||
"image_to_image": 15,
|
||||
"image_edit": 20,
|
||||
"image_effects": 25,
|
||||
"text_to_video": 30,
|
||||
"image_to_video": 35
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 测试连接
|
||||
|
||||
```
|
||||
POST /api/admin/jimeng/config/test
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"config": {
|
||||
"access_key": "your_access_key",
|
||||
"secret_key": "your_secret_key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 前端页面
|
||||
|
||||
### 访问路径
|
||||
|
||||
管理后台 -> 即梦 AI -> 配置设置
|
||||
|
||||
### 页面功能
|
||||
|
||||
1. **秘钥配置标签页**
|
||||
|
||||
- AccessKey 输入框(密码模式)
|
||||
- SecretKey 输入框(密码模式)
|
||||
- 测试连接按钮
|
||||
|
||||
2. **算力配置标签页**
|
||||
|
||||
- 各种任务类型的算力消耗配置
|
||||
- 数字输入框,支持 1-100 范围
|
||||
- 提示信息说明
|
||||
|
||||
3. **操作按钮**
|
||||
- 保存配置
|
||||
- 重置配置
|
||||
|
||||
## 配置存储
|
||||
|
||||
配置存储在数据库的`config`表中:
|
||||
|
||||
- 配置键:`jimeng`
|
||||
- 配置值:JSON 格式的即梦 AI 配置
|
||||
|
||||
## 默认配置
|
||||
|
||||
如果配置不存在,系统会使用以下默认值:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_key": "",
|
||||
"secret_key": "",
|
||||
"power": {
|
||||
"text_to_image": 10,
|
||||
"image_to_image": 15,
|
||||
"image_edit": 20,
|
||||
"image_effects": 25,
|
||||
"text_to_video": 30,
|
||||
"image_to_video": 35
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. **初始配置**
|
||||
|
||||
- 访问管理后台即梦 AI 配置页面
|
||||
- 填写 AccessKey 和 SecretKey
|
||||
- 点击"测试连接"验证配置
|
||||
- 调整各功能算力消耗
|
||||
- 保存配置
|
||||
|
||||
2. **配置更新**
|
||||
|
||||
- 修改需要更新的配置项
|
||||
- 保存配置
|
||||
- 配置立即生效
|
||||
|
||||
3. **故障排查**
|
||||
- 使用"测试连接"功能验证 API 密钥
|
||||
- 检查配置是否正确保存
|
||||
- 查看服务日志
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **权限要求**
|
||||
|
||||
- 只有管理员可以访问配置页面
|
||||
- 需要有效的管理员登录会话
|
||||
|
||||
2. **配置验证**
|
||||
|
||||
- AccessKey 和 SecretKey 不能为空
|
||||
- 算力消耗必须大于 0
|
||||
- 建议先测试连接再保存配置
|
||||
|
||||
3. **服务影响**
|
||||
- 配置更新不会影响正在进行的任务
|
||||
- 新任务会使用更新后的配置
|
||||
- 客户端配置会在下次请求时更新
|
||||
|
||||
## 错误处理
|
||||
|
||||
1. **配置加载失败**
|
||||
|
||||
- 使用默认配置
|
||||
- 记录错误日志
|
||||
|
||||
2. **连接测试失败**
|
||||
|
||||
- 显示具体错误信息
|
||||
- 建议检查 API 密钥
|
||||
|
||||
3. **配置保存失败**
|
||||
- 显示错误信息
|
||||
- 保留原有配置
|
||||
|
||||
## 开发说明
|
||||
|
||||
### 后端文件
|
||||
|
||||
- `api/handler/admin/jimeng_handler.go` - 配置管理 API
|
||||
- `api/service/jimeng/service.go` - 配置服务逻辑
|
||||
- `api/core/types/jimeng.go` - 配置类型定义
|
||||
|
||||
### 前端文件
|
||||
|
||||
- `web/src/views/admin/jimeng/JimengSetting.vue` - 配置页面
|
||||
|
||||
### 数据库
|
||||
|
||||
- `config`表存储配置信息
|
||||
- 配置键:`jimeng`
|
||||
- 配置值:JSON 格式
|
||||
@@ -34,6 +34,55 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AuthConfig 定义授权配置
|
||||
type AuthConfig struct {
|
||||
ExactPaths map[string]bool // 精确匹配的路径
|
||||
PrefixPaths map[string]bool // 前缀匹配的路径
|
||||
}
|
||||
|
||||
var authConfig = &AuthConfig{
|
||||
ExactPaths: map[string]bool{
|
||||
"/api/user/login": false,
|
||||
"/api/user/logout": false,
|
||||
"/api/user/resetPass": false,
|
||||
"/api/user/register": false,
|
||||
"/api/admin/login": false,
|
||||
"/api/admin/logout": false,
|
||||
"/api/admin/login/captcha": false,
|
||||
"/api/chat/history": false,
|
||||
"/api/chat/detail": false,
|
||||
"/api/chat/list": false,
|
||||
"/api/app/list": false,
|
||||
"/api/app/type/list": false,
|
||||
"/api/app/list/user": false,
|
||||
"/api/model/list": false,
|
||||
"/api/mj/imgWall": false,
|
||||
"/api/mj/notify": false,
|
||||
"/api/invite/hits": false,
|
||||
"/api/sd/imgWall": false,
|
||||
"/api/dall/imgWall": false,
|
||||
"/api/product/list": false,
|
||||
"/api/menu/list": false,
|
||||
"/api/markMap/client": false,
|
||||
"/api/payment/doPay": false,
|
||||
"/api/payment/payWays": false,
|
||||
"/api/suno/detail": false,
|
||||
"/api/suno/play": false,
|
||||
"/api/download": false,
|
||||
"/api/dall/models": false,
|
||||
},
|
||||
PrefixPaths: map[string]bool{
|
||||
"/api/test/": false,
|
||||
"/api/payment/notify/": false,
|
||||
"/api/user/clogin": false,
|
||||
"/api/config/": false,
|
||||
"/api/function/": false,
|
||||
"/api/sms/": false,
|
||||
"/api/captcha/": false,
|
||||
"/static/": false,
|
||||
},
|
||||
}
|
||||
|
||||
type AppServer struct {
|
||||
Config *types.AppConfig
|
||||
Engine *gin.Engine
|
||||
@@ -212,6 +261,11 @@ func corsMiddleware() gin.HandlerFunc {
|
||||
// 用户授权验证
|
||||
func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if !needLogin(c) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
clientProtocols := c.GetHeader("Sec-WebSocket-Protocol")
|
||||
var tokenString string
|
||||
isAdminApi := strings.Contains(c.Request.URL.Path, "/api/admin/")
|
||||
@@ -230,18 +284,13 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
if tokenString == "" {
|
||||
if needLogin(c) {
|
||||
resp.NotAuth(c, "You should put Authorization in request headers")
|
||||
c.Abort()
|
||||
return
|
||||
} else { // 直接放行
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
resp.NotAuth(c, "You should put Authorization in request headers")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok && needLogin(c) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
if isAdminApi {
|
||||
@@ -252,21 +301,21 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
|
||||
|
||||
})
|
||||
|
||||
if err != nil && needLogin(c) {
|
||||
if err != nil {
|
||||
resp.NotAuth(c, fmt.Sprintf("Error with parse auth token: %v", err))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || !token.Valid && needLogin(c) {
|
||||
if !ok || !token.Valid {
|
||||
resp.NotAuth(c, "Token is invalid")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
|
||||
if expr > 0 && int64(expr) < time.Now().Unix() && needLogin(c) {
|
||||
if expr > 0 && int64(expr) < time.Now().Unix() {
|
||||
resp.NotAuth(c, "Token is expired")
|
||||
c.Abort()
|
||||
return
|
||||
@@ -276,57 +325,48 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
|
||||
if isAdminApi {
|
||||
key = fmt.Sprintf("admin/%v", claims["user_id"])
|
||||
}
|
||||
if _, err := client.Get(context.Background(), key).Result(); err != nil && needLogin(c) {
|
||||
if _, err := client.Get(context.Background(), key).Result(); err != nil {
|
||||
resp.NotAuth(c, "Token is not found in redis")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set(types.LoginUserID, claims["user_id"])
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func needLogin(c *gin.Context) bool {
|
||||
if c.Request.URL.Path == "/api/user/login" ||
|
||||
c.Request.URL.Path == "/api/user/logout" ||
|
||||
c.Request.URL.Path == "/api/user/resetPass" ||
|
||||
c.Request.URL.Path == "/api/admin/login" ||
|
||||
c.Request.URL.Path == "/api/admin/logout" ||
|
||||
c.Request.URL.Path == "/api/admin/login/captcha" ||
|
||||
c.Request.URL.Path == "/api/user/register" ||
|
||||
c.Request.URL.Path == "/api/chat/history" ||
|
||||
c.Request.URL.Path == "/api/chat/detail" ||
|
||||
c.Request.URL.Path == "/api/chat/list" ||
|
||||
c.Request.URL.Path == "/api/app/list" ||
|
||||
c.Request.URL.Path == "/api/app/type/list" ||
|
||||
c.Request.URL.Path == "/api/app/list/user" ||
|
||||
c.Request.URL.Path == "/api/model/list" ||
|
||||
c.Request.URL.Path == "/api/mj/imgWall" ||
|
||||
c.Request.URL.Path == "/api/mj/notify" ||
|
||||
c.Request.URL.Path == "/api/invite/hits" ||
|
||||
c.Request.URL.Path == "/api/sd/imgWall" ||
|
||||
c.Request.URL.Path == "/api/dall/imgWall" ||
|
||||
c.Request.URL.Path == "/api/product/list" ||
|
||||
c.Request.URL.Path == "/api/menu/list" ||
|
||||
c.Request.URL.Path == "/api/markMap/client" ||
|
||||
c.Request.URL.Path == "/api/payment/doPay" ||
|
||||
c.Request.URL.Path == "/api/payment/payWays" ||
|
||||
c.Request.URL.Path == "/api/suno/detail" ||
|
||||
c.Request.URL.Path == "/api/suno/play" ||
|
||||
c.Request.URL.Path == "/api/download" ||
|
||||
c.Request.URL.Path == "/api/dall/models" ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/payment/notify/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/static/") {
|
||||
path := c.Request.URL.Path
|
||||
|
||||
// 如果不是 API 路径,不需要登录
|
||||
if !strings.HasPrefix(path, "/api") {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查精确匹配的路径
|
||||
if skip, exists := authConfig.ExactPaths[path]; exists {
|
||||
return skip
|
||||
}
|
||||
|
||||
// 检查前缀匹配的路径
|
||||
for prefix, skip := range authConfig.PrefixPaths {
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
return skip
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 跳过授权
|
||||
func (s *AppServer) SkipAuth(url string, prefix bool) {
|
||||
if prefix {
|
||||
authConfig.PrefixPaths[url] = false
|
||||
} else {
|
||||
authConfig.ExactPaths[url] = false
|
||||
}
|
||||
}
|
||||
|
||||
// 统一参数处理
|
||||
func parameterHandlerMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
@@ -49,12 +49,6 @@ type ApiConfig struct {
|
||||
JimengConfig JimengConfig // 即梦AI配置
|
||||
}
|
||||
|
||||
// JimengConfig 即梦AI配置
|
||||
type JimengConfig struct {
|
||||
AccessKey string // 火山引擎AccessKey
|
||||
SecretKey string // 火山引擎SecretKey
|
||||
}
|
||||
|
||||
type AlipayConfig struct {
|
||||
Enabled bool // 是否启用该支付通道
|
||||
SandBox bool // 是否沙盒环境
|
||||
|
||||
18
api/core/types/jimeng.go
Normal file
18
api/core/types/jimeng.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package types
|
||||
|
||||
// JimengConfig 即梦AI配置
|
||||
type JimengConfig struct {
|
||||
AccessKey string `json:"access_key"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
Power JimengPower `json:"power"`
|
||||
}
|
||||
|
||||
// JimengPower 即梦AI算力配置
|
||||
type JimengPower struct {
|
||||
TextToImage int `json:"text_to_image"`
|
||||
ImageToImage int `json:"image_to_image"`
|
||||
ImageEdit int `json:"image_edit"`
|
||||
ImageEffects int `json:"image_effects"`
|
||||
TextToVideo int `json:"text_to_video"`
|
||||
ImageToVideo int `json:"image_to_video"`
|
||||
}
|
||||
@@ -18,6 +18,7 @@ require (
|
||||
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
|
||||
github.com/qiniu/go-sdk/v7 v7.17.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23
|
||||
go.uber.org/zap v1.23.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/driver/mysql v1.4.7
|
||||
|
||||
64
api/go.sum
64
api/go.sum
@@ -1,3 +1,5 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.405 h1:cKNFQmeCQFN0WNfjScKoVrGi7vXxTVbkCvCqSrOf+P4=
|
||||
@@ -6,6 +8,7 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiw
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
@@ -13,11 +16,13 @@ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -28,6 +33,8 @@ github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0
|
||||
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@@ -84,12 +91,27 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-tika v0.3.1 h1:l+jr10hDhZjcgxFRfcQChRLo1bPXQeLFluMyvDhXTTA=
|
||||
@@ -131,6 +153,7 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
@@ -183,6 +206,7 @@ github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480 h1:IFhPCcB0/H
|
||||
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480/go.mod h1:BijIqAP84FMYC4XbdJgjyMpiSjusU8x0Y0W9K2t0QtU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
github.com/qiniu/go-sdk/v7 v7.17.1 h1:UoQv7fBKtzAiD1qZPIvTy62Se48YLKxcCYP9nAwWMa0=
|
||||
github.com/qiniu/go-sdk/v7 v7.17.1/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
|
||||
@@ -212,6 +236,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -234,6 +259,8 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
|
||||
github.com/xxl-job/xxl-job-executor-go v1.2.0 h1:MTl2DpwrK2+hNjRRks2k7vB3oy+3onqm9OaSarneeLQ=
|
||||
github.com/xxl-job/xxl-job-executor-go v1.2.0/go.mod h1:bUFhz/5Irp9zkdYk5MxhQcDDT6LlZrI8+rv5mHtQ1mo=
|
||||
github.com/ysmood/fetchup v0.3.0 h1:UhYz9xnLEVn2ukSuK3KCgcznWpHMdrmbsPpllcylyu8=
|
||||
@@ -279,15 +306,23 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
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=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -298,12 +333,15 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -342,16 +380,39 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
@@ -364,6 +425,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
@@ -376,4 +438,6 @@ gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8o
|
||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"strconv"
|
||||
|
||||
"geekai/core"
|
||||
"geekai/core/types"
|
||||
"geekai/handler"
|
||||
"geekai/service/jimeng"
|
||||
"geekai/store/model"
|
||||
"geekai/utils"
|
||||
"geekai/utils/resp"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AdminJimengHandler 管理后台即梦AI处理器
|
||||
@@ -19,13 +22,25 @@ type AdminJimengHandler struct {
|
||||
}
|
||||
|
||||
// NewAdminJimengHandler 创建管理后台即梦AI处理器
|
||||
func NewAdminJimengHandler(app *core.AppServer, jimengService *jimeng.Service) *AdminJimengHandler {
|
||||
func NewAdminJimengHandler(app *core.AppServer, db *gorm.DB, jimengService *jimeng.Service) *AdminJimengHandler {
|
||||
return &AdminJimengHandler{
|
||||
BaseHandler: handler.BaseHandler{App: app},
|
||||
BaseHandler: handler.BaseHandler{App: app, DB: db},
|
||||
jimengService: jimengService,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册即梦AI管理后台路由
|
||||
func (h *AdminJimengHandler) RegisterRoutes() {
|
||||
rg := h.App.Engine.Group("/api/admin/jimeng/")
|
||||
rg.GET("/jobs", h.Jobs)
|
||||
rg.GET("/jobs/:id", h.JobDetail)
|
||||
rg.DELETE("/jobs/:id", h.Remove)
|
||||
rg.POST("/jobs/batch-remove", h.BatchRemove)
|
||||
rg.GET("/stats", h.Stats)
|
||||
rg.GET("/config", h.GetConfig)
|
||||
rg.POST("/config", h.UpdateConfig)
|
||||
}
|
||||
|
||||
// Jobs 获取任务列表
|
||||
func (h *AdminJimengHandler) Jobs(c *gin.Context) {
|
||||
page := h.GetInt(c, "page", 1)
|
||||
@@ -174,4 +189,115 @@ func (h *AdminJimengHandler) Stats(c *gin.Context) {
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, result)
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig 获取即梦AI配置
|
||||
func (h *AdminJimengHandler) GetConfig(c *gin.Context) {
|
||||
var config model.Config
|
||||
err := h.DB.Debug().Where("name", "jimeng").First(&config).Error
|
||||
if err != nil {
|
||||
// 如果配置不存在,返回默认配置
|
||||
defaultConfig := types.JimengConfig{
|
||||
AccessKey: "",
|
||||
SecretKey: "",
|
||||
Power: types.JimengPower{
|
||||
TextToImage: 10,
|
||||
ImageToImage: 15,
|
||||
ImageEdit: 20,
|
||||
ImageEffects: 25,
|
||||
TextToVideo: 30,
|
||||
ImageToVideo: 35,
|
||||
},
|
||||
}
|
||||
resp.SUCCESS(c, defaultConfig)
|
||||
return
|
||||
}
|
||||
|
||||
var jimengConfig types.JimengConfig
|
||||
err = utils.JsonDecode(config.Value, &jimengConfig)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "解析配置失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, jimengConfig)
|
||||
}
|
||||
|
||||
// UpdateConfig 更新即梦AI配置
|
||||
func (h *AdminJimengHandler) UpdateConfig(c *gin.Context) {
|
||||
var req types.JimengConfig
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
resp.ERROR(c, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if req.AccessKey == "" {
|
||||
resp.ERROR(c, "AccessKey不能为空")
|
||||
return
|
||||
}
|
||||
if req.SecretKey == "" {
|
||||
resp.ERROR(c, "SecretKey不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
testErr := h.jimengService.TestConnection(req.AccessKey, req.SecretKey)
|
||||
if testErr != nil {
|
||||
resp.ERROR(c, "连接测试失败: "+testErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证算力配置
|
||||
if req.Power.TextToImage <= 0 {
|
||||
resp.ERROR(c, "文生图算力必须大于0")
|
||||
return
|
||||
}
|
||||
if req.Power.ImageToImage <= 0 {
|
||||
resp.ERROR(c, "图生图算力必须大于0")
|
||||
return
|
||||
}
|
||||
if req.Power.ImageEdit <= 0 {
|
||||
resp.ERROR(c, "图片编辑算力必须大于0")
|
||||
return
|
||||
}
|
||||
if req.Power.ImageEffects <= 0 {
|
||||
resp.ERROR(c, "图片特效算力必须大于0")
|
||||
return
|
||||
}
|
||||
if req.Power.TextToVideo <= 0 {
|
||||
resp.ERROR(c, "文生视频算力必须大于0")
|
||||
return
|
||||
}
|
||||
if req.Power.ImageToVideo <= 0 {
|
||||
resp.ERROR(c, "图生视频算力必须大于0")
|
||||
return
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
value := utils.JsonEncode(&req)
|
||||
config := model.Config{Name: "jimeng", Value: value}
|
||||
|
||||
err := h.DB.FirstOrCreate(&config, model.Config{Name: "jimeng"}).Error
|
||||
if err != nil {
|
||||
resp.ERROR(c, "保存配置失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if config.Id > 0 {
|
||||
config.Value = value
|
||||
err = h.DB.Updates(&config).Error
|
||||
if err != nil {
|
||||
resp.ERROR(c, "更新配置失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 更新服务中的客户端配置
|
||||
updateErr := h.jimengService.UpdateClientConfig(req.AccessKey, req.SecretKey)
|
||||
if updateErr != nil {
|
||||
// 配置已保存,但客户端更新失败,记录日志但不返回错误
|
||||
logger.Errorf("更新即梦AI客户端配置失败: %v", updateErr)
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, gin.H{"message": "配置更新成功"})
|
||||
}
|
||||
|
||||
@@ -154,7 +154,6 @@ func (h *MediaHandler) Remove(c *gin.Context) {
|
||||
remark = fmt.Sprintf("SUNO 任务失败,退回算力。任务ID:%d,Err: %s", job.Id, job.ErrMsg)
|
||||
progress = job.Progress
|
||||
fileURL = job.AudioURL
|
||||
break
|
||||
case "luma":
|
||||
case "keling":
|
||||
var job model.VideoJob
|
||||
@@ -174,7 +173,6 @@ func (h *MediaHandler) Remove(c *gin.Context) {
|
||||
if fileURL == "" {
|
||||
fileURL = job.WaterURL
|
||||
}
|
||||
break
|
||||
default:
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
|
||||
@@ -28,6 +28,20 @@ func NewJimengHandler(app *core.AppServer, jimengService *jimeng.Service) *Jimen
|
||||
}
|
||||
}
|
||||
|
||||
func (h *JimengHandler) RegisterRoutes() {
|
||||
rg := h.App.Engine.Group("/api/jimeng")
|
||||
rg.POST("text-to-image", h.TextToImage)
|
||||
rg.POST("image-to-image-portrait", h.ImageToImagePortrait)
|
||||
rg.POST("image-edit", h.ImageEdit)
|
||||
rg.POST("image-effects", h.ImageEffects)
|
||||
rg.POST("text-to-video", h.TextToVideo)
|
||||
rg.POST("image-to-video", h.ImageToVideo)
|
||||
rg.GET("jobs", h.Jobs)
|
||||
rg.GET("pending-count", h.PendingCount)
|
||||
rg.GET("remove", h.Remove)
|
||||
rg.GET("retry", h.Retry)
|
||||
}
|
||||
|
||||
// TextToImage 文生图
|
||||
func (h *JimengHandler) TextToImage(c *gin.Context) {
|
||||
var req struct {
|
||||
@@ -51,9 +65,12 @@ func (h *JimengHandler) TextToImage(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置中的算力消耗
|
||||
powerCost := h.getPowerFromConfig(model.JMTaskTypeTextToImage)
|
||||
|
||||
// 检查用户算力
|
||||
if user.Power < 20 { // 文生图消耗20算力
|
||||
resp.ERROR(c, "算力不足")
|
||||
if user.Power < powerCost {
|
||||
resp.ERROR(c, fmt.Sprintf("算力不足,需要%d算力", powerCost))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,7 +103,7 @@ func (h *JimengHandler) TextToImage(c *gin.Context) {
|
||||
Prompt: req.Prompt,
|
||||
Params: params,
|
||||
ReqKey: jimeng.ReqKeyTextToImage,
|
||||
Power: 20,
|
||||
Power: powerCost,
|
||||
}
|
||||
|
||||
job, err := h.jimengService.CreateTask(user.Id, taskReq)
|
||||
@@ -97,7 +114,7 @@ func (h *JimengHandler) TextToImage(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 扣除用户算力
|
||||
h.subUserPower(user.Id, 20, model.PowerLog{
|
||||
h.subUserPower(user.Id, powerCost, model.PowerLog{
|
||||
Type: types.PowerConsume,
|
||||
Model: "即梦文生图",
|
||||
Remark: fmt.Sprintf("任务ID:%d", job.Id),
|
||||
@@ -132,9 +149,12 @@ func (h *JimengHandler) ImageToImagePortrait(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置中的算力消耗
|
||||
powerCost := h.getPowerFromConfig(model.JMTaskTypeImageToImage)
|
||||
|
||||
// 检查用户算力
|
||||
if user.Power < 30 { // 图生图消耗30算力
|
||||
resp.ERROR(c, "算力不足")
|
||||
if user.Power < powerCost {
|
||||
resp.ERROR(c, fmt.Sprintf("算力不足,需要%d算力", powerCost))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -183,7 +203,7 @@ func (h *JimengHandler) ImageToImagePortrait(c *gin.Context) {
|
||||
Prompt: req.Prompt,
|
||||
Params: params,
|
||||
ReqKey: jimeng.ReqKeyImageToImagePortrait,
|
||||
Power: 30,
|
||||
Power: powerCost,
|
||||
}
|
||||
|
||||
job, err := h.jimengService.CreateTask(user.Id, taskReq)
|
||||
@@ -194,7 +214,7 @@ func (h *JimengHandler) ImageToImagePortrait(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 扣除用户算力
|
||||
h.subUserPower(user.Id, 30, model.PowerLog{
|
||||
h.subUserPower(user.Id, powerCost, model.PowerLog{
|
||||
Type: types.PowerConsume,
|
||||
Model: "即梦图生图",
|
||||
Remark: fmt.Sprintf("任务ID:%d", job.Id),
|
||||
@@ -230,9 +250,12 @@ func (h *JimengHandler) ImageEdit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置中的算力消耗
|
||||
powerCost := h.getPowerFromConfig(model.JMTaskTypeImageEdit)
|
||||
|
||||
// 检查用户算力
|
||||
if user.Power < 25 { // 图像编辑消耗25算力
|
||||
resp.ERROR(c, "算力不足")
|
||||
if user.Power < powerCost {
|
||||
resp.ERROR(c, fmt.Sprintf("算力不足,需要%d算力", powerCost))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -262,7 +285,7 @@ func (h *JimengHandler) ImageEdit(c *gin.Context) {
|
||||
Prompt: req.Prompt,
|
||||
Params: params,
|
||||
ReqKey: jimeng.ReqKeyImageEdit,
|
||||
Power: 25,
|
||||
Power: powerCost,
|
||||
}
|
||||
|
||||
job, err := h.jimengService.CreateTask(user.Id, taskReq)
|
||||
@@ -273,7 +296,7 @@ func (h *JimengHandler) ImageEdit(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 扣除用户算力
|
||||
h.subUserPower(user.Id, 25, model.PowerLog{
|
||||
h.subUserPower(user.Id, powerCost, model.PowerLog{
|
||||
Type: types.PowerConsume,
|
||||
Model: "即梦图像编辑",
|
||||
Remark: fmt.Sprintf("任务ID:%d", job.Id),
|
||||
@@ -303,9 +326,12 @@ func (h *JimengHandler) ImageEffects(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置中的算力消耗
|
||||
powerCost := h.getPowerFromConfig(model.JMTaskTypeImageEffects)
|
||||
|
||||
// 检查用户算力
|
||||
if user.Power < 15 { // 图像特效消耗15算力
|
||||
resp.ERROR(c, "算力不足")
|
||||
if user.Power < powerCost {
|
||||
resp.ERROR(c, fmt.Sprintf("算力不足,需要%d算力", powerCost))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -331,7 +357,7 @@ func (h *JimengHandler) ImageEffects(c *gin.Context) {
|
||||
Prompt: "",
|
||||
Params: params,
|
||||
ReqKey: jimeng.ReqKeyImageEffects,
|
||||
Power: 15,
|
||||
Power: powerCost,
|
||||
}
|
||||
|
||||
job, err := h.jimengService.CreateTask(user.Id, taskReq)
|
||||
@@ -342,7 +368,7 @@ func (h *JimengHandler) ImageEffects(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 扣除用户算力
|
||||
h.subUserPower(user.Id, 15, model.PowerLog{
|
||||
h.subUserPower(user.Id, powerCost, model.PowerLog{
|
||||
Type: types.PowerConsume,
|
||||
Model: "即梦图像特效",
|
||||
Remark: fmt.Sprintf("任务ID:%d", job.Id),
|
||||
@@ -371,9 +397,12 @@ func (h *JimengHandler) TextToVideo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置中的算力消耗
|
||||
powerCost := h.getPowerFromConfig(model.JMTaskTypeTextToVideo)
|
||||
|
||||
// 检查用户算力
|
||||
if user.Power < 100 { // 文生视频消耗100算力
|
||||
resp.ERROR(c, "算力不足")
|
||||
if user.Power < powerCost {
|
||||
resp.ERROR(c, fmt.Sprintf("算力不足,需要%d算力", powerCost))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -397,7 +426,7 @@ func (h *JimengHandler) TextToVideo(c *gin.Context) {
|
||||
Prompt: req.Prompt,
|
||||
Params: params,
|
||||
ReqKey: jimeng.ReqKeyTextToVideo,
|
||||
Power: 100,
|
||||
Power: powerCost,
|
||||
}
|
||||
|
||||
job, err := h.jimengService.CreateTask(user.Id, taskReq)
|
||||
@@ -408,7 +437,7 @@ func (h *JimengHandler) TextToVideo(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 扣除用户算力
|
||||
h.subUserPower(user.Id, 100, model.PowerLog{
|
||||
h.subUserPower(user.Id, powerCost, model.PowerLog{
|
||||
Type: types.PowerConsume,
|
||||
Model: "即梦文生视频",
|
||||
Remark: fmt.Sprintf("任务ID:%d", job.Id),
|
||||
@@ -444,9 +473,12 @@ func (h *JimengHandler) ImageToVideo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取配置中的算力消耗
|
||||
powerCost := h.getPowerFromConfig(model.JMTaskTypeImageToVideo)
|
||||
|
||||
// 检查用户算力
|
||||
if user.Power < 120 { // 图生视频消耗120算力
|
||||
resp.ERROR(c, "算力不足")
|
||||
if user.Power < powerCost {
|
||||
resp.ERROR(c, fmt.Sprintf("算力不足,需要%d算力", powerCost))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -473,7 +505,7 @@ func (h *JimengHandler) ImageToVideo(c *gin.Context) {
|
||||
Prompt: req.Prompt,
|
||||
Params: params,
|
||||
ReqKey: jimeng.ReqKeyImageToVideo,
|
||||
Power: 120,
|
||||
Power: powerCost,
|
||||
}
|
||||
|
||||
job, err := h.jimengService.CreateTask(user.Id, taskReq)
|
||||
@@ -484,7 +516,7 @@ func (h *JimengHandler) ImageToVideo(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 扣除用户算力
|
||||
h.subUserPower(user.Id, 120, model.PowerLog{
|
||||
h.subUserPower(user.Id, powerCost, model.PowerLog{
|
||||
Type: types.PowerConsume,
|
||||
Model: "即梦图生视频",
|
||||
Remark: fmt.Sprintf("任务ID:%d", job.Id),
|
||||
@@ -635,3 +667,45 @@ func (h *JimengHandler) subUserPower(userId uint, power int, powerLog model.Powe
|
||||
|
||||
session.Commit()
|
||||
}
|
||||
|
||||
// getPowerFromConfig 从配置中获取指定类型的算力消耗
|
||||
func (h *JimengHandler) getPowerFromConfig(taskType model.JMTaskType) int {
|
||||
config, err := h.jimengService.GetConfig()
|
||||
if err != nil {
|
||||
logger.Errorf("获取即梦AI配置失败: %v", err)
|
||||
// 返回默认值
|
||||
switch taskType {
|
||||
case model.JMTaskTypeTextToImage:
|
||||
return 10
|
||||
case model.JMTaskTypeImageToImage:
|
||||
return 15
|
||||
case model.JMTaskTypeImageEdit:
|
||||
return 20
|
||||
case model.JMTaskTypeImageEffects:
|
||||
return 25
|
||||
case model.JMTaskTypeTextToVideo:
|
||||
return 30
|
||||
case model.JMTaskTypeImageToVideo:
|
||||
return 35
|
||||
default:
|
||||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
switch taskType {
|
||||
case model.JMTaskTypeTextToImage:
|
||||
return config.Power.TextToImage
|
||||
case model.JMTaskTypeImageToImage:
|
||||
return config.Power.ImageToImage
|
||||
case model.JMTaskTypeImageEdit:
|
||||
return config.Power.ImageEdit
|
||||
case model.JMTaskTypeImageEffects:
|
||||
return config.Power.ImageEffects
|
||||
case model.JMTaskTypeTextToVideo:
|
||||
return config.Power.TextToVideo
|
||||
case model.JMTaskTypeImageToVideo:
|
||||
return config.Power.ImageToVideo
|
||||
default:
|
||||
return 10
|
||||
}
|
||||
}
|
||||
|
||||
33
api/main.go
33
api/main.go
@@ -155,9 +155,7 @@ func main() {
|
||||
fx.Provide(admin.NewOrderHandler),
|
||||
fx.Provide(admin.NewChatHandler),
|
||||
fx.Provide(admin.NewPowerLogHandler),
|
||||
fx.Provide(func(app *core.AppServer, service *jimeng.Service) *admin.AdminJimengHandler {
|
||||
return admin.NewAdminJimengHandler(app, service)
|
||||
}),
|
||||
fx.Provide(admin.NewAdminJimengHandler),
|
||||
|
||||
// 创建服务
|
||||
fx.Provide(sms.NewSendServiceManager),
|
||||
@@ -211,9 +209,17 @@ func main() {
|
||||
|
||||
// 即梦AI 服务
|
||||
fx.Provide(func(config *types.AppConfig) *jimeng.Client {
|
||||
return jimeng.NewClient(config.ApiConfig.JimengConfig.AccessKey, config.ApiConfig.JimengConfig.SecretKey)
|
||||
// 使用默认配置初始化客户端,后续会从数据库加载
|
||||
return jimeng.NewClient("", "")
|
||||
}),
|
||||
fx.Provide(jimeng.NewService),
|
||||
fx.Invoke(func(service *jimeng.Service) {
|
||||
// 从数据库加载配置
|
||||
err := service.LoadConfigFromDB()
|
||||
if err != nil {
|
||||
logger.Errorf("加载即梦AI配置失败: %v", err)
|
||||
}
|
||||
}),
|
||||
fx.Provide(jimeng.NewConsumer),
|
||||
fx.Invoke(func(consumer *jimeng.Consumer) {
|
||||
consumer.Start()
|
||||
@@ -515,25 +521,10 @@ func main() {
|
||||
|
||||
// 即梦AI 路由
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.JimengHandler) {
|
||||
group := s.Engine.Group("/api/jimeng")
|
||||
group.POST("text-to-image", h.TextToImage)
|
||||
group.POST("image-to-image-portrait", h.ImageToImagePortrait)
|
||||
group.POST("image-edit", h.ImageEdit)
|
||||
group.POST("image-effects", h.ImageEffects)
|
||||
group.POST("text-to-video", h.TextToVideo)
|
||||
group.POST("image-to-video", h.ImageToVideo)
|
||||
group.GET("jobs", h.Jobs)
|
||||
group.GET("pending-count", h.PendingCount)
|
||||
group.GET("remove", h.Remove)
|
||||
group.GET("retry", h.Retry)
|
||||
h.RegisterRoutes()
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *admin.AdminJimengHandler) {
|
||||
group := s.Engine.Group("/api/admin/jimeng")
|
||||
group.GET("jobs", h.Jobs)
|
||||
group.GET("job/:id", h.JobDetail)
|
||||
group.DELETE("job/:id", h.Remove)
|
||||
group.POST("batch-remove", h.BatchRemove)
|
||||
group.GET("stats", h.Stats)
|
||||
h.RegisterRoutes()
|
||||
}),
|
||||
fx.Provide(admin.NewChatAppTypeHandler),
|
||||
fx.Invoke(func(s *core.AppServer, h *admin.ChatAppTypeHandler) {
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
package jimeng
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/volcengine/volc-sdk-golang/base"
|
||||
"github.com/volcengine/volc-sdk-golang/service/visual"
|
||||
"geekai/logger"
|
||||
)
|
||||
|
||||
@@ -21,69 +15,69 @@ var clientLogger = logger.GetLogger()
|
||||
|
||||
// Client 即梦API客户端
|
||||
type Client struct {
|
||||
accessKey string
|
||||
secretKey string
|
||||
region string
|
||||
service string
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
visual *visual.Visual
|
||||
}
|
||||
|
||||
// NewClient 创建即梦API客户端
|
||||
func NewClient(accessKey, secretKey string) *Client {
|
||||
return &Client{
|
||||
accessKey: accessKey,
|
||||
secretKey: secretKey,
|
||||
region: "cn-north-1",
|
||||
service: "cv",
|
||||
baseURL: "https://visual.volcengineapi.com",
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
// 使用官方SDK的visual实例
|
||||
visualInstance := visual.NewInstance()
|
||||
visualInstance.Client.SetAccessKey(accessKey)
|
||||
visualInstance.Client.SetSecretKey(secretKey)
|
||||
|
||||
// 添加即梦AI专有的API配置
|
||||
jimengApis := map[string]*base.ApiInfo{
|
||||
"CVSync2AsyncSubmitTask": {
|
||||
Method: http.MethodPost,
|
||||
Path: "/",
|
||||
Query: url.Values{
|
||||
"Action": []string{"CVSync2AsyncSubmitTask"},
|
||||
"Version": []string{"2022-08-31"},
|
||||
},
|
||||
},
|
||||
"CVSync2AsyncGetResult": {
|
||||
Method: http.MethodPost,
|
||||
Path: "/",
|
||||
Query: url.Values{
|
||||
"Action": []string{"CVSync2AsyncGetResult"},
|
||||
"Version": []string{"2022-08-31"},
|
||||
},
|
||||
},
|
||||
"CVProcess": {
|
||||
Method: http.MethodPost,
|
||||
Path: "/",
|
||||
Query: url.Values{
|
||||
"Action": []string{"CVProcess"},
|
||||
"Version": []string{"2022-08-31"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 将即梦API添加到现有的ApiInfoList中
|
||||
for name, info := range jimengApis {
|
||||
visualInstance.Client.ApiInfoList[name] = info
|
||||
}
|
||||
|
||||
return &Client{
|
||||
visual: visualInstance,
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitTask 提交任务
|
||||
// SubmitTask 提交异步任务
|
||||
func (c *Client) SubmitTask(req *SubmitTaskRequest) (*SubmitTaskResponse, error) {
|
||||
// 构建请求URL
|
||||
queryParams := map[string]string{
|
||||
"Action": "CVSync2AsyncSubmitTask",
|
||||
"Version": "2022-08-31",
|
||||
}
|
||||
|
||||
reqURL := c.buildURL(queryParams)
|
||||
|
||||
// 序列化请求体
|
||||
reqBody, err := json.Marshal(req)
|
||||
// 直接将请求转为map[string]interface{}
|
||||
reqBodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal request body failed: %w", err)
|
||||
return nil, fmt.Errorf("marshal request failed: %w", err)
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(reqBody))
|
||||
// 直接使用序列化后的字节
|
||||
jsonBody := reqBodyBytes
|
||||
|
||||
// 调用SDK的JSON方法
|
||||
respBody, statusCode, err := c.visual.Client.Json("CVSync2AsyncSubmitTask", nil, string(jsonBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create http request failed: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 签名请求
|
||||
if err := c.signRequest(httpReq, reqBody); err != nil {
|
||||
return nil, fmt.Errorf("sign request failed: %w", err)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send http request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response body failed: %w", err)
|
||||
return nil, fmt.Errorf("submit task failed (status: %d): %w", statusCode, err)
|
||||
}
|
||||
|
||||
clientLogger.Infof("Jimeng SubmitTask Response: %s", string(respBody))
|
||||
@@ -97,47 +91,18 @@ func (c *Client) SubmitTask(req *SubmitTaskRequest) (*SubmitTaskResponse, error)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// QueryTask 查询任务
|
||||
// QueryTask 查询任务结果
|
||||
func (c *Client) QueryTask(req *QueryTaskRequest) (*QueryTaskResponse, error) {
|
||||
// 构建请求URL
|
||||
queryParams := map[string]string{
|
||||
"Action": "CVSync2AsyncGetResult",
|
||||
"Version": "2022-08-31",
|
||||
}
|
||||
|
||||
reqURL := c.buildURL(queryParams)
|
||||
|
||||
// 序列化请求体
|
||||
reqBody, err := json.Marshal(req)
|
||||
// 序列化请求
|
||||
jsonBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal request body failed: %w", err)
|
||||
return nil, fmt.Errorf("marshal request failed: %w", err)
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(reqBody))
|
||||
// 调用SDK的JSON方法
|
||||
respBody, statusCode, err := c.visual.Client.Json("CVSync2AsyncGetResult", nil, string(jsonBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create http request failed: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 签名请求
|
||||
if err := c.signRequest(httpReq, reqBody); err != nil {
|
||||
return nil, fmt.Errorf("sign request failed: %w", err)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send http request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response body failed: %w", err)
|
||||
return nil, fmt.Errorf("query task failed (status: %d): %w", statusCode, err)
|
||||
}
|
||||
|
||||
clientLogger.Infof("Jimeng QueryTask Response: %s", string(respBody))
|
||||
@@ -153,180 +118,25 @@ func (c *Client) QueryTask(req *QueryTaskRequest) (*QueryTaskResponse, error) {
|
||||
|
||||
// SubmitSyncTask 提交同步任务(仅用于文生图)
|
||||
func (c *Client) SubmitSyncTask(req *SubmitTaskRequest) (*QueryTaskResponse, error) {
|
||||
// 构建请求URL
|
||||
queryParams := map[string]string{
|
||||
"Action": "CVProcess",
|
||||
"Version": "2022-08-31",
|
||||
}
|
||||
|
||||
reqURL := c.buildURL(queryParams)
|
||||
|
||||
// 序列化请求体
|
||||
reqBody, err := json.Marshal(req)
|
||||
// 序列化请求
|
||||
jsonBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal request body failed: %w", err)
|
||||
return nil, fmt.Errorf("marshal request failed: %w", err)
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(reqBody))
|
||||
// 调用SDK的JSON方法
|
||||
respBody, statusCode, err := c.visual.Client.Json("CVProcess", nil, string(jsonBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create http request failed: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 签名请求
|
||||
if err := c.signRequest(httpReq, reqBody); err != nil {
|
||||
return nil, fmt.Errorf("sign request failed: %w", err)
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
resp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send http request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response body failed: %w", err)
|
||||
return nil, fmt.Errorf("submit sync task failed (status: %d): %w", statusCode, err)
|
||||
}
|
||||
|
||||
clientLogger.Infof("Jimeng SubmitSyncTask Response: %s", string(respBody))
|
||||
|
||||
// 解析响应
|
||||
// 解析响应,同步任务直接返回结果
|
||||
var result QueryTaskResponse
|
||||
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal response failed: %w", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// buildURL 构建请求URL
|
||||
func (c *Client) buildURL(queryParams map[string]string) string {
|
||||
u, _ := url.Parse(c.baseURL)
|
||||
q := u.Query()
|
||||
for k, v := range queryParams {
|
||||
q.Set(k, v)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// signRequest 签名请求
|
||||
func (c *Client) signRequest(req *http.Request, body []byte) error {
|
||||
now := time.Now().UTC()
|
||||
|
||||
// 设置基本头部
|
||||
req.Header.Set("X-Date", now.Format("20060102T150405Z"))
|
||||
req.Header.Set("Host", req.URL.Host)
|
||||
|
||||
// 计算内容哈希
|
||||
contentHash := sha256.Sum256(body)
|
||||
req.Header.Set("X-Content-Sha256", hex.EncodeToString(contentHash[:]))
|
||||
|
||||
// 构建签名字符串
|
||||
canonicalRequest := c.buildCanonicalRequest(req)
|
||||
credentialScope := fmt.Sprintf("%s/%s/%s/request", now.Format("20060102"), c.region, c.service)
|
||||
stringToSign := fmt.Sprintf("HMAC-SHA256\n%s\n%s\n%s",
|
||||
now.Format("20060102T150405Z"), credentialScope, sha256Hash(canonicalRequest))
|
||||
|
||||
// 计算签名
|
||||
signature := c.calculateSignature(stringToSign, now)
|
||||
|
||||
// 设置Authorization头部
|
||||
authorization := fmt.Sprintf("HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s",
|
||||
c.accessKey, credentialScope, c.getSignedHeaders(req), signature)
|
||||
req.Header.Set("Authorization", authorization)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildCanonicalRequest 构建规范请求
|
||||
func (c *Client) buildCanonicalRequest(req *http.Request) string {
|
||||
// HTTP方法
|
||||
method := req.Method
|
||||
|
||||
// 规范URI
|
||||
uri := req.URL.Path
|
||||
if uri == "" {
|
||||
uri = "/"
|
||||
}
|
||||
|
||||
// 规范查询字符串
|
||||
query := req.URL.Query()
|
||||
var queryParts []string
|
||||
for k, v := range query {
|
||||
for _, val := range v {
|
||||
queryParts = append(queryParts, fmt.Sprintf("%s=%s", url.QueryEscape(k), url.QueryEscape(val)))
|
||||
}
|
||||
}
|
||||
sort.Strings(queryParts)
|
||||
canonicalQuery := strings.Join(queryParts, "&")
|
||||
|
||||
// 规范头部
|
||||
var headerParts []string
|
||||
headers := make(map[string]string)
|
||||
for k, v := range req.Header {
|
||||
key := strings.ToLower(k)
|
||||
if len(v) > 0 {
|
||||
headers[key] = strings.TrimSpace(v[0])
|
||||
}
|
||||
}
|
||||
|
||||
var headerKeys []string
|
||||
for k := range headers {
|
||||
headerKeys = append(headerKeys, k)
|
||||
}
|
||||
sort.Strings(headerKeys)
|
||||
|
||||
for _, k := range headerKeys {
|
||||
headerParts = append(headerParts, fmt.Sprintf("%s:%s", k, headers[k]))
|
||||
}
|
||||
canonicalHeaders := strings.Join(headerParts, "\n") + "\n"
|
||||
|
||||
// 签名头部
|
||||
signedHeaders := c.getSignedHeaders(req)
|
||||
|
||||
// 载荷哈希
|
||||
payloadHash := req.Header.Get("X-Content-Sha256")
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
|
||||
method, uri, canonicalQuery, canonicalHeaders, signedHeaders, payloadHash)
|
||||
}
|
||||
|
||||
// getSignedHeaders 获取签名头部
|
||||
func (c *Client) getSignedHeaders(req *http.Request) string {
|
||||
var headers []string
|
||||
for k := range req.Header {
|
||||
headers = append(headers, strings.ToLower(k))
|
||||
}
|
||||
sort.Strings(headers)
|
||||
return strings.Join(headers, ";")
|
||||
}
|
||||
|
||||
// calculateSignature 计算签名
|
||||
func (c *Client) calculateSignature(stringToSign string, t time.Time) string {
|
||||
kDate := hmacSha256([]byte("HMAC-SHA256"+c.secretKey), []byte(t.Format("20060102")))
|
||||
kRegion := hmacSha256(kDate, []byte(c.region))
|
||||
kService := hmacSha256(kRegion, []byte(c.service))
|
||||
kSigning := hmacSha256(kService, []byte("request"))
|
||||
signature := hmacSha256(kSigning, []byte(stringToSign))
|
||||
return hex.EncodeToString(signature)
|
||||
}
|
||||
|
||||
// hmacSha256 计算HMAC-SHA256
|
||||
func hmacSha256(key []byte, data []byte) []byte {
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// sha256Hash 计算SHA256哈希
|
||||
func sha256Hash(data string) string {
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ func (c *Consumer) PushTaskToQueue(task map[string]interface{}) error {
|
||||
}
|
||||
|
||||
// GetTaskStats 获取任务统计信息
|
||||
func (c *Consumer) GetTaskStats() (map[string]interface{}, error) {
|
||||
func (c *Consumer) GetTaskStats() (map[string]any, error) {
|
||||
type StatResult struct {
|
||||
Status string `json:"status"`
|
||||
Count int64 `json:"count"`
|
||||
@@ -160,7 +160,7 @@ func (c *Consumer) GetTaskStats() (map[string]interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
result := map[string]any{
|
||||
"total": int64(0),
|
||||
"completed": int64(0),
|
||||
"processing": int64(0),
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"geekai/store/model"
|
||||
"geekai/utils"
|
||||
|
||||
"geekai/core/types"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
@@ -636,3 +638,89 @@ func (s *Service) DeleteJob(jobId uint, userId uint) error {
|
||||
func (s *Service) PushTaskToQueue(task map[string]interface{}) error {
|
||||
return s.taskQueue.RPush(task)
|
||||
}
|
||||
|
||||
// TestConnection 测试即梦AI连接
|
||||
func (s *Service) TestConnection(accessKey, secretKey string) error {
|
||||
// 创建临时客户端进行测试
|
||||
testClient := NewClient(accessKey, secretKey)
|
||||
|
||||
// 使用一个简单的查询任务来测试连接
|
||||
// 这里使用一个不存在的任务ID来测试API连接是否正常
|
||||
testReq := &QueryTaskRequest{
|
||||
ReqKey: "test_connection",
|
||||
TaskId: "test_task_id_12345",
|
||||
}
|
||||
|
||||
_, err := testClient.QueryTask(testReq)
|
||||
// 即使任务不存在,只要不是认证错误就说明连接正常
|
||||
if err != nil {
|
||||
// 检查是否是认证错误
|
||||
if err.Error() == "unauthorized" || err.Error() == "access denied" {
|
||||
return fmt.Errorf("认证失败,请检查AccessKey和SecretKey是否正确")
|
||||
}
|
||||
// 其他错误(如任务不存在)说明连接正常
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateClientConfig 更新客户端配置
|
||||
func (s *Service) UpdateClientConfig(accessKey, secretKey string) error {
|
||||
// 创建新的客户端
|
||||
newClient := NewClient(accessKey, secretKey)
|
||||
|
||||
// 测试新客户端是否可用
|
||||
err := s.TestConnection(accessKey, secretKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("新配置测试失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新客户端
|
||||
s.client = newClient
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig 获取即梦AI配置
|
||||
func (s *Service) GetConfig() (*types.JimengConfig, error) {
|
||||
var config model.Config
|
||||
err := s.db.Where("name", "jimeng").First(&config).Error
|
||||
if err != nil {
|
||||
// 如果配置不存在,返回默认配置
|
||||
return &types.JimengConfig{
|
||||
AccessKey: "",
|
||||
SecretKey: "",
|
||||
Power: types.JimengPower{
|
||||
TextToImage: 10,
|
||||
ImageToImage: 15,
|
||||
ImageEdit: 20,
|
||||
ImageEffects: 25,
|
||||
TextToVideo: 30,
|
||||
ImageToVideo: 35,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var jimengConfig types.JimengConfig
|
||||
err = utils.JsonDecode(config.Value, &jimengConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析配置失败: %w", err)
|
||||
}
|
||||
|
||||
return &jimengConfig, nil
|
||||
}
|
||||
|
||||
// LoadConfigFromDB 从数据库加载配置并更新客户端
|
||||
func (s *Service) LoadConfigFromDB() error {
|
||||
config, err := s.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果配置中有AccessKey和SecretKey,则更新客户端
|
||||
if config.AccessKey != "" && config.SecretKey != "" {
|
||||
return s.UpdateClientConfig(config.AccessKey, config.SecretKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="system-config form" v-loading="loading">
|
||||
<div class="container">
|
||||
<el-form
|
||||
:model="system"
|
||||
:model="jimengConfig"
|
||||
label-width="150px"
|
||||
label-position="right"
|
||||
ref="systemFormRef"
|
||||
ref="configFormRef"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-tabs type="border-card">
|
||||
@@ -14,8 +14,19 @@
|
||||
<i class="iconfont icon-token mr-1"></i>
|
||||
<span>秘钥配置</span>
|
||||
</template>
|
||||
<el-form-item label="网站标题" prop="title">
|
||||
<el-input v-model="system['title']" />
|
||||
<el-form-item label="AccessKey" prop="access_key">
|
||||
<el-input
|
||||
v-model="jimengConfig.access_key"
|
||||
placeholder="请输入即梦AI的AccessKey"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="SecretKey" prop="secret_key">
|
||||
<el-input
|
||||
v-model="jimengConfig.secret_key"
|
||||
placeholder="请输入即梦AI的SecretKey"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -24,17 +35,13 @@
|
||||
<i class="iconfont icon-logout mr-1"></i>
|
||||
<span>算力配置</span>
|
||||
</template>
|
||||
<el-form-item label="注册赠送算力" prop="init_power">
|
||||
<el-input v-model.number="system['init_power']" placeholder="新用户注册赠送算力" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
提示词算力
|
||||
文生图算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="生成AI绘图提示词,歌词,视频描述消耗的算力"
|
||||
content="用户使用文生图功能时消耗的算力"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
@@ -44,14 +51,140 @@
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model.number="system['prompt_power']" placeholder="" />
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.text_to_image"
|
||||
:min="1"
|
||||
:max="100"
|
||||
placeholder="请输入文生图算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
图生图算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="用户使用图生图功能时消耗的算力"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.image_to_image"
|
||||
:min="1"
|
||||
:max="100"
|
||||
placeholder="请输入图生图算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
图片编辑算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="用户使用图片编辑功能时消耗的算力"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.image_edit"
|
||||
:min="1"
|
||||
:max="100"
|
||||
placeholder="请输入图片编辑算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
图片特效算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="用户使用图片特效功能时消耗的算力"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.image_effects"
|
||||
:min="1"
|
||||
:max="100"
|
||||
placeholder="请输入图片特效算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
文生视频算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="用户使用文生视频功能时消耗的算力"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.text_to_video"
|
||||
:min="1"
|
||||
:max="100"
|
||||
placeholder="请输入文生视频算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="label-title">
|
||||
图生视频算力
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="用户使用图生视频功能时消耗的算力"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.image_to_video"
|
||||
:min="1"
|
||||
:max="100"
|
||||
placeholder="请输入图生视频算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div style="padding: 10px">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save('system')">保存</el-button>
|
||||
<el-button type="primary" @click="saveConfig" :loading="saving">保存配置</el-button>
|
||||
<el-button @click="resetConfig">重置</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -63,52 +196,107 @@
|
||||
import { httpGet, httpPost } from '@/utils/http'
|
||||
import { InfoFilled } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'md-editor-v3/lib/style.css'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const system = ref({ models: [] })
|
||||
const loading = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
// 加载系统配置
|
||||
httpGet('/api/admin/config/get?key=system')
|
||||
.then((res) => {
|
||||
system.value = res.data
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error('加载系统配置失败: ' + e.message)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
const jimengConfig = ref({
|
||||
access_key: '',
|
||||
secret_key: '',
|
||||
power: {
|
||||
text_to_image: 10,
|
||||
image_to_image: 15,
|
||||
image_edit: 20,
|
||||
image_effects: 25,
|
||||
text_to_video: 30,
|
||||
image_to_video: 35,
|
||||
},
|
||||
})
|
||||
|
||||
const save = function (key) {
|
||||
httpPost('/api/admin/config/update', {
|
||||
key: key,
|
||||
config: { content: notice.value, updated: true },
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success('操作成功!')
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error('操作失败:' + e.message)
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const testing = ref(false)
|
||||
const configFormRef = ref()
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
access_key: [{ required: true, message: '请输入AccessKey', trigger: 'blur' }],
|
||||
secret_key: [{ required: true, message: '请输入SecretKey', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
|
||||
// 加载配置
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const res = await httpGet('/api/admin/jimeng/config')
|
||||
jimengConfig.value = res.data
|
||||
} catch (e) {
|
||||
ElMessage.error('加载配置失败: ' + e.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
try {
|
||||
await configFormRef.value.validate()
|
||||
saving.value = true
|
||||
|
||||
await httpPost('/api/admin/jimeng/config', {
|
||||
config: jimengConfig.value,
|
||||
})
|
||||
|
||||
ElMessage.success('配置保存成功!')
|
||||
} catch (e) {
|
||||
if (e.message) {
|
||||
ElMessage.error('保存失败:' + e.message)
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置配置
|
||||
const resetConfig = () => {
|
||||
jimengConfig.value = {
|
||||
access_key: '',
|
||||
secret_key: '',
|
||||
power: {
|
||||
text_to_image: 10,
|
||||
image_to_image: 15,
|
||||
image_edit: 20,
|
||||
image_effects: 25,
|
||||
text_to_video: 30,
|
||||
image_to_video: 35,
|
||||
},
|
||||
}
|
||||
ElMessage.info('配置已重置')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '../../../assets/css/admin/form.styl'
|
||||
@import '../../../assets/css/main.styl'
|
||||
|
||||
.system-config {
|
||||
display flex
|
||||
justify-content center
|
||||
|
||||
.sys-tabs {
|
||||
.container {
|
||||
width 100%
|
||||
max-width 800px
|
||||
}
|
||||
|
||||
.label-title {
|
||||
display flex
|
||||
align-items center
|
||||
gap 5px
|
||||
}
|
||||
|
||||
.el-input-number {
|
||||
width 100%
|
||||
background-color var(--el-bg-color)
|
||||
padding 10px 20px 40px 20px
|
||||
//border: 1px solid var(--el-border-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user