restore use power when removed not finish jobs

This commit is contained in:
RockYang 2024-07-31 16:08:46 +08:00
parent 96f1126d02
commit 1d9d487f0e
25 changed files with 425 additions and 487 deletions

View File

@ -1,4 +1,8 @@
# 更新日志 # 更新日志
## v4.1.2
* Bug修复修复思维导图页面获取模型失败的问题
## v4.1.1 ## v4.1.1
* Bug修复修复 GPT 模型 function call 调用后没有输出的问题 * Bug修复修复 GPT 模型 function call 调用后没有输出的问题
* 功能新增:允许获取 License 授权用户可以自定义版权信息 * 功能新增:允许获取 License 授权用户可以自定义版权信息

View File

@ -225,7 +225,7 @@ func needLogin(c *gin.Context) bool {
c.Request.URL.Path == "/api/payment/doPay" || c.Request.URL.Path == "/api/payment/doPay" ||
c.Request.URL.Path == "/api/payment/payWays" || c.Request.URL.Path == "/api/payment/payWays" ||
c.Request.URL.Path == "/api/suno/client" || c.Request.URL.Path == "/api/suno/client" ||
c.Request.URL.Path == "/api/suno/Detail" || c.Request.URL.Path == "/api/suno/detail" ||
c.Request.URL.Path == "/api/suno/play" || c.Request.URL.Path == "/api/suno/play" ||
strings.HasPrefix(c.Request.URL.Path, "/api/test") || strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") || strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||

View File

@ -61,7 +61,6 @@ type ChatSession struct {
type ChatModel struct { type ChatModel struct {
Id uint `json:"id"` Id uint `json:"id"`
Platform string `json:"platform"`
Name string `json:"name"` Name string `json:"name"`
Value string `json:"value"` Value string `json:"value"`
Power int `json:"power"` Power int `json:"power"`

View File

@ -468,7 +468,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi
} else { } else {
client = http.DefaultClient client = http.DefaultClient
} }
logger.Debugf("Sending %s request, Channel:%s, API KEY:%s, PROXY: %s, Model: %s", session.Model.Platform, apiKey.ApiURL, apiURL, apiKey.ProxyURL, req.Model) logger.Debugf("Sending %s request, API KEY:%s, PROXY: %s, Model: %s", apiKey.ApiURL, apiURL, apiKey.ProxyURL, req.Model)
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value)) request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
// 更新API KEY 最后使用时间 // 更新API KEY 最后使用时间
h.DB.Model(&model.ApiKey{}).Where("id", apiKey.Id).UpdateColumn("last_used_at", time.Now().Unix()) h.DB.Model(&model.ApiKey{}).Where("id", apiKey.Id).UpdateColumn("last_used_at", time.Now().Unix())

View File

@ -8,6 +8,7 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import ( import (
"fmt"
"geekai/core" "geekai/core"
"geekai/core/types" "geekai/core/types"
"geekai/service/dalle" "geekai/service/dalle"
@ -16,9 +17,9 @@ import (
"geekai/store/vo" "geekai/store/vo"
"geekai/utils" "geekai/utils"
"geekai/utils/resp" "geekai/utils/resp"
"net/http"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"net/http"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
@ -178,7 +179,7 @@ func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize in
session := h.DB.Session(&gorm.Session{}) session := h.DB.Session(&gorm.Session{})
if finish { if finish {
session = session.Where("progress = ?", 100).Order("id DESC") session = session.Where("progress >= ?", 100).Order("id DESC")
} else { } else {
session = session.Where("progress < ?", 100).Order("id ASC") session = session.Where("progress < ?", 100).Order("id ASC")
} }
@ -215,20 +216,51 @@ func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize in
// Remove remove task image // Remove remove task image
func (h *DallJobHandler) Remove(c *gin.Context) { func (h *DallJobHandler) Remove(c *gin.Context) {
id := h.GetInt(c, "id", 0) id := h.GetInt(c, "id", 0)
userId := h.GetInt(c, "user_id", 0) userId := h.GetLoginUserId(c)
var job model.DallJob var job model.DallJob
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil { if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
resp.ERROR(c, "记录不存在") resp.ERROR(c, "记录不存在")
return return
} }
// remove job recode // 删除任务
res := h.DB.Delete(&model.DallJob{Id: job.Id}) tx := h.DB.Begin()
if res.Error != nil { if err := tx.Delete(&job).Error; err != nil {
resp.ERROR(c, res.Error.Error()) tx.Rollback()
resp.ERROR(c, err.Error())
return return
} }
// 如果任务未完成,或者任务失败,则恢复用户算力
if job.Progress != 100 {
err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
var user model.User
h.DB.Where("id = ?", job.UserId).First(&user)
err = tx.Create(&model.PowerLog{
UserId: user.Id,
Username: user.Username,
Type: types.PowerConsume,
Amount: job.Power,
Balance: user.Power,
Mark: types.PowerAdd,
Model: "dall-e-3",
Remark: fmt.Sprintf("任务失败退回算力。任务ID%dErr: %s", job.Id, job.ErrMsg),
CreatedAt: time.Now(),
}).Error
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
}
tx.Commit()
// remove image // remove image
err := h.uploader.GetUploadHandler().Delete(job.ImgURL) err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
if err != nil { if err != nil {
@ -241,10 +273,10 @@ func (h *DallJobHandler) Remove(c *gin.Context) {
// Publish 发布/取消发布图片到画廊显示 // Publish 发布/取消发布图片到画廊显示
func (h *DallJobHandler) Publish(c *gin.Context) { func (h *DallJobHandler) Publish(c *gin.Context) {
id := h.GetInt(c, "id", 0) id := h.GetInt(c, "id", 0)
userId := h.GetInt(c, "user_id", 0) userId := h.GetLoginUserId(c)
action := h.GetBool(c, "action") // 发布动作true => 发布false => 取消分享 action := h.GetBool(c, "action") // 发布动作true => 发布false => 取消分享
res := h.DB.Model(&model.DallJob{Id: uint(id), UserId: uint(userId)}).UpdateColumn("publish", action) res := h.DB.Model(&model.DallJob{Id: uint(id), UserId: userId}).UpdateColumn("publish", action)
if res.Error != nil { if res.Error != nil {
logger.Error("error with update database", res.Error) logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败") resp.ERROR(c, "更新数据库失败")

View File

@ -101,17 +101,13 @@ func (h *MarkMapHandler) sendMessage(client *types.WsClient, prompt string, mode
return fmt.Errorf("error with query chat model: %v", res.Error) return fmt.Errorf("error with query chat model: %v", res.Error)
} }
if user.Status == false {
return errors.New("当前用户被禁用")
}
if user.Power < chatModel.Power { if user.Power < chatModel.Power {
return fmt.Errorf("您当前剩余算力(%d已不足以支付当前模型算力%d", user.Power, chatModel.Power) return fmt.Errorf("您当前剩余算力(%d已不足以支付当前模型算力%d", user.Power, chatModel.Power)
} }
messages := make([]interface{}, 0) messages := make([]interface{}, 0)
messages = append(messages, types.Message{Role: "system", Content: ` messages = append(messages, types.Message{Role: "system", Content: `
你是一位非常优秀的思维导图助手你会把用户的所有提问都总结成思维导图然后以 Markdown 格式输出markdown 只需要输出一级标题二级标题三级标题四级标题最多输出四级除此之外不要输出任何其他 markdown 标记下面是一个合格的例子 你是一位非常优秀的思维导图助手 你能帮助用户整理思路根据用户提供的主题或内容快速生成结构清晰有条理的思维导图然后以 Markdown 格式输出markdown 只需要输出一级标题二级标题三级标题四级标题最多输出四级除此之外不要输出任何其他 markdown 标记下面是一个合格的例子
# Geek-AI 助手 # Geek-AI 助手
## 完整的开源系统 ## 完整的开源系统
@ -130,7 +126,7 @@ func (h *MarkMapHandler) sendMessage(client *types.WsClient, prompt string, mode
另外除此之外不要任何解释性语句 另外除此之外不要任何解释性语句
`}) `})
messages = append(messages, types.Message{Role: "user", Content: prompt}) messages = append(messages, types.Message{Role: "user", Content: fmt.Sprintf("请生成一份有关【%s】一份思维导图要求结构清晰有条理", prompt)})
var req = types.ApiRequest{ var req = types.ApiRequest{
Model: chatModel.Value, Model: chatModel.Value,
Stream: true, Stream: true,
@ -253,5 +249,6 @@ func (h *MarkMapHandler) doRequest(req types.ApiRequest, chatModel model.ChatMod
client = http.DefaultClient client = http.DefaultClient
} }
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value)) request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
logger.Debugf("Sending %s request, API KEY:%s, PROXY: %s, Model: %s", apiKey.ApiURL, apiURL, apiKey.ProxyURL, req.Model)
return client.Do(request) return client.Do(request)
} }

View File

@ -465,35 +465,37 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
return return
} }
// refund power // 如果任务未完成,或者任务失败,则恢复用户算力
err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error if job.Progress != 100 {
if err != nil { err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error
tx.Rollback() if err != nil {
resp.ERROR(c, err.Error()) tx.Rollback()
return resp.ERROR(c, err.Error())
} return
var user model.User }
h.DB.Where("id = ?", job.UserId).First(&user) var user model.User
err = tx.Create(&model.PowerLog{ h.DB.Where("id = ?", job.UserId).First(&user)
UserId: user.Id, err = tx.Create(&model.PowerLog{
Username: user.Username, UserId: user.Id,
Type: types.PowerConsume, Username: user.Username,
Amount: job.Power, Type: types.PowerConsume,
Balance: user.Power + job.Power, Amount: job.Power,
Mark: types.PowerAdd, Balance: user.Power,
Model: "mid-journey", Mark: types.PowerAdd,
Remark: fmt.Sprintf("绘画任务失败退回算力。任务ID%s", job.TaskId), Model: "mid-journey",
CreatedAt: time.Now(), Remark: fmt.Sprintf("绘画任务失败退回算力。任务ID%sErr: %s", job.TaskId, job.ErrMsg),
}).Error CreatedAt: time.Now(),
if err != nil { }).Error
tx.Rollback() if err != nil {
resp.ERROR(c, err.Error()) tx.Rollback()
return resp.ERROR(c, err.Error())
return
}
} }
tx.Commit() tx.Commit()
// remove image // remove image
err = h.uploader.GetUploadHandler().Delete(job.ImgURL) err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
if err != nil { if err != nil {
logger.Error("remove image failed: ", err) logger.Error("remove image failed: ", err)
} }

View File

@ -232,7 +232,7 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int,
session := h.DB.Session(&gorm.Session{}) session := h.DB.Session(&gorm.Session{})
if finish { if finish {
session = session.Where("progress = ?", 100).Order("id DESC") session = session.Where("progress >= ?", 100).Order("id DESC")
} else { } else {
session = session.Where("progress < ?", 100).Order("id ASC") session = session.Where("progress < ?", 100).Order("id ASC")
} }
@ -278,20 +278,50 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int,
// Remove remove task image // Remove remove task image
func (h *SdJobHandler) Remove(c *gin.Context) { func (h *SdJobHandler) Remove(c *gin.Context) {
id := h.GetInt(c, "id", 0) id := h.GetInt(c, "id", 0)
userId := h.GetInt(c, "user_id", 0) userId := h.GetLoginUserId(c)
var job model.SdJob var job model.SdJob
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil { if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
resp.ERROR(c, "记录不存在") resp.ERROR(c, "记录不存在")
return return
} }
// remove job recode // 删除任务
res := h.DB.Delete(&model.SdJob{Id: job.Id}) tx := h.DB.Begin()
if res.Error != nil { if err := tx.Delete(&job).Error; err != nil {
resp.ERROR(c, res.Error.Error()) tx.Rollback()
resp.ERROR(c, err.Error())
return return
} }
// 如果任务未完成,或者任务失败,则恢复用户算力
if job.Progress != 100 {
err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
var user model.User
h.DB.Where("id = ?", job.UserId).First(&user)
err = tx.Create(&model.PowerLog{
UserId: user.Id,
Username: user.Username,
Type: types.PowerConsume,
Amount: job.Power,
Balance: user.Power,
Mark: types.PowerAdd,
Model: "stable-diffusion",
Remark: fmt.Sprintf("任务失败退回算力。任务ID%s Err: %s", job.TaskId, job.ErrMsg),
CreatedAt: time.Now(),
}).Error
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
}
tx.Commit()
// remove image // remove image
err := h.uploader.GetUploadHandler().Delete(job.ImgURL) err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
if err != nil { if err != nil {
@ -309,10 +339,10 @@ func (h *SdJobHandler) Remove(c *gin.Context) {
// Publish 发布/取消发布图片到画廊显示 // Publish 发布/取消发布图片到画廊显示
func (h *SdJobHandler) Publish(c *gin.Context) { func (h *SdJobHandler) Publish(c *gin.Context) {
id := h.GetInt(c, "id", 0) id := h.GetInt(c, "id", 0)
userId := h.GetInt(c, "user_id", 0) userId := h.GetLoginUserId(c)
action := h.GetBool(c, "action") // 发布动作true => 发布false => 取消分享 action := h.GetBool(c, "action") // 发布动作true => 发布false => 取消分享
res := h.DB.Model(&model.SdJob{Id: uint(id), UserId: userId}).UpdateColumn("publish", action) res := h.DB.Model(&model.SdJob{Id: uint(id), UserId: int(userId)}).UpdateColumn("publish", action)
if res.Error != nil { if res.Error != nil {
logger.Error("error with update database", res.Error) logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败") resp.ERROR(c, "更新数据库失败")

View File

@ -210,7 +210,42 @@ func (h *SunoHandler) Remove(c *gin.Context) {
return return
} }
// 删除任务 // 删除任务
h.DB.Delete(&job) tx := h.DB.Begin()
if err := tx.Delete(&job).Error; err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
// 如果任务未完成,或者任务失败,则恢复用户算力
if job.Progress != 100 {
err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
var user model.User
h.DB.Where("id = ?", job.UserId).First(&user)
err = tx.Create(&model.PowerLog{
UserId: user.Id,
Username: user.Username,
Type: types.PowerConsume,
Amount: job.Power,
Balance: user.Power,
Mark: types.PowerAdd,
Model: job.ModelName,
Remark: fmt.Sprintf("Suno 任务失败退回算力。任务ID%sErr:%s", job.TaskId, job.ErrMsg),
CreatedAt: time.Now(),
}).Error
if err != nil {
tx.Rollback()
resp.ERROR(c, err.Error())
return
}
}
tx.Commit()
// 删除文件 // 删除文件
_ = h.uploader.GetUploadHandler().Delete(job.CoverURL) _ = h.uploader.GetUploadHandler().Delete(job.CoverURL)
_ = h.uploader.GetUploadHandler().Delete(job.AudioURL) _ = h.uploader.GetUploadHandler().Delete(job.AudioURL)

View File

@ -70,7 +70,7 @@ func (s *Service) Run() {
if err != nil { if err != nil {
logger.Errorf("error with image task: %v", err) logger.Errorf("error with image task: %v", err)
s.db.Model(&model.DallJob{Id: task.JobId}).UpdateColumns(map[string]interface{}{ s.db.Model(&model.DallJob{Id: task.JobId}).UpdateColumns(map[string]interface{}{
"progress": -1, "progress": 101,
"err_msg": err.Error(), "err_msg": err.Error(),
}) })
s.notifyQueue.RPush(sd.NotifyMessage{UserId: int(task.UserId), JobId: int(task.JobId), Message: sd.Failed}) s.notifyQueue.RPush(sd.NotifyMessage{UserId: int(task.UserId), JobId: int(task.JobId), Message: sd.Failed})
@ -148,7 +148,7 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
Where("enabled", true). Where("enabled", true).
Order("last_used_at ASC").First(&apiKey) Order("last_used_at ASC").First(&apiKey)
if tx.Error != nil { if tx.Error != nil {
return "", fmt.Errorf("no available IMG api key: %v", tx.Error) return "", fmt.Errorf("no available DALL-E api key: %v", tx.Error)
} }
var res imgRes var res imgRes
@ -225,6 +225,30 @@ func (s *Service) CheckTaskNotify() {
}() }()
} }
func (s *Service) CheckTaskStatus() {
go func() {
logger.Info("Running DALL-E task status checking ...")
for {
var jobs []model.DallJob
res := s.db.Where("progress < ?", 100).Find(&jobs)
if res.Error != nil {
time.Sleep(5 * time.Second)
continue
}
for _, job := range jobs {
// 超时的任务标记为失败
if time.Now().Sub(job.CreatedAt) > time.Minute*10 {
job.Progress = 101
job.ErrMsg = "任务超时"
s.db.Updates(&job)
}
}
time.Sleep(time.Second * 10)
}
}()
}
func (s *Service) DownloadImages() { func (s *Service) DownloadImages() {
go func() { go func() {
var items []model.DallJob var items []model.DallJob
@ -271,44 +295,3 @@ func (s *Service) downloadImage(jobId uint, userId int, orgURL string) (string,
s.notifyQueue.RPush(sd.NotifyMessage{UserId: userId, JobId: int(jobId), Message: sd.Finished}) s.notifyQueue.RPush(sd.NotifyMessage{UserId: userId, JobId: int(jobId), Message: sd.Finished})
return imgURL, nil return imgURL, nil
} }
// CheckTaskStatus 检查任务状态,自动删除过期或者失败的任务
func (s *Service) CheckTaskStatus() {
go func() {
logger.Info("Running Stable-Diffusion task status checking ...")
for {
var jobs []model.DallJob
res := s.db.Where("progress < ?", 100).Find(&jobs)
if res.Error != nil {
time.Sleep(5 * time.Second)
continue
}
for _, job := range jobs {
// 5 分钟还没完成的任务直接删除
if time.Now().Sub(job.CreatedAt) > time.Minute*5 || job.Progress == -1 {
s.db.Delete(&job)
var user model.User
s.db.Where("id = ?", job.UserId).First(&user)
// 退回绘图次数
res = s.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power))
if res.Error == nil && res.RowsAffected > 0 {
s.db.Create(&model.PowerLog{
UserId: user.Id,
Username: user.Username,
Type: types.PowerConsume,
Amount: job.Power,
Balance: user.Power + job.Power,
Mark: types.PowerAdd,
Model: "dall-e-3",
Remark: fmt.Sprintf("任务失败退回算力。任务ID%d", job.Id),
CreatedAt: time.Now(),
})
}
continue
}
}
time.Sleep(time.Second * 10)
}
}()
}

View File

@ -187,6 +187,14 @@ func (p *ServicePool) SyncTaskProgress() {
} }
for _, job := range jobs { for _, job := range jobs {
// 5 分钟还没完成的任务标记为失败
if time.Now().Sub(job.CreatedAt) > time.Minute*5 {
job.Progress = 101
job.ErrMsg = "任务超时"
p.db.Updates(&job)
continue
}
if servicePlus := p.getService(job.ChannelId); servicePlus != nil { if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
_ = servicePlus.Notify(job) _ = servicePlus.Notify(job)
} }

View File

@ -109,27 +109,11 @@ func (p *ServicePool) CheckTaskStatus() {
} }
for _, job := range jobs { for _, job := range jobs {
// 5 分钟还没完成的任务直接删除 // 5 分钟还没完成的任务标记为失败
if time.Now().Sub(job.CreatedAt) > time.Minute*5 || job.Progress == -1 { if time.Now().Sub(job.CreatedAt) > time.Minute*5 {
p.db.Delete(&job) job.Progress = 101
var user model.User job.ErrMsg = "任务超时"
p.db.Where("id = ?", job.UserId).First(&user) p.db.Updates(&job)
// 退回绘图次数
res = p.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power))
if res.Error == nil && res.RowsAffected > 0 {
p.db.Create(&model.PowerLog{
UserId: user.Id,
Username: user.Username,
Type: types.PowerConsume,
Amount: job.Power,
Balance: user.Power + job.Power,
Mark: types.PowerAdd,
Model: "stable-diffusion",
Remark: fmt.Sprintf("任务失败退回算力。任务ID%s", job.TaskId),
CreatedAt: time.Now(),
})
}
continue
} }
} }
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)

View File

@ -87,7 +87,7 @@ func (s *Service) Run() {
logger.Error("绘画任务执行失败:", err.Error()) logger.Error("绘画任务执行失败:", err.Error())
// update the task progress // update the task progress
s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(map[string]interface{}{ s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(map[string]interface{}{
"progress": -1, "progress": 101,
"err_msg": err.Error(), "err_msg": err.Error(),
}) })
// 通知前端,任务失败 // 通知前端,任务失败

View File

@ -4,8 +4,8 @@ VUE_APP_USER=18575670125
VUE_APP_PASS=12345678 VUE_APP_PASS=12345678
VUE_APP_ADMIN_USER=admin VUE_APP_ADMIN_USER=admin
VUE_APP_ADMIN_PASS=admin123 VUE_APP_ADMIN_PASS=admin123
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_ VUE_APP_KEY_PREFIX=GeekAI_DEV_
VUE_APP_TITLE="Geek-AI 创作系统" VUE_APP_TITLE="Geek-AI 创作系统"
VUE_APP_VERSION=v4.1.1 VUE_APP_VERSION=v4.1.2
VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_DOCS_URL=https://docs.geekai.me
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai

View File

@ -1,6 +1,6 @@
VUE_APP_API_HOST= VUE_APP_API_HOST=
VUE_APP_WS_HOST= VUE_APP_WS_HOST=
VUE_APP_KEY_PREFIX=ChatPLUS_ VUE_APP_KEY_PREFIX=GeekAI_
VUE_APP_VERSION=v4.1.1 VUE_APP_VERSION=v4.1.2
VUE_APP_DOCS_URL=https://docs.geekai.me VUE_APP_DOCS_URL=https://docs.geekai.me
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai

View File

@ -221,134 +221,7 @@
// //
@import "waterfall-list.styl"
.job-list-box {
@import "running-job-list.styl"
.finish-job-list {
#waterfall {
display flex
justify-content center
padding-top 20px
flex-flow column
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
}
.el-image {
width 100%
height 100%
overflow visible
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 200px
color #ffffff
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
img {
height 310px
}
.image-slot {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
} }
.no-more-data { .no-more-data {

View File

@ -220,136 +220,8 @@
} }
} }
// //
@import "waterfall-list.styl"
.job-list-box {
@import "running-job-list.styl"
.finish-job-list {
#waterfall {
display flex
justify-content center
padding-top 20px
flex-flow column
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
}
.el-image {
width 100%
height 100%
overflow visible
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 200px
color #ffffff
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
img {
height 310px
}
.image-slot {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
} }
.no-more-data { .no-more-data {

View File

@ -0,0 +1,146 @@
.job-list-box {
@import "running-job-list.styl"
.finish-job-list {
#waterfall {
display flex
justify-content center
padding-top 20px
flex-flow column
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
}
.el-image {
width 100%
height 100%
overflow visible
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 220px
color #ffffff
.err-msg-container {
overflow hidden
word-break break-all
padding 15px
.title {
font-size 20px
text-align center
font-weight bold
color #f56c6c
margin-bottom 30px
}
.opt {
display flex
justify-content center
}
}
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
img {
height 310px
}
.image-slot {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}

View File

@ -367,7 +367,6 @@ const initData = () => {
inputRef.value.addEventListener('paste', (event) => { inputRef.value.addEventListener('paste', (event) => {
const items = (event.clipboardData || window.clipboardData).items; const items = (event.clipboardData || window.clipboardData).items;
let fileFound = false; let fileFound = false;
loading.value = true
for (let item of items) { for (let item of items) {
if (item.kind === 'file') { if (item.kind === 'file') {
@ -376,6 +375,7 @@ const initData = () => {
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
loading.value = true
// //
httpPost('/api/upload', formData).then((res) => { httpPost('/api/upload', formData).then((res) => {
files.value.push(res.data) files.value.push(res.data)

View File

@ -125,6 +125,24 @@
</template> </template>
</el-image> </el-image>
<el-image v-else-if="slotProp.item.progress === 101">
<template #error>
<div class="image-slot">
<div class="err-msg-container">
<div class="title">任务失败</div>
<div class="opt">
<el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
<template #reference>
<el-button type="info">详情</el-button>
</template>
</el-popover>
<el-button type="danger" @click="removeImage(slotProp.item)">删除</el-button>
</div>
</div>
</div>
</template>
</el-image>
<el-image v-else> <el-image v-else>
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
@ -136,17 +154,17 @@
<div class="remove"> <div class="remove">
<el-tooltip content="删除" placement="top" effect="light"> <el-tooltip content="删除" placement="top" effect="light">
<el-button type="danger" :icon="Delete" @click="removeImage($event,slotProp.item)" circle/> <el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
</el-tooltip> </el-tooltip>
<el-tooltip content="分享" placement="top" effect="light" v-if="slotProp.item.publish"> <el-tooltip content="分享" placement="top" effect="light" v-if="slotProp.item.publish">
<el-button type="warning" <el-button type="warning"
@click="publishImage($event,slotProp.item, false)" @click="publishImage(slotProp.item, false)"
circle> circle>
<i class="iconfont icon-cancel-share"></i> <i class="iconfont icon-cancel-share"></i>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="取消分享" placement="top" effect="light" v-else> <el-tooltip content="取消分享" placement="top" effect="light" v-else>
<el-button type="success" @click="publishImage($event,slotProp.item, true)" circle> <el-button type="success" @click="publishImage(slotProp.item, true)" circle>
<i class="iconfont icon-share-bold"></i> <i class="iconfont icon-share-bold"></i>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
@ -185,7 +203,7 @@
</template> </template>
<script setup> <script setup>
import {onMounted, onUnmounted, ref} from "vue" import {nextTick, onMounted, onUnmounted, ref} from "vue"
import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue"; import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage, ElMessageBox, ElNotification} from "element-plus"; import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
@ -286,25 +304,9 @@ const connect = () => {
} }
} }
//
const sendHeartbeat = () => {
clearTimeout(heartbeatHandle.value)
new Promise((resolve, reject) => {
if (socket.value !== null) {
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
}
resolve("success")
}).then(() => {
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
});
}
const _socket = new WebSocket(host + `/api/dall/client?user_id=${userId.value}`); const _socket = new WebSocket(host + `/api/dall/client?user_id=${userId.value}`);
_socket.addEventListener('open', () => { _socket.addEventListener('open', () => {
socket.value = _socket; socket.value = _socket;
//
sendHeartbeat()
}); });
_socket.addEventListener('message', event => { _socket.addEventListener('message', event => {
@ -313,12 +315,12 @@ const connect = () => {
reader.readAsText(event.data, "UTF-8") reader.readAsText(event.data, "UTF-8")
reader.onload = () => { reader.onload = () => {
const message = String(reader.result) const message = String(reader.result)
if (message === "FINISH") { if (message === "FINISH" || message === "FAIL") {
page.value = 0 page.value = 0
isOver.value = false isOver.value = false
fetchFinishJobs(page.value) fetchFinishJobs(page.value)
} }
fetchRunningJobs() nextTick(() => fetchRunningJobs())
} }
} }
}); });
@ -336,22 +338,7 @@ const fetchRunningJobs = () => {
} }
// //
httpGet(`/api/dall/jobs?finish=false`).then(res => { httpGet(`/api/dall/jobs?finish=false`).then(res => {
const jobs = res.data runningJobs.value = res.data
const _jobs = []
for (let i = 0; i < jobs.length; i++) {
if (jobs[i].progress === -1) {
ElNotification({
title: '任务执行失败',
dangerouslyUseHTMLString: true,
message: `任务ID${jobs[i]['task_id']}<br />原因:${jobs[i]['err_msg']}`,
type: 'error',
})
power.value += dallPower.value
continue
}
_jobs.push(jobs[i])
}
runningJobs.value = _jobs
}).catch(e => { }).catch(e => {
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)
}) })
@ -409,8 +396,7 @@ const generate = () => {
}) })
} }
const removeImage = (event, item) => { const removeImage = (item) => {
event.stopPropagation()
ElMessageBox.confirm( ElMessageBox.confirm(
'此操作将会删除任务和图片,继续操作码?', '此操作将会删除任务和图片,继续操作码?',
'删除提示', '删除提示',
@ -420,7 +406,7 @@ const removeImage = (event, item) => {
type: 'warning', type: 'warning',
} }
).then(() => { ).then(() => {
httpGet("/api/dall/remove", {id: item.id, user_id: item.user}).then(() => { httpGet("/api/dall/remove", {id: item.id}).then(() => {
ElMessage.success("任务删除成功") ElMessage.success("任务删除成功")
page.value = 0 page.value = 0
isOver.value = false isOver.value = false
@ -437,18 +423,16 @@ const previewImg = (item) => {
} }
// //
const publishImage = (event, item, action) => { const publishImage = (item, action) => {
event.stopPropagation()
let text = "图片发布" let text = "图片发布"
if (action === false) { if (action === false) {
text = "取消发布" text = "取消发布"
} }
httpGet("/api/dall/publish", {id: item.id, action: action,user_id:item.user_id}).then(() => { httpGet("/api/dall/publish", {id: item.id, action: action}).then(() => {
ElMessage.success(text + "成功") ElMessage.success(text + "成功")
item.publish = action item.publish = action
page.value = 0 page.value = 0
isOver.value = false isOver.value = false
fetchFinishJobs()
}).catch(e => { }).catch(e => {
ElMessage.error(text + "失败:" + e.message) ElMessage.error(text + "失败:" + e.message)
}) })

View File

@ -17,7 +17,7 @@
effect="light" effect="light"
content="部署文档" content="部署文档"
placement="bottom"> placement="bottom">
<a href="https://docs.geekai.me/install/" class="link-button" target="_blank"> <a :href="docsURL" class="link-button" target="_blank">
<i class="iconfont icon-book"></i> <i class="iconfont icon-book"></i>
</a> </a>
</el-tooltip> </el-tooltip>

View File

@ -487,7 +487,7 @@
</div> </div>
</template> </template>
</el-image> </el-image>
<el-image v-else-if="slotProp.item['err_msg'] !== ''"> <el-image v-else-if="slotProp.item.progress === 101">
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
<div class="err-msg-container"> <div class="err-msg-container">

View File

@ -313,21 +313,41 @@
:isOver="isOver" :isOver="isOver"
@scrollReachBottom="fetchFinishJobs()"> @scrollReachBottom="fetchFinishJobs()">
<template #default="slotProp"> <template #default="slotProp">
<div class="job-item animate" @click="showTask(slotProp.item)"> <div class="job-item animate">
<el-image <el-image v-if="slotProp.item.progress === 101">
:src="slotProp.item['img_thumb']" <template #error>
fit="cover" <div class="image-slot">
loading="lazy"/> <div class="err-msg-container">
<div class="remove"> <div class="title">任务失败</div>
<el-button type="danger" :icon="Delete" @click="removeImage($event,slotProp.item)" circle/> <div class="opt">
<el-button type="warning" v-if="slotProp.item.publish" <el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
@click="publishImage($event,slotProp.item, false)" <template #reference>
circle> <el-button type="info">详情</el-button>
<i class="iconfont icon-cancel-share"></i> </template>
</el-button> </el-popover>
<el-button type="success" v-else @click="publishImage($event,slotProp.item, true)" circle> <el-button type="danger" @click="removeImage(slotProp.item)">删除</el-button>
<i class="iconfont icon-share-bold"></i> </div>
</el-button> </div>
</div>
</template>
</el-image>
<div v-else>
<el-image
:src="slotProp.item['img_thumb']"
@click="showTask(slotProp.item)"
fit="cover"
loading="lazy"/>
<div class="remove">
<el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
<el-button type="warning" v-if="slotProp.item.publish"
@click="publishImage(slotProp.item, false)"
circle>
<i class="iconfont icon-cancel-share"></i>
</el-button>
<el-button type="success" v-else @click="publishImage(slotProp.item, true)" circle>
<i class="iconfont icon-share-bold"></i>
</el-button>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -466,7 +486,7 @@
</template> </template>
<script setup> <script setup>
import {onMounted, onUnmounted, ref} from "vue" import {nextTick, onMounted, onUnmounted, ref} from "vue"
import {Delete, DocumentCopy, InfoFilled, Orange, Picture} from "@element-plus/icons-vue"; import {Delete, DocumentCopy, InfoFilled, Orange, Picture} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage, ElMessageBox, ElNotification} from "element-plus"; import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
@ -540,25 +560,9 @@ const connect = () => {
} }
} }
//
const sendHeartbeat = () => {
clearTimeout(heartbeatHandle.value)
new Promise((resolve, reject) => {
if (socket.value !== null) {
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
}
resolve("success")
}).then(() => {
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
});
}
const _socket = new WebSocket(host + `/api/sd/client?user_id=${userId.value}`); const _socket = new WebSocket(host + `/api/sd/client?user_id=${userId.value}`);
_socket.addEventListener('open', () => { _socket.addEventListener('open', () => {
socket.value = _socket; socket.value = _socket;
//
sendHeartbeat()
}); });
_socket.addEventListener('message', event => { _socket.addEventListener('message', event => {
@ -567,12 +571,12 @@ const connect = () => {
reader.readAsText(event.data, "UTF-8") reader.readAsText(event.data, "UTF-8")
reader.onload = () => { reader.onload = () => {
const message = String(reader.result) const message = String(reader.result)
if (message === "FINISH") { if (message === "FINISH" || message === "FAIL") {
page.value = 0 page.value = 0
isOver.value = false isOver.value = false
fetchFinishJobs() fetchFinishJobs()
} }
fetchRunningJobs() nextTick(() => fetchRunningJobs())
} }
} }
}); });
@ -633,22 +637,7 @@ const fetchRunningJobs = () => {
// //
httpGet(`/api/sd/jobs?finish=0`).then(res => { httpGet(`/api/sd/jobs?finish=0`).then(res => {
const jobs = res.data runningJobs.value = res.data
const _jobs = []
for (let i = 0; i < jobs.length; i++) {
if (jobs[i].progress === -1) {
ElNotification({
title: '任务执行失败',
dangerouslyUseHTMLString: true,
message: `任务ID${jobs[i]['task_id']}<br />原因:${jobs[i]['err_msg']}`,
type: 'error',
})
power.value += sdPower.value
continue
}
_jobs.push(jobs[i])
}
runningJobs.value = _jobs
}).catch(e => { }).catch(e => {
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)
}) })
@ -699,7 +688,7 @@ const generate = () => {
return return
} }
if (params.value.seed === '') { if (!params.value.seed) {
params.value.seed = -1 params.value.seed = -1
} }
params.value.session_id = getSessionId() params.value.session_id = getSessionId()
@ -721,8 +710,7 @@ const copyParams = (row) => {
showTaskDialog.value = false showTaskDialog.value = false
} }
const removeImage = (event, item) => { const removeImage = (item) => {
event.stopPropagation()
ElMessageBox.confirm( ElMessageBox.confirm(
'此操作将会删除任务和图片,继续操作码?', '此操作将会删除任务和图片,继续操作码?',
'删除提示', '删除提示',
@ -732,7 +720,7 @@ const removeImage = (event, item) => {
type: 'warning', type: 'warning',
} }
).then(() => { ).then(() => {
httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => { httpGet("/api/sd/remove", {id: item.id}).then(() => {
ElMessage.success("任务删除成功") ElMessage.success("任务删除成功")
page.value = 0 page.value = 0
isOver.value = false isOver.value = false
@ -745,13 +733,12 @@ const removeImage = (event, item) => {
} }
// //
const publishImage = (event, item, action) => { const publishImage = (item, action) => {
event.stopPropagation()
let text = "图片发布" let text = "图片发布"
if (action === false) { if (action === false) {
text = "取消发布" text = "取消发布"
} }
httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => { httpGet("/api/sd/publish", {id: item.id, action: action}).then(() => {
ElMessage.success(text + "成功") ElMessage.success(text + "成功")
item.publish = action item.publish = action
page.value = 0 page.value = 0

View File

@ -156,7 +156,7 @@ httpGet("/api/config/get?key=system").then(res => {
const initData = () => { const initData = () => {
httpGet("/api/model/list").then(res => { httpGet("/api/model/list").then(res => {
for (let v of res.data) { for (let v of res.data) {
if (v.platform === "OpenAI" && v.value.indexOf("gpt-4-gizmo") === -1) { if (v.value.indexOf("gpt-4-gizmo") === -1) {
models.value.push(v) models.value.push(v)
} }
} }

View File

@ -381,9 +381,9 @@ onMounted(() => {
checkSession().then(user => { checkSession().then(user => {
userId.value = user.id userId.value = user.id
fetchData(1)
connect() connect()
}) })
fetchData(1)
}) })
onUnmounted(() => { onUnmounted(() => {
@ -410,6 +410,8 @@ const fetchData = (_page) => {
list.value = items list.value = items
noData.value = list.value.length === 0 noData.value = list.value.length === 0
}).catch(e => { }).catch(e => {
loading.value = false
noData.value = true
showMessageError("获取作品列表失败:"+e.message) showMessageError("获取作品列表失败:"+e.message)
}) })
} }