mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat: midjourney page task and image list component is ready
This commit is contained in:
		@@ -34,4 +34,5 @@ const (
 | 
			
		||||
	OkMsg       = "Success"
 | 
			
		||||
	ErrorMsg    = "系统开小差了"
 | 
			
		||||
	InvalidArgs = "非法参数或参数解析失败"
 | 
			
		||||
	NoData      = "No Data"
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import (
 | 
			
		||||
	"chatplus/service"
 | 
			
		||||
	"chatplus/service/oss"
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/store/vo"
 | 
			
		||||
	"chatplus/utils"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
@@ -23,6 +24,7 @@ type TaskStatus string
 | 
			
		||||
const (
 | 
			
		||||
	Stopped  = TaskStatus("Stopped")
 | 
			
		||||
	Finished = TaskStatus("Finished")
 | 
			
		||||
	Running  = TaskStatus("Running")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Image struct {
 | 
			
		||||
@@ -190,6 +192,7 @@ func (h *MidJourneyHandler) notifyHandler(c *gin.Context, data notifyData) (erro
 | 
			
		||||
 | 
			
		||||
			// save the job
 | 
			
		||||
			job.UserId = task.UserId
 | 
			
		||||
			job.Type = task.Type.String()
 | 
			
		||||
			job.MessageId = data.MessageId
 | 
			
		||||
			job.ReferenceId = data.ReferenceId
 | 
			
		||||
			job.Prompt = data.Prompt
 | 
			
		||||
@@ -242,6 +245,7 @@ func (h *MidJourneyHandler) Proxy(c *gin.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type reqVo struct {
 | 
			
		||||
	Src         string `json:"src"`
 | 
			
		||||
	Index       int32  `json:"index"`
 | 
			
		||||
	MessageId   string `json:"message_id"`
 | 
			
		||||
	MessageHash string `json:"message_hash"`
 | 
			
		||||
@@ -259,15 +263,11 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	wsClient := h.App.ChatClients.Get(data.SessionId)
 | 
			
		||||
	if wsClient == nil {
 | 
			
		||||
		resp.ERROR(c, "No Websocket client online")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userId, _ := c.Get(types.LoginUserID)
 | 
			
		||||
	h.mjService.PushTask(service.MjTask{
 | 
			
		||||
		Id:          data.SessionId,
 | 
			
		||||
		Src:         service.TaskSrcChat,
 | 
			
		||||
		Src:         service.TaskSrc(data.Src),
 | 
			
		||||
		Type:        service.Upscale,
 | 
			
		||||
		Prompt:      data.Prompt,
 | 
			
		||||
		UserId:      utils.IntValue(utils.InterfaceToString(userId), 0),
 | 
			
		||||
@@ -279,30 +279,29 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
 | 
			
		||||
		MessageHash: data.MessageHash,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	content := fmt.Sprintf("**%s** 已推送 upscale 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
 | 
			
		||||
	utils.ReplyMessage(wsClient, content)
 | 
			
		||||
	if h.App.MjTaskClients.Get(data.SessionId) == nil {
 | 
			
		||||
		h.App.MjTaskClients.Put(data.SessionId, wsClient)
 | 
			
		||||
	wsClient := h.App.ChatClients.Get(data.SessionId)
 | 
			
		||||
	if wsClient != nil {
 | 
			
		||||
		content := fmt.Sprintf("**%s** 已推送 upscale 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
 | 
			
		||||
		utils.ReplyMessage(wsClient, content)
 | 
			
		||||
		if h.App.MjTaskClients.Get(data.SessionId) == nil {
 | 
			
		||||
			h.App.MjTaskClients.Put(data.SessionId, wsClient)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Variation send variation command to MidJourney Bot
 | 
			
		||||
func (h *MidJourneyHandler) Variation(c *gin.Context) {
 | 
			
		||||
	var data reqVo
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	wsClient := h.App.ChatClients.Get(data.SessionId)
 | 
			
		||||
	if wsClient == nil {
 | 
			
		||||
		resp.ERROR(c, "No Websocket client online")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userId, _ := c.Get(types.LoginUserID)
 | 
			
		||||
	h.mjService.PushTask(service.MjTask{
 | 
			
		||||
		Id:          data.SessionId,
 | 
			
		||||
		Src:         service.TaskSrcChat,
 | 
			
		||||
		Src:         service.TaskSrc(data.Src),
 | 
			
		||||
		Type:        service.Variation,
 | 
			
		||||
		Prompt:      data.Prompt,
 | 
			
		||||
		UserId:      utils.IntValue(utils.InterfaceToString(userId), 0),
 | 
			
		||||
@@ -313,10 +312,43 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
 | 
			
		||||
		MessageId:   data.MessageId,
 | 
			
		||||
		MessageHash: data.MessageHash,
 | 
			
		||||
	})
 | 
			
		||||
	content := fmt.Sprintf("**%s** 已推送 variation 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
 | 
			
		||||
	utils.ReplyMessage(wsClient, content)
 | 
			
		||||
	if h.App.MjTaskClients.Get(data.SessionId) == nil {
 | 
			
		||||
		h.App.MjTaskClients.Put(data.SessionId, wsClient)
 | 
			
		||||
 | 
			
		||||
	// 从聊天窗口发送的请求,记录客户端信息
 | 
			
		||||
	wsClient := h.App.ChatClients.Get(data.SessionId)
 | 
			
		||||
	if wsClient != nil {
 | 
			
		||||
		content := fmt.Sprintf("**%s** 已推送 variation 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
 | 
			
		||||
		utils.ReplyMessage(wsClient, content)
 | 
			
		||||
		if h.App.MjTaskClients.Get(data.SessionId) == nil {
 | 
			
		||||
			h.App.MjTaskClients.Put(data.SessionId, wsClient)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JobList 获取 MJ 任务列表
 | 
			
		||||
func (h *MidJourneyHandler) JobList(c *gin.Context) {
 | 
			
		||||
	status := h.GetInt(c, "status", 0)
 | 
			
		||||
	var items []model.MidJourneyJob
 | 
			
		||||
	var res *gorm.DB
 | 
			
		||||
	userId, _ := c.Get(types.LoginUserID)
 | 
			
		||||
	if status == 1 {
 | 
			
		||||
		res = h.db.Where("user_id = ? AND progress = 100", userId).Find(&items)
 | 
			
		||||
	} else {
 | 
			
		||||
		res = h.db.Where("user_id = ? AND progress < 100", userId).Find(&items)
 | 
			
		||||
	}
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, types.NoData)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var jobs = make([]vo.MidJourneyJob, 0)
 | 
			
		||||
	for _, item := range items {
 | 
			
		||||
		var job vo.MidJourneyJob
 | 
			
		||||
		err := utils.CopyObject(item, &job)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		jobs = append(jobs, job)
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, jobs)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -193,6 +193,7 @@ func main() {
 | 
			
		||||
			group.POST("notify", h.Notify)
 | 
			
		||||
			group.POST("upscale", h.Upscale)
 | 
			
		||||
			group.POST("variation", h.Variation)
 | 
			
		||||
			group.GET("jobs", h.JobList)
 | 
			
		||||
			group.GET("proxy", h.Proxy)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,10 @@ const MjRunningJobKey = "MidJourney_Running_Job"
 | 
			
		||||
 | 
			
		||||
type TaskType string
 | 
			
		||||
 | 
			
		||||
func (t TaskType) String() string {
 | 
			
		||||
	return string(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Image     = TaskType("image")
 | 
			
		||||
	Upscale   = TaskType("upscale")
 | 
			
		||||
@@ -100,9 +104,9 @@ func (s *MjService) Run() {
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Error("绘画任务执行失败:", err)
 | 
			
		||||
			if task.RetryCount > 5 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			//if task.RetryCount > 5 {
 | 
			
		||||
			//	continue
 | 
			
		||||
			//}
 | 
			
		||||
			task.RetryCount += 1
 | 
			
		||||
			s.taskQueue.RPush(task)
 | 
			
		||||
			// TODO: 执行失败通知聊天客户端
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import "time"
 | 
			
		||||
 | 
			
		||||
type MidJourneyJob struct {
 | 
			
		||||
	Id          uint `gorm:"primarykey;column:id"`
 | 
			
		||||
	Type        string
 | 
			
		||||
	UserId      int
 | 
			
		||||
	MessageId   string
 | 
			
		||||
	ReferenceId string
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								api/store/vo/mj_job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								api/store/vo/mj_job.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type MidJourneyJob struct {
 | 
			
		||||
	Id          uint      `json:"id"`
 | 
			
		||||
	Type        string    `json:"type"`
 | 
			
		||||
	UserId      int       `json:"user_id"`
 | 
			
		||||
	MessageId   string    `json:"message_id"`
 | 
			
		||||
	ReferenceId string    `json:"reference_id"`
 | 
			
		||||
	ImgURL      string    `json:"img_url"`
 | 
			
		||||
	Hash        string    `json:"hash"`
 | 
			
		||||
	Progress    int       `json:"progress"`
 | 
			
		||||
	Prompt      string    `json:"prompt"`
 | 
			
		||||
	CreatedAt   time.Time `json:"created_at"`
 | 
			
		||||
}
 | 
			
		||||
@@ -4,4 +4,7 @@ ALTER TABLE `chatgpt_mj_jobs` DROP `content`;
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs` DROP `chat_id`;
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs` ADD `progress` SMALLINT(5) NULL DEFAULT '0' COMMENT '任务进度' AFTER `prompt`;
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs` ADD `hash` VARCHAR(100) NULL DEFAULT NULL COMMENT 'message hash' AFTER `prompt`;
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs` ADD `img_url` VARCHAR(255) NULL DEFAULT NULL COMMENT '图片URL' AFTER `prompt`;
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs` ADD `img_url` VARCHAR(255) NULL DEFAULT NULL COMMENT '图片URL' AFTER `prompt`;
 | 
			
		||||
 | 
			
		||||
-- 2023-09-15
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs` ADD `type` VARCHAR(20) NULL DEFAULT 'image' COMMENT '任务类别' AFTER `user_id`;
 | 
			
		||||
@@ -3,6 +3,10 @@
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner {
 | 
			
		||||
  display: flex;
 | 
			
		||||
/* 修改滚动条的颜色 */
 | 
			
		||||
/* 修改滚动条轨道的背景颜色 */
 | 
			
		||||
/* 修改滚动条的滑块颜色 */
 | 
			
		||||
/* 修改滚动条的滑块的悬停颜色 */
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .mj-box {
 | 
			
		||||
  margin: 10px;
 | 
			
		||||
@@ -143,27 +147,60 @@
 | 
			
		||||
.page-mj .inner .el-form .el-slider {
 | 
			
		||||
  width: 180px;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner ::-webkit-scrollbar {
 | 
			
		||||
  width: 10px; /* 滚动条宽度 */
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner ::-webkit-scrollbar-track {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner ::-webkit-scrollbar-thumb {
 | 
			
		||||
  background-color: #444;
 | 
			
		||||
  border-radius: 10px;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner ::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
  background-color: #666;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .grid-content {
 | 
			
		||||
.page-mj .inner .task-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 .el-image {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .running-job-list .job-item .el-image .image-slot {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: column;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .running-job-list .job-item .el-image .image-slot .iconfont {
 | 
			
		||||
  font-size: 50px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-item {
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .grid-content .opt .opt-line {
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-item .opt .opt-line {
 | 
			
		||||
  margin: 6px 0;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .grid-content .opt .opt-line ul {
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-item .opt .opt-line ul {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: row;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .grid-content .opt .opt-line ul li {
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-item .opt .opt-line ul li {
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .grid-content .opt .opt-line ul li a {
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-item .opt .opt-line ul li a {
 | 
			
		||||
  padding: 3px 0;
 | 
			
		||||
  width: 50px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
@@ -173,6 +210,12 @@
 | 
			
		||||
  background-color: #4e5058;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .grid-content .opt .opt-line ul li a:hover {
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-item .opt .opt-line ul li a:hover {
 | 
			
		||||
  background-color: #6d6f78;
 | 
			
		||||
}
 | 
			
		||||
.mj-list-item-prompt .el-icon {
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  top: 2px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -182,17 +182,68 @@
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 修改滚动条的颜色 */
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar {
 | 
			
		||||
      width: 10px; /* 滚动条宽度 */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 修改滚动条轨道的背景颜色 */
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar-track {
 | 
			
		||||
      background-color: #282C34;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 修改滚动条的滑块颜色 */
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar-thumb {
 | 
			
		||||
      background-color: #444444;
 | 
			
		||||
      border-radius 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 修改滚动条的滑块的悬停颜色 */
 | 
			
		||||
 | 
			
		||||
    ::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
      background-color: #666666;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .task-list-box {
 | 
			
		||||
      width 100%
 | 
			
		||||
      padding 10px
 | 
			
		||||
      color #ffffff
 | 
			
		||||
      overflow-x hidden
 | 
			
		||||
      overflow-y auto
 | 
			
		||||
 | 
			
		||||
      .running-job-list {
 | 
			
		||||
        .job-item {
 | 
			
		||||
          //border: 1px solid #454545;
 | 
			
		||||
          width: 100%;
 | 
			
		||||
          padding 2px
 | 
			
		||||
          background-color #555555
 | 
			
		||||
 | 
			
		||||
          .el-image {
 | 
			
		||||
            width 100%
 | 
			
		||||
            height 100%
 | 
			
		||||
 | 
			
		||||
            .image-slot {
 | 
			
		||||
              display flex
 | 
			
		||||
              flex-flow column
 | 
			
		||||
              justify-content center
 | 
			
		||||
              align-items center
 | 
			
		||||
              height 100%
 | 
			
		||||
              color #ffffff
 | 
			
		||||
 | 
			
		||||
              .iconfont {
 | 
			
		||||
                font-size 50px
 | 
			
		||||
                margin-bottom 10px
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .task-list-inner {
 | 
			
		||||
 | 
			
		||||
        .grid-content {
 | 
			
		||||
        .job-item {
 | 
			
		||||
          margin-bottom 20px
 | 
			
		||||
 | 
			
		||||
          .opt {
 | 
			
		||||
@@ -231,4 +282,13 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mj-list-item-prompt {
 | 
			
		||||
  .el-icon {
 | 
			
		||||
    margin-left 10px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    position relative
 | 
			
		||||
    top 2px
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -109,6 +109,7 @@ const send = (url, index) => {
 | 
			
		||||
  emits('disable-input')
 | 
			
		||||
  httpPost(url, {
 | 
			
		||||
    index: index,
 | 
			
		||||
    src: "chat",
 | 
			
		||||
    message_id: data.value?.["message_id"],
 | 
			
		||||
    message_hash: data.value?.["image"]?.hash,
 | 
			
		||||
    session_id: getSessionId(),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								web/src/components/ItemList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								web/src/components/ItemList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="waterfall" ref="container">
 | 
			
		||||
    <div class="waterfall-inner">
 | 
			
		||||
      <div
 | 
			
		||||
          class="waterfall-item"
 | 
			
		||||
          v-for="(item, index) in items"
 | 
			
		||||
          :key="index"
 | 
			
		||||
          :style="{width:itemWidth + 'px', height:height+'px', marginBottom: margin*2+'px'}"
 | 
			
		||||
      >
 | 
			
		||||
        <div :style="{marginLeft: margin+'px', marginRight: margin+'px'}">
 | 
			
		||||
          <div class="item-wrapper">
 | 
			
		||||
            <slot :item="item" :index="index"></slot>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
// 列表组件
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  items: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    required: true
 | 
			
		||||
  },
 | 
			
		||||
  gap: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 10
 | 
			
		||||
  },
 | 
			
		||||
  width: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 240
 | 
			
		||||
  },
 | 
			
		||||
  height: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 240
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const container = ref(null)
 | 
			
		||||
const itemWidth = ref(props.width)
 | 
			
		||||
const margin = ref(props.gap)
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  computeSize()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const computeSize = () => {
 | 
			
		||||
  const w = container.value.offsetWidth - 10 // 减去滚动条的宽度
 | 
			
		||||
  let cols = Math.floor(w / props.width)
 | 
			
		||||
  itemWidth.value = w / cols
 | 
			
		||||
  while (itemWidth.value < props.width && cols > 1) {
 | 
			
		||||
    cols -= 1
 | 
			
		||||
    itemWidth.value = w / cols
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (props.gap > 0) {
 | 
			
		||||
    margin.value = props.gap / 2
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.onresize = () => {
 | 
			
		||||
  computeSize()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
 | 
			
		||||
.waterfall {
 | 
			
		||||
 | 
			
		||||
  .waterfall-inner {
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-wrap wrap
 | 
			
		||||
 | 
			
		||||
    .waterfall-item {
 | 
			
		||||
 | 
			
		||||
      div {
 | 
			
		||||
        display flex
 | 
			
		||||
        height 100%
 | 
			
		||||
        overflow hidden
 | 
			
		||||
 | 
			
		||||
        .item-wrapper {
 | 
			
		||||
          height 100%
 | 
			
		||||
          width 100%
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -69,6 +69,8 @@ import {showNotify} from "vant";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  content: Object,
 | 
			
		||||
  icon: String,
 | 
			
		||||
  chatId: String,
 | 
			
		||||
  roleId: Number,
 | 
			
		||||
  createdAt: String
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -104,11 +106,15 @@ const send = (url, index) => {
 | 
			
		||||
  emits('disable-input')
 | 
			
		||||
  httpPost(url, {
 | 
			
		||||
    index: index,
 | 
			
		||||
    src: "chat",
 | 
			
		||||
    message_id: data.value?.["message_id"],
 | 
			
		||||
    message_hash: data.value?.["image"]?.hash,
 | 
			
		||||
    session_id: getSessionId(),
 | 
			
		||||
    key: data.value?.["key"],
 | 
			
		||||
    prompt: data.value?.["prompt"],
 | 
			
		||||
    chat_id: props.chatId,
 | 
			
		||||
    role_id: props.roleId,
 | 
			
		||||
    icon: props.icon,
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    showNotify({type: "success", message: "任务推送成功,请耐心等待任务执行..."})
 | 
			
		||||
    loading.value = false
 | 
			
		||||
 
 | 
			
		||||
@@ -118,7 +118,6 @@ const changeNav = (item) => {
 | 
			
		||||
  .content {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -120,7 +120,7 @@
 | 
			
		||||
              <el-form-item label="原始模式">
 | 
			
		||||
                <template #default>
 | 
			
		||||
                  <div class="form-item-inner">
 | 
			
		||||
                    <el-switch v-model="params.raw"/>
 | 
			
		||||
                    <el-switch v-model="params.raw" style="--el-switch-on-color: #47fff1;"/>
 | 
			
		||||
                    <el-tooltip
 | 
			
		||||
                        effect="light"
 | 
			
		||||
                        content="启用新的RAW模式,以“不带偏见”的方式生成图像。<br/> 同时也意味着您需要添加更长的提示。"
 | 
			
		||||
@@ -166,6 +166,7 @@
 | 
			
		||||
                  :auto-upload="true"
 | 
			
		||||
                  :show-file-list="false"
 | 
			
		||||
                  :http-request="afterRead"
 | 
			
		||||
                  style="--el-color-primary:#47fff1"
 | 
			
		||||
              >
 | 
			
		||||
                <el-image v-if="params.img !== ''" :src="params.img" fit="cover"/>
 | 
			
		||||
                <el-icon v-else class="uploader-icon">
 | 
			
		||||
@@ -178,7 +179,8 @@
 | 
			
		||||
              <el-form-item label="图像权重">
 | 
			
		||||
                <template #default>
 | 
			
		||||
                  <div class="form-item-inner">
 | 
			
		||||
                    <el-slider v-model="params.weight" :max="1" :step="0.01" style="width: 180px"/>
 | 
			
		||||
                    <el-slider v-model="params.weight" :max="1" :step="0.01"
 | 
			
		||||
                               style="width: 180px;--el-slider-main-bg-color:#47fff1"/>
 | 
			
		||||
                    <el-tooltip
 | 
			
		||||
                        effect="light"
 | 
			
		||||
                        content="使用图像权重参数--iw来调整图像 URL 与文本的重要性 <br/>权重较高时意味着图像提示将对完成的作业产生更大的影响"
 | 
			
		||||
@@ -209,24 +211,107 @@
 | 
			
		||||
          <el-button color="#47fff1" :dark="false" round @click="generate">立即生成</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="task-list-box" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
        <div class="task-list-inner">
 | 
			
		||||
      <div class="task-list-box">
 | 
			
		||||
        <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
          <h2>任务列表</h2>
 | 
			
		||||
          <div class="running-job-list">
 | 
			
		||||
            <el-row :gutter="20">
 | 
			
		||||
              <el-col :span="4" v-for="n in 10">
 | 
			
		||||
                <div class="grid-content">
 | 
			
		||||
                  <el-image src="http://172.22.11.47:9010/chatgpt-plus/1694167591080692.png"/>
 | 
			
		||||
            <waterfall :items="runningJobs" v-if="runningJobs.length > 0">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <div class="job-item">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      :title="getTaskType(scope.item.type)"
 | 
			
		||||
                      :width="240"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-image :src="scope.item.img_url"
 | 
			
		||||
                                :zoom-rate="1.2"
 | 
			
		||||
                                :preview-src-list="[scope.item.img_url]"
 | 
			
		||||
                                fit="cover"
 | 
			
		||||
                                :initial-index="0" loading="lazy" v-if="scope.item.progress > 0">
 | 
			
		||||
                        <template #placeholder>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            正在加载图片
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            <el-icon>
 | 
			
		||||
                              <Picture/>
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
                      <el-image fit="cover" v-else>
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
                            <span>任务正在排队中</span>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <template #default>
 | 
			
		||||
                      <div class="mj-list-item-prompt">
 | 
			
		||||
                        <span>{{ scope.item.prompt }}</span>
 | 
			
		||||
                        <el-icon class="copy-prompt" :data-clipboard-text="scope.item.prompt">
 | 
			
		||||
                          <DocumentCopy/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
              </el-col>
 | 
			
		||||
            </el-row>
 | 
			
		||||
              </template>
 | 
			
		||||
            </waterfall>
 | 
			
		||||
            <el-empty :image-size="100" v-else/>
 | 
			
		||||
          </div>
 | 
			
		||||
          <h2>创作记录</h2>
 | 
			
		||||
          <div class="finish-job-list">
 | 
			
		||||
            <el-row :gutter="20">
 | 
			
		||||
              <el-col :span="4" v-for="n in 100">
 | 
			
		||||
                <div class="grid-content">
 | 
			
		||||
                  <el-image src="http://172.22.11.47:9010/chatgpt-plus/1694568531910050.png" fit="cover"/>
 | 
			
		||||
            <waterfall :items="finishedJobs" height="350" v-if="finishedJobs.length > 0">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <div class="job-item">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      title="提示词"
 | 
			
		||||
                      :width="240"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-image :src="scope.item.img_url"
 | 
			
		||||
                                :zoom-rate="1.2"
 | 
			
		||||
                                :preview-src-list="previewImgList"
 | 
			
		||||
                                fit="cover"
 | 
			
		||||
                                :initial-index="scope.index" loading="lazy" v-if="scope.item.progress > 0">
 | 
			
		||||
                        <template #placeholder>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            正在加载图片
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            <el-icon>
 | 
			
		||||
                              <Picture/>
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
                    </template>
 | 
			
		||||
 | 
			
		||||
                    <template #default>
 | 
			
		||||
                      <div class="mj-list-item-prompt">
 | 
			
		||||
                        <span>{{ scope.item.prompt }}</span>
 | 
			
		||||
                        <el-icon class="copy-prompt" :data-clipboard-text="scope.item.prompt">
 | 
			
		||||
                          <DocumentCopy/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
 | 
			
		||||
                  <div class="opt">
 | 
			
		||||
                    <div class="opt-line">
 | 
			
		||||
                      <ul>
 | 
			
		||||
@@ -247,23 +332,28 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </el-col>
 | 
			
		||||
            </el-row>
 | 
			
		||||
          </div>
 | 
			
		||||
              </template>
 | 
			
		||||
            </waterfall>
 | 
			
		||||
          </div> <!-- end finish job list-->
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
        <el-backtop :right="100" :bottom="100"/>
 | 
			
		||||
      </div><!-- end task list box -->
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue"
 | 
			
		||||
import {DeleteFilled, InfoFilled, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {DeleteFilled, DocumentCopy, InfoFilled, Picture, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import Waterfall from "@/components/ItemList.vue";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = window.innerHeight - 20
 | 
			
		||||
const listBoxHeight = window.innerHeight - 40
 | 
			
		||||
const mjBoxHeight = window.innerHeight - 150
 | 
			
		||||
const rates = [
 | 
			
		||||
  {css: "horizontal", value: "16:9", text: "横图"},
 | 
			
		||||
@@ -286,6 +376,43 @@ const params = ref({
 | 
			
		||||
  prompt: ""
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const runningJobs = ref([])
 | 
			
		||||
const finishedJobs = ref([])
 | 
			
		||||
const previewImgList = ref([])
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  fetchFinishedJobs()
 | 
			
		||||
  fetchRunningJobs()
 | 
			
		||||
 | 
			
		||||
  const clipboard = new Clipboard('.copy-prompt');
 | 
			
		||||
  clipboard.on('success', () => {
 | 
			
		||||
    ElMessage.success('复制成功!');
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  clipboard.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const fetchFinishedJobs = () => {
 | 
			
		||||
  httpGet("/api/mj/jobs?status=1").then(res => {
 | 
			
		||||
    finishedJobs.value = res.data
 | 
			
		||||
    for (let index in finishedJobs.value) {
 | 
			
		||||
      previewImgList.value.push(finishedJobs.value[index]["img_url"])
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fetchRunningJobs = () => {
 | 
			
		||||
  httpGet("/api/mj/jobs?status=0").then(res => {
 | 
			
		||||
    runningJobs.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 切换图片比例
 | 
			
		||||
const changeRate = (item) => {
 | 
			
		||||
  params.value.rate = item.value
 | 
			
		||||
@@ -317,6 +444,18 @@ const afterRead = (file) => {
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getTaskType = (type) => {
 | 
			
		||||
  switch (type) {
 | 
			
		||||
    case "image":
 | 
			
		||||
      return "绘画任务"
 | 
			
		||||
    case "upscale":
 | 
			
		||||
      return "放大任务"
 | 
			
		||||
    case "variation":
 | 
			
		||||
      return "变化任务"
 | 
			
		||||
  }
 | 
			
		||||
  return "未知任务"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建绘图任务
 | 
			
		||||
const generate = () => {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,8 @@
 | 
			
		||||
            <chat-mid-journey v-else-if="item.type==='mj'"
 | 
			
		||||
                              :content="item.content"
 | 
			
		||||
                              :icon="item.icon"
 | 
			
		||||
                              :role-id="role"
 | 
			
		||||
                              :chat-id="chatId"
 | 
			
		||||
                              @disable-input="disableInput(true)"
 | 
			
		||||
                              @enable-input="enableInput"
 | 
			
		||||
                              :created-at="dateFormat(item['created_at'])"/>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user