mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: add mj image list component for mobile page. fixed bug for html tag escape
This commit is contained in:
		@@ -97,7 +97,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	// use old chat data override the chat model and role ID
 | 
			
		||||
	var chat model.ChatItem
 | 
			
		||||
	res = h.db.Where("chat_id=?", chatId).First(&chat)
 | 
			
		||||
	res = h.db.Where("chat_id = ?", chatId).First(&chat)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		chatModel.Id = chat.ModelId
 | 
			
		||||
		roleId = int(chat.RoleId)
 | 
			
		||||
 
 | 
			
		||||
@@ -342,6 +342,7 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
		if job.Progress == -1 {
 | 
			
		||||
			h.db.Delete(&model.MidJourneyJob{Id: job.Id})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,12 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回
 | 
			
		||||
	if order.Status == types.OrderPaidSuccess {
 | 
			
		||||
		resp.ERROR(c, "This order had been paid, please do not pay twice")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 更新扫码状态
 | 
			
		||||
	h.db.Model(&order).UpdateColumn("status", types.OrderScanned)
 | 
			
		||||
	if payWay == "alipay" { // 支付宝
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
package wanx
 | 
			
		||||
@@ -11,7 +11,7 @@ import (
 | 
			
		||||
 | 
			
		||||
func NewGormConfig() *gorm.Config {
 | 
			
		||||
	return &gorm.Config{
 | 
			
		||||
		Logger: logger.Default.LogMode(logger.Warn),
 | 
			
		||||
		Logger: logger.Default.LogMode(logger.Silent),
 | 
			
		||||
		NamingStrategy: schema.NamingStrategy{
 | 
			
		||||
			TablePrefix:   "chatgpt_", // 设置表前缀
 | 
			
		||||
			SingularTable: false,      // 使用单数表名形式
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,3 @@
 | 
			
		||||
.mobile-mj .content .van-field__label {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  text-align: right;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .text-line {
 | 
			
		||||
  padding: 6px;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
@@ -48,3 +44,91 @@
 | 
			
		||||
.mobile-mj .content .text-line .van-row .van-col .active {
 | 
			
		||||
  background-color: #e5e5e5;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .text-line .van-button {
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .text-line .van-button .van-tag {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 20px;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .van-image {
 | 
			
		||||
  min-height: 100px;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .progress {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background: rgba(50,50,50,0.5);
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .progress .van-circle__text {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: column;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  color: #c1c1c1;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue .icon {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue .icon .iconfont {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content .task-in-queue .text {
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .finish-job-list .van-grid .van-grid-item .van-grid-item__content .job-item {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.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;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  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 .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 .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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
.mobile-mj {
 | 
			
		||||
  .content {
 | 
			
		||||
    padding-bottom 60px
 | 
			
		||||
 | 
			
		||||
    .text-line {
 | 
			
		||||
      padding 6px
 | 
			
		||||
      font-size 14px
 | 
			
		||||
@@ -70,5 +68,126 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .running-job-list {
 | 
			
		||||
      .van-grid {
 | 
			
		||||
        .van-grid-item {
 | 
			
		||||
          .van-grid-item__content {
 | 
			
		||||
            padding 0
 | 
			
		||||
            position relative
 | 
			
		||||
 | 
			
		||||
            .van-image {
 | 
			
		||||
              min-height 100px
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .progress {
 | 
			
		||||
              display flex
 | 
			
		||||
              justify-content center
 | 
			
		||||
              align-items center
 | 
			
		||||
              width 100%
 | 
			
		||||
              height 100%
 | 
			
		||||
              background rgba(50, 50, 50, 0.5)
 | 
			
		||||
              position absolute
 | 
			
		||||
              left 0
 | 
			
		||||
              top 0
 | 
			
		||||
 | 
			
		||||
              .van-circle__text {
 | 
			
		||||
                color #ffffff
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // end progress
 | 
			
		||||
 | 
			
		||||
            .task-in-queue {
 | 
			
		||||
              display flex
 | 
			
		||||
              flex-flow column
 | 
			
		||||
              justify-content center
 | 
			
		||||
              color #c1c1c1
 | 
			
		||||
 | 
			
		||||
              .icon {
 | 
			
		||||
                text-align center
 | 
			
		||||
 | 
			
		||||
                .iconfont {
 | 
			
		||||
                  font-size 24px
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .text {
 | 
			
		||||
                font-size 14px
 | 
			
		||||
                margin-top 5px
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //end running jobs
 | 
			
		||||
 | 
			
		||||
    .finish-job-list {
 | 
			
		||||
      .van-grid {
 | 
			
		||||
        .van-grid-item {
 | 
			
		||||
          .van-grid-item__content {
 | 
			
		||||
            padding 0
 | 
			
		||||
 | 
			
		||||
            .job-item {
 | 
			
		||||
              overflow hidden
 | 
			
		||||
              border-radius 6px
 | 
			
		||||
              position relative
 | 
			
		||||
 | 
			
		||||
              .opt {
 | 
			
		||||
                .opt-line {
 | 
			
		||||
                  margin 6px 0
 | 
			
		||||
 | 
			
		||||
                  ul {
 | 
			
		||||
                    display flex
 | 
			
		||||
                    flex-flow row
 | 
			
		||||
 | 
			
		||||
                    li {
 | 
			
		||||
                      margin-right 6px
 | 
			
		||||
 | 
			
		||||
                      a {
 | 
			
		||||
                        padding 3px 0
 | 
			
		||||
                        width 40px
 | 
			
		||||
                        text-align center
 | 
			
		||||
                        border-radius 5px
 | 
			
		||||
                        display block
 | 
			
		||||
                        cursor pointer
 | 
			
		||||
                        background-color #4E5058
 | 
			
		||||
                        color #ffffff
 | 
			
		||||
 | 
			
		||||
                        &:hover {
 | 
			
		||||
                          background-color #6D6F78
 | 
			
		||||
                        }
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    .show-prompt {
 | 
			
		||||
                      font-size 20px
 | 
			
		||||
                      cursor pointer
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .remove {
 | 
			
		||||
                display none
 | 
			
		||||
                position absolute
 | 
			
		||||
                right 10px
 | 
			
		||||
                top 10px
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              &:hover {
 | 
			
		||||
                .remove {
 | 
			
		||||
                  display block
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,6 +9,7 @@ import {
 | 
			
		||||
    Button,
 | 
			
		||||
    Cell,
 | 
			
		||||
    CellGroup,
 | 
			
		||||
    Circle,
 | 
			
		||||
    Col,
 | 
			
		||||
    Collapse,
 | 
			
		||||
    CollapseItem,
 | 
			
		||||
@@ -16,11 +17,17 @@ import {
 | 
			
		||||
    Dialog,
 | 
			
		||||
    DropdownItem,
 | 
			
		||||
    DropdownMenu,
 | 
			
		||||
    Empty,
 | 
			
		||||
    Field,
 | 
			
		||||
    Form,
 | 
			
		||||
    Grid,
 | 
			
		||||
    GridItem,
 | 
			
		||||
    Icon,
 | 
			
		||||
    Image,
 | 
			
		||||
    ImagePreview,
 | 
			
		||||
    Lazyload,
 | 
			
		||||
    List,
 | 
			
		||||
    Loading,
 | 
			
		||||
    NavBar,
 | 
			
		||||
    Notify,
 | 
			
		||||
    Overlay,
 | 
			
		||||
@@ -79,6 +86,13 @@ app.use(Slider)
 | 
			
		||||
app.use(Badge)
 | 
			
		||||
app.use(Collapse);
 | 
			
		||||
app.use(CollapseItem);
 | 
			
		||||
app.use(Grid);
 | 
			
		||||
app.use(GridItem);
 | 
			
		||||
app.use(Empty);
 | 
			
		||||
app.use(Circle);
 | 
			
		||||
app.use(Loading);
 | 
			
		||||
app.use(Lazyload);
 | 
			
		||||
app.use(ImagePreview);
 | 
			
		||||
app.use(router).use(ElementPlus).mount('#app')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -197,3 +197,9 @@ export function processContent(content) {
 | 
			
		||||
    return lines.join("\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function escapeHTML(html) {
 | 
			
		||||
    return html.replace(/&/g, "&")
 | 
			
		||||
        .replace(/</g, "<")
 | 
			
		||||
        .replace(/>/g, ">");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -259,7 +259,16 @@ import {
 | 
			
		||||
  VideoPause
 | 
			
		||||
} from '@element-plus/icons-vue'
 | 
			
		||||
import 'highlight.js/styles/a11y-dark.css'
 | 
			
		||||
import {dateFormat, isImage, isMobile, processContent, randString, removeArrayItem, UUID} from "@/utils/libs";
 | 
			
		||||
import {
 | 
			
		||||
  dateFormat,
 | 
			
		||||
  escapeHTML,
 | 
			
		||||
  isImage,
 | 
			
		||||
  isMobile,
 | 
			
		||||
  processContent,
 | 
			
		||||
  randString,
 | 
			
		||||
  removeArrayItem,
 | 
			
		||||
  UUID
 | 
			
		||||
} from "@/utils/libs";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
 | 
			
		||||
@@ -717,7 +726,7 @@ const sendMessage = function () {
 | 
			
		||||
    type: "prompt",
 | 
			
		||||
    id: randString(32),
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: md.render(processContent(prompt.value)),
 | 
			
		||||
    content: md.render(escapeHTML(processContent(prompt.value))),
 | 
			
		||||
    created_at: new Date().getTime(),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@@ -815,7 +824,7 @@ const reGenerate = function () {
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: md.render(text)
 | 
			
		||||
  });
 | 
			
		||||
  socket.value.send(text);
 | 
			
		||||
  socket.value.send(previousText);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const chatName = ref('')
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ const onChange = (index) => {
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@import '@/assets/iconfont/iconfont.css';
 | 
			
		||||
.mobile-home {
 | 
			
		||||
  .container {
 | 
			
		||||
    .van-nav-bar {
 | 
			
		||||
@@ -47,7 +48,7 @@ const onChange = (index) => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      padding 46px 10px 0 10px;
 | 
			
		||||
      padding 46px 10px 60px 10px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,128 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </van-form>
 | 
			
		||||
 | 
			
		||||
      <h2>任务列表</h2>
 | 
			
		||||
      <h3>任务列表</h3>
 | 
			
		||||
      <div class="running-job-list">
 | 
			
		||||
        <van-empty v-if="runningJobs.length ===0"
 | 
			
		||||
                   image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
 | 
			
		||||
                   image-size="80"
 | 
			
		||||
                   description="暂无记录"
 | 
			
		||||
        />
 | 
			
		||||
        <van-grid :gutter="10" :column-num="3" v-else>
 | 
			
		||||
          <van-grid-item v-for="item in runningJobs">
 | 
			
		||||
            <div v-if="item.progress > 0">
 | 
			
		||||
              <van-image :src="item['img_url']">
 | 
			
		||||
                <template v-slot:error>加载失败</template>
 | 
			
		||||
              </van-image>
 | 
			
		||||
              <div class="progress">
 | 
			
		||||
                <van-circle
 | 
			
		||||
                    v-model:current-rate="item.progress"
 | 
			
		||||
                    :rate="item.progress"
 | 
			
		||||
                    :speed="100"
 | 
			
		||||
                    :text="item.progress+'%'"
 | 
			
		||||
                    :stroke-width="60"
 | 
			
		||||
                    size="90px"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div v-else class="task-in-queue">
 | 
			
		||||
              <span class="icon"><i class="iconfont icon-quick-start"></i></span>
 | 
			
		||||
              <span class="text">排队中</span>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
          </van-grid-item>
 | 
			
		||||
        </van-grid>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <h3>创作记录</h3>
 | 
			
		||||
      <div class="finish-job-list">
 | 
			
		||||
        <van-empty v-if="finishedJobs.length ===0"
 | 
			
		||||
                   image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
 | 
			
		||||
                   image-size="80"
 | 
			
		||||
                   description="暂无记录"
 | 
			
		||||
        />
 | 
			
		||||
        <van-grid :gutter="10" :column-num="3" v-else>
 | 
			
		||||
          <van-grid-item v-for="item in finishedJobs">
 | 
			
		||||
            <div class="job-item">
 | 
			
		||||
              <el-image
 | 
			
		||||
                  :src="item['thumb_url']"
 | 
			
		||||
                  :class="item['can_opt'] ? '' : 'upscale'" :zoom-rate="1.2"
 | 
			
		||||
                  :preview-src-list="[item['img_url']]" fit="cover" :initial-index="0"
 | 
			
		||||
                  loading="lazy" v-if="item.progress > 0">
 | 
			
		||||
                <template #placeholder>
 | 
			
		||||
                  <div class="image-slot">
 | 
			
		||||
                    正在加载图片
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #error>
 | 
			
		||||
                  <div class="image-slot" v-if="item['img_url'] === ''">
 | 
			
		||||
                    <i class="iconfont icon-loading"></i>
 | 
			
		||||
                    <span>正在下载图片</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="image-slot" v-else>
 | 
			
		||||
                    <el-icon>
 | 
			
		||||
                      <Picture/>
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-image>
 | 
			
		||||
 | 
			
		||||
              <div class="opt" v-if="item['can_opt']">
 | 
			
		||||
                <div class="opt-line">
 | 
			
		||||
                  <ul>
 | 
			
		||||
                    <li><a @click="upscale(1, item)">U1</a></li>
 | 
			
		||||
                    <li><a @click="upscale(2, item)">U2</a></li>
 | 
			
		||||
                    <li><a @click="upscale(3, item)">U3</a></li>
 | 
			
		||||
                    <li><a @click="upscale(4, item)">U4</a></li>
 | 
			
		||||
                    <li class="show-prompt">
 | 
			
		||||
 | 
			
		||||
                      <el-popover placement="left" title="提示词" :width="240" trigger="hover">
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                          <el-icon>
 | 
			
		||||
                            <ChromeFilled/>
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #default>
 | 
			
		||||
                          <div class="mj-list-item-prompt">
 | 
			
		||||
                            <span>{{ item.prompt }}</span>
 | 
			
		||||
                            <el-icon class="copy-prompt"
 | 
			
		||||
                                     :data-clipboard-text="item.prompt">
 | 
			
		||||
                              <DocumentCopy/>
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-popover>
 | 
			
		||||
                    </li>
 | 
			
		||||
                  </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="opt-line">
 | 
			
		||||
                  <ul>
 | 
			
		||||
                    <li><a @click="variation(1, item)">V1</a></li>
 | 
			
		||||
                    <li><a @click="variation(2, item)">V2</a></li>
 | 
			
		||||
                    <li><a @click="variation(3, item)">V3</a></li>
 | 
			
		||||
                    <li><a @click="variation(4, item)">V4</a></li>
 | 
			
		||||
                  </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="remove">
 | 
			
		||||
                <el-button type="danger" :icon="Delete" @click="removeImage(item)" circle/>
 | 
			
		||||
                <el-button type="warning" v-if="item.publish" @click="publishImage(item, false)"
 | 
			
		||||
                           circle>
 | 
			
		||||
                  <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button type="success" v-else @click="publishImage(item, true)" circle>
 | 
			
		||||
                  <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-grid-item>
 | 
			
		||||
        </van-grid>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
@@ -107,14 +228,15 @@
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {showFailToast, showToast} from "vant";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {showFailToast, showNotify, showToast} from "vant";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
const title = ref('MidJourney 绘画')
 | 
			
		||||
const activeColspan = ref([""])
 | 
			
		||||
@@ -154,14 +276,18 @@ const params = ref({
 | 
			
		||||
const imgCalls = ref(0)
 | 
			
		||||
const userId = ref(0)
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const runningJobs = ref([])
 | 
			
		||||
const finishedJobs = ref([])
 | 
			
		||||
const socket = ref(null)
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    imgCalls.value = user['img_calls']
 | 
			
		||||
    userId.value = user.id
 | 
			
		||||
 | 
			
		||||
    // fetchRunningJobs(userId.value)
 | 
			
		||||
    // fetchFinishJobs(userId.value)
 | 
			
		||||
    // connect()
 | 
			
		||||
    fetchRunningJobs(userId.value)
 | 
			
		||||
    fetchFinishJobs(userId.value)
 | 
			
		||||
    connect()
 | 
			
		||||
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    router.push('/login')
 | 
			
		||||
@@ -176,6 +302,98 @@ onMounted(() => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const heartbeatHandle = ref(null)
 | 
			
		||||
const connect = () => {
 | 
			
		||||
  let host = process.env.VUE_APP_WS_HOST
 | 
			
		||||
  if (host === '') {
 | 
			
		||||
    if (location.protocol === 'https:') {
 | 
			
		||||
      host = 'wss://' + location.host;
 | 
			
		||||
    } else {
 | 
			
		||||
      host = 'ws://' + location.host;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 心跳函数
 | 
			
		||||
  const sendHeartbeat = () => {
 | 
			
		||||
    clearTimeout(heartbeatHandle.value)
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      if (socket.value !== null) {
 | 
			
		||||
        socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
 | 
			
		||||
      }
 | 
			
		||||
      resolve("success")
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
      heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/mj/client?user_id=${userId.value}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    socket.value = _socket;
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
    if (event.data instanceof Blob) {
 | 
			
		||||
      fetchRunningJobs(userId.value)
 | 
			
		||||
      fetchFinishJobs(userId.value)
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('close', () => {
 | 
			
		||||
    connect()
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取运行中的任务
 | 
			
		||||
const fetchRunningJobs = (userId) => {
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i].progress === -1) {
 | 
			
		||||
        showNotify({
 | 
			
		||||
          message: `任务执行失败:${jobs[i]['err_msg']}`,
 | 
			
		||||
          type: 'error',
 | 
			
		||||
        })
 | 
			
		||||
        imgCalls.value += 1
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      _jobs.push(jobs[i])
 | 
			
		||||
    }
 | 
			
		||||
    runningJobs.value = _jobs
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fetchFinishJobs = (userId) => {
 | 
			
		||||
  // 获取已完成的任务
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=1&user_id=${userId}`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i]['use_proxy']) {
 | 
			
		||||
        jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?x-oss-process=image/quality,q_60&format=webp'
 | 
			
		||||
      } else {
 | 
			
		||||
        if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
 | 
			
		||||
          jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
 | 
			
		||||
        } else {
 | 
			
		||||
          jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (jobs[i].type === 'image' || jobs[i].type === 'variation') {
 | 
			
		||||
        jobs[i]['can_opt'] = true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    finishedJobs.value = jobs
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 切换图片比例
 | 
			
		||||
const changeRate = (item) => {
 | 
			
		||||
  params.value.rate = item.value
 | 
			
		||||
@@ -227,8 +445,9 @@ const generate = () => {
 | 
			
		||||
    ElMessage.error("任务推送失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@import "@/assets/css/mobile/image-mj.styl"
 | 
			
		||||
</style>
 | 
			
		||||
		Reference in New Issue
	
	Block a user