mirror of
https://gitee.com/blackfox/geekai.git
synced 2024-11-29 18:57:34 +08:00
tidy apis
This commit is contained in:
parent
a5ef4299ec
commit
d1965deff1
@ -32,31 +32,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AppServer struct {
|
type AppServer struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
Config *types.AppConfig
|
Config *types.AppConfig
|
||||||
Engine *gin.Engine
|
Engine *gin.Engine
|
||||||
ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
|
|
||||||
|
|
||||||
SysConfig *types.SystemConfig // system config cache
|
SysConfig *types.SystemConfig // system config cache
|
||||||
|
|
||||||
// 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
|
|
||||||
// 防止第三方直接连接 socket 调用 OpenAI API
|
|
||||||
ChatSession *types.LMap[string, *types.ChatSession] //map[sessionId]UserId
|
|
||||||
ChatClients *types.LMap[string, *types.WsClient] // map[sessionId]Websocket 连接集合
|
|
||||||
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(appConfig *types.AppConfig) *AppServer {
|
func NewServer(appConfig *types.AppConfig) *AppServer {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
gin.DefaultWriter = io.Discard
|
gin.DefaultWriter = io.Discard
|
||||||
return &AppServer{
|
return &AppServer{
|
||||||
Debug: false,
|
Debug: false,
|
||||||
Config: appConfig,
|
Config: appConfig,
|
||||||
Engine: gin.Default(),
|
Engine: gin.Default(),
|
||||||
ChatContexts: types.NewLMap[string, []types.Message](),
|
|
||||||
ChatSession: types.NewLMap[string, *types.ChatSession](),
|
|
||||||
ChatClients: types.NewLMap[string, *types.WsClient](),
|
|
||||||
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,9 +53,8 @@ type Delta struct {
|
|||||||
// ChatSession 聊天会话对象
|
// ChatSession 聊天会话对象
|
||||||
type ChatSession struct {
|
type ChatSession struct {
|
||||||
SessionId string `json:"session_id"`
|
SessionId string `json:"session_id"`
|
||||||
|
UserId uint `json:"user_id"`
|
||||||
ClientIP string `json:"client_ip"` // 客户端 IP
|
ClientIP string `json:"client_ip"` // 客户端 IP
|
||||||
Username string `json:"username"` // 当前登录的 username
|
|
||||||
UserId uint `json:"user_id"` // 当前登录的 user ID
|
|
||||||
ChatId string `json:"chat_id"` // 客户端聊天会话 ID, 多会话模式专用字段
|
ChatId string `json:"chat_id"` // 客户端聊天会话 ID, 多会话模式专用字段
|
||||||
Model ChatModel `json:"model"` // GPT 模型
|
Model ChatModel `json:"model"` // GPT 模型
|
||||||
}
|
}
|
||||||
|
@ -53,10 +53,10 @@ type SdTaskParams struct {
|
|||||||
NegPrompt string `json:"neg_prompt"` // 反向提示词
|
NegPrompt string `json:"neg_prompt"` // 反向提示词
|
||||||
Steps int `json:"steps"` // 迭代步数,默认20
|
Steps int `json:"steps"` // 迭代步数,默认20
|
||||||
Sampler string `json:"sampler"` // 采样器
|
Sampler string `json:"sampler"` // 采样器
|
||||||
Scheduler string `json:"scheduler"`
|
Scheduler string `json:"scheduler"` // 采样调度
|
||||||
FaceFix bool `json:"face_fix"` // 面部修复
|
FaceFix bool `json:"face_fix"` // 面部修复
|
||||||
CfgScale float32 `json:"cfg_scale"` //引导系数,默认 7
|
CfgScale float32 `json:"cfg_scale"` //引导系数,默认 7
|
||||||
Seed int64 `json:"seed"` // 随机数种子
|
Seed int64 `json:"seed"` // 随机数种子
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
Width int `json:"width"`
|
Width int `json:"width"`
|
||||||
HdFix bool `json:"hd_fix"` // 启用高清修复
|
HdFix bool `json:"hd_fix"` // 启用高清修复
|
||||||
|
@ -37,11 +37,9 @@ type BizCode int
|
|||||||
const (
|
const (
|
||||||
Success = BizCode(0)
|
Success = BizCode(0)
|
||||||
Failed = BizCode(1)
|
Failed = BizCode(1)
|
||||||
NotAuthorized = BizCode(400) // 未授权
|
NotAuthorized = BizCode(401) // 未授权
|
||||||
NotPermission = BizCode(403) // 没有权限
|
|
||||||
|
|
||||||
OkMsg = "Success"
|
OkMsg = "Success"
|
||||||
ErrorMsg = "系统开小差了"
|
ErrorMsg = "系统开小差了"
|
||||||
InvalidArgs = "非法参数或参数解析失败"
|
InvalidArgs = "非法参数或参数解析失败"
|
||||||
NoData = "No Data"
|
|
||||||
)
|
)
|
||||||
|
@ -44,6 +44,8 @@ type ChatHandler struct {
|
|||||||
redis *redis.Client
|
redis *redis.Client
|
||||||
uploadManager *oss.UploaderManager
|
uploadManager *oss.UploaderManager
|
||||||
licenseService *service.LicenseService
|
licenseService *service.LicenseService
|
||||||
|
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
|
||||||
|
ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ChatHandler {
|
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ChatHandler {
|
||||||
@ -52,6 +54,8 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manag
|
|||||||
redis: redis,
|
redis: redis,
|
||||||
uploadManager: manager,
|
uploadManager: manager,
|
||||||
licenseService: licenseService,
|
licenseService: licenseService,
|
||||||
|
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
|
||||||
|
ChatContexts: types.NewLMap[string, []types.Message](),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,21 +93,10 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session := h.App.ChatSession.Get(sessionId)
|
session := &types.ChatSession{
|
||||||
if session == nil {
|
SessionId: sessionId,
|
||||||
user, err := h.GetLoginUser(c)
|
ClientIP: c.ClientIP(),
|
||||||
if err != nil {
|
UserId: h.GetLoginUserId(c),
|
||||||
logger.Info("用户未登录")
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session = &types.ChatSession{
|
|
||||||
SessionId: sessionId,
|
|
||||||
ClientIP: c.ClientIP(),
|
|
||||||
Username: user.Username,
|
|
||||||
UserId: user.Id,
|
|
||||||
}
|
|
||||||
h.App.ChatSession.Put(sessionId, session)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// use old chat data override the chat model and role ID
|
// use old chat data override the chat model and role ID
|
||||||
@ -125,22 +118,18 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
Temperature: chatModel.Temperature,
|
Temperature: chatModel.Temperature,
|
||||||
KeyId: chatModel.KeyId,
|
KeyId: chatModel.KeyId,
|
||||||
Platform: chatModel.Platform}
|
Platform: chatModel.Platform}
|
||||||
logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
|
logger.Infof("New websocket connected, IP: %s", c.ClientIP())
|
||||||
|
|
||||||
// 保存会话连接
|
|
||||||
h.App.ChatClients.Put(sessionId, client)
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
_, msg, err := client.Receive()
|
_, msg, err := client.Receive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debugf("close connection: %s", client.Conn.RemoteAddr())
|
logger.Debugf("close connection: %s", client.Conn.RemoteAddr())
|
||||||
client.Close()
|
client.Close()
|
||||||
h.App.ChatClients.Delete(sessionId)
|
cancelFunc := h.ReqCancelFunc.Get(sessionId)
|
||||||
h.App.ChatSession.Delete(sessionId)
|
|
||||||
cancelFunc := h.App.ReqCancelFunc.Get(sessionId)
|
|
||||||
if cancelFunc != nil {
|
if cancelFunc != nil {
|
||||||
cancelFunc()
|
cancelFunc()
|
||||||
h.App.ReqCancelFunc.Delete(sessionId)
|
h.ReqCancelFunc.Delete(sessionId)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -160,7 +149,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
logger.Info("Receive a message: ", message.Content)
|
logger.Info("Receive a message: ", message.Content)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
h.App.ReqCancelFunc.Put(sessionId, cancel)
|
h.ReqCancelFunc.Put(sessionId, cancel)
|
||||||
// 回复消息
|
// 回复消息
|
||||||
err = h.sendMessage(ctx, session, chatRole, utils.InterfaceToString(message.Content), client)
|
err = h.sendMessage(ctx, session, chatRole, utils.InterfaceToString(message.Content), client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -274,8 +263,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
chatCtx := make([]types.Message, 0)
|
chatCtx := make([]types.Message, 0)
|
||||||
messages := make([]types.Message, 0)
|
messages := make([]types.Message, 0)
|
||||||
if h.App.SysConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
if h.App.ChatContexts.Has(session.ChatId) {
|
if h.ChatContexts.Has(session.ChatId) {
|
||||||
messages = h.App.ChatContexts.Get(session.ChatId)
|
messages = h.ChatContexts.Get(session.ChatId)
|
||||||
} else {
|
} else {
|
||||||
_ = utils.JsonDecode(role.Context, &messages)
|
_ = utils.JsonDecode(role.Context, &messages)
|
||||||
if h.App.SysConfig.ContextDeep > 0 {
|
if h.App.SysConfig.ContextDeep > 0 {
|
||||||
@ -468,9 +457,9 @@ func getTotalTokens(req types.ApiRequest) int {
|
|||||||
// StopGenerate 停止生成
|
// StopGenerate 停止生成
|
||||||
func (h *ChatHandler) StopGenerate(c *gin.Context) {
|
func (h *ChatHandler) StopGenerate(c *gin.Context) {
|
||||||
sessionId := c.Query("session_id")
|
sessionId := c.Query("session_id")
|
||||||
if h.App.ReqCancelFunc.Has(sessionId) {
|
if h.ReqCancelFunc.Has(sessionId) {
|
||||||
h.App.ReqCancelFunc.Get(sessionId)()
|
h.ReqCancelFunc.Get(sessionId)()
|
||||||
h.App.ReqCancelFunc.Delete(sessionId)
|
h.ReqCancelFunc.Delete(sessionId)
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, types.OkMsg)
|
resp.SUCCESS(c, types.OkMsg)
|
||||||
}
|
}
|
||||||
@ -628,7 +617,7 @@ func (h *ChatHandler) saveChatHistory(
|
|||||||
if h.App.SysConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
chatCtx = append(chatCtx, message) // 回复消息
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
h.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加聊天记录
|
// 追加聊天记录
|
||||||
@ -686,7 +675,7 @@ func (h *ChatHandler) saveChatHistory(
|
|||||||
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
chatItem.ChatId = session.ChatId
|
chatItem.ChatId = session.ChatId
|
||||||
chatItem.UserId = session.UserId
|
chatItem.UserId = userVo.Id
|
||||||
chatItem.RoleId = role.Id
|
chatItem.RoleId = role.Id
|
||||||
chatItem.ModelId = session.Model.Id
|
chatItem.ModelId = session.Model.Id
|
||||||
if utf8.RuneCountInString(prompt) > 30 {
|
if utf8.RuneCountInString(prompt) > 30 {
|
||||||
|
@ -96,7 +96,7 @@ func (h *ChatHandler) Clear(c *gin.Context) {
|
|||||||
for _, chat := range chats {
|
for _, chat := range chats {
|
||||||
chatIds = append(chatIds, chat.ChatId)
|
chatIds = append(chatIds, chat.ChatId)
|
||||||
// 清空会话上下文
|
// 清空会话上下文
|
||||||
h.App.ChatContexts.Delete(chat.ChatId)
|
h.ChatContexts.Delete(chat.ChatId)
|
||||||
}
|
}
|
||||||
err = h.DB.Transaction(func(tx *gorm.DB) error {
|
err = h.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
res := h.DB.Where("user_id =?", user.Id).Delete(&model.ChatItem{})
|
res := h.DB.Where("user_id =?", user.Id).Delete(&model.ChatItem{})
|
||||||
@ -108,8 +108,6 @@ func (h *ChatHandler) Clear(c *gin.Context) {
|
|||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
return res.Error
|
return res.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -175,7 +173,7 @@ func (h *ChatHandler) Remove(c *gin.Context) {
|
|||||||
// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
|
// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
|
||||||
|
|
||||||
// 清空会话上下文
|
// 清空会话上下文
|
||||||
h.App.ChatContexts.Delete(chatId)
|
h.ChatContexts.Delete(chatId)
|
||||||
resp.SUCCESS(c, types.OkMsg)
|
resp.SUCCESS(c, types.OkMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,13 +158,13 @@ func (h *DallJobHandler) ImgWall(c *gin.Context) {
|
|||||||
|
|
||||||
// JobList 获取 SD 任务列表
|
// JobList 获取 SD 任务列表
|
||||||
func (h *DallJobHandler) JobList(c *gin.Context) {
|
func (h *DallJobHandler) JobList(c *gin.Context) {
|
||||||
status := h.GetBool(c, "status")
|
finish := h.GetBool(c, "finish")
|
||||||
userId := h.GetLoginUserId(c)
|
userId := h.GetLoginUserId(c)
|
||||||
page := h.GetInt(c, "page", 0)
|
page := h.GetInt(c, "page", 0)
|
||||||
pageSize := h.GetInt(c, "page_size", 0)
|
pageSize := h.GetInt(c, "page_size", 0)
|
||||||
publish := h.GetBool(c, "publish")
|
publish := h.GetBool(c, "publish")
|
||||||
|
|
||||||
err, jobs := h.getData(status, userId, page, pageSize, publish)
|
err, jobs := h.getData(finish, userId, page, pageSize, publish)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
@ -214,25 +214,23 @@ 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) {
|
||||||
var data struct {
|
id := h.GetInt(c, "id", 0)
|
||||||
Id uint `json:"id"`
|
userId := h.GetInt(c, "user_id", 0)
|
||||||
UserId uint `json:"user_id"`
|
var job model.DallJob
|
||||||
ImgURL string `json:"img_url"`
|
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
|
||||||
}
|
resp.ERROR(c, "记录不存在")
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove job recode
|
// remove job recode
|
||||||
res := h.DB.Delete(&model.DallJob{Id: data.Id})
|
res := h.DB.Delete(&model.DallJob{Id: job.Id})
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
resp.ERROR(c, res.Error.Error())
|
resp.ERROR(c, res.Error.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove image
|
// remove image
|
||||||
err := h.uploader.GetUploadHandler().Delete(data.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)
|
||||||
}
|
}
|
||||||
@ -242,16 +240,11 @@ func (h *DallJobHandler) Remove(c *gin.Context) {
|
|||||||
|
|
||||||
// Publish 发布/取消发布图片到画廊显示
|
// Publish 发布/取消发布图片到画廊显示
|
||||||
func (h *DallJobHandler) Publish(c *gin.Context) {
|
func (h *DallJobHandler) Publish(c *gin.Context) {
|
||||||
var data struct {
|
id := h.GetInt(c, "id", 0)
|
||||||
Id uint `json:"id"`
|
userId := h.GetInt(c, "user_id", 0)
|
||||||
Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
|
action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := h.DB.Model(&model.DallJob{Id: data.Id}).UpdateColumn("publish", true)
|
res := h.DB.Model(&model.DallJob{Id: uint(id), UserId: uint(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, "更新数据库失败")
|
||||||
|
@ -92,19 +92,18 @@ func (h *MidJourneyHandler) Client(c *gin.Context) {
|
|||||||
// Image 创建一个绘画任务
|
// Image 创建一个绘画任务
|
||||||
func (h *MidJourneyHandler) Image(c *gin.Context) {
|
func (h *MidJourneyHandler) Image(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
SessionId string `json:"session_id"`
|
|
||||||
TaskType string `json:"task_type"`
|
TaskType string `json:"task_type"`
|
||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
NegPrompt string `json:"neg_prompt"`
|
NegPrompt string `json:"neg_prompt"`
|
||||||
Rate string `json:"rate"`
|
Rate string `json:"rate"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"` // 模型
|
||||||
Chaos int `json:"chaos"`
|
Chaos int `json:"chaos"` // 创意度取值范围: 0-100
|
||||||
Raw bool `json:"raw"`
|
Raw bool `json:"raw"` // 是否开启原始模型
|
||||||
Seed int64 `json:"seed"`
|
Seed int64 `json:"seed"` // 随机数
|
||||||
Stylize int `json:"stylize"`
|
Stylize int `json:"stylize"` // 风格化
|
||||||
ImgArr []string `json:"img_arr"`
|
ImgArr []string `json:"img_arr"`
|
||||||
Tile bool `json:"tile"`
|
Tile bool `json:"tile"` // 重复平铺
|
||||||
Quality float32 `json:"quality"`
|
Quality float32 `json:"quality"` // 画质
|
||||||
Iw float32 `json:"iw"`
|
Iw float32 `json:"iw"`
|
||||||
CRef string `json:"cref"` //生成角色一致的图像
|
CRef string `json:"cref"` //生成角色一致的图像
|
||||||
SRef string `json:"sref"` //生成风格一致的图像
|
SRef string `json:"sref"` //生成风格一致的图像
|
||||||
@ -243,17 +242,12 @@ type reqVo struct {
|
|||||||
ChannelId string `json:"channel_id"`
|
ChannelId string `json:"channel_id"`
|
||||||
MessageId string `json:"message_id"`
|
MessageId string `json:"message_id"`
|
||||||
MessageHash string `json:"message_hash"`
|
MessageHash string `json:"message_hash"`
|
||||||
SessionId string `json:"session_id"`
|
|
||||||
Prompt string `json:"prompt"`
|
|
||||||
ChatId string `json:"chat_id"`
|
|
||||||
RoleId int `json:"role_id"`
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upscale send upscale command to MidJourney Bot
|
// Upscale send upscale command to MidJourney Bot
|
||||||
func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||||
var data reqVo
|
var data reqVo
|
||||||
if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -271,7 +265,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
|||||||
UserId: userId,
|
UserId: userId,
|
||||||
TaskId: taskId,
|
TaskId: taskId,
|
||||||
Progress: 0,
|
Progress: 0,
|
||||||
Prompt: data.Prompt,
|
|
||||||
Power: h.App.SysConfig.MjActionPower,
|
Power: h.App.SysConfig.MjActionPower,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@ -283,7 +276,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
|||||||
h.pool.PushTask(types.MjTask{
|
h.pool.PushTask(types.MjTask{
|
||||||
Id: job.Id,
|
Id: job.Id,
|
||||||
Type: types.TaskUpscale,
|
Type: types.TaskUpscale,
|
||||||
Prompt: data.Prompt,
|
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
ChannelId: data.ChannelId,
|
ChannelId: data.ChannelId,
|
||||||
Index: data.Index,
|
Index: data.Index,
|
||||||
@ -318,7 +310,7 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
|||||||
// Variation send variation command to MidJourney Bot
|
// Variation send variation command to MidJourney Bot
|
||||||
func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||||
var data reqVo
|
var data reqVo
|
||||||
if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -337,7 +329,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
|||||||
UserId: userId,
|
UserId: userId,
|
||||||
TaskId: taskId,
|
TaskId: taskId,
|
||||||
Progress: 0,
|
Progress: 0,
|
||||||
Prompt: data.Prompt,
|
|
||||||
Power: h.App.SysConfig.MjActionPower,
|
Power: h.App.SysConfig.MjActionPower,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
@ -349,7 +340,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
|||||||
h.pool.PushTask(types.MjTask{
|
h.pool.PushTask(types.MjTask{
|
||||||
Id: job.Id,
|
Id: job.Id,
|
||||||
Type: types.TaskVariation,
|
Type: types.TaskVariation,
|
||||||
Prompt: data.Prompt,
|
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
Index: data.Index,
|
Index: data.Index,
|
||||||
ChannelId: data.ChannelId,
|
ChannelId: data.ChannelId,
|
||||||
@ -397,13 +387,13 @@ func (h *MidJourneyHandler) ImgWall(c *gin.Context) {
|
|||||||
|
|
||||||
// JobList 获取 MJ 任务列表
|
// JobList 获取 MJ 任务列表
|
||||||
func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
||||||
status := h.GetBool(c, "status")
|
finish := h.GetBool(c, "finish")
|
||||||
userId := h.GetLoginUserId(c)
|
userId := h.GetLoginUserId(c)
|
||||||
page := h.GetInt(c, "page", 0)
|
page := h.GetInt(c, "page", 0)
|
||||||
pageSize := h.GetInt(c, "page_size", 0)
|
pageSize := h.GetInt(c, "page_size", 0)
|
||||||
publish := h.GetBool(c, "publish")
|
publish := h.GetBool(c, "publish")
|
||||||
|
|
||||||
err, jobs := h.getData(status, userId, page, pageSize, publish)
|
err, jobs := h.getData(finish, userId, page, pageSize, publish)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
@ -459,30 +449,27 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
|
|||||||
|
|
||||||
// Remove remove task image
|
// Remove remove task image
|
||||||
func (h *MidJourneyHandler) Remove(c *gin.Context) {
|
func (h *MidJourneyHandler) Remove(c *gin.Context) {
|
||||||
var data struct {
|
id := h.GetInt(c, "id", 0)
|
||||||
Id uint `json:"id"`
|
userId := h.GetInt(c, "user_id", 0)
|
||||||
UserId uint `json:"user_id"`
|
var job model.MidJourneyJob
|
||||||
ImgURL string `json:"img_url"`
|
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
|
||||||
}
|
resp.ERROR(c, "记录不存在")
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove job recode
|
// remove job recode
|
||||||
res := h.DB.Delete(&model.MidJourneyJob{Id: data.Id})
|
res := h.DB.Delete(&job)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
resp.ERROR(c, res.Error.Error())
|
resp.ERROR(c, res.Error.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove image
|
// remove image
|
||||||
err := h.uploader.GetUploadHandler().Delete(data.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := h.pool.Clients.Get(data.UserId)
|
client := h.pool.Clients.Get(uint(job.UserId))
|
||||||
if client != nil {
|
if client != nil {
|
||||||
_ = client.Send([]byte("Task Updated"))
|
_ = client.Send([]byte("Task Updated"))
|
||||||
}
|
}
|
||||||
@ -492,16 +479,10 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
|
|||||||
|
|
||||||
// Publish 发布图片到画廊显示
|
// Publish 发布图片到画廊显示
|
||||||
func (h *MidJourneyHandler) Publish(c *gin.Context) {
|
func (h *MidJourneyHandler) Publish(c *gin.Context) {
|
||||||
var data struct {
|
id := h.GetInt(c, "id", 0)
|
||||||
Id uint `json:"id"`
|
userId := h.GetInt(c, "user_id", 0)
|
||||||
Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
|
action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
|
||||||
}
|
res := h.DB.Model(&model.MidJourneyJob{Id: uint(id), UserId: userId}).UpdateColumn("publish", action)
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := h.DB.Model(&model.MidJourneyJob{Id: data.Id}).UpdateColumn("publish", data.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, "更新数据库失败")
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"geekai/store/vo"
|
"geekai/store/vo"
|
||||||
"geekai/utils"
|
"geekai/utils"
|
||||||
"geekai/utils/resp"
|
"geekai/utils/resp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -27,23 +28,18 @@ func NewOrderHandler(app *core.AppServer, db *gorm.DB) *OrderHandler {
|
|||||||
return &OrderHandler{BaseHandler: BaseHandler{App: app, DB: db}}
|
return &OrderHandler{BaseHandler: BaseHandler{App: app, DB: db}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List 订单列表
|
||||||
func (h *OrderHandler) List(c *gin.Context) {
|
func (h *OrderHandler) List(c *gin.Context) {
|
||||||
var data struct {
|
page := h.GetInt(c, "page", 1)
|
||||||
Page int `json:"page"`
|
pageSize := h.GetInt(c, "page_size", 20)
|
||||||
PageSize int `json:"page_size"`
|
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userId := h.GetLoginUserId(c)
|
userId := h.GetLoginUserId(c)
|
||||||
session := h.DB.Session(&gorm.Session{}).Where("user_id = ? AND status = ?", userId, types.OrderPaidSuccess)
|
session := h.DB.Session(&gorm.Session{}).Where("user_id = ? AND status = ?", userId, types.OrderPaidSuccess)
|
||||||
var total int64
|
var total int64
|
||||||
session.Model(&model.Order{}).Count(&total)
|
session.Model(&model.Order{}).Count(&total)
|
||||||
var items []model.Order
|
var items []model.Order
|
||||||
var list = make([]vo.Order, 0)
|
var list = make([]vo.Order, 0)
|
||||||
offset := (data.Page - 1) * data.PageSize
|
offset := (page - 1) * pageSize
|
||||||
res := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&items)
|
res := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items)
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
var order vo.Order
|
var order vo.Order
|
||||||
@ -58,5 +54,35 @@ func (h *OrderHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list))
|
resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query 查询订单状态
|
||||||
|
func (h *OrderHandler) Query(c *gin.Context) {
|
||||||
|
orderNo := h.GetTrim(c, "order_no")
|
||||||
|
var order model.Order
|
||||||
|
res := h.DB.Where("order_no = ?", orderNo).First(&order)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "Order not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.Status == types.OrderPaidSuccess {
|
||||||
|
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
var item model.Order
|
||||||
|
h.DB.Where("order_no = ?", orderNo).First(&item)
|
||||||
|
if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
|
||||||
|
order.Status = item.Status
|
||||||
|
break
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
|||||||
|
|
||||||
// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回
|
// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回
|
||||||
if order.Status == types.OrderPaidSuccess {
|
if order.Status == types.OrderPaidSuccess {
|
||||||
resp.ERROR(c, "This order had been paid, please do not pay twice")
|
resp.ERROR(c, "订单已支付成功,无需重复支付!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,49 +148,11 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
|||||||
resp.ERROR(c, "Invalid operations")
|
resp.ERROR(c, "Invalid operations")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderQuery 查询订单状态
|
|
||||||
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
|
||||||
var data struct {
|
|
||||||
OrderNo string `json:"order_no"`
|
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var order model.Order
|
|
||||||
res := h.DB.Where("order_no = ?", data.OrderNo).First(&order)
|
|
||||||
if res.Error != nil {
|
|
||||||
resp.ERROR(c, "Order not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if order.Status == types.OrderPaidSuccess {
|
|
||||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
counter := 0
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
var item model.Order
|
|
||||||
h.DB.Where("order_no = ?", data.OrderNo).First(&item)
|
|
||||||
if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
|
|
||||||
order.Status = item.Status
|
|
||||||
break
|
|
||||||
}
|
|
||||||
counter++
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayQrcode 生成支付 URL 二维码
|
// PayQrcode 生成支付 URL 二维码
|
||||||
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
PayWay string `json:"pay_way"` // 支付方式
|
PayWay string `json:"pay_way"` // 支付方式
|
||||||
ProductId uint `json:"product_id"`
|
ProductId uint `json:"product_id"`
|
||||||
UserId int `json:"user_id"`
|
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
@ -209,10 +171,9 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
|||||||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var user model.User
|
user, err := h.GetLoginUser(c)
|
||||||
res = h.DB.First(&user, data.UserId)
|
if err != nil {
|
||||||
if res.Error != nil {
|
resp.NotAuth(c)
|
||||||
resp.ERROR(c, "Invalid user ID")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +294,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|||||||
var data struct {
|
var data struct {
|
||||||
PayWay string `json:"pay_way"` // 支付方式
|
PayWay string `json:"pay_way"` // 支付方式
|
||||||
ProductId uint `json:"product_id"`
|
ProductId uint `json:"product_id"`
|
||||||
UserId int `json:"user_id"`
|
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
@ -352,10 +312,9 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|||||||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var user model.User
|
user, err := h.GetLoginUser(c)
|
||||||
res = h.DB.First(&user, data.UserId)
|
if err != nil {
|
||||||
if res.Error != nil {
|
resp.NotAuth(c)
|
||||||
resp.ERROR(c, "Invalid user ID")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,7 +408,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.SUCCESS(c, payURL)
|
resp.SUCCESS(c, gin.H{"url": payURL, "order_no": orderNo})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步通知回调公共逻辑
|
// 异步通知回调公共逻辑
|
||||||
|
@ -99,10 +99,7 @@ func (h *SdJobHandler) Image(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var data struct {
|
var data types.SdTaskParams
|
||||||
SessionId string `json:"session_id"`
|
|
||||||
types.SdTaskParams
|
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil || data.Prompt == "" {
|
if err := c.ShouldBindJSON(&data); err != nil || data.Prompt == "" {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
@ -215,13 +212,13 @@ func (h *SdJobHandler) ImgWall(c *gin.Context) {
|
|||||||
|
|
||||||
// JobList 获取 SD 任务列表
|
// JobList 获取 SD 任务列表
|
||||||
func (h *SdJobHandler) JobList(c *gin.Context) {
|
func (h *SdJobHandler) JobList(c *gin.Context) {
|
||||||
status := h.GetBool(c, "status")
|
finish := h.GetBool(c, "finish")
|
||||||
userId := h.GetLoginUserId(c)
|
userId := h.GetLoginUserId(c)
|
||||||
page := h.GetInt(c, "page", 0)
|
page := h.GetInt(c, "page", 0)
|
||||||
pageSize := h.GetInt(c, "page_size", 0)
|
pageSize := h.GetInt(c, "page_size", 0)
|
||||||
publish := h.GetBool(c, "publish")
|
publish := h.GetBool(c, "publish")
|
||||||
|
|
||||||
err, jobs := h.getData(status, userId, page, pageSize, publish)
|
err, jobs := h.getData(finish, userId, page, pageSize, publish)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
@ -280,30 +277,28 @@ 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) {
|
||||||
var data struct {
|
id := h.GetInt(c, "id", 0)
|
||||||
Id uint `json:"id"`
|
userId := h.GetInt(c, "user_id", 0)
|
||||||
UserId uint `json:"user_id"`
|
var job model.SdJob
|
||||||
ImgURL string `json:"img_url"`
|
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
|
||||||
}
|
resp.ERROR(c, "记录不存在")
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove job recode
|
// remove job recode
|
||||||
res := h.DB.Delete(&model.SdJob{Id: data.Id})
|
res := h.DB.Delete(&model.SdJob{Id: job.Id})
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
resp.ERROR(c, res.Error.Error())
|
resp.ERROR(c, res.Error.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove image
|
// remove image
|
||||||
err := h.uploader.GetUploadHandler().Delete(data.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := h.pool.Clients.Get(data.UserId)
|
client := h.pool.Clients.Get(uint(job.UserId))
|
||||||
if client != nil {
|
if client != nil {
|
||||||
_ = client.Send([]byte(sd.Finished))
|
_ = client.Send([]byte(sd.Finished))
|
||||||
}
|
}
|
||||||
@ -313,16 +308,11 @@ func (h *SdJobHandler) Remove(c *gin.Context) {
|
|||||||
|
|
||||||
// Publish 发布/取消发布图片到画廊显示
|
// Publish 发布/取消发布图片到画廊显示
|
||||||
func (h *SdJobHandler) Publish(c *gin.Context) {
|
func (h *SdJobHandler) Publish(c *gin.Context) {
|
||||||
var data struct {
|
id := h.GetInt(c, "id", 0)
|
||||||
Id uint `json:"id"`
|
userId := h.GetInt(c, "user_id", 0)
|
||||||
Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
|
action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := h.DB.Model(&model.SdJob{Id: data.Id}).UpdateColumn("publish", true)
|
res := h.DB.Model(&model.SdJob{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, "更新数据库失败")
|
||||||
|
@ -49,14 +49,20 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
|
|||||||
var data struct {
|
var data struct {
|
||||||
Receiver string `json:"receiver"` // 接收者
|
Receiver string `json:"receiver"` // 接收者
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Dots string `json:"dots"`
|
Dots string `json:"dots,omitempty"`
|
||||||
|
X int `json:"x,omitempty"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var check bool
|
||||||
if !h.captcha.Check(data) {
|
if data.X != 0 {
|
||||||
|
check = h.captcha.SlideCheck(data)
|
||||||
|
} else {
|
||||||
|
check = h.captcha.Check(data)
|
||||||
|
}
|
||||||
|
if !check {
|
||||||
resp.ERROR(c, "验证码错误,请先完人机验证")
|
resp.ERROR(c, "验证码错误,请先完人机验证")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -89,5 +95,9 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.SUCCESS(c)
|
if h.App.Debug {
|
||||||
|
resp.SUCCESS(c, code)
|
||||||
|
} else {
|
||||||
|
resp.SUCCESS(c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
|||||||
|
|
||||||
func (h *UploadHandler) List(c *gin.Context) {
|
func (h *UploadHandler) List(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
Urls []string `json:"urls"`
|
Urls []string `json:"urls,omitempty"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
@ -185,7 +185,7 @@ func (h *UserHandler) Register(c *gin.Context) {
|
|||||||
resp.ERROR(c, "error with save token: "+err.Error())
|
resp.ERROR(c, "error with save token: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, tokenString)
|
resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login 用户登录
|
// Login 用户登录
|
||||||
@ -244,7 +244,7 @@ func (h *UserHandler) Login(c *gin.Context) {
|
|||||||
resp.ERROR(c, "error with save token: "+err.Error())
|
resp.ERROR(c, "error with save token: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, tokenString)
|
resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout 注 销
|
// Logout 注 销
|
||||||
@ -256,8 +256,8 @@ func (h *UserHandler) Logout(c *gin.Context) {
|
|||||||
resp.SUCCESS(c)
|
resp.SUCCESS(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLoginRequest 第三方登录请求二维码
|
// Clogin 第三方登录请求二维码
|
||||||
func (h *UserHandler) CLoginRequest(c *gin.Context) {
|
func (h *UserHandler) Clogin(c *gin.Context) {
|
||||||
returnURL := h.GetTrim(c, "return_url")
|
returnURL := h.GetTrim(c, "return_url")
|
||||||
var res types.BizVo
|
var res types.BizVo
|
||||||
apiURL := fmt.Sprintf("%s/api/clogin/request", h.App.Config.ApiConfig.ApiURL)
|
apiURL := fmt.Sprintf("%s/api/clogin/request", h.App.Config.ApiConfig.ApiURL)
|
||||||
|
18
api/main.go
18
api/main.go
@ -240,7 +240,7 @@ func main() {
|
|||||||
group.POST("password", h.UpdatePass)
|
group.POST("password", h.UpdatePass)
|
||||||
group.POST("bind/username", h.BindUsername)
|
group.POST("bind/username", h.BindUsername)
|
||||||
group.POST("resetPass", h.ResetPass)
|
group.POST("resetPass", h.ResetPass)
|
||||||
group.GET("clogin/request", h.CLoginRequest)
|
group.GET("clogin", h.Clogin)
|
||||||
group.GET("clogin/callback", h.CLoginCallback)
|
group.GET("clogin/callback", h.CLoginCallback)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *chatimpl.ChatHandler) {
|
fx.Invoke(func(s *core.AppServer, h *chatimpl.ChatHandler) {
|
||||||
@ -283,8 +283,8 @@ func main() {
|
|||||||
group.POST("variation", h.Variation)
|
group.POST("variation", h.Variation)
|
||||||
group.GET("jobs", h.JobList)
|
group.GET("jobs", h.JobList)
|
||||||
group.GET("imgWall", h.ImgWall)
|
group.GET("imgWall", h.ImgWall)
|
||||||
group.POST("remove", h.Remove)
|
group.GET("remove", h.Remove)
|
||||||
group.POST("publish", h.Publish)
|
group.GET("publish", h.Publish)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
|
||||||
group := s.Engine.Group("/api/sd")
|
group := s.Engine.Group("/api/sd")
|
||||||
@ -292,8 +292,8 @@ func main() {
|
|||||||
group.POST("image", h.Image)
|
group.POST("image", h.Image)
|
||||||
group.GET("jobs", h.JobList)
|
group.GET("jobs", h.JobList)
|
||||||
group.GET("imgWall", h.ImgWall)
|
group.GET("imgWall", h.ImgWall)
|
||||||
group.POST("remove", h.Remove)
|
group.GET("remove", h.Remove)
|
||||||
group.POST("publish", h.Publish)
|
group.GET("publish", h.Publish)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.ConfigHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.ConfigHandler) {
|
||||||
group := s.Engine.Group("/api/config/")
|
group := s.Engine.Group("/api/config/")
|
||||||
@ -370,7 +370,6 @@ func main() {
|
|||||||
group := s.Engine.Group("/api/payment/")
|
group := s.Engine.Group("/api/payment/")
|
||||||
group.GET("doPay", h.DoPay)
|
group.GET("doPay", h.DoPay)
|
||||||
group.GET("payWays", h.GetPayWays)
|
group.GET("payWays", h.GetPayWays)
|
||||||
group.POST("query", h.OrderQuery)
|
|
||||||
group.POST("qrcode", h.PayQrcode)
|
group.POST("qrcode", h.PayQrcode)
|
||||||
group.POST("mobile", h.Mobile)
|
group.POST("mobile", h.Mobile)
|
||||||
group.POST("alipay/notify", h.AlipayNotify)
|
group.POST("alipay/notify", h.AlipayNotify)
|
||||||
@ -393,7 +392,8 @@ func main() {
|
|||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.OrderHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.OrderHandler) {
|
||||||
group := s.Engine.Group("/api/order/")
|
group := s.Engine.Group("/api/order/")
|
||||||
group.POST("list", h.List)
|
group.GET("list", h.List)
|
||||||
|
group.GET("query", h.Query)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.ProductHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.ProductHandler) {
|
||||||
group := s.Engine.Group("/api/product/")
|
group := s.Engine.Group("/api/product/")
|
||||||
@ -472,8 +472,8 @@ func main() {
|
|||||||
group.POST("image", h.Image)
|
group.POST("image", h.Image)
|
||||||
group.GET("jobs", h.JobList)
|
group.GET("jobs", h.JobList)
|
||||||
group.GET("imgWall", h.ImgWall)
|
group.GET("imgWall", h.ImgWall)
|
||||||
group.POST("remove", h.Remove)
|
group.GET("remove", h.Remove)
|
||||||
group.POST("publish", h.Publish)
|
group.GET("publish", h.Publish)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -24,28 +24,20 @@ func SUCCESS(c *gin.Context, values ...interface{}) {
|
|||||||
|
|
||||||
func ERROR(c *gin.Context, messages ...string) {
|
func ERROR(c *gin.Context, messages ...string) {
|
||||||
if messages != nil {
|
if messages != nil {
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: messages[0]})
|
c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed, Message: messages[0]})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed})
|
c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func HACKER(c *gin.Context) {
|
func HACKER(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"})
|
c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotAuth(c *gin.Context, messages ...string) {
|
func NotAuth(c *gin.Context, messages ...string) {
|
||||||
if messages != nil {
|
if messages != nil {
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: messages[0]})
|
c.JSON(http.StatusUnauthorized, types.BizVo{Code: types.NotAuthorized, Message: messages[0]})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"})
|
c.JSON(http.StatusUnauthorized, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NotPermission(c *gin.Context, messages ...string) {
|
|
||||||
if messages != nil {
|
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotPermission, Message: messages[0]})
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotPermission, Message: "Not Permission"})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数';
|
ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数';
|
||||||
ALTER TABLE `chatgpt_users` ADD `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`;
|
ALTER TABLE `chatgpt_users` ADD `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`;
|
||||||
ALTER TABLE `chatgpt_users` ADD UNIQUE(`openid`);
|
|
||||||
ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
|
ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
|
||||||
ALTER TABLE `chatgpt_users` CHANGE `avatar` `avatar` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像';
|
ALTER TABLE `chatgpt_users` CHANGE `avatar` `avatar` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像';
|
||||||
ALTER TABLE `chatgpt_chat_history` CHANGE `icon` `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标';
|
ALTER TABLE `chatgpt_chat_history` CHANGE `icon` `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标';
|
||||||
|
ALTER TABLE `chatgpt_orders` CHANGE `status` `status` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付成功)';
|
@ -46,8 +46,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref, watch} from "vue";
|
import {onMounted, ref} from "vue";
|
||||||
import {httpPost} from "@/utils/http";
|
import {httpGet} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {dateFormat} from "@/utils/libs";
|
import {dateFormat} from "@/utils/libs";
|
||||||
import {DocumentCopy} from "@element-plus/icons-vue";
|
import {DocumentCopy} from "@element-plus/icons-vue";
|
||||||
@ -73,7 +73,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchData = () => {
|
const fetchData = () => {
|
||||||
httpPost('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
httpGet('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
items.value = res.data.items
|
items.value = res.data.items
|
||||||
total.value = res.data.total
|
total.value = res.data.total
|
||||||
|
@ -26,17 +26,15 @@ axios.interceptors.request.use(
|
|||||||
})
|
})
|
||||||
axios.interceptors.response.use(
|
axios.interceptors.response.use(
|
||||||
response => {
|
response => {
|
||||||
let data = response.data;
|
if (response.status === 401) {
|
||||||
if (data.code === 0) {
|
|
||||||
return response
|
|
||||||
} else if (data.code === 400) {
|
|
||||||
if (response.request.responseURL.indexOf("/api/admin") !== -1) {
|
if (response.request.responseURL.indexOf("/api/admin") !== -1) {
|
||||||
removeAdminToken()
|
removeAdminToken()
|
||||||
} else {
|
} else {
|
||||||
removeUserToken()
|
removeUserToken()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return Promise.reject(response.data)
|
return Promise.reject(response.data)
|
||||||
|
}
|
||||||
|
return response
|
||||||
}, error => {
|
}, error => {
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
})
|
})
|
||||||
|
@ -557,6 +557,7 @@ const socket = ref(null);
|
|||||||
const activelyClose = ref(false); // 主动关闭
|
const activelyClose = ref(false); // 主动关闭
|
||||||
const canSend = ref(true);
|
const canSend = ref(true);
|
||||||
const heartbeatHandle = ref(null)
|
const heartbeatHandle = ref(null)
|
||||||
|
const sessionId = ref("")
|
||||||
const connect = function (chat_id, role_id) {
|
const connect = function (chat_id, role_id) {
|
||||||
let isNewChat = false;
|
let isNewChat = false;
|
||||||
if (!chat_id) {
|
if (!chat_id) {
|
||||||
@ -571,7 +572,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
|
|
||||||
const _role = getRoleById(role_id);
|
const _role = getRoleById(role_id);
|
||||||
// 初始化 WebSocket 对象
|
// 初始化 WebSocket 对象
|
||||||
const _sessionId = getSessionId();
|
sessionId.value = getSessionId();
|
||||||
let host = process.env.VUE_APP_WS_HOST
|
let host = process.env.VUE_APP_WS_HOST
|
||||||
if (host === '') {
|
if (host === '') {
|
||||||
if (location.protocol === 'https:') {
|
if (location.protocol === 'https:') {
|
||||||
@ -593,7 +594,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
||||||
_socket.addEventListener('open', () => {
|
_socket.addEventListener('open', () => {
|
||||||
chatData.value = []; // 初始化聊天数据
|
chatData.value = []; // 初始化聊天数据
|
||||||
enableInput()
|
enableInput()
|
||||||
@ -852,7 +853,7 @@ const loadChatHistory = function (chatId) {
|
|||||||
|
|
||||||
const stopGenerate = function () {
|
const stopGenerate = function () {
|
||||||
showStopGenerate.value = false;
|
showStopGenerate.value = false;
|
||||||
httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
|
httpGet("/api/chat/stop?session_id=" + sessionId.value).then(() => {
|
||||||
enableInput()
|
enableInput()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -334,7 +334,7 @@ const fetchRunningJobs = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 获取运行中的任务
|
// 获取运行中的任务
|
||||||
httpGet(`/api/dall/jobs?status=0`).then(res => {
|
httpGet(`/api/dall/jobs?finish=false`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
const _jobs = []
|
const _jobs = []
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
@ -367,7 +367,7 @@ const fetchFinishJobs = () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
page.value = page.value + 1
|
page.value = page.value + 1
|
||||||
|
|
||||||
httpGet(`/api/dall/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
httpGet(`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||||
if (res.data.length < pageSize.value) {
|
if (res.data.length < pageSize.value) {
|
||||||
isOver.value = true
|
isOver.value = true
|
||||||
}
|
}
|
||||||
@ -419,7 +419,7 @@ const removeImage = (event, item) => {
|
|||||||
type: 'warning',
|
type: 'warning',
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
httpGet("/api/dall/remove", {id: item.id, user_id: item.user}).then(() => {
|
||||||
ElMessage.success("任务删除成功")
|
ElMessage.success("任务删除成功")
|
||||||
page.value = 0
|
page.value = 0
|
||||||
isOver.value = false
|
isOver.value = false
|
||||||
@ -442,7 +442,7 @@ const publishImage = (event, item, action) => {
|
|||||||
if (action === false) {
|
if (action === false) {
|
||||||
text = "取消发布"
|
text = "取消发布"
|
||||||
}
|
}
|
||||||
httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
|
httpGet("/api/dall/publish", {id: item.id, action: action,user_id:item.user_id}).then(() => {
|
||||||
ElMessage.success(text + "成功")
|
ElMessage.success(text + "成功")
|
||||||
item.publish = action
|
item.publish = action
|
||||||
page.value = 0
|
page.value = 0
|
||||||
|
@ -782,7 +782,7 @@ const fetchRunningJobs = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
httpGet(`/api/mj/jobs?status=0`).then(res => {
|
httpGet(`/api/mj/jobs?finish=false`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
const _jobs = []
|
const _jobs = []
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
@ -820,7 +820,7 @@ const fetchFinishJobs = () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
page.value = page.value + 1
|
page.value = page.value + 1
|
||||||
// 获取已完成的任务
|
// 获取已完成的任务
|
||||||
httpGet(`/api/mj/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
httpGet(`/api/mj/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
if (jobs[i]['img_url'] !== "") {
|
if (jobs[i]['img_url'] !== "") {
|
||||||
@ -961,7 +961,7 @@ const removeImage = (item) => {
|
|||||||
type: 'warning',
|
type: 'warning',
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
httpGet("/api/mj/remove", {id: item.id, user_id: item.user_id}).then(() => {
|
||||||
ElMessage.success("任务删除成功")
|
ElMessage.success("任务删除成功")
|
||||||
page.value = 0
|
page.value = 0
|
||||||
isOver.value = false
|
isOver.value = false
|
||||||
@ -979,7 +979,7 @@ const publishImage = (item, action) => {
|
|||||||
if (action === false) {
|
if (action === false) {
|
||||||
text = "取消发布"
|
text = "取消发布"
|
||||||
}
|
}
|
||||||
httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
|
httpGet("/api/mj/publish", {id: item.id, action: action,user_id: item.user_id}).then(() => {
|
||||||
ElMessage.success(text + "成功")
|
ElMessage.success(text + "成功")
|
||||||
item.publish = action
|
item.publish = action
|
||||||
page.value = 0
|
page.value = 0
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-line" style="padding-top: 10px">
|
<div class="param-line" style="padding-top: 10px">
|
||||||
<el-form-item label="采样调度器">
|
<el-form-item label="采样调度">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="form-item-inner">
|
<div class="form-item-inner">
|
||||||
<el-select v-model="params.scheduler" style="width:176px">
|
<el-select v-model="params.scheduler" style="width:176px">
|
||||||
@ -631,7 +631,7 @@ const fetchRunningJobs = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取运行中的任务
|
// 获取运行中的任务
|
||||||
httpGet(`/api/sd/jobs?status=0`).then(res => {
|
httpGet(`/api/sd/jobs?finish=0`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
const _jobs = []
|
const _jobs = []
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
@ -664,7 +664,7 @@ const fetchFinishJobs = () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
page.value = page.value + 1
|
page.value = page.value + 1
|
||||||
|
|
||||||
httpGet(`/api/sd/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
httpGet(`/api/sd/jobs?finish=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||||
if (res.data.length < pageSize.value) {
|
if (res.data.length < pageSize.value) {
|
||||||
isOver.value = true
|
isOver.value = true
|
||||||
}
|
}
|
||||||
@ -731,7 +731,7 @@ const removeImage = (event, item) => {
|
|||||||
type: 'warning',
|
type: 'warning',
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
|
||||||
ElMessage.success("任务删除成功")
|
ElMessage.success("任务删除成功")
|
||||||
page.value = 0
|
page.value = 0
|
||||||
isOver.value = false
|
isOver.value = false
|
||||||
@ -750,7 +750,7 @@ const publishImage = (event, item, action) => {
|
|||||||
if (action === false) {
|
if (action === false) {
|
||||||
text = "取消发布"
|
text = "取消发布"
|
||||||
}
|
}
|
||||||
httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => {
|
||||||
ElMessage.success(text + "成功")
|
ElMessage.success(text + "成功")
|
||||||
item.publish = action
|
item.publish = action
|
||||||
page.value = 0
|
page.value = 0
|
||||||
|
@ -110,7 +110,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const returnURL = `${location.protocol}//${location.host}/login/callback`
|
const returnURL = `${location.protocol}//${location.host}/login/callback`
|
||||||
httpGet("/api/user/clogin/request?return_url="+returnURL).then(res => {
|
httpGet("/api/user/clogin?return_url="+returnURL).then(res => {
|
||||||
wechatLoginURL.value = res.data.url
|
wechatLoginURL.value = res.data.url
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@ -132,7 +132,7 @@ const login = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
||||||
setUserToken(res.data)
|
setUserToken(res.data.token)
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
router.push('/mobile')
|
router.push('/mobile')
|
||||||
} else {
|
} else {
|
||||||
|
@ -333,7 +333,7 @@ const wechatPay = (row) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryOrder = (orderNo) => {
|
const queryOrder = (orderNo) => {
|
||||||
httpPost("/api/payment/query", {order_no: orderNo}).then(res => {
|
httpGet("/api/order/query", {order_no: orderNo}).then(res => {
|
||||||
if (res.data.status === 1) {
|
if (res.data.status === 1) {
|
||||||
text.value = "扫码成功,请在手机上进行支付!"
|
text.value = "扫码成功,请在手机上进行支付!"
|
||||||
queryOrder(orderNo)
|
queryOrder(orderNo)
|
||||||
|
@ -1,637 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mobile-mj">
|
|
||||||
<van-form @submit="generate">
|
|
||||||
<div class="text-line">图片比例</div>
|
|
||||||
<div class="text-line">
|
|
||||||
<van-row :gutter="10">
|
|
||||||
<van-col :span="4" v-for="item in rates" :key="item.value">
|
|
||||||
<div
|
|
||||||
:class="item.value === params.rate ? 'rate active' : 'rate'"
|
|
||||||
@click="changeRate(item)">
|
|
||||||
<div class="icon">
|
|
||||||
<van-image :src="item.img" fit="cover"></van-image>
|
|
||||||
</div>
|
|
||||||
<div class="text">{{ item.text }}</div>
|
|
||||||
</div>
|
|
||||||
</van-col>
|
|
||||||
</van-row>
|
|
||||||
</div>
|
|
||||||
<div class="text-line">模型选择</div>
|
|
||||||
<div class="text-line">
|
|
||||||
<van-row :gutter="10">
|
|
||||||
<van-col :span="8" v-for="item in models" :key="item.value">
|
|
||||||
<div :class="item.value === params.model ? 'model active' : 'model'"
|
|
||||||
@click="changeModel(item)">
|
|
||||||
<div class="icon">
|
|
||||||
<van-image :src="item.img" fit="cover"></van-image>
|
|
||||||
</div>
|
|
||||||
<div class="text">
|
|
||||||
<van-text-ellipsis :content="item.text"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</van-col>
|
|
||||||
</van-row>
|
|
||||||
</div>
|
|
||||||
<div class="text-line">
|
|
||||||
<van-field label="创意度">
|
|
||||||
<template #input>
|
|
||||||
<van-slider v-model.number="params.chaos" :max="100" :step="1"
|
|
||||||
@update:model-value="showToast('当前值:' + params.chaos)"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-field label="风格化">
|
|
||||||
<template #input>
|
|
||||||
<van-slider v-model.number="params.stylize" :max="1000" :step="1"
|
|
||||||
@update:model-value="showToast('当前值:' + params.stylize)"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-field label="原始模式">
|
|
||||||
<template #input>
|
|
||||||
<van-switch v-model="params.raw"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-tabs v-model:active="activeName" @change="tabChange" animated>
|
|
||||||
<van-tab title="文生图" name="txt2img">
|
|
||||||
<div class="text-line">
|
|
||||||
<van-field v-model="params.prompt"
|
|
||||||
rows="3"
|
|
||||||
autosize
|
|
||||||
type="textarea"
|
|
||||||
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"/>
|
|
||||||
</div>
|
|
||||||
</van-tab>
|
|
||||||
<van-tab title="图生图" name="img2img">
|
|
||||||
<div class="text-line">
|
|
||||||
<van-field v-model="params.prompt"
|
|
||||||
rows="3"
|
|
||||||
autosize
|
|
||||||
type="textarea"
|
|
||||||
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-uploader v-model="imgList" :after-read="uploadImg"/>
|
|
||||||
</div>
|
|
||||||
<div class="text-line">
|
|
||||||
<van-field label="垫图权重">
|
|
||||||
<template #input>
|
|
||||||
<van-slider v-model.number="params.iw" :max="1" :step="0.01"
|
|
||||||
@update:model-value="showToast('当前值:' + params.iw)"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tip-text">提示:只有于 niji6 和 v6 模型支持一致性功能,如果选择其他模型此功能将会生成失败。</div>
|
|
||||||
<van-cell-group>
|
|
||||||
<van-field
|
|
||||||
v-model="params.cref"
|
|
||||||
center
|
|
||||||
clearable
|
|
||||||
label="角色一致性"
|
|
||||||
placeholder="请输入图片URL或者上传图片"
|
|
||||||
>
|
|
||||||
<template #button>
|
|
||||||
<van-uploader @click="beforeUpload('cref')" :after-read="uploadImg">
|
|
||||||
<van-button size="mini" type="primary" icon="plus"/>
|
|
||||||
</van-uploader>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</van-cell-group>
|
|
||||||
|
|
||||||
<van-cell-group>
|
|
||||||
<van-field
|
|
||||||
v-model="params.sref"
|
|
||||||
center
|
|
||||||
clearable
|
|
||||||
label="风格一致性"
|
|
||||||
placeholder="请输入图片URL或者上传图片"
|
|
||||||
>
|
|
||||||
<template #button>
|
|
||||||
<van-uploader @click="beforeUpload('sref')" :after-read="uploadImg">
|
|
||||||
<van-button size="mini" type="primary" icon="plus"/>
|
|
||||||
</van-uploader>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</van-cell-group>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-field label="一致性权重">
|
|
||||||
<template #input>
|
|
||||||
<van-slider v-model.number="params.cw" :max="100" :step="1"
|
|
||||||
@update:model-value="showToast('当前值:' + params.cw)"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</div>
|
|
||||||
</van-tab>
|
|
||||||
<van-tab title="融图" name="blend">
|
|
||||||
<div class="tip-text">请上传两张以上的图片,最多不超过五张,超过五张图片请使用图生图功能。</div>
|
|
||||||
<div class="text-line">
|
|
||||||
<van-uploader v-model="imgList" :after-read="uploadImg"/>
|
|
||||||
</div>
|
|
||||||
</van-tab>
|
|
||||||
<van-tab title="换脸" name="swapFace">
|
|
||||||
<div class="tip-text">请上传两张有脸部的图片,用左边图片的脸替换右边图片的脸。</div>
|
|
||||||
<div class="text-line">
|
|
||||||
<van-uploader v-model="imgList" :after-read="uploadImg"/>
|
|
||||||
</div>
|
|
||||||
</van-tab>
|
|
||||||
</van-tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-collapse v-model="activeColspan">
|
|
||||||
<van-collapse-item title="反向提示词" name="neg_prompt">
|
|
||||||
<van-field
|
|
||||||
v-model="params.neg_prompt"
|
|
||||||
rows="3"
|
|
||||||
autosize
|
|
||||||
type="textarea"
|
|
||||||
placeholder="不想出现在图片上的元素(例如:树,建筑)"
|
|
||||||
/>
|
|
||||||
</van-collapse-item>
|
|
||||||
</van-collapse>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<el-tag>绘图消耗{{ mjPower }}算力,U/V 操作消耗{{ mjActionPower }}算力,当前算力:{{ power }}</el-tag>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-button round block type="primary" native-type="submit">
|
|
||||||
立即生成
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</van-form>
|
|
||||||
|
|
||||||
<h3>任务列表</h3>
|
|
||||||
<div class="running-job-list">
|
|
||||||
<van-empty v-if="runningJobs.length ===0"
|
|
||||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
|
||||||
image-size="80"
|
|
||||||
description="暂无记录"
|
|
||||||
/>
|
|
||||||
<van-grid :gutter="10" :column-num="3" v-else>
|
|
||||||
<van-grid-item v-for="item in runningJobs">
|
|
||||||
<div v-if="item.progress > 0">
|
|
||||||
<van-image :src="item['img_url']">
|
|
||||||
<template v-slot:error>加载失败</template>
|
|
||||||
</van-image>
|
|
||||||
<div class="progress">
|
|
||||||
<van-circle
|
|
||||||
v-model:current-rate="item.progress"
|
|
||||||
:rate="item.progress"
|
|
||||||
:speed="100"
|
|
||||||
:text="item.progress+'%'"
|
|
||||||
:stroke-width="60"
|
|
||||||
size="90px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="task-in-queue">
|
|
||||||
<span class="icon"><i class="iconfont icon-quick-start"></i></span>
|
|
||||||
<span class="text">排队中</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</van-grid-item>
|
|
||||||
</van-grid>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>创作记录</h3>
|
|
||||||
<div class="finish-job-list">
|
|
||||||
<van-empty v-if="finishedJobs.length ===0"
|
|
||||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
|
||||||
image-size="80"
|
|
||||||
description="暂无记录"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<van-list v-else
|
|
||||||
v-model:error="error"
|
|
||||||
v-model:loading="loading"
|
|
||||||
:finished="finished"
|
|
||||||
error-text="请求失败,点击重新加载"
|
|
||||||
finished-text="没有更多了"
|
|
||||||
@load="onLoad"
|
|
||||||
>
|
|
||||||
<van-grid :gutter="10" :column-num="2">
|
|
||||||
<van-grid-item v-for="item in finishedJobs">
|
|
||||||
<div class="job-item">
|
|
||||||
<van-image
|
|
||||||
:src="item['thumb_url']"
|
|
||||||
:class="item['can_opt'] ? '' : 'upscale'"
|
|
||||||
lazy-load
|
|
||||||
@click="imageView(item)"
|
|
||||||
fit="cover">
|
|
||||||
<template v-slot:loading>
|
|
||||||
<van-loading type="spinner" size="20"/>
|
|
||||||
</template>
|
|
||||||
</van-image>
|
|
||||||
|
|
||||||
<div class="opt" v-if="item['can_opt']">
|
|
||||||
|
|
||||||
<van-grid :gutter="3" :column-num="4">
|
|
||||||
<van-grid-item><a @click="upscale(1, item)" class="opt-btn">U1</a></van-grid-item>
|
|
||||||
<van-grid-item><a @click="upscale(2, item)" class="opt-btn">U2</a></van-grid-item>
|
|
||||||
<van-grid-item><a @click="upscale(3, item)" class="opt-btn">U3</a></van-grid-item>
|
|
||||||
<van-grid-item><a @click="upscale(4, item)" class="opt-btn">U4</a></van-grid-item>
|
|
||||||
<van-grid-item><a @click="variation(1, item)" class="opt-btn">V1</a></van-grid-item>
|
|
||||||
<van-grid-item><a @click="variation(2, item)" class="opt-btn">V2</a></van-grid-item>
|
|
||||||
<van-grid-item><a @click="variation(3, item)" class="opt-btn">V3</a></van-grid-item>
|
|
||||||
<van-grid-item><a @click="variation(4, item)" class="opt-btn">V4</a></van-grid-item>
|
|
||||||
</van-grid>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="remove">
|
|
||||||
<el-button type="danger" :icon="Delete" @click="removeImage(item)" circle/>
|
|
||||||
<el-button type="warning" v-if="item.publish" @click="publishImage(item, false)"
|
|
||||||
circle>
|
|
||||||
<i class="iconfont icon-cancel-share"></i>
|
|
||||||
</el-button>
|
|
||||||
<el-button type="success" v-else @click="publishImage(item, true)" circle>
|
|
||||||
<i class="iconfont icon-share-bold"></i>
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="showPrompt(item)" circle>
|
|
||||||
<i class="iconfont icon-prompt"></i>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</van-grid-item>
|
|
||||||
</van-grid>
|
|
||||||
</van-list>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {nextTick, onMounted, onUnmounted, ref} from "vue";
|
|
||||||
import {
|
|
||||||
showConfirmDialog,
|
|
||||||
showFailToast,
|
|
||||||
showNotify,
|
|
||||||
showToast,
|
|
||||||
showDialog,
|
|
||||||
showImagePreview,
|
|
||||||
showSuccessToast
|
|
||||||
} from "vant";
|
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
|
||||||
import Compressor from "compressorjs";
|
|
||||||
import {getSessionId} from "@/store/session";
|
|
||||||
import {checkSession} from "@/action/session";
|
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
import {Delete} from "@element-plus/icons-vue";
|
|
||||||
|
|
||||||
const activeColspan = ref([""])
|
|
||||||
|
|
||||||
const rates = [
|
|
||||||
{css: "square", value: "1:1", text: "1:1", img: "/images/mj/rate_1_1.png"},
|
|
||||||
{css: "size2-3", value: "2:3", text: "2:3", img: "/images/mj/rate_3_4.png"},
|
|
||||||
{css: "size3-4", value: "3:4", text: "3:4", img: "/images/mj/rate_3_4.png"},
|
|
||||||
{css: "size4-3", value: "4:3", text: "4:3", img: "/images/mj/rate_4_3.png"},
|
|
||||||
{css: "size16-9", value: "16:9", text: "16:9", img: "/images/mj/rate_16_9.png"},
|
|
||||||
{css: "size9-16", value: "9:16", text: "9:16", img: "/images/mj/rate_9_16.png"},
|
|
||||||
]
|
|
||||||
const models = [
|
|
||||||
{text: "MJ-6.0", value: " --v 6", img: "/images/mj/mj-v6.png"},
|
|
||||||
{text: "MJ-5.2", value: " --v 5.2", img: "/images/mj/mj-v5.2.png"},
|
|
||||||
{text: "Niji5", value: " --niji 5", img: "/images/mj/mj-niji.png"},
|
|
||||||
{text: "Niji5 可爱", value: " --niji 5 --style cute", img: "/images/mj/nj1.jpg"},
|
|
||||||
{text: "Niji5 风景", value: " --niji 5 --style scenic", img: "/images/mj/nj2.jpg"},
|
|
||||||
{text: "Niji6", value: " --niji 6", img: "/images/mj/nj3.jpg"},
|
|
||||||
]
|
|
||||||
const imgList = ref([])
|
|
||||||
const params = ref({
|
|
||||||
task_type: "image",
|
|
||||||
rate: rates[0].value,
|
|
||||||
model: models[0].value,
|
|
||||||
chaos: 0,
|
|
||||||
stylize: 0,
|
|
||||||
seed: 0,
|
|
||||||
img_arr: [],
|
|
||||||
raw: false,
|
|
||||||
iw: 0,
|
|
||||||
prompt: "",
|
|
||||||
neg_prompt: "",
|
|
||||||
tile: false,
|
|
||||||
quality: 0,
|
|
||||||
cref: "",
|
|
||||||
sref: "",
|
|
||||||
cw: 0,
|
|
||||||
})
|
|
||||||
const userId = ref(0)
|
|
||||||
const router = useRouter()
|
|
||||||
const runningJobs = ref([])
|
|
||||||
const finishedJobs = ref([])
|
|
||||||
const socket = ref(null)
|
|
||||||
const power = ref(0)
|
|
||||||
const activeName = ref("txt2img")
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
checkSession().then(user => {
|
|
||||||
power.value = user['power']
|
|
||||||
userId.value = user.id
|
|
||||||
|
|
||||||
fetchRunningJobs()
|
|
||||||
fetchFinishJobs(1)
|
|
||||||
connect()
|
|
||||||
|
|
||||||
}).catch(() => {
|
|
||||||
router.push('/login')
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
socket.value = null
|
|
||||||
})
|
|
||||||
|
|
||||||
const mjPower = ref(1)
|
|
||||||
const mjActionPower = ref(1)
|
|
||||||
httpGet("/api/config/get?key=system").then(res => {
|
|
||||||
mjPower.value = res.data["mj_power"]
|
|
||||||
mjActionPower.value = res.data["mj_action_power"]
|
|
||||||
}).catch(e => {
|
|
||||||
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
|
|
||||||
})
|
|
||||||
|
|
||||||
const heartbeatHandle = ref(null)
|
|
||||||
const connect = () => {
|
|
||||||
let host = process.env.VUE_APP_WS_HOST
|
|
||||||
if (host === '') {
|
|
||||||
if (location.protocol === 'https:') {
|
|
||||||
host = 'wss://' + location.host;
|
|
||||||
} else {
|
|
||||||
host = 'ws://' + location.host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 心跳函数
|
|
||||||
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/mj/client?user_id=${userId.value}`);
|
|
||||||
_socket.addEventListener('open', () => {
|
|
||||||
socket.value = _socket;
|
|
||||||
|
|
||||||
// 发送心跳消息
|
|
||||||
sendHeartbeat()
|
|
||||||
});
|
|
||||||
|
|
||||||
_socket.addEventListener('message', event => {
|
|
||||||
if (event.data instanceof Blob) {
|
|
||||||
fetchRunningJobs()
|
|
||||||
fetchFinishJobs(1)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_socket.addEventListener('close', () => {
|
|
||||||
if (socket.value !== null) {
|
|
||||||
connect()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取运行中的任务
|
|
||||||
const fetchRunningJobs = (userId) => {
|
|
||||||
httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
|
|
||||||
const jobs = res.data
|
|
||||||
const _jobs = []
|
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
|
||||||
if (jobs[i].progress === -1) {
|
|
||||||
showNotify({
|
|
||||||
message: `任务执行失败:${jobs[i]['err_msg']}`,
|
|
||||||
type: 'danger',
|
|
||||||
})
|
|
||||||
if (jobs[i].type === 'image') {
|
|
||||||
power.value += mjPower.value
|
|
||||||
} else {
|
|
||||||
power.value += mjActionPower.value
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_jobs.push(jobs[i])
|
|
||||||
}
|
|
||||||
runningJobs.value = _jobs
|
|
||||||
}).catch(e => {
|
|
||||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const finished = ref(false)
|
|
||||||
const error = ref(false)
|
|
||||||
const page = ref(0)
|
|
||||||
const pageSize = ref(10)
|
|
||||||
const fetchFinishJobs = (page) => {
|
|
||||||
loading.value = true
|
|
||||||
// 获取已完成的任务
|
|
||||||
httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
|
||||||
const jobs = res.data
|
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
|
||||||
if (jobs[i].progress === -1) {
|
|
||||||
showNotify({
|
|
||||||
message: `任务ID:${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
|
|
||||||
type: 'danger',
|
|
||||||
})
|
|
||||||
if (jobs[i].type === 'image') {
|
|
||||||
power.value += mjPower.value
|
|
||||||
} else {
|
|
||||||
power.value += mjActionPower.value
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jobs[i]['use_proxy']) {
|
|
||||||
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?x-oss-process=image/quality,q_60&format=webp'
|
|
||||||
} else {
|
|
||||||
if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
|
|
||||||
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
|
|
||||||
} else {
|
|
||||||
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jobs[i].type === 'image' || jobs[i].type === 'variation') {
|
|
||||||
jobs[i]['can_opt'] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jobs.length < pageSize.value) {
|
|
||||||
finished.value = true
|
|
||||||
}
|
|
||||||
if (page === 1) {
|
|
||||||
finishedJobs.value = jobs
|
|
||||||
} else {
|
|
||||||
finishedJobs.value = finishedJobs.value.concat(jobs)
|
|
||||||
}
|
|
||||||
nextTick(() => loading.value = false)
|
|
||||||
}).catch(e => {
|
|
||||||
loading.value = false
|
|
||||||
error.value = true
|
|
||||||
showFailToast("获取任务失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLoad = () => {
|
|
||||||
page.value += 1
|
|
||||||
fetchFinishJobs(page.value)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 切换图片比例
|
|
||||||
const changeRate = (item) => {
|
|
||||||
params.value.rate = item.value
|
|
||||||
}
|
|
||||||
// 切换模型
|
|
||||||
const changeModel = (item) => {
|
|
||||||
params.value.model = item.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const imgKey = ref("")
|
|
||||||
const beforeUpload = (key) => {
|
|
||||||
imgKey.value = key
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片上传
|
|
||||||
const uploadImg = (file) => {
|
|
||||||
file.status = "uploading"
|
|
||||||
// 压缩图片并上传
|
|
||||||
new Compressor(file.file, {
|
|
||||||
quality: 0.6,
|
|
||||||
success(result) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', result, result.name);
|
|
||||||
// 执行上传操作
|
|
||||||
httpPost('/api/upload', formData).then(res => {
|
|
||||||
file.url = res.data.url
|
|
||||||
if (imgKey.value !== "") { // 单张图片上传
|
|
||||||
params.value[imgKey.value] = res.data.url
|
|
||||||
imgKey.value = ''
|
|
||||||
}
|
|
||||||
file.status = "done"
|
|
||||||
}).catch(e => {
|
|
||||||
file.status = 'failed'
|
|
||||||
file.message = '上传失败'
|
|
||||||
showFailToast("图片上传失败:" + e.message)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
error(err) {
|
|
||||||
console.log(err.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const send = (url, index, item) => {
|
|
||||||
httpPost(url, {
|
|
||||||
index: index,
|
|
||||||
channel_id: item.channel_id,
|
|
||||||
message_id: item.message_id,
|
|
||||||
message_hash: item.hash,
|
|
||||||
session_id: getSessionId(),
|
|
||||||
prompt: item.prompt,
|
|
||||||
}).then(() => {
|
|
||||||
showSuccessToast("任务推送成功,请耐心等待任务执行...")
|
|
||||||
power.value -= mjActionPower.value
|
|
||||||
}).catch(e => {
|
|
||||||
showFailToast("任务推送失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片放大任务
|
|
||||||
const upscale = (index, item) => {
|
|
||||||
send('/api/mj/upscale', index, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片变换任务
|
|
||||||
const variation = (index, item) => {
|
|
||||||
send('/api/mj/variation', index, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
const generate = () => {
|
|
||||||
if (params.value.prompt === '' && params.value.task_type === "image") {
|
|
||||||
return showFailToast("请输入绘画提示词!")
|
|
||||||
}
|
|
||||||
if (params.value.model.indexOf("niji") !== -1 && params.value.raw) {
|
|
||||||
return showFailToast("动漫模型不允许启用原始模式")
|
|
||||||
}
|
|
||||||
params.value.session_id = getSessionId()
|
|
||||||
params.value.img_arr = imgList.value.map(img => img.url)
|
|
||||||
httpPost("/api/mj/image", params.value).then(() => {
|
|
||||||
showToast("绘画任务推送成功,请耐心等待任务执行")
|
|
||||||
power.value -= mjPower.value
|
|
||||||
}).catch(e => {
|
|
||||||
showFailToast("任务推送失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeImage = (item) => {
|
|
||||||
showConfirmDialog({
|
|
||||||
title: '标题',
|
|
||||||
message:
|
|
||||||
'此操作将会删除任务和图片,继续操作码?',
|
|
||||||
}).then(() => {
|
|
||||||
httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
|
||||||
showSuccessToast("任务删除成功")
|
|
||||||
}).catch(e => {
|
|
||||||
showFailToast("任务删除失败:" + e.message)
|
|
||||||
})
|
|
||||||
}).catch(() => {
|
|
||||||
showToast("您取消了操作")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 发布图片到作品墙
|
|
||||||
const publishImage = (item, action) => {
|
|
||||||
let text = "图片发布"
|
|
||||||
if (action === false) {
|
|
||||||
text = "取消发布"
|
|
||||||
}
|
|
||||||
httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
|
|
||||||
showSuccessToast(text + "成功")
|
|
||||||
item.publish = action
|
|
||||||
}).catch(e => {
|
|
||||||
showFailToast(text + "失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const showPrompt = (item) => {
|
|
||||||
showDialog({
|
|
||||||
title: "绘画提示词",
|
|
||||||
message: item.prompt,
|
|
||||||
}).then(() => {
|
|
||||||
// on close
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageView = (item) => {
|
|
||||||
showImagePreview([item['img_url']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换菜单
|
|
||||||
const tabChange = (tab) => {
|
|
||||||
if (tab === "txt2img" || tab === "img2img") {
|
|
||||||
params.value.task_type = "image"
|
|
||||||
} else {
|
|
||||||
params.value.task_type = tab
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
@import "@/assets/css/mobile/image-mj.styl"
|
|
||||||
</style>
|
|
@ -1,523 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mobile-sd">
|
|
||||||
<van-form @submit="generate">
|
|
||||||
<van-cell-group inset>
|
|
||||||
<div>
|
|
||||||
<van-field
|
|
||||||
v-model="params.sampler"
|
|
||||||
is-link
|
|
||||||
readonly
|
|
||||||
label="采样方法"
|
|
||||||
placeholder="选择采样方法"
|
|
||||||
@click="showSamplerPicker = true"
|
|
||||||
/>
|
|
||||||
<van-popup v-model:show="showSamplerPicker" position="bottom" teleport="#app">
|
|
||||||
<van-picker
|
|
||||||
:columns="samplers"
|
|
||||||
@cancel="showSamplerPicker = false"
|
|
||||||
@confirm="samplerConfirm"
|
|
||||||
/>
|
|
||||||
</van-popup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<van-field label="图片尺寸">
|
|
||||||
<template #input>
|
|
||||||
<van-row gutter="20">
|
|
||||||
<van-col span="12">
|
|
||||||
<el-input v-model="params.width" size="small" placeholder="宽"/>
|
|
||||||
</van-col>
|
|
||||||
<van-col span="12">
|
|
||||||
<el-input v-model="params.height" size="small" placeholder="高"/>
|
|
||||||
</van-col>
|
|
||||||
</van-row>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
|
|
||||||
<van-field v-model.number="params.steps" label="迭代步数"
|
|
||||||
placeholder="">
|
|
||||||
<template #right-icon>
|
|
||||||
<van-icon name="info-o"
|
|
||||||
@click="showInfo('值越大则代表细节越多,同时也意味着出图速度越慢,一般推荐20-30')"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
<van-field v-model.number="params.cfg_scale" label="引导系数" placeholder="">
|
|
||||||
<template #right-icon>
|
|
||||||
<van-icon name="info-o"
|
|
||||||
@click="showInfo('提示词引导系数,图像在多大程度上服从提示词,较低值会产生更有创意的结果')"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
<van-field v-model.number="params.seed" label="随机因子" placeholder="">
|
|
||||||
<template #right-icon>
|
|
||||||
<van-icon name="info-o"
|
|
||||||
@click="showInfo('随机数种子,相同的种子会得到相同的结果,设置为 -1 则每次随机生成种子')"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
|
|
||||||
<van-field label="高清修复">
|
|
||||||
<template #input>
|
|
||||||
<van-switch v-model="params.hd_fix"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
|
|
||||||
<div v-if="params.hd_fix">
|
|
||||||
<div>
|
|
||||||
<van-field
|
|
||||||
v-model="params.hd_scale_alg"
|
|
||||||
is-link
|
|
||||||
readonly
|
|
||||||
label="放大算法"
|
|
||||||
placeholder="选择放大算法"
|
|
||||||
@click="showUpscalePicker = true"
|
|
||||||
/>
|
|
||||||
<van-popup v-model:show="showUpscalePicker" position="bottom" teleport="#app">
|
|
||||||
<van-picker
|
|
||||||
:columns="upscaleAlgArr"
|
|
||||||
@cancel="showUpscalePicker = false"
|
|
||||||
@confirm="upscaleConfirm"
|
|
||||||
/>
|
|
||||||
</van-popup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<van-field v-model.number="params.hd_scale" label="放大倍数"/>
|
|
||||||
<van-field v-model.number="params.hd_steps" label="迭代步数"/>
|
|
||||||
|
|
||||||
<van-field label="重绘幅度">
|
|
||||||
<template #input>
|
|
||||||
<van-slider v-model.number="params.hd_redraw_rate" :max="1" :step="0.1"
|
|
||||||
@update:model-value="showToast('当前值:' + params.hd_redraw_rate)"/>
|
|
||||||
</template>
|
|
||||||
<template #right-icon>
|
|
||||||
<van-icon name="info-o"
|
|
||||||
@click="showInfo('决定算法对图像内容的影响程度,较大的值将得到越有创意的图像')"/>
|
|
||||||
</template>
|
|
||||||
</van-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<van-field
|
|
||||||
v-model="params.prompt"
|
|
||||||
rows="3"
|
|
||||||
autosize
|
|
||||||
type="textarea"
|
|
||||||
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<van-collapse v-model="activeColspan">
|
|
||||||
<van-collapse-item title="反向提示词" name="neg_prompt">
|
|
||||||
<van-field
|
|
||||||
v-model="params.neg_prompt"
|
|
||||||
rows="3"
|
|
||||||
autosize
|
|
||||||
type="textarea"
|
|
||||||
placeholder="不想出现在图片上的元素(例如:树,建筑)"
|
|
||||||
/>
|
|
||||||
</van-collapse-item>
|
|
||||||
</van-collapse>
|
|
||||||
|
|
||||||
<div class="text-line pt-6">
|
|
||||||
<el-tag>绘图消耗{{ sdPower }}算力,当前算力:{{ power }}</el-tag>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-line">
|
|
||||||
<van-button round block type="primary" native-type="submit">
|
|
||||||
立即生成
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</van-cell-group>
|
|
||||||
</van-form>
|
|
||||||
|
|
||||||
<h3>任务列表</h3>
|
|
||||||
<div class="running-job-list">
|
|
||||||
<van-empty v-if="runningJobs.length ===0"
|
|
||||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
|
||||||
image-size="80"
|
|
||||||
description="暂无记录"
|
|
||||||
/>
|
|
||||||
<van-grid :gutter="10" :column-num="3" v-else>
|
|
||||||
<van-grid-item v-for="item in runningJobs">
|
|
||||||
<div v-if="item.progress > 0">
|
|
||||||
<van-image :src="item['img_url']">
|
|
||||||
<template v-slot:error>加载失败</template>
|
|
||||||
</van-image>
|
|
||||||
<div class="progress">
|
|
||||||
<van-circle
|
|
||||||
v-model:current-rate="item.progress"
|
|
||||||
:rate="item.progress"
|
|
||||||
:speed="100"
|
|
||||||
:text="item.progress+'%'"
|
|
||||||
:stroke-width="60"
|
|
||||||
size="90px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="task-in-queue">
|
|
||||||
<span class="icon"><i class="iconfont icon-quick-start"></i></span>
|
|
||||||
<span class="text">排队中</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</van-grid-item>
|
|
||||||
</van-grid>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>创作记录</h3>
|
|
||||||
<div class="finish-job-list">
|
|
||||||
<van-empty v-if="finishedJobs.length ===0"
|
|
||||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
|
||||||
image-size="80"
|
|
||||||
description="暂无记录"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<van-list v-else
|
|
||||||
v-model:error="error"
|
|
||||||
v-model:loading="loading"
|
|
||||||
:finished="finished"
|
|
||||||
error-text="请求失败,点击重新加载"
|
|
||||||
finished-text="没有更多了"
|
|
||||||
@load="onLoad"
|
|
||||||
>
|
|
||||||
<van-grid :gutter="10" :column-num="2">
|
|
||||||
<van-grid-item v-for="item in finishedJobs">
|
|
||||||
<div class="job-item">
|
|
||||||
<van-image
|
|
||||||
:src="item['img_url']"
|
|
||||||
:class="item['can_opt'] ? '' : 'upscale'"
|
|
||||||
lazy-load
|
|
||||||
@click="imageView(item)"
|
|
||||||
fit="cover">
|
|
||||||
<template v-slot:loading>
|
|
||||||
<van-loading type="spinner" size="20"/>
|
|
||||||
</template>
|
|
||||||
</van-image>
|
|
||||||
|
|
||||||
<div class="remove">
|
|
||||||
<el-button type="danger" :icon="Delete" @click="removeImage($event, item)" circle/>
|
|
||||||
<el-button type="warning" v-if="item.publish" @click="publishImage($event,item, false)"
|
|
||||||
circle>
|
|
||||||
<i class="iconfont icon-cancel-share"></i>
|
|
||||||
</el-button>
|
|
||||||
<el-button type="success" v-else @click="publishImage($event, item, true)" circle>
|
|
||||||
<i class="iconfont icon-share-bold"></i>
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="showTask(item)" circle>
|
|
||||||
<i class="iconfont icon-prompt"></i>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</van-grid-item>
|
|
||||||
</van-grid>
|
|
||||||
</van-list>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {onMounted, onUnmounted, ref} from "vue"
|
|
||||||
import {Delete} from "@element-plus/icons-vue";
|
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
|
||||||
import Clipboard from "clipboard";
|
|
||||||
import {checkSession} from "@/action/session";
|
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
import {getSessionId} from "@/store/session";
|
|
||||||
import {
|
|
||||||
showConfirmDialog, showDialog,
|
|
||||||
showFailToast,
|
|
||||||
showImagePreview,
|
|
||||||
showNotify,
|
|
||||||
showSuccessToast,
|
|
||||||
showToast
|
|
||||||
} from "vant";
|
|
||||||
|
|
||||||
const listBoxHeight = ref(window.innerHeight - 40)
|
|
||||||
const mjBoxHeight = ref(window.innerHeight - 150)
|
|
||||||
const showTaskDialog = ref(false)
|
|
||||||
const item = ref({})
|
|
||||||
const showLoginDialog = ref(false)
|
|
||||||
const isLogin = ref(false)
|
|
||||||
const activeColspan = ref([""])
|
|
||||||
|
|
||||||
window.onresize = () => {
|
|
||||||
listBoxHeight.value = window.innerHeight - 40
|
|
||||||
mjBoxHeight.value = window.innerHeight - 150
|
|
||||||
}
|
|
||||||
const samplers = ref([
|
|
||||||
{text: "Euler a", value: "Euler a"},
|
|
||||||
{text: "DPM++ 2S a Karras", value: "DPM++ 2S a Karras"},
|
|
||||||
{text: "DPM++ 2M Karras", value: "DPM++ 2M Karras"},
|
|
||||||
{text: "DPM++ 2M SDE Karras", value: "DPM++ 2M SDE Karras"},
|
|
||||||
{text: "DPM++ 2M Karras", value: "DPM++ 2M Karras"},
|
|
||||||
{text: "DPM++ 3M SDE Karras", value: "DPM++ 3M SDE Karras"},
|
|
||||||
])
|
|
||||||
const showSamplerPicker = ref(false)
|
|
||||||
|
|
||||||
const upscaleAlgArr = ref([
|
|
||||||
{text: "Latent", value: "Latent"},
|
|
||||||
{text: "ESRGAN_4x", value: "ESRGAN_4x"},
|
|
||||||
{text: "ESRGAN 4x+", value: "ESRGAN 4x+"},
|
|
||||||
{text: "SwinIR_4x", value: "SwinIR_4x"},
|
|
||||||
{text: "LDSR", value: "LDSR"},
|
|
||||||
])
|
|
||||||
const showUpscalePicker = ref(false)
|
|
||||||
|
|
||||||
const params = ref({
|
|
||||||
width: 1024,
|
|
||||||
height: 1024,
|
|
||||||
sampler: samplers.value[0].value,
|
|
||||||
seed: -1,
|
|
||||||
steps: 20,
|
|
||||||
cfg_scale: 7,
|
|
||||||
hd_fix: false,
|
|
||||||
hd_redraw_rate: 0.7,
|
|
||||||
hd_scale: 2,
|
|
||||||
hd_scale_alg: upscaleAlgArr.value[0].value,
|
|
||||||
hd_steps: 0,
|
|
||||||
prompt: "",
|
|
||||||
neg_prompt: "nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet",
|
|
||||||
})
|
|
||||||
|
|
||||||
const runningJobs = ref([])
|
|
||||||
const finishedJobs = ref([])
|
|
||||||
const router = useRouter()
|
|
||||||
// 检查是否有画同款的参数
|
|
||||||
const _params = router.currentRoute.value.params["copyParams"]
|
|
||||||
if (_params) {
|
|
||||||
params.value = JSON.parse(_params)
|
|
||||||
}
|
|
||||||
const power = ref(0)
|
|
||||||
const sdPower = ref(0) // 画一张 SD 图片消耗算力
|
|
||||||
|
|
||||||
const socket = ref(null)
|
|
||||||
const userId = ref(0)
|
|
||||||
const heartbeatHandle = ref(null)
|
|
||||||
const connect = () => {
|
|
||||||
let host = process.env.VUE_APP_WS_HOST
|
|
||||||
if (host === '') {
|
|
||||||
if (location.protocol === 'https:') {
|
|
||||||
host = 'wss://' + location.host;
|
|
||||||
} else {
|
|
||||||
host = 'ws://' + location.host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 心跳函数
|
|
||||||
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 => {
|
|
||||||
if (event.data instanceof Blob) {
|
|
||||||
fetchRunningJobs()
|
|
||||||
finished.value = false
|
|
||||||
page.value = 1
|
|
||||||
fetchFinishJobs(page.value)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_socket.addEventListener('close', () => {
|
|
||||||
if (socket.value !== null) {
|
|
||||||
connect()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const clipboard = ref(null)
|
|
||||||
onMounted(() => {
|
|
||||||
initData()
|
|
||||||
clipboard.value = new Clipboard('.copy-prompt-sd');
|
|
||||||
clipboard.value.on('success', () => {
|
|
||||||
showNotify({type: "success", message: "复制成功!"});
|
|
||||||
})
|
|
||||||
|
|
||||||
clipboard.value.on('error', () => {
|
|
||||||
showNotify({type: "danger", message: '复制失败!'});
|
|
||||||
})
|
|
||||||
|
|
||||||
httpGet("/api/config/get?key=system").then(res => {
|
|
||||||
sdPower.value = res.data["sd_power"]
|
|
||||||
}).catch(e => {
|
|
||||||
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
clipboard.value.destroy()
|
|
||||||
socket.value = null
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const initData = () => {
|
|
||||||
checkSession().then(user => {
|
|
||||||
power.value = user['power']
|
|
||||||
userId.value = user.id
|
|
||||||
isLogin.value = true
|
|
||||||
fetchRunningJobs()
|
|
||||||
fetchFinishJobs(1)
|
|
||||||
connect()
|
|
||||||
}).catch(() => {
|
|
||||||
loading.value = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchRunningJobs = () => {
|
|
||||||
// 获取运行中的任务
|
|
||||||
httpGet(`/api/sd/jobs?status=0`).then(res => {
|
|
||||||
const jobs = res.data
|
|
||||||
const _jobs = []
|
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
|
||||||
if (jobs[i].progress === -1) {
|
|
||||||
showNotify({
|
|
||||||
message: `任务ID:${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
|
|
||||||
type: 'danger',
|
|
||||||
})
|
|
||||||
power.value += sdPower.value
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_jobs.push(jobs[i])
|
|
||||||
}
|
|
||||||
runningJobs.value = _jobs
|
|
||||||
}).catch(e => {
|
|
||||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const finished = ref(false)
|
|
||||||
const error = ref(false)
|
|
||||||
const page = ref(0)
|
|
||||||
const pageSize = ref(10)
|
|
||||||
// 获取已完成的任务
|
|
||||||
const fetchFinishJobs = (page) => {
|
|
||||||
loading.value = true
|
|
||||||
httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
|
||||||
if (res.data.length < pageSize.value) {
|
|
||||||
finished.value = true
|
|
||||||
}
|
|
||||||
if (page === 1) {
|
|
||||||
finishedJobs.value = res.data
|
|
||||||
} else {
|
|
||||||
finishedJobs.value = finishedJobs.value.concat(res.data)
|
|
||||||
}
|
|
||||||
loading.value = false
|
|
||||||
}).catch(e => {
|
|
||||||
loading.value = false
|
|
||||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLoad = () => {
|
|
||||||
page.value += 1
|
|
||||||
fetchFinishJobs(page.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建绘图任务
|
|
||||||
const promptRef = ref(null)
|
|
||||||
const generate = () => {
|
|
||||||
if (params.value.prompt === '') {
|
|
||||||
promptRef.value.focus()
|
|
||||||
return showToast("请输入绘画提示词!")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLogin.value) {
|
|
||||||
showLoginDialog.value = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.value.seed === '') {
|
|
||||||
params.value.seed = -1
|
|
||||||
}
|
|
||||||
params.value.session_id = getSessionId()
|
|
||||||
httpPost("/api/sd/image", params.value).then(() => {
|
|
||||||
showSuccessToast("绘画任务推送成功,请耐心等待任务执行...")
|
|
||||||
power.value -= sdPower.value
|
|
||||||
}).catch(e => {
|
|
||||||
showFailToast("任务推送失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const showTask = (row) => {
|
|
||||||
item.value = row
|
|
||||||
showTaskDialog.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyParams = (row) => {
|
|
||||||
params.value = row.params
|
|
||||||
showTaskDialog.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeImage = (event, item) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
showConfirmDialog({
|
|
||||||
title: '标题',
|
|
||||||
message:
|
|
||||||
'此操作将会删除任务和图片,继续操作码?',
|
|
||||||
}).then(() => {
|
|
||||||
httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
|
||||||
showSuccessToast("任务删除成功")
|
|
||||||
}).catch(e => {
|
|
||||||
showFailToast("任务删除失败:" + e.message)
|
|
||||||
})
|
|
||||||
}).catch(() => {
|
|
||||||
showToast("您取消了操作")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发布图片到作品墙
|
|
||||||
const publishImage = (event, item, action) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
let text = "图片发布"
|
|
||||||
if (action === false) {
|
|
||||||
text = "取消发布"
|
|
||||||
}
|
|
||||||
httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
|
||||||
showSuccessToast(text + "成功")
|
|
||||||
item.publish = action
|
|
||||||
}).catch(e => {
|
|
||||||
showFailToast(text + "失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageView = (item) => {
|
|
||||||
showImagePreview([item['img_url']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const samplerConfirm = (item) => {
|
|
||||||
params.value.sampler = item.selectedOptions[0].text;
|
|
||||||
showSamplerPicker.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const upscaleConfirm = (item) => {
|
|
||||||
params.value.hd_scale_alg = item.selectedOptions[0].text;
|
|
||||||
showUpscalePicker.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const showInfo = (message) => {
|
|
||||||
showDialog({
|
|
||||||
title: "参数说明",
|
|
||||||
message: message,
|
|
||||||
}).then(() => {
|
|
||||||
// on close
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
@import "@/assets/css/mobile/image-sd.styl"
|
|
||||||
</style>
|
|
@ -1,133 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="img-wall container">
|
|
||||||
<van-nav-bar :title="title"/>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<van-tabs v-model:active="activeName">
|
|
||||||
<van-tab title="MidJourney" name="mj">
|
|
||||||
<van-list
|
|
||||||
v-model:error="data['mj'].error"
|
|
||||||
v-model:loading="data['mj'].loading"
|
|
||||||
:finished="data['mj'].finished"
|
|
||||||
error-text="请求失败,点击重新加载"
|
|
||||||
finished-text="没有更多了"
|
|
||||||
@load="onLoad"
|
|
||||||
style="height: 100%;width: 100%;"
|
|
||||||
>
|
|
||||||
<van-cell v-for="item in data['mj'].data" :key="item.id">
|
|
||||||
<van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
|
|
||||||
</van-cell>
|
|
||||||
</van-list>
|
|
||||||
</van-tab>
|
|
||||||
<van-tab title="StableDiffusion" name="sd">
|
|
||||||
<van-list
|
|
||||||
v-model:error="data['sd'].error"
|
|
||||||
v-model:loading="data['sd'].loading"
|
|
||||||
:finished="data['sd'].finished"
|
|
||||||
error-text="请求失败,点击重新加载"
|
|
||||||
finished-text="没有更多了"
|
|
||||||
@load="onLoad"
|
|
||||||
>
|
|
||||||
<van-cell v-for="item in data['sd'].data" :key="item.id">
|
|
||||||
<van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
|
|
||||||
</van-cell>
|
|
||||||
</van-list>
|
|
||||||
</van-tab>
|
|
||||||
<van-tab title="DALLE3" name="dalle3">
|
|
||||||
<van-empty description="功能正在开发中"/>
|
|
||||||
</van-tab>
|
|
||||||
</van-tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {onMounted, ref} from "vue";
|
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
|
||||||
import {showDialog, showFailToast, showSuccessToast} from "vant";
|
|
||||||
import {ElMessage} from "element-plus";
|
|
||||||
|
|
||||||
const title = ref('图片创作广场')
|
|
||||||
const activeName = ref("mj")
|
|
||||||
const data = ref({
|
|
||||||
"mj": {
|
|
||||||
loading: false,
|
|
||||||
finished: false,
|
|
||||||
error: false,
|
|
||||||
page: 1,
|
|
||||||
pageSize: 12,
|
|
||||||
url: "/api/mj/jobs",
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
"sd": {
|
|
||||||
loading: false,
|
|
||||||
finished: false,
|
|
||||||
error: false,
|
|
||||||
page: 1,
|
|
||||||
pageSize: 12,
|
|
||||||
url: "/api/sd/jobs",
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
"dalle3": {
|
|
||||||
loading: false,
|
|
||||||
finished: false,
|
|
||||||
error: false,
|
|
||||||
page: 1,
|
|
||||||
pageSize: 12,
|
|
||||||
url: "/api/dalle3/jobs",
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const onLoad = () => {
|
|
||||||
const d = data.value[activeName.value]
|
|
||||||
httpGet(`${d.url}?status=1&page=${d.page}&page_size=${d.pageSize}&publish=true`).then(res => {
|
|
||||||
d.loading = false
|
|
||||||
if (res.data.length === 0) {
|
|
||||||
d.finished = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成缩略图
|
|
||||||
const imageList = res.data
|
|
||||||
for (let i = 0; i < imageList.length; i++) {
|
|
||||||
imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
|
|
||||||
}
|
|
||||||
if (imageList.length < d.pageSize) {
|
|
||||||
d.finished = true
|
|
||||||
}
|
|
||||||
if (d.data.length === 0) {
|
|
||||||
d.data = imageList
|
|
||||||
} else {
|
|
||||||
d.data = d.data.concat(imageList)
|
|
||||||
}
|
|
||||||
d.page += 1
|
|
||||||
}).catch(() => {
|
|
||||||
d.error = true
|
|
||||||
showFailToast("加载图片数据失败")
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const showPrompt = (item) => {
|
|
||||||
showDialog({
|
|
||||||
title: "绘画提示词",
|
|
||||||
message: item.prompt,
|
|
||||||
}).then(() => {
|
|
||||||
// on close
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus">
|
|
||||||
.img-wall {
|
|
||||||
.content {
|
|
||||||
padding-top 60px
|
|
||||||
|
|
||||||
.van-cell__value {
|
|
||||||
.van-image {
|
|
||||||
width 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -302,7 +302,7 @@ const pay = (payWay, item) => {
|
|||||||
if (isWeChatBrowser() && payWay === 'wechat') {
|
if (isWeChatBrowser() && payWay === 'wechat') {
|
||||||
showFailToast("请在系统自带浏览器打开支付页面,或者在 PC 端进行扫码支付")
|
showFailToast("请在系统自带浏览器打开支付页面,或者在 PC 端进行扫码支付")
|
||||||
} else {
|
} else {
|
||||||
location.href = res.data
|
location.href = res.data.url
|
||||||
}
|
}
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
showFailToast("生成支付订单失败:" + e.message)
|
showFailToast("生成支付订单失败:" + e.message)
|
||||||
|
@ -317,7 +317,7 @@ const initData = () => {
|
|||||||
|
|
||||||
const fetchRunningJobs = () => {
|
const fetchRunningJobs = () => {
|
||||||
// 获取运行中的任务
|
// 获取运行中的任务
|
||||||
httpGet(`/api/dall/jobs?status=0`).then(res => {
|
httpGet(`/api/dall/jobs?finish=0`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
const _jobs = []
|
const _jobs = []
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
@ -345,7 +345,7 @@ const pageSize = ref(10)
|
|||||||
// 获取已完成的任务
|
// 获取已完成的任务
|
||||||
const fetchFinishJobs = (page) => {
|
const fetchFinishJobs = (page) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
httpGet(`/api/dall/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||||
if (res.data.length < pageSize.value) {
|
if (res.data.length < pageSize.value) {
|
||||||
finished.value = true
|
finished.value = true
|
||||||
}
|
}
|
||||||
@ -410,7 +410,7 @@ const removeImage = (event, item) => {
|
|||||||
message:
|
message:
|
||||||
'此操作将会删除任务和图片,继续操作码?',
|
'此操作将会删除任务和图片,继续操作码?',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
httpGet("/api/dall/remove", {id: item.id, user_id: item.user_id}).then(() => {
|
||||||
showSuccessToast("任务删除成功")
|
showSuccessToast("任务删除成功")
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
showFailToast("任务删除失败:" + e.message)
|
showFailToast("任务删除失败:" + e.message)
|
||||||
@ -427,7 +427,7 @@ const publishImage = (event, item, action) => {
|
|||||||
if (action === false) {
|
if (action === false) {
|
||||||
text = "取消发布"
|
text = "取消发布"
|
||||||
}
|
}
|
||||||
httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
|
httpGet("/api/dall/publish", {id: item.id, action: action, user_id: item.user_id}).then(() => {
|
||||||
showSuccessToast(text + "成功")
|
showSuccessToast(text + "成功")
|
||||||
item.publish = action
|
item.publish = action
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
@ -421,7 +421,7 @@ const connect = () => {
|
|||||||
|
|
||||||
// 获取运行中的任务
|
// 获取运行中的任务
|
||||||
const fetchRunningJobs = (userId) => {
|
const fetchRunningJobs = (userId) => {
|
||||||
httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
|
httpGet(`/api/mj/jobs?finish=0&user_id=${userId}`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
const _jobs = []
|
const _jobs = []
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
@ -453,7 +453,7 @@ const pageSize = ref(10)
|
|||||||
const fetchFinishJobs = (page) => {
|
const fetchFinishJobs = (page) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
// 获取已完成的任务
|
// 获取已完成的任务
|
||||||
httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
httpGet(`/api/mj/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
if (jobs[i].progress === -1) {
|
if (jobs[i].progress === -1) {
|
||||||
@ -600,7 +600,7 @@ const removeImage = (item) => {
|
|||||||
message:
|
message:
|
||||||
'此操作将会删除任务和图片,继续操作码?',
|
'此操作将会删除任务和图片,继续操作码?',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
httpGet("/api/mj/remove", {id: item.id, user_id: item.user_id}).then(() => {
|
||||||
showSuccessToast("任务删除成功")
|
showSuccessToast("任务删除成功")
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
showFailToast("任务删除失败:" + e.message)
|
showFailToast("任务删除失败:" + e.message)
|
||||||
@ -615,7 +615,7 @@ const publishImage = (item, action) => {
|
|||||||
if (action === false) {
|
if (action === false) {
|
||||||
text = "取消发布"
|
text = "取消发布"
|
||||||
}
|
}
|
||||||
httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
|
httpGet("/api/mj/publish", {id: item.id, action: action,user_id: item.user_id}).then(() => {
|
||||||
showSuccessToast(text + "成功")
|
showSuccessToast(text + "成功")
|
||||||
item.publish = action
|
item.publish = action
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
@ -381,7 +381,7 @@ const initData = () => {
|
|||||||
|
|
||||||
const fetchRunningJobs = () => {
|
const fetchRunningJobs = () => {
|
||||||
// 获取运行中的任务
|
// 获取运行中的任务
|
||||||
httpGet(`/api/sd/jobs?status=0`).then(res => {
|
httpGet(`/api/sd/jobs?finish=0`).then(res => {
|
||||||
const jobs = res.data
|
const jobs = res.data
|
||||||
const _jobs = []
|
const _jobs = []
|
||||||
for (let i = 0; i < jobs.length; i++) {
|
for (let i = 0; i < jobs.length; i++) {
|
||||||
@ -409,7 +409,7 @@ const pageSize = ref(10)
|
|||||||
// 获取已完成的任务
|
// 获取已完成的任务
|
||||||
const fetchFinishJobs = (page) => {
|
const fetchFinishJobs = (page) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
httpGet(`/api/sd/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||||
if (res.data.length < pageSize.value) {
|
if (res.data.length < pageSize.value) {
|
||||||
finished.value = true
|
finished.value = true
|
||||||
}
|
}
|
||||||
@ -474,7 +474,7 @@ const removeImage = (event, item) => {
|
|||||||
message:
|
message:
|
||||||
'此操作将会删除任务和图片,继续操作码?',
|
'此操作将会删除任务和图片,继续操作码?',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
|
||||||
showSuccessToast("任务删除成功")
|
showSuccessToast("任务删除成功")
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
showFailToast("任务删除失败:" + e.message)
|
showFailToast("任务删除失败:" + e.message)
|
||||||
@ -491,7 +491,7 @@ const publishImage = (event, item, action) => {
|
|||||||
if (action === false) {
|
if (action === false) {
|
||||||
text = "取消发布"
|
text = "取消发布"
|
||||||
}
|
}
|
||||||
httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => {
|
||||||
showSuccessToast(text + "成功")
|
showSuccessToast(text + "成功")
|
||||||
item.publish = action
|
item.publish = action
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
Loading…
Reference in New Issue
Block a user