mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	restore use power when removed not finish jobs
This commit is contained in:
		@@ -1,4 +1,8 @@
 | 
			
		||||
# 更新日志
 | 
			
		||||
 | 
			
		||||
## v4.1.2
 | 
			
		||||
* Bug修复:修复思维导图页面获取模型失败的问题
 | 
			
		||||
 | 
			
		||||
## v4.1.1
 | 
			
		||||
* Bug修复:修复 GPT 模型 function call 调用后没有输出的问题
 | 
			
		||||
* 功能新增:允许获取 License 授权用户可以自定义版权信息
 | 
			
		||||
 
 | 
			
		||||
@@ -225,7 +225,7 @@ func needLogin(c *gin.Context) bool {
 | 
			
		||||
		c.Request.URL.Path == "/api/payment/doPay" ||
 | 
			
		||||
		c.Request.URL.Path == "/api/payment/payWays" ||
 | 
			
		||||
		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" ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,6 @@ type ChatSession struct {
 | 
			
		||||
 | 
			
		||||
type ChatModel struct {
 | 
			
		||||
	Id          uint    `json:"id"`
 | 
			
		||||
	Platform    string  `json:"platform"`
 | 
			
		||||
	Name        string  `json:"name"`
 | 
			
		||||
	Value       string  `json:"value"`
 | 
			
		||||
	Power       int     `json:"power"`
 | 
			
		||||
 
 | 
			
		||||
@@ -468,7 +468,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi
 | 
			
		||||
	} else {
 | 
			
		||||
		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))
 | 
			
		||||
	// 更新API KEY 最后使用时间
 | 
			
		||||
	h.DB.Model(&model.ApiKey{}).Where("id", apiKey.Id).UpdateColumn("last_used_at", time.Now().Unix())
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ package handler
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"geekai/core"
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	"geekai/service/dalle"
 | 
			
		||||
@@ -16,9 +17,9 @@ import (
 | 
			
		||||
	"geekai/store/vo"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"geekai/utils/resp"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"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{})
 | 
			
		||||
	if finish {
 | 
			
		||||
		session = session.Where("progress = ?", 100).Order("id DESC")
 | 
			
		||||
		session = session.Where("progress >= ?", 100).Order("id DESC")
 | 
			
		||||
	} else {
 | 
			
		||||
		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
 | 
			
		||||
func (h *DallJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	var job model.DallJob
 | 
			
		||||
	if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "记录不存在")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove job recode
 | 
			
		||||
	res := h.DB.Delete(&model.DallJob{Id: job.Id})
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, res.Error.Error())
 | 
			
		||||
	// 删除任务
 | 
			
		||||
	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:     "dall-e-3",
 | 
			
		||||
			Remark:    fmt.Sprintf("任务失败,退回算力。任务ID:%d,Err: %s", job.Id, job.ErrMsg),
 | 
			
		||||
			CreatedAt: time.Now(),
 | 
			
		||||
		}).Error
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			tx.Rollback()
 | 
			
		||||
			resp.ERROR(c, err.Error())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	tx.Commit()
 | 
			
		||||
 | 
			
		||||
	// remove image
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -241,10 +273,10 @@ func (h *DallJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
// Publish 发布/取消发布图片到画廊显示
 | 
			
		||||
func (h *DallJobHandler) Publish(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	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 {
 | 
			
		||||
		logger.Error("error with update database:", res.Error)
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败")
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if user.Status == false {
 | 
			
		||||
		return errors.New("当前用户被禁用")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if user.Power < chatModel.Power {
 | 
			
		||||
		return fmt.Errorf("您当前剩余算力(%d)已不足以支付当前模型算力(%d)!", user.Power, chatModel.Power)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	messages := make([]interface{}, 0)
 | 
			
		||||
	messages = append(messages, types.Message{Role: "system", Content: `
 | 
			
		||||
你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子:
 | 
			
		||||
你是一位非常优秀的思维导图助手, 你能帮助用户整理思路,根据用户提供的主题或内容,快速生成结构清晰,有条理的思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子:
 | 
			
		||||
# 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{
 | 
			
		||||
		Model:    chatModel.Value,
 | 
			
		||||
		Stream:   true,
 | 
			
		||||
@@ -253,5 +249,6 @@ func (h *MarkMapHandler) doRequest(req types.ApiRequest, chatModel model.ChatMod
 | 
			
		||||
		client = http.DefaultClient
 | 
			
		||||
	}
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -465,35 +465,37 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// refund power
 | 
			
		||||
	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 + job.Power,
 | 
			
		||||
		Mark:      types.PowerAdd,
 | 
			
		||||
		Model:     "mid-journey",
 | 
			
		||||
		Remark:    fmt.Sprintf("绘画任务失败,退回算力。任务ID:%s", job.TaskId),
 | 
			
		||||
		CreatedAt: time.Now(),
 | 
			
		||||
	}).Error
 | 
			
		||||
	if 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:     "mid-journey",
 | 
			
		||||
			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
 | 
			
		||||
	err = h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error("remove image failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -232,7 +232,7 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int,
 | 
			
		||||
 | 
			
		||||
	session := h.DB.Session(&gorm.Session{})
 | 
			
		||||
	if finish {
 | 
			
		||||
		session = session.Where("progress = ?", 100).Order("id DESC")
 | 
			
		||||
		session = session.Where("progress >= ?", 100).Order("id DESC")
 | 
			
		||||
	} else {
 | 
			
		||||
		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
 | 
			
		||||
func (h *SdJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	var job model.SdJob
 | 
			
		||||
	if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "记录不存在")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove job recode
 | 
			
		||||
	res := h.DB.Delete(&model.SdJob{Id: job.Id})
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, res.Error.Error())
 | 
			
		||||
	// 删除任务
 | 
			
		||||
	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:     "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
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -309,10 +339,10 @@ func (h *SdJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
// Publish 发布/取消发布图片到画廊显示
 | 
			
		||||
func (h *SdJobHandler) Publish(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	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 {
 | 
			
		||||
		logger.Error("error with update database:", res.Error)
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败")
 | 
			
		||||
 
 | 
			
		||||
@@ -210,7 +210,42 @@ func (h *SunoHandler) Remove(c *gin.Context) {
 | 
			
		||||
		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:%s,Err:%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.AudioURL)
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@ func (s *Service) Run() {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Errorf("error with image task: %v", err)
 | 
			
		||||
				s.db.Model(&model.DallJob{Id: task.JobId}).UpdateColumns(map[string]interface{}{
 | 
			
		||||
					"progress": -1,
 | 
			
		||||
					"progress": 101,
 | 
			
		||||
					"err_msg":  err.Error(),
 | 
			
		||||
				})
 | 
			
		||||
				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).
 | 
			
		||||
		Order("last_used_at ASC").First(&apiKey)
 | 
			
		||||
	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
 | 
			
		||||
@@ -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() {
 | 
			
		||||
	go func() {
 | 
			
		||||
		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})
 | 
			
		||||
	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)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -187,6 +187,14 @@ func (p *ServicePool) SyncTaskProgress() {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			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 {
 | 
			
		||||
					_ = servicePlus.Notify(job)
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -109,27 +109,11 @@ func (p *ServicePool) CheckTaskStatus() {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, job := range jobs {
 | 
			
		||||
				// 5 分钟还没完成的任务直接删除
 | 
			
		||||
				if time.Now().Sub(job.CreatedAt) > time.Minute*5 || job.Progress == -1 {
 | 
			
		||||
					p.db.Delete(&job)
 | 
			
		||||
					var user model.User
 | 
			
		||||
					p.db.Where("id = ?", job.UserId).First(&user)
 | 
			
		||||
					// 退回绘图次数
 | 
			
		||||
					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
 | 
			
		||||
				// 5 分钟还没完成的任务标记为失败
 | 
			
		||||
				if time.Now().Sub(job.CreatedAt) > time.Minute*5 {
 | 
			
		||||
					job.Progress = 101
 | 
			
		||||
					job.ErrMsg = "任务超时"
 | 
			
		||||
					p.db.Updates(&job)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			time.Sleep(time.Second * 5)
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,7 @@ func (s *Service) Run() {
 | 
			
		||||
			logger.Error("绘画任务执行失败:", err.Error())
 | 
			
		||||
			// update the task progress
 | 
			
		||||
			s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(map[string]interface{}{
 | 
			
		||||
				"progress": -1,
 | 
			
		||||
				"progress": 101,
 | 
			
		||||
				"err_msg":  err.Error(),
 | 
			
		||||
			})
 | 
			
		||||
			// 通知前端,任务失败
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,8 @@ VUE_APP_USER=18575670125
 | 
			
		||||
VUE_APP_PASS=12345678
 | 
			
		||||
VUE_APP_ADMIN_USER=admin
 | 
			
		||||
VUE_APP_ADMIN_PASS=admin123
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
 | 
			
		||||
VUE_APP_KEY_PREFIX=GeekAI_DEV_
 | 
			
		||||
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_GIT_URL=https://github.com/yangjian102621/geekai
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
VUE_APP_API_HOST=
 | 
			
		||||
VUE_APP_WS_HOST=
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_
 | 
			
		||||
VUE_APP_VERSION=v4.1.1
 | 
			
		||||
VUE_APP_KEY_PREFIX=GeekAI_
 | 
			
		||||
VUE_APP_VERSION=v4.1.2
 | 
			
		||||
VUE_APP_DOCS_URL=https://docs.geekai.me
 | 
			
		||||
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai
 | 
			
		||||
 
 | 
			
		||||
@@ -221,134 +221,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 任务列表
 | 
			
		||||
 | 
			
		||||
        .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
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        @import "waterfall-list.styl"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .no-more-data {
 | 
			
		||||
 
 | 
			
		||||
@@ -220,136 +220,8 @@
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // 任务列表
 | 
			
		||||
 | 
			
		||||
        .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
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        @import "waterfall-list.styl"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .no-more-data {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										146
									
								
								web/src/assets/css/waterfall-list.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								web/src/assets/css/waterfall-list.styl
									
									
									
									
									
										Normal 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
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -367,7 +367,6 @@ const initData = () => {
 | 
			
		||||
  inputRef.value.addEventListener('paste', (event) => {
 | 
			
		||||
    const items = (event.clipboardData || window.clipboardData).items;
 | 
			
		||||
    let fileFound = false;
 | 
			
		||||
    loading.value = true
 | 
			
		||||
 | 
			
		||||
    for (let item of items) {
 | 
			
		||||
      if (item.kind === 'file') {
 | 
			
		||||
@@ -376,6 +375,7 @@ const initData = () => {
 | 
			
		||||
 | 
			
		||||
        const formData = new FormData();
 | 
			
		||||
        formData.append('file', file);
 | 
			
		||||
        loading.value = true
 | 
			
		||||
        // 执行上传操作
 | 
			
		||||
        httpPost('/api/upload', formData).then((res) => {
 | 
			
		||||
          files.value.push(res.data)
 | 
			
		||||
@@ -389,7 +389,7 @@ const initData = () => {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    if (!fileFound) {
 | 
			
		||||
      document.getElementById('status').innerText = 'No file found in paste data.';
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,24 @@
 | 
			
		||||
                          </template>
 | 
			
		||||
                        </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>
 | 
			
		||||
                          <template #error>
 | 
			
		||||
                            <div class="image-slot">
 | 
			
		||||
@@ -136,17 +154,17 @@
 | 
			
		||||
 | 
			
		||||
                        <div class="remove">
 | 
			
		||||
                          <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 content="分享" placement="top" effect="light" v-if="slotProp.item.publish">
 | 
			
		||||
                            <el-button type="warning"
 | 
			
		||||
                                       @click="publishImage($event,slotProp.item, false)"
 | 
			
		||||
                                       @click="publishImage(slotProp.item, false)"
 | 
			
		||||
                                       circle>
 | 
			
		||||
                              <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                            </el-button>
 | 
			
		||||
                          </el-tooltip>
 | 
			
		||||
                          <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>
 | 
			
		||||
                            </el-button>
 | 
			
		||||
                          </el-tooltip>
 | 
			
		||||
@@ -185,7 +203,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<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 {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
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}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    socket.value = _socket;
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
@@ -313,12 +315,12 @@ const connect = () => {
 | 
			
		||||
      reader.readAsText(event.data, "UTF-8")
 | 
			
		||||
      reader.onload = () => {
 | 
			
		||||
        const message = String(reader.result)
 | 
			
		||||
        if (message === "FINISH") {
 | 
			
		||||
        if (message === "FINISH" || message === "FAIL") {
 | 
			
		||||
          page.value = 0
 | 
			
		||||
          isOver.value = false
 | 
			
		||||
          fetchFinishJobs(page.value)
 | 
			
		||||
        }
 | 
			
		||||
        fetchRunningJobs()
 | 
			
		||||
        nextTick(() => fetchRunningJobs())
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
@@ -336,22 +338,7 @@ const fetchRunningJobs = () => {
 | 
			
		||||
  }
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=false`).then(res => {
 | 
			
		||||
    const jobs = 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
 | 
			
		||||
    runningJobs.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
@@ -409,8 +396,7 @@ const generate = () => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const removeImage = (event, item) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const removeImage = (item) => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
@@ -420,7 +406,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/dall/remove", {id: item.id, user_id: item.user}).then(() => {
 | 
			
		||||
    httpGet("/api/dall/remove", {id: item.id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
@@ -437,18 +423,16 @@ const previewImg = (item) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (event, item, action) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const publishImage = (item, action) => {
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    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 + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    isOver.value = false
 | 
			
		||||
    fetchFinishJobs()
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error(text + "失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
            effect="light"
 | 
			
		||||
            content="部署文档"
 | 
			
		||||
            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>
 | 
			
		||||
          </a>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
 
 | 
			
		||||
@@ -487,7 +487,7 @@
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
                      <el-image v-else-if="slotProp.item['err_msg'] !== ''">
 | 
			
		||||
                      <el-image v-else-if="slotProp.item.progress === 101">
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            <div class="err-msg-container">
 | 
			
		||||
 
 | 
			
		||||
@@ -313,21 +313,41 @@
 | 
			
		||||
                      :isOver="isOver"
 | 
			
		||||
                      @scrollReachBottom="fetchFinishJobs()">
 | 
			
		||||
                    <template #default="slotProp">
 | 
			
		||||
                      <div class="job-item animate" @click="showTask(slotProp.item)">
 | 
			
		||||
                        <el-image
 | 
			
		||||
                            :src="slotProp.item['img_thumb']"
 | 
			
		||||
                            fit="cover"
 | 
			
		||||
                            loading="lazy"/>
 | 
			
		||||
                        <div class="remove">
 | 
			
		||||
                          <el-button type="danger" :icon="Delete" @click="removeImage($event,slotProp.item)" circle/>
 | 
			
		||||
                          <el-button type="warning" v-if="slotProp.item.publish"
 | 
			
		||||
                                     @click="publishImage($event,slotProp.item, false)"
 | 
			
		||||
                                     circle>
 | 
			
		||||
                            <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                          </el-button>
 | 
			
		||||
                          <el-button type="success" v-else @click="publishImage($event,slotProp.item, true)" circle>
 | 
			
		||||
                            <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                          </el-button>
 | 
			
		||||
                      <div class="job-item animate">
 | 
			
		||||
                        <el-image v-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>
 | 
			
		||||
                        <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>
 | 
			
		||||
                    </template>
 | 
			
		||||
@@ -466,7 +486,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<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 {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
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}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    socket.value = _socket;
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
@@ -567,12 +571,12 @@ const connect = () => {
 | 
			
		||||
      reader.readAsText(event.data, "UTF-8")
 | 
			
		||||
      reader.onload = () => {
 | 
			
		||||
        const message = String(reader.result)
 | 
			
		||||
        if (message === "FINISH") {
 | 
			
		||||
        if (message === "FINISH" || message === "FAIL") {
 | 
			
		||||
          page.value = 0
 | 
			
		||||
          isOver.value = false
 | 
			
		||||
          fetchFinishJobs()
 | 
			
		||||
        }
 | 
			
		||||
        fetchRunningJobs()
 | 
			
		||||
        nextTick(() => fetchRunningJobs())
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
@@ -633,22 +637,7 @@ const fetchRunningJobs = () => {
 | 
			
		||||
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=0`).then(res => {
 | 
			
		||||
    const jobs = 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
 | 
			
		||||
    runningJobs.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
@@ -699,7 +688,7 @@ const generate = () => {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (params.value.seed === '') {
 | 
			
		||||
  if (!params.value.seed) {
 | 
			
		||||
    params.value.seed = -1
 | 
			
		||||
  }
 | 
			
		||||
  params.value.session_id = getSessionId()
 | 
			
		||||
@@ -721,8 +710,7 @@ const copyParams = (row) => {
 | 
			
		||||
  showTaskDialog.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const removeImage = (event, item) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const removeImage = (item) => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
@@ -732,7 +720,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
 | 
			
		||||
    httpGet("/api/sd/remove", {id: item.id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
@@ -745,13 +733,12 @@ const removeImage = (event, item) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (event, item, action) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
const publishImage = (item, action) => {
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    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 + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
 
 | 
			
		||||
@@ -156,7 +156,7 @@ httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
const initData = () => {
 | 
			
		||||
  httpGet("/api/model/list").then(res => {
 | 
			
		||||
    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)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -381,9 +381,9 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    userId.value = user.id
 | 
			
		||||
    fetchData(1)
 | 
			
		||||
    connect()
 | 
			
		||||
  })
 | 
			
		||||
  fetchData(1)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
@@ -410,6 +410,8 @@ const fetchData = (_page) => {
 | 
			
		||||
    list.value = items
 | 
			
		||||
    noData.value = list.value.length === 0
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    noData.value = true
 | 
			
		||||
    showMessageError("获取作品列表失败:"+e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user