From 855a953010d59e0bbd77bc08b862e4c86740e816 Mon Sep 17 00:00:00 2001 From: RockYang Date: Fri, 1 Mar 2024 23:08:05 +0800 Subject: [PATCH 01/29] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 97363624..6970e36f 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,11 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 **演示站不提供任何充值点卡售卖或者VIP充值服务。** 如果您体验过后觉得还不错的话,可以花两分钟用下面的一键部署脚本自己部署一套。 ```shell -bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.5-400fea2598.sh)" +bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.7-6c232bdaf8.sh)" ``` +最新版本的一键部署脚本请参考 [**ChatGPT-Plus 文档**](https://ai.r9it.com/docs/install/)。 + 目前仅支持 Ubuntu 和 Centos 系统。 部署成功之后可以访问下面地址 * 前端访问地址:http://localhost:8080/chat 使用移动设备访问会自动跳转到移动端页面。 From 04e990aeb385565c1e77c454d594ebfe8c32a814 Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 24 Jan 2024 09:33:04 +0800 Subject: [PATCH 02/29] feat: add websocket heartbeat message for mj page --- api/handler/sd_handler.go | 23 +++++++++++++++++++++++ api/service/sd/pool.go | 17 +++++++++++------ api/service/sd/service.go | 8 ++++++-- web/src/views/ImageMj.vue | 9 +++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/api/handler/sd_handler.go b/api/handler/sd_handler.go index 171a57fd..37a0e6e4 100644 --- a/api/handler/sd_handler.go +++ b/api/handler/sd_handler.go @@ -11,6 +11,8 @@ import ( "chatplus/utils/resp" "encoding/base64" "fmt" + "github.com/gorilla/websocket" + "net/http" "time" "github.com/gin-gonic/gin" @@ -36,6 +38,27 @@ func NewSdJobHandler(app *core.AppServer, db *gorm.DB, pool *sd.ServicePool, man return &h } +// Client WebSocket 客户端,用于通知任务状态变更 +func (h *SdJobHandler) Client(c *gin.Context) { + ws, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil) + if err != nil { + logger.Error(err) + c.Abort() + return + } + + userId := h.GetInt(c, "user_id", 0) + if userId == 0 { + logger.Info("Invalid user ID") + c.Abort() + return + } + + client := types.NewWsClient(ws) + h.pool.Clients.Put(uint(userId), client) + logger.Infof("New websocket connected, IP: %s", c.RemoteIP()) +} + func (h *SdJobHandler) checkLimits(c *gin.Context) bool { user, err := utils.GetLoginUser(c, h.db) if err != nil { diff --git a/api/service/sd/pool.go b/api/service/sd/pool.go index b31aedf9..131b66c1 100644 --- a/api/service/sd/pool.go +++ b/api/service/sd/pool.go @@ -11,13 +11,16 @@ import ( ) type ServicePool struct { - services []*Service - taskQueue *store.RedisQueue + services []*Service + taskQueue *store.RedisQueue + notifyQueue *store.RedisQueue + Clients *types.LMap[uint, *types.WsClient] // UserId => Client } func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig) *ServicePool { services := make([]*Service, 0) - queue := store.NewRedisQueue("StableDiffusion_Task_Queue", redisCli) + taskQueue := store.NewRedisQueue("StableDiffusion_Task_Queue", redisCli) + notifyQueue := store.NewRedisQueue("StableDiffusion_Queue", redisCli) // create mj client and service for k, config := range appConfig.SdConfigs { if config.Enabled == false { @@ -26,7 +29,7 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa // create sd service name := fmt.Sprintf("StableDifffusion Service-%d", k) - service := NewService(name, 1, 300, config, queue, db, manager) + service := NewService(name, 1, 300, config, taskQueue, notifyQueue, db, manager) // run sd service go func() { service.Run() @@ -36,8 +39,10 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa } return &ServicePool{ - taskQueue: queue, - services: services, + taskQueue: taskQueue, + notifyQueue: notifyQueue, + services: services, + Clients: types.NewLMap[uint, *types.WsClient](), } } diff --git a/api/service/sd/service.go b/api/service/sd/service.go index 1741028f..c090583f 100644 --- a/api/service/sd/service.go +++ b/api/service/sd/service.go @@ -24,6 +24,7 @@ type Service struct { httpClient *req.Client config types.StableDiffusionConfig taskQueue *store.RedisQueue + notifyQueue *store.RedisQueue db *gorm.DB uploadManager *oss.UploaderManager name string // service name @@ -33,12 +34,13 @@ type Service struct { taskTimeout int64 } -func NewService(name string, maxTaskNum int32, timeout int64, config types.StableDiffusionConfig, queue *store.RedisQueue, db *gorm.DB, manager *oss.UploaderManager) *Service { +func NewService(name string, maxTaskNum int32, timeout int64, config types.StableDiffusionConfig, taskQueue *store.RedisQueue, notifyQueue *store.RedisQueue, db *gorm.DB, manager *oss.UploaderManager) *Service { return &Service{ name: name, config: config, httpClient: req.C(), - taskQueue: queue, + taskQueue: taskQueue, + notifyQueue: notifyQueue, db: db, uploadManager: manager, taskTimeout: timeout, @@ -73,6 +75,8 @@ func (s *Service) Run() { s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) // release task num atomic.AddInt32(&s.handledTaskNum, -1) + // 通知前端,任务失败 + s.notifyQueue.RPush(task.UserId) continue } diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index 5e3b36e8..6a909aab 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -563,6 +563,7 @@ const translatePrompt = () => { }) } +const heartbeatHandle = ref(null) const connect = () => { let host = process.env.VUE_APP_WS_HOST if (host === '') { @@ -575,6 +576,14 @@ const connect = () => { const _socket = new WebSocket(host + `/api/mj/client?user_id=${userId.value}`); _socket.addEventListener('open', () => { socket.value = _socket; + + // 发送心跳消息 + clearInterval(heartbeatHandle.value) + heartbeatHandle.value = setInterval(() => { + if (socket.value !== null) { + socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"})) + } + }, 5000); }); _socket.addEventListener('message', event => { From 6d9b9bd3f7678fbbb92cb3adc91f6a2518433a3a Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 24 Jan 2024 17:34:30 +0800 Subject: [PATCH 03/29] feat: use vant replace element-plus as mobile UI framework --- web/public/index.html | 2 +- web/src/assets/css/mobile/apps.css | 17 - web/src/assets/css/mobile/apps.styl | 20 - web/src/assets/css/mobile/chat-list.css | 47 -- web/src/assets/css/mobile/chat-list.styl | 58 -- web/src/assets/css/mobile/chat-session.css | 57 +- web/src/assets/css/mobile/chat-session.styl | 112 +-- web/src/assets/css/mobile/custom-scroll.css | 13 - web/src/assets/css/mobile/custom-scroll.styl | 26 - web/src/assets/css/mobile/home.css | 45 -- web/src/assets/css/mobile/home.styl | 45 -- web/src/assets/css/mobile/image-list.css | 252 ------- web/src/assets/css/mobile/image-list.styl | 106 --- web/src/assets/css/mobile/image-sd.css | 252 ------- web/src/assets/css/mobile/image-sd.styl | 106 --- web/src/assets/css/mobile/invitation.css | 93 --- web/src/assets/css/mobile/invitation.styl | 94 --- web/src/assets/css/mobile/profile.css | 142 ---- web/src/assets/css/mobile/profile.styl | 149 ---- web/src/assets/css/mobile/sd-task-dialog.css | 72 -- web/src/assets/css/mobile/sd-task-dialog.styl | 96 --- web/src/assets/css/mobile/task-list.css | 106 --- web/src/assets/css/mobile/task-list.styl | 142 ---- web/src/router.js | 16 +- web/src/views/ChatPlus.vue | 22 +- web/src/views/ImageMj.vue | 21 +- web/src/views/mobile/Apps.vue | 15 - web/src/views/mobile/ChatList.vue | 46 +- web/src/views/mobile/ChatSession.vue | 248 ++++--- web/src/views/mobile/Home.vue | 58 +- web/src/views/mobile/ImageSd.vue | 657 ------------------ web/src/views/mobile/Invitation.vue | 152 ---- web/src/views/mobile/Profile.vue | 365 +++------- 33 files changed, 437 insertions(+), 3215 deletions(-) delete mode 100644 web/src/assets/css/mobile/apps.css delete mode 100644 web/src/assets/css/mobile/apps.styl delete mode 100644 web/src/assets/css/mobile/chat-list.css delete mode 100644 web/src/assets/css/mobile/chat-list.styl delete mode 100644 web/src/assets/css/mobile/custom-scroll.css delete mode 100644 web/src/assets/css/mobile/custom-scroll.styl delete mode 100644 web/src/assets/css/mobile/home.css delete mode 100644 web/src/assets/css/mobile/home.styl delete mode 100644 web/src/assets/css/mobile/image-list.css delete mode 100644 web/src/assets/css/mobile/image-list.styl delete mode 100644 web/src/assets/css/mobile/image-sd.css delete mode 100644 web/src/assets/css/mobile/image-sd.styl delete mode 100644 web/src/assets/css/mobile/invitation.css delete mode 100644 web/src/assets/css/mobile/invitation.styl delete mode 100644 web/src/assets/css/mobile/profile.css delete mode 100644 web/src/assets/css/mobile/profile.styl delete mode 100644 web/src/assets/css/mobile/sd-task-dialog.css delete mode 100644 web/src/assets/css/mobile/sd-task-dialog.styl delete mode 100644 web/src/assets/css/mobile/task-list.css delete mode 100644 web/src/assets/css/mobile/task-list.styl delete mode 100644 web/src/views/mobile/Apps.vue delete mode 100644 web/src/views/mobile/ImageSd.vue delete mode 100644 web/src/views/mobile/Invitation.vue diff --git a/web/public/index.html b/web/public/index.html index 9b7921be..4a3b2b4e 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -4,7 +4,7 @@ - + ChatGPT-Plus diff --git a/web/src/assets/css/mobile/apps.css b/web/src/assets/css/mobile/apps.css deleted file mode 100644 index 8def06a8..00000000 --- a/web/src/assets/css/mobile/apps.css +++ /dev/null @@ -1,17 +0,0 @@ -.title { - color: #fff; - text-align: center; - font-size: 16px; - font-weight: bold; -} -.app-background { - background-color: #282828; - height: 100vh; -} -.mobile-setting .content { - padding-top: 60px; -} -.mobile-setting .content .van-field__label { - width: 100px; - text-align: right; -} diff --git a/web/src/assets/css/mobile/apps.styl b/web/src/assets/css/mobile/apps.styl deleted file mode 100644 index 235dcbd4..00000000 --- a/web/src/assets/css/mobile/apps.styl +++ /dev/null @@ -1,20 +0,0 @@ -.title { - color #fff - text-align center - font-size 16px - font-weight bold -} -.app-background { - background-color #282828 - height 100vh -} -.mobile-setting { - .content { - padding-top 60px - - .van-field__label { - width 100px - text-align right - } - } -} \ No newline at end of file diff --git a/web/src/assets/css/mobile/chat-list.css b/web/src/assets/css/mobile/chat-list.css deleted file mode 100644 index 88410db8..00000000 --- a/web/src/assets/css/mobile/chat-list.css +++ /dev/null @@ -1,47 +0,0 @@ -.app-background { - background-color: #1c1c1c; - height: 100vh; -} -.mobile-chat-list { - background-color: #1c1c1c; - position: absolute; - top: 40px; - width: 100%; -} -.content .van-cell__value .chat-list-item { - display: flex; - font-size: 16px; - color: #fff; - background-color: #1c1c1c; -} -.content .van-cell__value .chat-list-item .van-image { - min-width: 32px; - width: 32px; - height: 32px; -} -.content .van-cell__value .chat-list-item .van-ellipsis { - margin-top: 5px; - margin-left: 10px; -} -.van-picker-column .picker-option { - display: flex; - width: 100%; - padding: 0 10px; -} -.van-picker-column .picker-option .van-image { - width: 20px; - height: 20px; - margin-right: 5px; -} -.van-nav-bar .van-nav-bar__right .van-icon { - font-size: 20px; -} -.popup { - background-color: #1c1c1c; -} -.dialog { - background-color: #1c1c1c; -} -.field { - background-color: #1c1c1c; -} diff --git a/web/src/assets/css/mobile/chat-list.styl b/web/src/assets/css/mobile/chat-list.styl deleted file mode 100644 index a5362d80..00000000 --- a/web/src/assets/css/mobile/chat-list.styl +++ /dev/null @@ -1,58 +0,0 @@ -$fontSize = 16px; -.app-background { - background-color: #1c1c1c; - height: 100vh; -} -.mobile-chat-list { - background-color: #1c1c1c; - position: absolute; - top: 40px; - width: 100%; -} - .content { - .van-cell__value { - .chat-list-item { - display flex - font-size $fontSize - color: #fff; - background-color: #1c1c1c; - .van-image { - min-width 32px - width 32px - height 32px - } - .van-ellipsis { - margin-top 5px; - margin-left 10px; - } - } - } - } - .van-picker-column { - .picker-option { - display flex - width 100% - padding 0 10px - .van-image { - width 20px; - height 20px; - margin-right 5px - } - } - } - .van-nav-bar { - .van-nav-bar__right { - .van-icon { - font-size 20px; - } - } - } - .popup { - background-color: #1c1c1c; - } - .dialog { - background-color: #1c1c1c; - } - .field { - background-color: #1c1c1c; - } \ No newline at end of file diff --git a/web/src/assets/css/mobile/chat-session.css b/web/src/assets/css/mobile/chat-session.css index 1d077298..ca21ccaa 100644 --- a/web/src/assets/css/mobile/chat-session.css +++ b/web/src/assets/css/mobile/chat-session.css @@ -1,45 +1,36 @@ -.app-background { - background-color: #1c1c1c; - height: 100vh; +.mobile-chat .message-list-box { + padding-top: 50px; + padding-bottom: 10px; + overflow-x: auto; + background: #f5f5f5; } -body, -.mobile-chat, -.van-sticky, -.van-nav-bar, -.van-list, -.message-list-box, -.van-cell, -.chat-box, -.van-cell-group, -.van-field { - background-color: #1c1c1c !important; - color: #fff !important; +.mobile-chat .message-list-box .van-cell { + background: none; + font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; } -.chat-box-wrapper { - position: fixed; - bottom: 0; - width: 100%; - background-color: #1c1c1c; +.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset { + margin: 0; } -.icon-box .van-icon, -.mobile-chat .van-nav-bar__title .van-dropdown-menu__title, -.mobile-chat .van-nav-bar__title .van-cell__title, -.mobile-chat .van-nav-bar__right .van-icon { - color: #fff !important; +.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset .van-cell { + padding: 10px; +} +.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset .van-cell .icon-box .van-icon { + font-size: 24px; + margin: 10px 0; +} +.mobile-chat .chat-box-wrapper .van-sticky .van-cell-group--inset .van-cell .button-voice { + padding: 0 5px; + height: 30px; } .mobile-chat .van-nav-bar__title .van-dropdown-menu__title { - margin-right: 15px; + margin-right: 10px; } .mobile-chat .van-nav-bar__title .van-cell__title { text-align: left; } -.mobile-chat .chat-list-wrapper { - position: fixed; - top: 50px; - bottom: 60px; - width: 100vw; - overflow-y: scroll; -} .mobile-chat .van-nav-bar__right .van-icon { font-size: 20px; } +.van-theme-dark .mobile-chat .message-list-box { + background: #232425; +} diff --git a/web/src/assets/css/mobile/chat-session.styl b/web/src/assets/css/mobile/chat-session.styl index 861cc8b6..9d6dbf89 100644 --- a/web/src/assets/css/mobile/chat-session.styl +++ b/web/src/assets/css/mobile/chat-session.styl @@ -1,51 +1,61 @@ -.app-background { - background-color #1c1c1c - height 100vh - } - body, - .mobile-chat, - .van-sticky, - .van-nav-bar, - .van-list, - .message-list-box - .van-cell, - .chat-box, - .van-cell-group, - .van-field { - background-color #1c1c1c !important - color #fff !important - } - .chat-box-wrapper { - position fixed - bottom 0 - width 100% - background-color #1c1c1c - } - .icon-box .van-icon, - .mobile-chat .van-nav-bar__title .van-dropdown-menu__title, - .mobile-chat .van-nav-bar__title .van-cell__title, - .mobile-chat .van-nav-bar__right .van-icon { - color #fff !important - } - .mobile-chat { - .van-nav-bar__title { - .van-dropdown-menu__title { - margin-right 15px - } - .van-cell__title { - text-align left - } - } - .chat-list-wrapper { - position fixed - top 50px - bottom 60px - width 100vw - overflow-y scroll - } - .van-nav-bar__right { - .van-icon { - font-size 20px - } - } - } \ No newline at end of file +.mobile-chat { + .message-list-box { + padding-top 50px + padding-bottom 10px + overflow-x auto + background #F5F5F5; + + .van-cell { + background none + font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; + } + } + + .chat-box-wrapper { + .van-sticky { + .van-cell-group--inset { + margin 0 + + .van-cell { + padding 10px + + .icon-box { + .van-icon { + font-size 24px + margin 10px 0 + } + } + + .button-voice { + padding 0 5px + height 30px + } + } + } + } + } + + .van-nav-bar__title { + .van-dropdown-menu__title { + margin-right 10px + } + + .van-cell__title { + text-align left + } + } + + .van-nav-bar__right { + .van-icon { + font-size 20px + } + } +} + +.van-theme-dark { + .mobile-chat { + .message-list-box { + background #232425; + } + } +} \ No newline at end of file diff --git a/web/src/assets/css/mobile/custom-scroll.css b/web/src/assets/css/mobile/custom-scroll.css deleted file mode 100644 index eb7e1d0a..00000000 --- a/web/src/assets/css/mobile/custom-scroll.css +++ /dev/null @@ -1,13 +0,0 @@ -.custom-scroll ::-webkit-scrollbar { - width: 8px; /* 滚动条宽度 */ -} -.custom-scroll ::-webkit-scrollbar-track { - background-color: #282c34; -} -.custom-scroll ::-webkit-scrollbar-thumb { - background-color: #444; - border-radius: 8px; -} -.custom-scroll ::-webkit-scrollbar-thumb:hover { - background-color: #666; -} diff --git a/web/src/assets/css/mobile/custom-scroll.styl b/web/src/assets/css/mobile/custom-scroll.styl deleted file mode 100644 index eb03fff7..00000000 --- a/web/src/assets/css/mobile/custom-scroll.styl +++ /dev/null @@ -1,26 +0,0 @@ -.custom-scroll { - /* 修改滚动条的颜色 */ - - ::-webkit-scrollbar { - width: 8px; /* 滚动条宽度 */ - } - - /* 修改滚动条轨道的背景颜色 */ - - ::-webkit-scrollbar-track { - background-color: #282C34; - } - - /* 修改滚动条的滑块颜色 */ - - ::-webkit-scrollbar-thumb { - background-color: #444444; - border-radius 8px - } - - /* 修改滚动条的滑块的悬停颜色 */ - - ::-webkit-scrollbar-thumb:hover { - background-color: #666666; - } -} \ No newline at end of file diff --git a/web/src/assets/css/mobile/home.css b/web/src/assets/css/mobile/home.css deleted file mode 100644 index 25214688..00000000 --- a/web/src/assets/css/mobile/home.css +++ /dev/null @@ -1,45 +0,0 @@ -.my-tabbar { - background-color: #171717; - box-shadow: -3px 4px 16px #000; - box-sizing: border-box; -} -.banner { - display: flex; - justify-content: center; - align-items: center; - background-color: #171717; - border-bottom-right-radius: 10px; - box-shadow: 0 -4px 13px #000; - height: 40px; - width: 100%; - position: fixed; - z-index: 2 /* Add z-index */; -} -.banner-title { - color: #2cc995; - font-size: 20px; - font-weight: bold; - text-shadow: 0 -4px 13px #000; -} -.mobile-home { - background-color: #171717; -} -.mobile-home .van-tabbar before { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 50px; - background-color: #171717; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - opacity: 0; - transition: opacity 0.5s; - z-index: -1; -} -.mobile-home .van-tabbar.activeTab before { - opacity: 1; -} -.router-view { - margin-bottom: 50px; -} diff --git a/web/src/assets/css/mobile/home.styl b/web/src/assets/css/mobile/home.styl deleted file mode 100644 index d03c2a51..00000000 --- a/web/src/assets/css/mobile/home.styl +++ /dev/null @@ -1,45 +0,0 @@ -.my-tabbar { - background-color #171717 - box-shadow -3px 4px 16px #000 - box-sizing border-box -} -.banner { - display flex - justify-content center - align-items center - background-color #171717 - border-bottom-right-radius 10px - box-shadow 0 -4px 13px #000 - height 40px - width 100% - position fixed - z-index 2 /* Add z-index */ -} -.banner-title { - color #2CC995 - font-size 20px - font-weight bold - text-shadow 0 -4px 13px #000 -} -.mobile-home { - background-color #171717 -} -.mobile-home .van-tabbar before { - position absolute - bottom 0 - left 0 - right 0 - height 50px - background-color #171717 - border-top-left-radius 10px - border-top-right-radius 10px - opacity 0 - transition opacity 0.5s - z-index -1 -} -.mobile-home .van-tabbar.activeTab before { - opacity 1 -} -.router-view { - margin-bottom 50px -} \ No newline at end of file diff --git a/web/src/assets/css/mobile/image-list.css b/web/src/assets/css/mobile/image-list.css deleted file mode 100644 index 909001cc..00000000 --- a/web/src/assets/css/mobile/image-list.css +++ /dev/null @@ -1,252 +0,0 @@ - -.page-sd { - background-color: #282828; -} -.page-sd .inner { - display: flex; -} -.page-sd .inner .sd-box { - margin: 10px; - background-color: #282828; - min-width: 92%; - max-width: 92%; - padding: 0px; - border-radius: 0px; - color: #fff; - font-size: 14px; -} -.page-sd .inner .sd-box h2 { - font-weight: bold; - font-size: 20px; - text-align: center; - color: #fff; -} -.page-sd .inner .sd-box ::-webkit-scrollbar { - width: 0; - height: 0; - background-color: transparent; -} -.page-sd .inner .sd-box .sd-params { - margin-top: 10px; - overflow: auto; -} -.page-sd .inner .sd-box .sd-params .param-line { - padding: 0 10px; -} -.page-sd .inner .sd-box .sd-params .param-line .el-icon { - position: relative; - top: 3px; -} -.page-sd .inner .sd-box .sd-params .param-line .el-input__suffix-inner .el-icon { - top: 0; -} -.page-sd .inner .sd-box .sd-params .param-line .grid-content, -.page-sd .inner .sd-box .sd-params .param-line .form-item-inner { - display: flex; -} -.page-sd .inner .sd-box .sd-params .param-line .grid-content .el-icon, -.page-sd .inner .sd-box .sd-params .param-line .form-item-inner .el-icon { - margin-left: 10px; - margin-top: 2px; -} -.page-sd .inner .sd-box .sd-params .param-line.pt { - padding-top: 5px; - padding-bottom: 5px; -} -.page-sd .inner .sd-box .submit-btn { - padding: 10px 15px 0 15px; - text-align: center; -} -.page-sd .inner .sd-box .submit-btn .el-button { - width: 100%; -} -.page-sd .inner .sd-box .submit-btn .el-button span { - color: #2d3a4b; -} -.page-sd .inner .el-form .el-form-item__label { - color: #fff; -} -.page-sd .inner .task-list-box { - width: 100%; - padding: 10px; - color: #fff; - overflow-x: hidden; -} -.page-sd .inner .task-list-box .running-job-list .job-item { - width: 100%; - padding: 2px; - background-color: #555; -} -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner { - position: relative; - height: 100%; - overflow: hidden; -} -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - display: flex; - justify-content: center; - align-items: center; -} -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span { - font-size: 20px; - color: #fff; -} -.page-sd .inner .task-list-box .finish-job-list .job-item { - width: 100%; - height: 100%; - border: 1px solid #666; - padding: 6px; - overflow: hidden; - border-radius: 6px; - transition: all 0.3s ease; /* 添加过渡效果 */ -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line { - margin: 6px 0; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul { - display: flex; - flex-flow: row; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li { - margin-right: 6px; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a { - padding: 3px 0; - width: 40px; - text-align: center; - border-radius: 5px; - display: block; - cursor: pointer; - background-color: #4e5058; - color: #fff; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover { - background-color: #6d6f78; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt { - font-size: 20px; - cursor: pointer; -} -.page-sd .inner .task-list-box .finish-job-list .animate:hover { - box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ - transform: translateY(-10px); /* 向上移动10像素 */ -} -.page-sd .inner .task-list-box .el-image { - width: 100%; - height: 100%; - overflow: visible; -} -.page-sd .inner .task-list-box .el-image img { - height: 100%; -} -.page-sd .inner .task-list-box .el-image .el-image-viewer__wrapper img { - width: auto; - height: auto; -} -.page-sd .inner .task-list-box .el-image .image-slot { - display: flex; - flex-flow: column; - justify-content: center; - align-items: center; - height: 100%; - min-height: 200px; - color: #fff; - height: 240px; -} -.page-sd .inner .task-list-box .el-image .image-slot .iconfont { - font-size: 50px; - margin-bottom: 10px; -} -.page-sd .inner .task-list-box .el-image.upscale { - max-height: 100%; -} -.page-sd .inner .task-list-box .el-image.upscale img { - height: 100%; -} -.page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img { - width: auto; - height: auto; -} -.page-sd .el-overlay-dialog .el-dialog { - background-color: #1a1b1e; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__header .el-dialog__title { - color: #f5f5f5; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body { - padding: 0 0 0 15px !important; - display: flex; - height: 100%; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row { - width: 100%; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container { - display: flex; - justify-content: center; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot { - display: flex; - height: 100vh; - align-items: center; - justify-content: center; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot .el-icon { - font-size: 60px; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info { - background-color: #25262b; - padding: 1rem 1.5rem; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line { - width: 100%; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt { - background-color: #35363b; - padding: 10px; - color: #999; - overflow: auto; - max-height: 100%; - min-height: 100%; - position: relative; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt .el-icon { - position: absolute; - right: 10px; - bottom: 10px; - cursor: pointer; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper { - margin-top: 10px; - display: flex; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper label { - display: flex; - width:100%; - color: #a5a5a5; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper .item-value { - display: flex; - width: 100%; - background-color: #35363b; - padding: 2px 5px; - border-radius: 5px; - color: #f5f5f5; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params { - padding: 20px 0 10px 0; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button { - width: 100%; -} -.page-sd .mj-list-item-prompt .el-icon { - margin-left: 10px; - cursor: pointer; - position: relative; - top: 2px; -} diff --git a/web/src/assets/css/mobile/image-list.styl b/web/src/assets/css/mobile/image-list.styl deleted file mode 100644 index c01efe10..00000000 --- a/web/src/assets/css/mobile/image-list.styl +++ /dev/null @@ -1,106 +0,0 @@ -.page-sd { - background-color: #282828; - - .inner { - display: flex; - align-items: center; - justify-content: center; - - .sd-box { - background-color #282828 - min-width 92% - max-width 92% - padding 0px - border-radius 0px - color #ffffff; - font-size 14px - - h2 { - font-weight: bold; - font-size 20px - text-align center - color #fff - } - - // 隐藏滚动条 - - ::-webkit-scrollbar { - width: 0; - height: 0; - background-color: transparent; - } - - .sd-params { - margin-top 10px - overflow auto - - - .param-line { - padding 0 10px - - .el-icon { - position relative - top 3px - } - - .el-input__suffix-inner { - .el-icon { - top 0 - } - } - - .grid-content - .form-item-inner { - display flex - - .el-icon { - margin-left 10px - margin-top 2px - } - } - - } - - .param-line.pt { - padding-top 5px - padding-bottom 5px - } - } - - .submit-btn { - padding 10px 15px 0 15px - text-align center - - .el-button { - width 100% - - span { - color #2D3A4B - } - } - } - } - - .el-form { - .el-form-item__label { - color #ffffff - } - } - - @import "mobile/task-mobile-list.styl" - } - - @import "sd-task-mobile-dialog.styl" - - - .mj-list-item-prompt { - .el-icon { - margin-left 10px - cursor pointer - position relative - top 2px - } - } - -} - diff --git a/web/src/assets/css/mobile/image-sd.css b/web/src/assets/css/mobile/image-sd.css deleted file mode 100644 index 6502ea4c..00000000 --- a/web/src/assets/css/mobile/image-sd.css +++ /dev/null @@ -1,252 +0,0 @@ -.page-sd { - background-color: #282828; -} -.page-sd .inner { - display: flex; - align-items: center; - justify-content: center; -} -.page-sd .inner .sd-box { - background-color: #282828; - min-width: 92%; - max-width: 92%; - padding: 0px; - border-radius: 0px; - color: #fff; - font-size: 14px; -} -.page-sd .inner .sd-box h2 { - font-weight: bold; - font-size: 16px; - text-align: center; - color: #fff; -} -.page-sd .inner .sd-box ::-webkit-scrollbar { - width: 0; - height: 0; - background-color: transparent; -} -.page-sd .inner .sd-box .sd-params { - margin-top: 10px; - overflow: auto; -} -.page-sd .inner .sd-box .sd-params .param-line { - padding: 0 10px; -} -.page-sd .inner .sd-box .sd-params .param-line .el-icon { - position: relative; - top: 3px; -} -.page-sd .inner .sd-box .sd-params .param-line .el-input__suffix-inner .el-icon { - top: 0; -} -.page-sd .inner .sd-box .sd-params .param-line .grid-content, -.page-sd .inner .sd-box .sd-params .param-line .form-item-inner { - display: flex; -} -.page-sd .inner .sd-box .sd-params .param-line .grid-content .el-icon, -.page-sd .inner .sd-box .sd-params .param-line .form-item-inner .el-icon { - margin-left: 10px; - margin-top: 2px; -} -.page-sd .inner .sd-box .sd-params .param-line.pt { - padding-top: 5px; - padding-bottom: 5px; -} -.page-sd .inner .sd-box .submit-btn { - padding: 10px 15px 0 15px; - text-align: center; -} -.page-sd .inner .sd-box .submit-btn .el-button { - width: 100%; -} -.page-sd .inner .sd-box .submit-btn .el-button span { - color: #2d3a4b; -} -.page-sd .inner .el-form .el-form-item__label { - color: #fff; -} -.page-sd .inner .task-list-box { - width: 100%; - padding: 5px; - color: #fff; - overflow-x: hidden; -} -.page-sd .inner .task-list-box .running-job-list .job-item { - width: 100%; - padding: 2px; - background-color: #555; -} -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner { - position: relative; - height: 100%; - overflow: hidden; -} -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - display: flex; - justify-content: center; - align-items: center; -} -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span { - font-size: 20px; - color: #fff; -} -.page-sd .inner .task-list-box .finish-job-list .job-item { - width: 100%; - height: 100%; - border: 1px solid #666; - padding: 6px; - overflow: hidden; - border-radius: 6px; - transition: all 0.3s ease; /* 添加过渡效果 */ -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line { - margin: 6px 0; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul { - display: flex; - flex-flow: row; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li { - margin-right: 6px; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a { - padding: 3px 0; - width: 40px; - text-align: center; - border-radius: 5px; - display: block; - cursor: pointer; - background-color: #4e5058; - color: #fff; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover { - background-color: #6d6f78; -} -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt { - font-size: 20px; - cursor: pointer; -} -.page-sd .inner .task-list-box .finish-job-list .animate:hover { - box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ - transform: translateY(-10px); /* 向上移动10像素 */ -} -.page-sd .inner .task-list-box .el-image { - width: 100%; - height: 100%; - overflow: visible; -} -.page-sd .inner .task-list-box .el-image img { - height: 240px; -} -.page-sd .inner .task-list-box .el-image .el-image-viewer__wrapper img { - width: auto; - height: auto; -} -.page-sd .inner .task-list-box .el-image .image-slot { - display: flex; - flex-flow: column; - justify-content: center; - align-items: center; - height: 100%; - min-height: 200px; - color: #fff; - height: 240px; -} -.page-sd .inner .task-list-box .el-image .image-slot .iconfont { - font-size: 50px; - margin-bottom: 10px; -} -.page-sd .inner .task-list-box .el-image.upscale { - max-height: 310px; -} -.page-sd .inner .task-list-box .el-image.upscale img { - height: 310px; -} -.page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img { - width: auto; - height: auto; -} -.page-sd .el-overlay-dialog .el-dialog { - background-color: #1a1b1e; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__header .el-dialog__title { - color: #f5f5f5; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body { - padding: 0 0 0 15px !important; - display: flex; - height: 100%; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row { - width: 100%; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container { - display: flex; - justify-content: center; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot { - display: flex; - height: 100vh; - align-items: center; - justify-content: center; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot .el-icon { - font-size: 60px; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info { - background-color: #25262b; - padding: 1rem 1.5rem; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line { - width: 100%; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt { - background-color: #35363b; - padding: 10px; - color: #999; - overflow: auto; - max-height: 100px; - min-height: 50px; - position: relative; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt .el-icon { - position: absolute; - right: 10px; - bottom: 10px; - cursor: pointer; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper { - margin-top: 10px; - display: flex; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper label { - display: flex; - width: 100px; - color: #a5a5a5; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper .item-value { - display: flex; - width: 100%; - background-color: #35363b; - padding: 2px 5px; - border-radius: 5px; - color: #f5f5f5; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params { - padding: 30px 0 10px 0; -} -.page-sd .el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button { - width: 100%; -} -.page-sd .mj-list-item-prompt .el-icon { - margin-left: 10px; - cursor: pointer; - position: relative; - top: 2px; -} diff --git a/web/src/assets/css/mobile/image-sd.styl b/web/src/assets/css/mobile/image-sd.styl deleted file mode 100644 index 7cc97a20..00000000 --- a/web/src/assets/css/mobile/image-sd.styl +++ /dev/null @@ -1,106 +0,0 @@ -.page-sd { - background-color: #282828; - - .inner { - display: flex; - align-items: center; - justify-content: center; - - .sd-box { - background-color #282828 - min-width 92% - max-width 92% - padding 0px - border-radius 0px - color #ffffff; - font-size 14px - - h2 { - font-weight: bold; - font-size 16px - text-align center - color #fff - } - - // 隐藏滚动条 - - ::-webkit-scrollbar { - width: 0; - height: 0; - background-color: transparent; - } - - .sd-params { - margin-top 10px - overflow auto - - - .param-line { - padding 0 10px - - .el-icon { - position relative - top 3px - } - - .el-input__suffix-inner { - .el-icon { - top 0 - } - } - - .grid-content - .form-item-inner { - display flex - - .el-icon { - margin-left 10px - margin-top 2px - } - } - - } - - .param-line.pt { - padding-top 5px - padding-bottom 5px - } - } - - .submit-btn { - padding 10px 15px 0 15px - text-align center - - .el-button { - width 100% - - span { - color #2D3A4B - } - } - } - } - - .el-form { - .el-form-item__label { - color #ffffff - } - } - - @import "./task-list.styl" - } - - @import "./sd-task-dialog.styl" - - - .mj-list-item-prompt { - .el-icon { - margin-left 10px - cursor pointer - position relative - top 2px - } - } - -} - diff --git a/web/src/assets/css/mobile/invitation.css b/web/src/assets/css/mobile/invitation.css deleted file mode 100644 index 2d8e6d55..00000000 --- a/web/src/assets/css/mobile/invitation.css +++ /dev/null @@ -1,93 +0,0 @@ -.page-invitation { - display: flex; - justify-content: center; - background-color: #1c1c1c; - height: 100vh; - overflow-x: hidden; - overflow-y: visible; -} -.page-invitation .inner { - max-width: 100%; - width: 100%; - color: #fff; -} -.page-invitation .inner .center { - display: flex; - justify-content: center; - align-items: center; -} -.page-invitation .inner .title { - color: #fff; - text-align: center; - font-size: 16px; - font-weight: bold; -} -.page-invitation .inner .share-box .info { - line-height: 1.5; - border: 1px solid #444; - border-radius: 10px; - padding: 10px; - margin: 10px; - background-color: #1c1c1c; -} -.page-invitation .inner .share-box .info strong { - color: #f56c6c; -} -.page-invitation .inner .share-box .invite-qrcode { - text-align: center; - margin: 15px; -} -.page-invitation .inner .share-box .invite-url { - margin: 10px; - padding: 8px; - display: flex; - justify-content: space-between; - border: 1px solid #444; - border-radius: 10px; - background-color: #1c1c1c; -} -.page-invitation .inner .share-box .invite-url span { - position: relative; - font-size: 14px; - font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; - top: 0px; -} -.page-invitation .inner .invite-stats { - padding: 10px 10px; -} -.page-invitation .inner .invite-stats .item-box { - border-radius: 10px; - padding: 10 0px; -} -.page-invitation .inner .invite-stats .item-box .el-col { - height: 80px; - display: flex; - align-items: center; - justify-content: center; -} -.page-invitation .inner .invite-stats .item-box .el-col .iconfont { - font-size: 30px; -} -.page-invitation .inner .invite-stats .item-box .el-col .item-info { - font-size: 14px; -} -.page-invitation .inner .invite-stats .item-box .el-col .item-info .text, -.page-invitation .inner .invite-stats .item-box .el-col .item-info .num { - padding: 3px 0; - text-align: center; -} -.page-invitation .inner .invite-stats .item-box .el-col .item-info .num { - font-size: 14px; -} -.page-invitation .inner .invite-stats .yellow { - background-color: #fec; - color: #d68f00; -} -.page-invitation .inner .invite-stats .blue { - background-color: #d6e4ff; - color: #1062fe; -} -.page-invitation .inner .invite-stats .green { - background-color: #e7f8eb; - color: #2d9f46; -} diff --git a/web/src/assets/css/mobile/invitation.styl b/web/src/assets/css/mobile/invitation.styl deleted file mode 100644 index b6a0b40c..00000000 --- a/web/src/assets/css/mobile/invitation.styl +++ /dev/null @@ -1,94 +0,0 @@ -.page-invitation { - display flex - justify-content center - background-color #1c1c1c - height 100vh - overflow-x hidden - overflow-y visible - .inner { - max-width 100% - width 100% - color #fff - .center { - display flex - justify-content center - align-items center - } - .title { - color #fff - text-align center - font-size 16px - font-weight bold - } - .share-box { - .info { - line-height 1.5 - border 1px solid #444444 - border-radius 10px - padding 10px - margin 10px - background-color #1c1c1c - strong { - color #f56c6c - } - } - .invite-qrcode { - text-align center - margin 15px - } - .invite-url { - margin 10px - padding 8px - display flex - justify-content space-between - border 1px solid #444444 - border-radius 10px - background-color #1c1c1c - span { - position relative - font-size 14px - font-family 'Microsoft YaHei', '微软雅黑', Arial, sans-serif - top 0px - } - } - } - .invite-stats { - padding 10px 10px - .item-box { - border-radius 10px - padding 10 0px - .el-col { - height 80px - display flex - align-items center - justify-content center - .iconfont { - font-size 30px - } - .item-info { - font-size 14px - .text, .num { - padding 3px 0 - text-align center - } - .num { - font-size 14px - } - } - } - } - .yellow { - background-color #ffeecc - color #D68F00 - } - .blue { - background-color #D6E4FF - color #1062FE - } - .green { - background-color #E7F8EB - color #2D9F46 - } - } - } -} \ No newline at end of file diff --git a/web/src/assets/css/mobile/profile.css b/web/src/assets/css/mobile/profile.css deleted file mode 100644 index b80f603f..00000000 --- a/web/src/assets/css/mobile/profile.css +++ /dev/null @@ -1,142 +0,0 @@ -.app-background { - background-color: #1c1c1c; - height: 100vh; -} -.member { - background-color: #1c1c1c; - height: 100vh; -} -.member .el-dialog .el-dialog__body { - padding-top: 5px; -} -.member .el-dialog .el-dialog__body .pay-container .count-down { - display: flex; - justify-content: center; -} -.member .el-dialog .el-dialog__body .pay-container .pay-qrcode { - display: flex; - justify-content: center; -} -.member .el-dialog .el-dialog__body .pay-container .pay-qrcode .el-image { - width: 280px; - height: 280px; -} -.member .el-dialog .el-dialog__body .pay-container .tip { - display: flex; - justify-content: center; -} -.member .el-dialog .el-dialog__body .pay-container .tip .el-icon { - font-size: 20px; -} -.member .el-dialog .el-dialog__body .pay-container .tip .text { - font-size: 16px; - margin-left: 10px; -} -.member .el-dialog .el-dialog__body .pay-container .tip.success { - color: #07c160; -} -.member .title { - text-align: center; - background-color: #1c1c1c; - font-size: 16px; - color: #fff; - padding: 5px; - font-weight: bold; -} -.member .inner { - color: #fff; - overflow-x: hidden; - overflow-y: visible; -} -.member .inner .user-profile { - padding: 0 20px 0 20px; - background-color: #1c1c1c; - color: #fff; - border-radius: 10px; - width: 100%; - height: 91vh; -} -.member .inner .user-profile .el-form-item__label { - color: #fff; - justify-content: start; -} -.member .inner .user-profile .user-opt .el-col { - padding: 8px; -} -.member .inner .user-profile .user-opt .el-col .el-button { - width: 100%; -} -.member .inner .product-box .info { - padding: 0 20px 0 20px; -} -.member .inner .product-box .info .el-alert__description { - font-size: 14px !important; - color: #1c1c1c; - margin: 0; -} -.member .inner .product-box .list-box { - padding: 0 20px 0 5px; -} -.member .inner .product-box .list-box .product-item { - border: 1px solid #5f5f5f; - border-radius: 6px; - overflow: hidden; - cursor: pointer; - transition: all 0.3s ease /* 添加过渡效果 */; -} -.member .inner .product-box .list-box .product-item .image-container { - display: flex; - justify-content: center; -} -.member .inner .product-box .list-box .product-item .image-container .el-image { - width: 50px; -} -.member .inner .product-box .list-box .product-item .image-container .el-image .el-image__inner { - border-radius: 10px; -} -.member .inner .product-box .list-box .product-item .product-title { - display: flex; - padding: 5px; -} -.member .inner .product-box .list-box .product-item .product-title .name { - width: 100%; - text-align: center; - font-size: 16px; - font-weight: bold; - color: #2cc995; -} -.member .inner .product-box .list-box .product-item .product-info { - padding: 10px 20px; - font-size: 14px; - color: #999; -} -.member .inner .product-box .list-box .product-item .product-info .info-line { - display: flex; - width: 100%; - padding: 2px 0; -} -.member .inner .product-box .list-box .product-item .product-info .info-line .label { - display: flex; - width: 100%; -} -.member .inner .product-box .list-box .product-item .product-info .info-line .price, -.member .inner .product-box .list-box .product-item .product-info .info-line .expire { - display: flex; - width: 100%; - justify-content: right; -} -.member .inner .product-box .list-box .product-item .product-info .info-line .price { - color: #f56c6c; -} -.member .inner .product-box .list-box .product-item .product-info .info-line .expire { - color: #409eff; -} -.member .inner .product-box .list-box .product-item .product-info .pay-way { - padding: 5px 0; - display: flex; - justify-content: space-between; -} -.member .inner .product-box .list-box .product-item hover { - box-shadow: 0 0 10px rgba(71,255,241,0.6) /* 添加阴影效果 */; - transform: translateY(-10px) /* 向上移动10像素 */; -} diff --git a/web/src/assets/css/mobile/profile.styl b/web/src/assets/css/mobile/profile.styl deleted file mode 100644 index ee8c4a2c..00000000 --- a/web/src/assets/css/mobile/profile.styl +++ /dev/null @@ -1,149 +0,0 @@ -.app-background { - background-color #1c1c1c - height 100vh -} -.member { - background-color #1c1c1c - height 100vh - .el-dialog { - .el-dialog__body { - padding-top 5px - .pay-container { - .count-down { - display flex - justify-content center - } - .pay-qrcode { - display flex - justify-content center - .el-image { - width 280px - height 280px - } - } - .tip { - display flex - justify-content center - .el-icon { - font-size 20px - } - .text { - font-size 16px - margin-left 10px - } - } - .tip.success { - color #07c160 - } - } - } - } - .title { - text-align center - background-color #1c1c1c - font-size 16px - color #ffffff - padding 5px - font-weight bold - } - .inner { - color #ffffff - overflow-x hidden - overflow-y visible - .user-profile { - padding 0 20px 0 20px - background-color #1c1c1c - color #ffffff - border-radius 10px - width 100% - height 91vh - .el-form-item__label { - color #ffffff - justify-content start - } - .user-opt { - .el-col { - padding 8px - .el-button { - width 100% - } - } - } - } - .product-box { - .info { - .el-alert__description { - font-size 14px !important - color #1c1c1c - margin 0 - } - padding 0 20px 0 20px - } - .list-box { - padding 0 20px 0 5px - .product-item { - border 1px solid #5f5f5f - border-radius 6px - overflow hidden - cursor pointer - transition all 0.3s ease /* 添加过渡效果 */ - .image-container { - display flex - justify-content center - .el-image { - width 50px - .el-image__inner { - border-radius 10px - } - } - } - .product-title { - display flex - padding 5px - .name { - width 100% - text-align center - font-size 16px - font-weight bold - color #2cc995 - } - } - .product-info { - padding 10px 20px - font-size 14px - color #999999 - .info-line { - display flex - width 100% - padding 2px 0 - .label { - display flex - width 100% - } - .price, .expire { - display flex - width 100% - justify-content right - } - .price { - color #f56c6c - } - .expire { - color #409eff - } - } - .pay-way { - padding 5px 0 - display flex - justify-content space-between - } - } - & hover { - box-shadow 0 0 10px rgba(71, 255, 241, 0.6) /* 添加阴影效果 */ - transform translateY(-10px) /* 向上移动10像素 */ - } - } - } - } - } -} \ No newline at end of file diff --git a/web/src/assets/css/mobile/sd-task-dialog.css b/web/src/assets/css/mobile/sd-task-dialog.css deleted file mode 100644 index 611e7d38..00000000 --- a/web/src/assets/css/mobile/sd-task-dialog.css +++ /dev/null @@ -1,72 +0,0 @@ -.el-overlay-dialog .el-dialog { - background-color: #1a1b1e; -} -.el-overlay-dialog .el-dialog .el-dialog__header .el-dialog__title { - color: #f5f5f5; -} -.el-overlay-dialog .el-dialog .el-dialog__body { - padding: 0 0 0 15px !important; - display: flex; - height: 100%; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row { - width: 100%; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container { - display: flex; - justify-content: center; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot { - display: flex; - height: 100vh; - align-items: center; - justify-content: center; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .img-container .image-slot .el-icon { - font-size: 60px; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info { - background-color: #25262b; - padding: 1rem 1.5rem; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line { - width: 100%; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt { - background-color: #35363b; - padding: 10px; - color: #999; - overflow: auto; - max-height: 100px; - min-height: 50px; - position: relative; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .prompt .el-icon { - position: absolute; - right: 10px; - bottom: 10px; - cursor: pointer; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper { - margin-top: 10px; - display: flex; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper label { - display: flex; - width: 100px; - color: #a5a5a5; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .info-line .wrapper .item-value { - display: flex; - width: 100%; - background-color: #35363b; - padding: 2px 5px; - border-radius: 5px; - color: #f5f5f5; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params { - padding: 30px 0 10px 0; -} -.el-overlay-dialog .el-dialog .el-dialog__body .el-row .task-info .copy-params .el-button { - width: 100%; -} diff --git a/web/src/assets/css/mobile/sd-task-dialog.styl b/web/src/assets/css/mobile/sd-task-dialog.styl deleted file mode 100644 index d0c93b1c..00000000 --- a/web/src/assets/css/mobile/sd-task-dialog.styl +++ /dev/null @@ -1,96 +0,0 @@ -.el-overlay-dialog { - .el-dialog { - background-color #1a1b1e - - .el-dialog__header { - .el-dialog__title { - color #F5F5F5 - } - } - - .el-dialog__body { - padding 0 0 0 15px !important - display flex - height 100% - - .el-row { - width 100% - - .img-container { - display flex - justify-content center - - .image-slot { - display flex - height 100vh - align-items center - justify-content center - - .el-icon { - font-size 60px - } - } - } - - .task-info { - background-color #25262b - padding 1rem 1.5rem - - .info-line { - width 100% - - .prompt { - background-color #35363b - padding 10px - color #999999 - overflow auto - max-height 100px - min-height 50px - - position relative - - .el-icon { - position absolute - right 10px - bottom 10px - cursor pointer - } - } - - .wrapper { - margin-top 10px - display flex - - label { - display flex - width 100px - color #a5a5a5 - } - - .item-value { - display flex - width 100% - background-color #35363b - padding 2px 5px - border-radius 5px - color #F5F5F5 - } - } - - } - - .copy-params { - padding 30px 0 10px 0 - - .el-button { - width 100% - } - } - } - } - - // end el-row - - } - } -} \ No newline at end of file diff --git a/web/src/assets/css/mobile/task-list.css b/web/src/assets/css/mobile/task-list.css deleted file mode 100644 index 6754ac94..00000000 --- a/web/src/assets/css/mobile/task-list.css +++ /dev/null @@ -1,106 +0,0 @@ -.task-list-box { - width: 100%; - padding: 5px; - color: #fff; - overflow-x: hidden; -} -.task-list-box .running-job-list .job-item { - width: 100%; - padding: 2px; - background-color: #555; -} -.task-list-box .running-job-list .job-item .job-item-inner { - position: relative; - height: 100%; - overflow: hidden; -} -.task-list-box .running-job-list .job-item .job-item-inner .progress { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - display: flex; - justify-content: center; - align-items: center; -} -.task-list-box .running-job-list .job-item .job-item-inner .progress span { - font-size: 20px; - color: #fff; -} -.task-list-box .finish-job-list .job-item { - width: 100%; - height: 100%; - border: 1px solid #666; - padding: 6px; - overflow: hidden; - border-radius: 6px; - transition: all 0.3s ease; /* 添加过渡效果 */ -} -.task-list-box .finish-job-list .job-item .opt .opt-line { - margin: 6px 0; -} -.task-list-box .finish-job-list .job-item .opt .opt-line ul { - display: flex; - flex-flow: row; -} -.task-list-box .finish-job-list .job-item .opt .opt-line ul li { - margin-right: 6px; -} -.task-list-box .finish-job-list .job-item .opt .opt-line ul li a { - padding: 3px 0; - width: 40px; - text-align: center; - border-radius: 5px; - display: block; - cursor: pointer; - background-color: #4e5058; - color: #fff; -} -.task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover { - background-color: #6d6f78; -} -.task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt { - font-size: 20px; - cursor: pointer; -} -.task-list-box .finish-job-list .animate:hover { - box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ - transform: translateY(-10px); /* 向上移动10像素 */ -} -.task-list-box .el-image { - width: 100%; - height: 100%; - overflow: visible; -} -.task-list-box .el-image img { - height: 240px; -} -.task-list-box .el-image .el-image-viewer__wrapper img { - width: auto; - height: auto; -} -.task-list-box .el-image .image-slot { - display: flex; - flex-flow: column; - justify-content: center; - align-items: center; - height: 100%; - min-height: 200px; - color: #fff; - height: 240px; -} -.task-list-box .el-image .image-slot .iconfont { - font-size: 50px; - margin-bottom: 10px; -} -.task-list-box .el-image.upscale { - max-height: 310px; -} -.task-list-box .el-image.upscale img { - height: 310px; -} -.task-list-box .el-image.upscale .el-image-viewer__wrapper img { - width: auto; - height: auto; -} diff --git a/web/src/assets/css/mobile/task-list.styl b/web/src/assets/css/mobile/task-list.styl deleted file mode 100644 index 994a68d9..00000000 --- a/web/src/assets/css/mobile/task-list.styl +++ /dev/null @@ -1,142 +0,0 @@ -.task-list-box { - width 100% - padding 5px - color #ffffff - overflow-x hidden - - - .running-job-list { - .job-item { - //border: 1px solid #454545; - width: 100%; - padding 2px - background-color #555555 - - .job-item-inner { - position relative - height 100% - overflow hidden - - .progress { - position absolute - width 100% - height 100% - top 0 - left 0 - display flex - justify-content center - align-items center - - span { - font-size 20px - color #ffffff - } - } - } - } - } - - - .finish-job-list { - .job-item { - width 100% - height 100% - border 1px solid #666666 - padding 6px - overflow hidden - border-radius 6px - transition: all 0.3s ease; /* 添加过渡效果 */ - - .opt { - .opt-line { - margin 6px 0 - - ul { - display flex - flex-flow row - - li { - margin-right 6px - - a { - padding 3px 0 - width 40px - text-align center - border-radius 5px - display block - cursor pointer - background-color #4E5058 - color #ffffff - - &:hover { - background-color #6D6F78 - } - } - } - - .show-prompt { - font-size 20px - cursor pointer - } - } - } - } - } - - .animate { - &:hover { - box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */ - transform: translateY(-10px); /* 向上移动10像素 */ - } - } - - } - - .el-image { - width 100% - height 100% - overflow visible - - img { - height 240px - } - - .el-image-viewer__wrapper { - img { - width auto - height auto - } - } - - .image-slot { - display flex - flex-flow column - justify-content center - align-items center - height 100% - min-height 200px - color #ffffff - height 240px - - .iconfont { - font-size 50px - margin-bottom 10px - } - } - } - - .el-image.upscale { - max-height 310px - - img { - height 310px - } - - .el-image-viewer__wrapper { - img { - width auto - height auto - } - } - } -} \ No newline at end of file diff --git a/web/src/router.js b/web/src/router.js index 6cec3486..d0ba8254 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -176,26 +176,12 @@ const routes = [ name: 'mobile-chat-list', component: () => import('@/views/mobile/ChatList.vue'), }, - { - path: '/mobile/imageSd', - name: 'mobile-imageSd', - component: () => import('@/views/mobile/ImageSd.vue'), - }, - { - path: '/mobile/apps', - name: 'mobile-apps', - component: () => import('@/views/mobile/Apps.vue'), - }, + { path: '/mobile/profile', name: 'mobile-profile', component: () => import('@/views/mobile/Profile.vue'), }, - { - path: '/mobile/invitation', - name: 'mobile-invitation', - component: () => import('@/views/mobile/Invitation.vue'), - }, ] }, { diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index cd7f8b4a..e61adcf5 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -567,6 +567,19 @@ const connect = function (chat_id, role_id) { 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/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`); _socket.addEventListener('open', () => { chatData.value = []; // 初始化聊天数据 @@ -589,15 +602,8 @@ const connect = function (chat_id, role_id) { } else { // 加载聊天记录 loadChatHistory(chat_id); } - // 发送心跳消息 - clearInterval(heartbeatHandle.value) - heartbeatHandle.value = setInterval(() => { - if (socket.value !== null) { - socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"})) - } - }, 5000); - + sendHeartbeat() }); _socket.addEventListener('message', event => { diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index 6a909aab..a1cd80ff 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -573,17 +573,26 @@ const connect = () => { 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; // 发送心跳消息 - clearInterval(heartbeatHandle.value) - heartbeatHandle.value = setInterval(() => { - if (socket.value !== null) { - socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"})) - } - }, 5000); + sendHeartbeat() }); _socket.addEventListener('message', event => { diff --git a/web/src/views/mobile/Apps.vue b/web/src/views/mobile/Apps.vue deleted file mode 100644 index c4c3ab54..00000000 --- a/web/src/views/mobile/Apps.vue +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/web/src/views/mobile/ChatList.vue b/web/src/views/mobile/ChatList.vue index 61ca3a4d..3bd4b0b5 100644 --- a/web/src/views/mobile/ChatList.vue +++ b/web/src/views/mobile/ChatList.vue @@ -261,5 +261,49 @@ const removeChat = (item) => { \ No newline at end of file diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index 973365a3..72ed540b 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -1,99 +1,103 @@ - \ No newline at end of file diff --git a/web/src/views/mobile/Home.vue b/web/src/views/mobile/Home.vue index d9971072..2ff621fd 100644 --- a/web/src/views/mobile/Home.vue +++ b/web/src/views/mobile/Home.vue @@ -1,26 +1,17 @@ - + \ No newline at end of file diff --git a/web/src/views/mobile/ImageSd.vue b/web/src/views/mobile/ImageSd.vue deleted file mode 100644 index ae874ee2..00000000 --- a/web/src/views/mobile/ImageSd.vue +++ /dev/null @@ -1,657 +0,0 @@ - - - - - diff --git a/web/src/views/mobile/Invitation.vue b/web/src/views/mobile/Invitation.vue deleted file mode 100644 index c6df7bc1..00000000 --- a/web/src/views/mobile/Invitation.vue +++ /dev/null @@ -1,152 +0,0 @@ - - - - - diff --git a/web/src/views/mobile/Profile.vue b/web/src/views/mobile/Profile.vue index 28dcaae6..1231fdf6 100644 --- a/web/src/views/mobile/Profile.vue +++ b/web/src/views/mobile/Profile.vue @@ -1,287 +1,128 @@ - + + \ No newline at end of file From 96785ca0371783f5eb990f9edce8e46f83730252 Mon Sep 17 00:00:00 2001 From: RockYang Date: Fri, 26 Jan 2024 11:57:08 +0800 Subject: [PATCH 07/29] feat: blend and swap face function for midjourney-plus is ready --- api/handler/mj_handler.go | 7 +- api/service/mj/plus/client.go | 4 +- api/service/mj/pool.go | 9 +- web/src/assets/css/image-mj.css | 74 ++-- web/src/assets/css/image-sd.css | 74 ++-- web/src/assets/css/task-list.styl | 323 +++++++++-------- web/src/views/ImageMj.vue | 581 +++++++++++++++--------------- 7 files changed, 568 insertions(+), 504 deletions(-) diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index 89995d98..b1c78756 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -157,6 +157,11 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { Prompt: prompt, CreatedAt: time.Now(), } + if data.TaskType == types.TaskBlend.String() { + data.Prompt = "融图:" + strings.Join(data.ImgArr, ",") + } else if data.TaskType == types.TaskSwapFace.String() { + data.Prompt = "换脸:" + strings.Join(data.ImgArr, ",") + } if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 { resp.ERROR(c, "添加任务失败:"+res.Error.Error()) return @@ -166,7 +171,7 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { Id: int(job.Id), TaskId: taskId, SessionId: data.SessionId, - Type: types.TaskImage, + Type: types.TaskType(data.TaskType), Prompt: prompt, UserId: userId, ImgArr: data.ImgArr, diff --git a/api/service/mj/plus/client.go b/api/service/mj/plus/client.go index 45ba1481..b79afa24 100644 --- a/api/service/mj/plus/client.go +++ b/api/service/mj/plus/client.go @@ -98,7 +98,7 @@ func (c *Client) Blend(task types.MjTask) (ImageRes, error) { BotType: "MID_JOURNEY", Dimensions: "SQUARE", NotifyHook: c.Config.NotifyURL, - Base64Array: make([]string, 1), + Base64Array: make([]string, 0), } // 生成图片 Base64 编码 if len(task.ImgArr) > 0 { @@ -107,7 +107,7 @@ func (c *Client) Blend(task types.MjTask) (ImageRes, error) { if err != nil { logger.Error("error with download image: ", err) } else { - body.Base64Array[0] = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imageData) + body.Base64Array = append(body.Base64Array, "data:image/png;base64,"+base64.StdEncoding.EncodeToString(imageData)) } } } diff --git a/api/service/mj/pool.go b/api/service/mj/pool.go index 8aee04a6..2e406407 100644 --- a/api/service/mj/pool.go +++ b/api/service/mj/pool.go @@ -167,7 +167,7 @@ func (p *ServicePool) HasAvailableService() bool { } func (p *ServicePool) Notify(data plus.CBReq) error { - logger.Infof("收到任务回调:%+v", data) + logger.Debugf("收到任务回调:%+v", data) var job model.MidJourneyJob res := p.db.Where("task_id = ?", data.Id).First(&job) if res.Error != nil { @@ -190,7 +190,7 @@ func (p *ServicePool) SyncTaskProgress() { go func() { var items []model.MidJourneyJob for { - res := p.db.Where("progress < ?", 100).Find(&items) + res := p.db.Where("progress >= ? AND progress < ?", 0, 100).Find(&items) if res.Error != nil { continue } @@ -215,6 +215,11 @@ func (p *ServicePool) SyncTaskProgress() { if err != nil { continue } + // 任务失败了 + if task.FailReason != "" { + p.db.Model(&model.MidJourneyJob{Id: v.Id}).UpdateColumn("progress", -1) + continue + } if len(task.Buttons) > 0 { v.Hash = getImageHash(task.Buttons[0].CustomId) } diff --git a/web/src/assets/css/image-mj.css b/web/src/assets/css/image-mj.css index cdb9197e..4bc3c538 100644 --- a/web/src/assets/css/image-mj.css +++ b/web/src/assets/css/image-mj.css @@ -231,32 +231,49 @@ .page-mj .inner .task-list-box .task-list-inner .el-form-item__label { color: #fff; } -.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload { +.page-mj .inner .task-list-box .task-list-inner .img-inline { + display: flex; +} +.page-mj .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; - width: 300px; + width: 120px; transition: var(--el-transition-duration-fast); margin-bottom: 20px; } -.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload:hover { +.page-mj .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload:hover { border-color: var(--el-color-primary); } -.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon { +.page-mj .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload .el-icon.uploader-icon { font-size: 28px; color: #8c939d; width: 100%; height: 120px; text-align: center; } -.page-mj .inner .task-list-box .task-list-inner .img-inline { +.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box { display: flex; } -.page-mj .inner .task-list-box .task-list-inner .img-inline .img-uploader { +.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item { + width: 120px; + position: relative; margin-right: 10px; } +.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-image { + width: 120px; + height: 120px; + border-radius: 5px; +} +.page-mj .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-button { + position: absolute; + right: 5px; + top: 5px; + width: 20px; + height: 20px; +} .page-mj .inner .task-list-box .task-list-inner .submit-btn { display: flex; margin: 20px 0; @@ -270,17 +287,17 @@ justify-content: right; align-items: center; } -.page-mj .inner .task-list-box .running-job-list .job-item { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item { width: 100%; padding: 2px; background-color: #555; } -.page-mj .inner .task-list-box .running-job-list .job-item .job-item-inner { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner { position: relative; height: 100%; overflow: hidden; } -.page-mj .inner .task-list-box .running-job-list .job-item .job-item-inner .progress { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress { position: absolute; width: 100%; height: 100%; @@ -290,11 +307,11 @@ justify-content: center; align-items: center; } -.page-mj .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress span { font-size: 20px; color: #fff; } -.page-mj .inner .task-list-box .finish-job-list .job-item { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item { width: 100%; height: 100%; border: 1px solid #666; @@ -304,17 +321,17 @@ transition: all 0.3s ease; /* 添加过渡效果 */ position: relative; } -.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line { margin: 6px 0; } -.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul { display: flex; flex-flow: row; } -.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li { margin-right: 6px; } -.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a { padding: 3px 0; width: 40px; text-align: center; @@ -324,59 +341,58 @@ background-color: #4e5058; color: #fff; } -.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover { background-color: #6d6f78; } -.page-mj .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt { font-size: 20px; cursor: pointer; } -.page-mj .inner .task-list-box .finish-job-list .job-item .remove { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .remove { display: none; position: absolute; right: 10px; top: 10px; } -.page-mj .inner .task-list-box .finish-job-list .job-item:hover .remove { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item:hover .remove { display: block; } -.page-mj .inner .task-list-box .finish-job-list .animate:hover { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover { box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ transform: translateY(-10px); /* 向上移动10像素 */ } -.page-mj .inner .task-list-box .el-image { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image { width: 100%; height: 100%; overflow: visible; } -.page-mj .inner .task-list-box .el-image img { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image img { height: 240px; } -.page-mj .inner .task-list-box .el-image .el-image-viewer__wrapper img { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image .el-image-viewer__wrapper img { width: auto; height: auto; } -.page-mj .inner .task-list-box .el-image .image-slot { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot { display: flex; flex-flow: column; justify-content: center; align-items: center; - height: 100%; min-height: 200px; color: #fff; height: 240px; } -.page-mj .inner .task-list-box .el-image .image-slot .iconfont { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot .iconfont { font-size: 50px; margin-bottom: 10px; } -.page-mj .inner .task-list-box .el-image.upscale { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale { max-height: 310px; } -.page-mj .inner .task-list-box .el-image.upscale img { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale img { height: 310px; } -.page-mj .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img { +.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale .el-image-viewer__wrapper img { width: auto; height: auto; } diff --git a/web/src/assets/css/image-sd.css b/web/src/assets/css/image-sd.css index 15251961..3504fcd0 100644 --- a/web/src/assets/css/image-sd.css +++ b/web/src/assets/css/image-sd.css @@ -116,32 +116,49 @@ .page-sd .inner .task-list-box .task-list-inner .el-form-item__label { color: #fff; } -.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload { +.page-sd .inner .task-list-box .task-list-inner .img-inline { + display: flex; +} +.page-sd .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; - width: 300px; + width: 120px; transition: var(--el-transition-duration-fast); margin-bottom: 20px; } -.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload:hover { +.page-sd .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload:hover { border-color: var(--el-color-primary); } -.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon { +.page-sd .inner .task-list-box .task-list-inner .img-inline .img-uploader .el-upload .el-icon.uploader-icon { font-size: 28px; color: #8c939d; width: 100%; height: 120px; text-align: center; } -.page-sd .inner .task-list-box .task-list-inner .img-inline { +.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box { display: flex; } -.page-sd .inner .task-list-box .task-list-inner .img-inline .img-uploader { +.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item { + width: 120px; + position: relative; margin-right: 10px; } +.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-image { + width: 120px; + height: 120px; + border-radius: 5px; +} +.page-sd .inner .task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-button { + position: absolute; + right: 5px; + top: 5px; + width: 20px; + height: 20px; +} .page-sd .inner .task-list-box .task-list-inner .submit-btn { display: flex; margin: 20px 0; @@ -155,17 +172,17 @@ justify-content: right; align-items: center; } -.page-sd .inner .task-list-box .running-job-list .job-item { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item { width: 100%; padding: 2px; background-color: #555; } -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner { position: relative; height: 100%; overflow: hidden; } -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress { position: absolute; width: 100%; height: 100%; @@ -175,11 +192,11 @@ justify-content: center; align-items: center; } -.page-sd .inner .task-list-box .running-job-list .job-item .job-item-inner .progress span { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress span { font-size: 20px; color: #fff; } -.page-sd .inner .task-list-box .finish-job-list .job-item { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item { width: 100%; height: 100%; border: 1px solid #666; @@ -189,17 +206,17 @@ transition: all 0.3s ease; /* 添加过渡效果 */ position: relative; } -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line { margin: 6px 0; } -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul { display: flex; flex-flow: row; } -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li { margin-right: 6px; } -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a { padding: 3px 0; width: 40px; text-align: center; @@ -209,59 +226,58 @@ background-color: #4e5058; color: #fff; } -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover { background-color: #6d6f78; } -.page-sd .inner .task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt { font-size: 20px; cursor: pointer; } -.page-sd .inner .task-list-box .finish-job-list .job-item .remove { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .remove { display: none; position: absolute; right: 10px; top: 10px; } -.page-sd .inner .task-list-box .finish-job-list .job-item:hover .remove { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .job-item:hover .remove { display: block; } -.page-sd .inner .task-list-box .finish-job-list .animate:hover { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover { box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ transform: translateY(-10px); /* 向上移动10像素 */ } -.page-sd .inner .task-list-box .el-image { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image { width: 100%; height: 100%; overflow: visible; } -.page-sd .inner .task-list-box .el-image img { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image img { height: 240px; } -.page-sd .inner .task-list-box .el-image .el-image-viewer__wrapper img { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image .el-image-viewer__wrapper img { width: auto; height: auto; } -.page-sd .inner .task-list-box .el-image .image-slot { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot { display: flex; flex-flow: column; justify-content: center; align-items: center; - height: 100%; min-height: 200px; color: #fff; height: 240px; } -.page-sd .inner .task-list-box .el-image .image-slot .iconfont { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image .image-slot .iconfont { font-size: 50px; margin-bottom: 10px; } -.page-sd .inner .task-list-box .el-image.upscale { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale { max-height: 310px; } -.page-sd .inner .task-list-box .el-image.upscale img { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale img { height: 310px; } -.page-sd .inner .task-list-box .el-image.upscale .el-image-viewer__wrapper img { +.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image.upscale .el-image-viewer__wrapper img { width: auto; height: auto; } diff --git a/web/src/assets/css/task-list.styl b/web/src/assets/css/task-list.styl index 9717c90b..7d046fa5 100644 --- a/web/src/assets/css/task-list.styl +++ b/web/src/assets/css/task-list.styl @@ -61,39 +61,63 @@ color #ffffff } - .img-uploader { - .el-upload { - border: 1px dashed var(--el-border-color); - border-radius: 6px; - cursor: pointer; - position: relative; - overflow: hidden; - width 300px; - transition: var(--el-transition-duration-fast); - margin-bottom: 20px; - - &:hover { - border-color: var(--el-color-primary); - } - - .el-icon.uploader-icon { - font-size: 28px - color: #8c939d - width 100% - height: 120px - text-align: center - } - } - } + // 图片上传样式 .img-inline { display flex .img-uploader { - margin-right 10px + .el-upload { + border: 1px dashed var(--el-border-color); + border-radius: 6px; + cursor: pointer; + position: relative; + overflow: hidden; + width 120px; + transition: var(--el-transition-duration-fast); + margin-bottom: 20px; + + &:hover { + border-color: var(--el-color-primary); + } + + .el-icon.uploader-icon { + font-size: 28px + color: #8c939d + width 100% + height: 120px + text-align: center + } + } + } + + .img-list-box { + display flex + + .img-item { + width 120px + position relative + margin-right 10px + + .el-image { + width 120px + height 120px + border-radius 5px + } + + .el-button { + position absolute + right 5px + top 5px + width 20px + height 20px + } + } } } + // 提交按钮 + .submit-btn { display flex margin: 20px 0 @@ -109,154 +133,159 @@ align-items center } } - } - .running-job-list { - .job-item { - //border: 1px solid #454545; - width: 100%; - padding 2px - background-color #555555 - .job-item-inner { - position relative - height 100% - overflow hidden + // 任务列表 - .progress { - position absolute - width 100% - height 100% - top 0 - left 0 - display flex - justify-content center - align-items center + .job-list-box { + .running-job-list { + .job-item { + //border: 1px solid #454545; + width: 100%; + padding 2px + background-color #555555 - span { - font-size 20px - color #ffffff + .job-item-inner { + position relative + height 100% + overflow hidden + + .progress { + position absolute + width 100% + height 100% + top 0 + left 0 + display flex + justify-content center + align-items center + + span { + font-size 20px + color #ffffff + } + } } } } - } - } - .finish-job-list { - .job-item { - width 100% - height 100% - border 1px solid #666666 - padding 6px - overflow hidden - border-radius 6px - transition: all 0.3s ease; /* 添加过渡效果 */ - position relative + .finish-job-list { + .job-item { + width 100% + height 100% + border 1px solid #666666 + padding 6px + overflow hidden + border-radius 6px + transition: all 0.3s ease; /* 添加过渡效果 */ + position relative - .opt { - .opt-line { - margin 6px 0 + .opt { + .opt-line { + margin 6px 0 - ul { - display flex - flex-flow row + ul { + display flex + flex-flow row - li { - margin-right 6px + li { + margin-right 6px - a { - padding 3px 0 - width 40px - text-align center - border-radius 5px - display block - cursor pointer - background-color #4E5058 - color #ffffff + a { + padding 3px 0 + width 40px + text-align center + border-radius 5px + display block + cursor pointer + background-color #4E5058 + color #ffffff - &:hover { - background-color #6D6F78 + &:hover { + background-color #6D6F78 + } + } + } + + .show-prompt { + font-size 20px + cursor pointer } } } + } - .show-prompt { - font-size 20px - cursor pointer + .remove { + display none + position absolute + right 10px + top 10px + } + + &:hover { + .remove { + display block } } } + + + .animate { + &:hover { + box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */ + transform: translateY(-10px); /* 向上移动10像素 */ + } + } + + } + + + .el-image { + width 100% + height 100% + overflow visible + + img { + height 240px + } + + .el-image-viewer__wrapper { + img { + width auto + height auto + } + } + + .image-slot { + display flex + flex-flow column + justify-content center + align-items center + min-height 200px + color #ffffff + height 240px + + .iconfont { + font-size 50px + margin-bottom 10px + } + } } - .remove { - display none - position absolute - right 10px - top 10px - } + .el-image.upscale { + max-height 310px - &:hover { - .remove { - display block + img { + height 310px } - } - } - - .animate { - &:hover { - box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */ - transform: translateY(-10px); /* 向上移动10像素 */ - } - } - - } - - .el-image { - width 100% - height 100% - overflow visible - - img { - height 240px - } - - .el-image-viewer__wrapper { - img { - width auto - height auto - } - } - - .image-slot { - display flex - flex-flow column - justify-content center - align-items center - height 100% - min-height 200px - color #ffffff - height 240px - - .iconfont { - font-size 50px - margin-bottom 10px - } - } - } - - .el-image.upscale { - max-height 310px - - img { - height 310px - } - - .el-image-viewer__wrapper { - img { - width auto - height auto + .el-image-viewer__wrapper { + img { + width auto + height auto + } + } } } } diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index b8209755..72fb31e4 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -165,191 +165,241 @@
-

AI绘画

- - - -
图生图:以某张图片为底稿参考来创作绘画,生成类似风格或类型图像,支持 PNG 和 JPG 格式图片; -
-
- - - -
- -
- - - - - - -
- -
- - - -
- -
+
+ + + +
图生图:以某张图片为底稿参考来创作绘画,生成类似风格或类型图像,支持 PNG 和 JPG 格式图片; +
-
-
- 提示词: - - - + + + +
+ +
+
+
+
+ + +
+
-
- + + + + + +
+
+ +
+ + + +
+ +
+
+
+
+ 提示词: + + + + + +
+
+ + + + + 翻译 + + + + + + + + 翻译并重写 + + +
+
+
+ +
+ +
+ +
+
+
+ 不希望出现的内容:(可选) + + + + + +
+ 翻译 - - - - - - - 翻译并重写 - -
-
-
- -
- -
-
-
- 不希望出现的内容:(可选) - - - - - -
- - - - - 翻译 - +
+
+ -
- + +
请上传两张以上的图片,最多不超过五张,超过五张图片请使用文生图功能
+
+
+
+ + +
+ +
+ + + + +
+
+ + +
请上传两张有脸部的图片,用右边图片的脸替换左边图片的脸
+
+
+
+ + +
+ +
+ + + + + +
+
+ + +
+ 立即生成 +
+ 绘图可用额度:{{ imgCalls }}
- - - -
请上传两张以上的图片
-
- - - - - - - - - - - - - -
-
- - -
请上传两张有脸部的图片,用右边图片的脸替换左边图片的脸
-
- - - - - - - - - - - - - -
-
- - -
- 立即生成 -
- 绘图可用额度:{{ imgCalls }}
-
- + +
-

任务列表

-
- - - - - -
+ +
+
@@ -496,6 +504,7 @@ import Clipboard from "clipboard"; import {checkSession} from "@/action/session"; import {useRouter} from "vue-router"; import {getSessionId} from "@/store/session"; +import {removeArrayItem} from "@/utils/libs"; const listBoxHeight = ref(window.innerHeight - 40) const mjBoxHeight = ref(window.innerHeight - 150) @@ -553,10 +562,8 @@ const params = ref({ chaos: 0, stylize: 100, seed: 0, - raw: false, - img: "", - img2: "", img_arr: [], + raw: false, weight: 0.25, prompt: "", neg_prompt: "", @@ -564,6 +571,8 @@ const params = ref({ quality: 0 }) +const imgList = ref([]) + const activeName = ref('image') const runningJobs = ref([]) @@ -735,27 +744,7 @@ const uploadImg = (file) => { formData.append('file', result, result.name); // 执行上传操作 httpPost('/api/upload', formData).then((res) => { - params.value.img = res.data.url - ElMessage.success('上传成功') - }).catch((e) => { - ElMessage.error('上传失败:' + e.message) - }) - }, - error(err) { - console.log(err.message); - }, - }); -}; -const uploadImg2 = (file) => { - // 压缩图片并上传 - 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) => { - params.value.img2 = res.data.url + imgList.value.push(res.data.url) ElMessage.success('上传成功') }).catch((e) => { ElMessage.error('上传失败:' + e.message) @@ -766,6 +755,7 @@ const uploadImg2 = (file) => { }, }); }; + // 创建绘图任务 const promptRef = ref(null) const generate = () => { @@ -776,13 +766,11 @@ const generate = () => { if (params.value.model.indexOf("niji") !== -1 && params.value.raw) { return ElMessage.error("动漫模型不允许启用原始模式") } + if (imgList.value.length !== 2 && params.value.task_type === "swapFace") { + return ElMessage.error("换脸操作需要上传两张图片") + } params.value.session_id = getSessionId() - if (params.value.img !== "") { - params.value.img_arr.push(params.value.img) - } - if (params.value.img2 !== "") { - params.value.img_arr.push(params.value.img2) - } + params.value.img_arr = imgList.value httpPost("/api/mj/image", params.value).then(() => { ElMessage.success("绘画任务推送成功,请耐心等待任务执行...") imgCalls.value -= 1 @@ -855,6 +843,11 @@ const tabChange = (tab) => { params.value.task_type = tab } +// 删除已上传图片 +const removeUploadImage = (url) => { + imgList.value = removeArrayItem(imgList.value, url) +} + \ No newline at end of file From 6e6885849b7f4cce96662d36cd1925acfdbd9148 Mon Sep 17 00:00:00 2001 From: RockYang Date: Thu, 15 Feb 2024 18:11:22 +0800 Subject: [PATCH 17/29] feat: mobile mj list page is ready --- api/handler/mj_handler.go | 10 ++-- api/service/mj/plus/service.go | 2 +- api/service/mj/pool.go | 2 +- web/src/assets/css/mobile/image-mj.css | 49 +++++++++--------- web/src/assets/css/mobile/image-mj.styl | 68 +++++++++++-------------- web/src/views/ChatPlus.vue | 2 +- web/src/views/ImageMj.vue | 8 ++- web/src/views/mobile/ChatSession.vue | 3 +- web/src/views/mobile/ImageMj.vue | 58 ++++++--------------- 9 files changed, 88 insertions(+), 114 deletions(-) diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index 2764a9ea..7da787eb 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -340,20 +340,22 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) { continue } + // 失败的任务直接删除 if job.Progress == -1 { h.db.Delete(&model.MidJourneyJob{Id: job.Id}) + jobs = append(jobs, job) continue } if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" { - // 正在运行中任务使用代理访问图片 - if job.UseProxy { - job.ImgURL = job.OrgURL - } else { + // discord 服务器图片需要使用代理转发图片数据流 + if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") { image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL) if err == nil { job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image) } + } else { + job.ImgURL = job.OrgURL } } diff --git a/api/service/mj/plus/service.go b/api/service/mj/plus/service.go index 1f9acbd9..88e8a684 100644 --- a/api/service/mj/plus/service.go +++ b/api/service/mj/plus/service.go @@ -108,7 +108,7 @@ func (s *Service) Run() { s.taskStartTimes[int(task.Id)] = time.Now() atomic.AddInt32(&s.HandledTaskNum, 1) // 更新任务 ID/频道 - s.db.Debug().Model(&model.MidJourneyJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ + s.db.Model(&model.MidJourneyJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ "task_id": res.Result, "channel_id": s.Name, }) diff --git a/api/service/mj/pool.go b/api/service/mj/pool.go index c791ef9f..4c676589 100644 --- a/api/service/mj/pool.go +++ b/api/service/mj/pool.go @@ -201,7 +201,7 @@ func (p *ServicePool) SyncTaskProgress() { for _, v := range items { // 30 分钟还没完成的任务直接删除 if time.Now().Sub(v.CreatedAt) > time.Minute*30 { - //p.db.Delete(&v) + p.db.Delete(&v) // 非放大任务,退回绘图次数 if v.Type != types.TaskUpscale.String() { p.db.Model(&model.User{}).Where("id = ?", v.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1)) diff --git a/web/src/assets/css/mobile/image-mj.css b/web/src/assets/css/mobile/image-mj.css index 18700837..a16bb696 100644 --- a/web/src/assets/css/mobile/image-mj.css +++ b/web/src/assets/css/mobile/image-mj.css @@ -55,7 +55,8 @@ padding: 0; position: relative; } -.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .van-image { +.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .van-image, +.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue { min-height: 100px; } .mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .progress { @@ -95,40 +96,38 @@ overflow: hidden; border-radius: 6px; position: relative; + height: 100%; + width: 100%; } -.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-line { - margin: 6px 0; -} -.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-line ul { - display: flex; - flex-flow: row; -} -.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-line ul li { - margin-right: 6px; -} -.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-line ul li a { - padding: 3px 0; - width: 40px; +.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-btn { + padding: 3px 10px; text-align: center; border-radius: 5px; + margin: 3px 0; display: block; cursor: pointer; background-color: #4e5058; color: #fff; } -.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-line ul li a:hover { - background-color: #6d6f78; +.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .el-image { + width: 100%; + height: 200px; } -.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .opt .opt-line ul .show-prompt { - font-size: 20px; - cursor: pointer; +.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .el-image .image-slot { + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} +.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .el-image .image-slot .iconfont { + margin-right: 5px; +} +.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .upscale { + height: 260px; + width: 100%; } .mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .remove { - display: none; position: absolute; - right: 10px; - top: 10px; -} -.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item:hover .remove { - display: block; + right: 5px; + top: 5px; } diff --git a/web/src/assets/css/mobile/image-mj.styl b/web/src/assets/css/mobile/image-mj.styl index 8bb6003e..0370020e 100644 --- a/web/src/assets/css/mobile/image-mj.styl +++ b/web/src/assets/css/mobile/image-mj.styl @@ -76,7 +76,7 @@ padding 0 position relative - .van-image { + .van-image, .task-in-queue { min-height 100px } @@ -134,53 +134,47 @@ overflow hidden border-radius 6px position relative + height 100% + width 100% .opt { - .opt-line { - margin 6px 0 + .opt-btn { + padding 3px 10px + text-align center + border-radius 5px + margin 3px 0 + display block + cursor pointer + background-color #4E5058 + color #ffffff + } + } - ul { - display flex - flex-flow row + .el-image { + width 100% + height 200px - li { - margin-right 6px + .image-slot { + height 100% + display flex + justify-content center + align-items center - a { - padding 3px 0 - width 40px - text-align center - border-radius 5px - display block - cursor pointer - background-color #4E5058 - color #ffffff - - &:hover { - background-color #6D6F78 - } - } - } - - .show-prompt { - font-size 20px - cursor pointer - } + .iconfont { + margin-right 5px } } } - .remove { - display none - position absolute - right 10px - top 10px + .upscale { + height 260px + width 100% } - &:hover { - .remove { - display block - } + .remove { + position absolute + right 5px + top 5px } } diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index fc7723da..38e5294d 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -824,7 +824,7 @@ const reGenerate = function () { icon: loginUser.value.avatar, content: md.render(text) }); - socket.value.send(previousText); + socket.value.send(previousText.value); } const chatName = ref('') diff --git a/web/src/views/ImageMj.vue b/web/src/views/ImageMj.vue index 2dcb485c..a50c0403 100644 --- a/web/src/views/ImageMj.vue +++ b/web/src/views/ImageMj.vue @@ -504,7 +504,7 @@ import Clipboard from "clipboard"; import {checkSession} from "@/action/session"; import {useRouter} from "vue-router"; import {getSessionId} from "@/store/session"; -import {removeArrayItem} from "@/utils/libs"; +import {isMobile, removeArrayItem} from "@/utils/libs"; const listBoxHeight = ref(window.innerHeight - 40) const mjBoxHeight = ref(window.innerHeight - 150) @@ -560,7 +560,7 @@ const params = ref({ rate: rates[0].value, model: models[0].value, chaos: 0, - stylize: 100, + stylize: 0, seed: 0, img_arr: [], raw: false, @@ -584,6 +584,10 @@ const imgCalls = ref(0) const loading = ref(false) const userId = ref(0) +if (isMobile()) { + router.replace("/mobile/mj") +} + const rewritePrompt = () => { loading.value = true httpPost("/api/prompt/rewrite", {"prompt": params.value.prompt}).then(res => { diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index 6feca504..d84622f9 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -377,6 +377,7 @@ const sendMessage = () => { } if (prompt.value.trim().length === 0) { + showToast("请输入需要 AI 回答的问题") return false; } @@ -417,7 +418,7 @@ const reGenerate = () => { icon: loginUser.value.avatar, content: renderInputText(text) }); - socket.value.send(text); + socket.value.send(previousText.value); } const showShare = ref(false) diff --git a/web/src/views/mobile/ImageMj.vue b/web/src/views/mobile/ImageMj.vue index 5d040ce0..61c63f9b 100644 --- a/web/src/views/mobile/ImageMj.vue +++ b/web/src/views/mobile/ImageMj.vue @@ -140,7 +140,7 @@ image-size="80" description="暂无记录" /> - +
-
- -
- -
- -
+ + U1 + U2 + U3 + U4 + V1 + V2 + V3 + V4 +
@@ -236,7 +210,7 @@ import {getSessionId} from "@/store/session"; import {checkSession} from "@/action/session"; import Clipboard from "clipboard"; import {useRouter} from "vue-router"; -import {ChromeFilled, Delete, DocumentCopy, Picture} from "@element-plus/icons-vue"; +import {Delete, Picture} from "@element-plus/icons-vue"; const title = ref('MidJourney 绘画') const activeColspan = ref([""]) @@ -263,7 +237,7 @@ const params = ref({ rate: rates[0].value, model: models[0].value, chaos: 0, - stylize: 100, + stylize: 0, seed: 0, img_arr: [], raw: false, @@ -356,7 +330,7 @@ const fetchRunningJobs = (userId) => { if (jobs[i].progress === -1) { showNotify({ message: `任务执行失败:${jobs[i]['err_msg']}`, - type: 'error', + type: 'danger', }) imgCalls.value += 1 continue @@ -439,10 +413,10 @@ const generate = () => { params.value.session_id = getSessionId() params.value.img_arr = imgList.value.map(img => img.url) httpPost("/api/mj/image", params.value).then(() => { - ElMessage.success("绘画任务推送成功,请耐心等待任务执行...") + showToast("绘画任务推送成功,请耐心等待任务执行") imgCalls.value -= 1 }).catch(e => { - ElMessage.error("任务推送失败:" + e.message) + showFailToast("任务推送失败:" + e.message) }) } From 4ea73526740d9a1bc2021b9f67c6e6e75aae63ec Mon Sep 17 00:00:00 2001 From: RockYang Date: Fri, 16 Feb 2024 15:55:04 +0800 Subject: [PATCH 18/29] feat: midjourney mobile page all function is ready --- api/handler/mj_handler.go | 5 ++ api/service/mj/plus/service.go | 29 +++++----- web/src/assets/css/mobile/image-mj.css | 5 ++ web/src/assets/css/mobile/image-mj.styl | 6 +++ web/src/assets/iconfont/iconfont.css | 10 ++-- web/src/assets/iconfont/iconfont.js | 2 +- web/src/assets/iconfont/iconfont.json | 7 +++ web/src/assets/iconfont/iconfont.ttf | Bin 18088 -> 18352 bytes web/src/assets/iconfont/iconfont.woff | Bin 12376 -> 12540 bytes web/src/assets/iconfont/iconfont.woff2 | Bin 10860 -> 11028 bytes web/src/views/mobile/ChatApps.vue | 35 +----------- web/src/views/mobile/Home.vue | 2 +- web/src/views/mobile/ImageMj.vue | 69 +++++++++++++++++++++++- 13 files changed, 116 insertions(+), 54 deletions(-) diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index 7da787eb..2a41f390 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -141,6 +141,11 @@ func (h *MidJourneyHandler) Image(c *gin.Context) { prompt += fmt.Sprintf(" %s", data.Model) } + // 处理融图和换脸的提示词 + if data.TaskType == types.TaskSwapFace.String() || data.TaskType == types.TaskBlend.String() { + prompt = fmt.Sprintf("%s:%s", data.TaskType, strings.Join(data.ImgArr, ",")) + } + idValue, _ := c.Get(types.LoginUserID) userId := utils.IntValue(utils.InterfaceToString(idValue), 0) // generate task id diff --git a/api/service/mj/plus/service.go b/api/service/mj/plus/service.go index 88e8a684..309a3158 100644 --- a/api/service/mj/plus/service.go +++ b/api/service/mj/plus/service.go @@ -58,12 +58,12 @@ func (s *Service) Run() { } // if it's reference message, check if it's this channel's message - if task.ChannelId != "" && task.ChannelId != s.Name { - logger.Debugf("handle other service task, name: %s, channel_id: %s, drop it.", s.Name, task.ChannelId) - s.taskQueue.RPush(task) - time.Sleep(time.Second) - continue - } + //if task.ChannelId != "" && task.ChannelId != s.Name { + // logger.Debugf("handle other service task, name: %s, channel_id: %s, drop it.", s.Name, task.ChannelId) + // s.taskQueue.RPush(task) + // time.Sleep(time.Second) + // continue + //} logger.Infof("%s handle a new MidJourney task: %+v", s.Name, task) var res ImageRes @@ -85,14 +85,15 @@ func (s *Service) Run() { break } + var job model.MidJourneyJob + s.db.Where("id = ?", task.Id).First(&job) if err != nil || (res.Code != 1 && res.Code != 22) { errMsg := fmt.Sprintf("%v,%s", err, res.Description) logger.Error("绘画任务执行失败:", errMsg) + job.Progress = -1 + job.ErrMsg = errMsg // update the task progress - s.db.Model(&model.MidJourneyJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ - "progress": -1, - "err_msg": errMsg, - }) + s.db.Updates(&job) // 任务失败,通知前端 s.notifyQueue.RPush(task.UserId) // restore img_call quota @@ -108,11 +109,9 @@ func (s *Service) Run() { s.taskStartTimes[int(task.Id)] = time.Now() atomic.AddInt32(&s.HandledTaskNum, 1) // 更新任务 ID/频道 - s.db.Model(&model.MidJourneyJob{Id: task.Id}).UpdateColumns(map[string]interface{}{ - "task_id": res.Result, - "channel_id": s.Name, - }) - + job.TaskId = res.Result + job.ChannelId = s.Name + s.db.Updates(&job) } } diff --git a/web/src/assets/css/mobile/image-mj.css b/web/src/assets/css/mobile/image-mj.css index a16bb696..c221b867 100644 --- a/web/src/assets/css/mobile/image-mj.css +++ b/web/src/assets/css/mobile/image-mj.css @@ -131,3 +131,8 @@ right: 5px; top: 5px; } +.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item .remove .el-button { + margin-left: 5px; + height: auto; + padding: 5px; +} diff --git a/web/src/assets/css/mobile/image-mj.styl b/web/src/assets/css/mobile/image-mj.styl index 0370020e..8f71dbce 100644 --- a/web/src/assets/css/mobile/image-mj.styl +++ b/web/src/assets/css/mobile/image-mj.styl @@ -175,6 +175,12 @@ position absolute right 5px top 5px + + .el-button { + margin-left 5px + height auto + padding 5px + } } } diff --git a/web/src/assets/iconfont/iconfont.css b/web/src/assets/iconfont/iconfont.css index 8f761cb7..8706b25b 100644 --- a/web/src/assets/iconfont/iconfont.css +++ b/web/src/assets/iconfont/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 4125778 */ - src: url('iconfont.woff2?t=1705615887594') format('woff2'), - url('iconfont.woff?t=1705615887594') format('woff'), - url('iconfont.ttf?t=1705615887594') format('truetype'); + src: url('iconfont.woff2?t=1708054962140') format('woff2'), + url('iconfont.woff?t=1708054962140') format('woff'), + url('iconfont.ttf?t=1708054962140') format('truetype'); } .iconfont { @@ -13,6 +13,10 @@ -moz-osx-font-smoothing: grayscale; } +.icon-prompt:before { + content: "\e6ce"; +} + .icon-share-bold:before { content: "\e626"; } diff --git a/web/src/assets/iconfont/iconfont.js b/web/src/assets/iconfont/iconfont.js index 89b3d061..2c4dfc7b 100644 --- a/web/src/assets/iconfont/iconfont.js +++ b/web/src/assets/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4125778='',function(a){var l=(l=document.getElementsByTagName("script"))[l.length-1],c=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var t,h,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(c&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}t=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(h=function(){document.removeEventListener("DOMContentLoaded",h,!1),t()},document.addEventListener("DOMContentLoaded",h,!1)):document.attachEvent&&(i=t,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,v())})}function v(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}v()}}(window); \ No newline at end of file +window._iconfont_svg_string_4125778='',function(a){var l=(l=document.getElementsByTagName("script"))[l.length-1],c=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var t,h,i,o,z,m=function(l,c){c.parentNode.insertBefore(l,c)};if(c&&!a.__iconfont__svg__cssinject__){a.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}t=function(){var l,c=document.createElement("div");c.innerHTML=a._iconfont_svg_string_4125778,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(l=document.body).firstChild?m(c,l.firstChild):l.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(h=function(){document.removeEventListener("DOMContentLoaded",h,!1),t()},document.addEventListener("DOMContentLoaded",h,!1)):document.attachEvent&&(i=t,o=a.document,z=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,v())})}function v(){z||(z=!0,i())}function s(){try{o.documentElement.doScroll("left")}catch(l){return void setTimeout(s,50)}v()}}(window); \ No newline at end of file diff --git a/web/src/assets/iconfont/iconfont.json b/web/src/assets/iconfont/iconfont.json index 06eb9ce8..3186e019 100644 --- a/web/src/assets/iconfont/iconfont.json +++ b/web/src/assets/iconfont/iconfont.json @@ -5,6 +5,13 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "8017627", + "name": "prompt", + "font_class": "prompt", + "unicode": "e6ce", + "unicode_decimal": 59086 + }, { "icon_id": "1132455", "name": "share-bold", diff --git a/web/src/assets/iconfont/iconfont.ttf b/web/src/assets/iconfont/iconfont.ttf index 860d6bea403359b7a83bb574f117c3a877b78920..cc125590b9b74d6c48eb890ef41980c1df950628 100644 GIT binary patch delta 1489 zcmZ{kT}&KR7>3_7v&`-c%(B4#Kv~+|Z3~uSp$lx&N^4axjYL!Lj5aHz@)LxzK~nHS zw`rPah(DC|QjNyM7&V53t2T`>vDeKSAeqVq0yHIr&3>30HM!;L+=g`^uN$_`H$Hya}jv-d`UbKiU7*D`(!}{x0618tb1fs9tq|&ue*~I@Lcm z5c#NU9(e2+&m1a@zvNH$eYWQ}pofbV&R^R3a1M&^l8zsK8GXL8>o1fCxB&coXJ-Gu z8^E9 zT`gtpr`eyiPsa4w$MNic*9qj2p9>_4gBaerMlgcW|Gj`()L=iV(T)h(5XV03!Gow| zm=(yP4jnwU1wQJr6Xl3v8&hzRLYU!oVmks1vMVR1uvC$sX0zVVJzFOr95f6ELS-}z3_`Uu3=v`*7%YS$YZx}9%)kU7)J(%v zAUs9Gq#$|^VS12ak{w~DkhFn0LoxNNvBh1Kf@x(lm68R#>t{%)Y-u-0dw z_pml(pbN3~A}O+?A+h$FL0gk;LIu>Ex@*nbu=Do4Kq0VJcG`h+&RGr)hN7Xt&~5i| zcg9PVuT*qZ-0{B-C&P=Crz6>_eASieTQzMpPt<%G&BtnEgRwv2$Krn`niFSgx7U79 zH(9s7ZMpu-WOcGJx!CX&^VcG8&e}Ka0_%SO-FT9}j&#O}RYl|N>Ff>_KcZSyNA4k& z&2?6FG}=*Tuj1oG)XB7}#$bA%;&QG%or}lHGERev#_KS-h}7-o|<-p z-&QszmF0WOuJ8AR-J$P1U-{L+b3u3Gf*TII)~{}OV=|IRq!NjU8SHW2yWi~j&QjQ_ zkCxo9?^)k_{`~v0i R)hSGjj};~t4)=_xe*lk$ID`NI delta 1258 zcmZ{iTS!z<6o&tO&Y5w{Yop_JGA+xlW~G@SCQ%U;Aq5c?q;^qEQ_9MEXu_Ao$V>AO z1VtoBRD@wdK}6U?P!Ca5R75voX&^mWy=DKN6}`0=-`STrXZBkE`iFYWwR5JgB;w_5 z`vf=-0bhMX^N9nY@Qo25&<9l9ZQNhCujshD-<3u4 z%-5*jd0zA-wugBSH?~^Dc^Ys10?+U=Y(X`ANRIw?Dxwu`P4ui=A2B@L@h^5_H>37% z)I(z*G!DQBz@?6e9z(u>iR!z#P`@AGqzA27st2yqqW3ZdKq16iz4BuKGBxghp(Lg66xJwgc~w$ReQ^$Q(mp{|e$g$hGf zDb@#4r67%SwL7$RmZWfIL9>xzUG$F>%u>X9(`Y|@nn|#ydR5?A)gxl@@ z_Kx{HUyncNuk}yHU5%Rzv;|(qXT{%2IGpe#u`_9V()Hw;6ffmr>csS|!9&3Z!8d6u z(nrPPiug-g;qb)P3Yu4PT5euQX-R P7weh~hS#reG6sJDZ88r= diff --git a/web/src/assets/iconfont/iconfont.woff b/web/src/assets/iconfont/iconfont.woff index 9aa4b517c682354af4a979a7adf9d61ec6da6f7c..fd9a3d8429aab2cc96f65a9761092b4f28fc28a8 100644 GIT binary patch delta 11997 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 delta 11783 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 diff --git a/web/src/assets/iconfont/iconfont.woff2 b/web/src/assets/iconfont/iconfont.woff2 index 1b52a11f9a0c3431f6ad59d062c6c9cf4c9357ab..0021a3aefbdd6738defbcd6ae258bcc981cde6cb 100644 GIT binary patch literal 11028 zcmV+vE9=yEPew8T0RR9104o##3jhEB07tL@04l`*0RR9100000000000000000000 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 Cu_P=2 diff --git a/web/src/views/mobile/ChatApps.vue b/web/src/views/mobile/ChatApps.vue index 1de545bd..fef2232e 100644 --- a/web/src/views/mobile/ChatApps.vue +++ b/web/src/views/mobile/ChatApps.vue @@ -3,39 +3,8 @@

- - - - - - -
- - 提交 - -
-
+
- - - -
@@ -45,7 +14,7 @@ import {httpGet, httpPost} from "@/utils/http"; import {showFailToast, showSuccessToast} from "vant"; import {ElMessage} from "element-plus"; -const title = ref('聊天设置') +const title = ref('图片创作广场') const form = ref({ chat_config: { api_keys: {OpenAI: "", Azure: "", ChatGLM: ""} diff --git a/web/src/views/mobile/Home.vue b/web/src/views/mobile/Home.vue index d7ea04a4..478b6805 100644 --- a/web/src/views/mobile/Home.vue +++ b/web/src/views/mobile/Home.vue @@ -6,7 +6,7 @@ 对话 绘图 - 应用 + 广场 我的 diff --git a/web/src/views/mobile/ImageMj.vue b/web/src/views/mobile/ImageMj.vue index 61c63f9b..a35acd78 100644 --- a/web/src/views/mobile/ImageMj.vue +++ b/web/src/views/mobile/ImageMj.vue @@ -190,6 +190,9 @@ + + +
@@ -202,7 +205,7 @@ diff --git a/web/src/views/mobile/Home.vue b/web/src/views/mobile/Home.vue index 478b6805..a22ce998 100644 --- a/web/src/views/mobile/Home.vue +++ b/web/src/views/mobile/Home.vue @@ -6,7 +6,7 @@ 对话 绘图 - 广场 + 广场 我的 diff --git a/web/src/views/mobile/ImageMj.vue b/web/src/views/mobile/ImageMj.vue index a35acd78..a517fafc 100644 --- a/web/src/views/mobile/ImageMj.vue +++ b/web/src/views/mobile/ImageMj.vue @@ -269,15 +269,6 @@ onMounted(() => { }).catch(() => { router.push('/login') }); - - const clipboard = new Clipboard('.copy-prompt'); - clipboard.on('success', () => { - ElMessage.success("复制成功!"); - }) - - clipboard.on('error', () => { - ElMessage.error('复制失败!'); - }) }) const heartbeatHandle = ref(null) diff --git a/web/src/views/mobile/ImgWall.vue b/web/src/views/mobile/ImgWall.vue new file mode 100644 index 00000000..94783446 --- /dev/null +++ b/web/src/views/mobile/ImgWall.vue @@ -0,0 +1,132 @@ + + + + + From b99b3103066a1c648be411fa621d70c2752be6bf Mon Sep 17 00:00:00 2001 From: RockYang Date: Tue, 20 Feb 2024 18:38:03 +0800 Subject: [PATCH 25/29] feat: download image which ai generated in dialog and replace the image url --- api/handler/chatimpl/chat_handler.go | 40 ++++++++++++++++++++--- api/handler/chatimpl/openai_handler.go | 2 +- api/test/test.go | 44 ++++++++++++++++++++++++-- web/src/views/mobile/Profile.vue | 7 ++-- 4 files changed, 80 insertions(+), 13 deletions(-) diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index be4e771b..4b150554 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -6,6 +6,7 @@ import ( "chatplus/core/types" "chatplus/handler" logger2 "chatplus/logger" + "chatplus/service/oss" "chatplus/store/model" "chatplus/store/vo" "chatplus/utils" @@ -16,6 +17,7 @@ import ( "fmt" "net/http" "net/url" + "regexp" "strings" "time" @@ -33,14 +35,16 @@ var logger = logger2.GetLogger() type ChatHandler struct { handler.BaseHandler - db *gorm.DB - redis *redis.Client + db *gorm.DB + redis *redis.Client + uploadManager *oss.UploaderManager } -func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client) *ChatHandler { +func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager) *ChatHandler { h := ChatHandler{ - db: db, - redis: redis, + db: db, + redis: redis, + uploadManager: manager, } h.App = app return &h @@ -546,3 +550,29 @@ func (h *ChatHandler) incUserTokenFee(userId uint, tokens int) { h.db.Model(&model.User{}).Where("id = ?", userId). UpdateColumn("tokens", gorm.Expr("tokens + ?", tokens)) } + +// 将AI回复消息中生成的图片链接下载到本地 +func (h *ChatHandler) extractImgUrl(text string) string { + pattern := `!\[([^\]]*)]\(([^)]+)\)` + re := regexp.MustCompile(pattern) + matches := re.FindAllStringSubmatch(text, -1) + + // 下载图片并替换链接地址 + for _, match := range matches { + imageURL := match[2] + logger.Debug(imageURL) + // 对于相同地址的图片,已经被替换了,就不再重复下载了 + if !strings.Contains(text, imageURL) { + continue + } + + newImgURL, err := h.uploadManager.GetUploadHandler().PutImg(imageURL, false) + if err != nil { + logger.Error("error with download image: ", err) + continue + } + + text = strings.ReplaceAll(text, imageURL, newImgURL) + } + return text +} diff --git a/api/handler/chatimpl/openai_handler.go b/api/handler/chatimpl/openai_handler.go index ff2fd20b..3c228ead 100644 --- a/api/handler/chatimpl/openai_handler.go +++ b/api/handler/chatimpl/openai_handler.go @@ -233,7 +233,7 @@ func (h *ChatHandler) sendOpenAiMessage( RoleId: role.Id, Type: types.ReplyMsg, Icon: role.Icon, - Content: message.Content, + Content: h.extractImgUrl(message.Content), Tokens: totalTokens, UseContext: useContext, Model: req.Model, diff --git a/api/test/test.go b/api/test/test.go index 667f3f7b..f07cb9ac 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -2,10 +2,48 @@ package main import ( "fmt" - "net/url" + "regexp" ) func main() { - u, err := url.Parse("https://api.chat-plus.net/mj/image/1706368258238514?aaa=bbb") - fmt.Println(u.Path, u.RawQuery, err) + text := ` +> search("Shenzhen weather January 15, 2024") + +> mclick([0, 9, 16]) + +> **end-searching** + +今天深圳的天气情况如下: + +- 白天气温预计在21°C至24°C之间,天气晴朗。 +- 晚上气温预计在21°C左右,云量较多,可能会有间断性小雨。 +- 风向主要是东南风,风速大约在6至12公里每小时之间。 + +这些信息表明深圳今天的天气相对舒适,适合户外活动。晚上可能需要带伞以应对间断性小雨。温度较为宜人,早晚可能稍微凉爽一些【[Shenzhen weather in January 2024 | Shenzhen 14 day weather](https://www.weather25.com/asia/china/guangdong/shenzhen?page=month&month=January)】【[Hourly forecast for Shenzhen, Guangdong, China](https://www.timeanddate.com/weather/china/shenzhen/hourly)】【[Shenzhen Guangdong China 15 Day Weather Forecast](https://www.weatheravenue.com/en/asia/cn/guangdong/shenzhen-weather-15-days.html)】。 + +我将根据这些信息生成一张气象图,展示深圳今天的天气情况。 + + {"prompt":"A detailed weather map for Shenzhen, China, on January 15, 2024. The map shows a sunny day with clear skies during the day and partly cloudy skies at night. Temperatures range from 21\u00b0C to 24\u00b0C during the day and around 21\u00b0C at night. There are indications of light southeast winds during the day and evening, with wind speeds ranging from 6 to 12 km/h. The map includes symbols for sunshine, light clouds, and wind direction arrows, along with temperature readings for different times of the day. The layout is clear, with a focus on Shenzhen's geographical location and the surrounding region.","size":"1024x1024"} + + +![image1](https://filesystem.site/cdn/20240115/XD6EjyPDGCD4X3AQt3h3FijRmSb6fB.webp) + +![下载1](https://filesystem.site/cdn/download/20240115/XD6EjyPDGCD4X3AQt3h3FijRmSb6fB.webp) + +And here is another image link: ![another image](https://example.com/another-image.png). + + +这是根据今天深圳的天气情况制作的气象图。图中展示了白天晴朗、夜间部分多云的天气,以及相关的温度和风向信息。` + pattern := `!\[([^\]]*)]\(([^)]+)\)` + + // 编译正则表达式 + re := regexp.MustCompile(pattern) + + // 查找匹配的字符串 + matches := re.FindAllStringSubmatch(text, -1) + + // 提取链接并打印 + for _, match := range matches { + fmt.Println(match[2]) + } } diff --git a/web/src/views/mobile/Profile.vue b/web/src/views/mobile/Profile.vue index 1231fdf6..6215edc9 100644 --- a/web/src/views/mobile/Profile.vue +++ b/web/src/views/mobile/Profile.vue @@ -6,12 +6,11 @@