feat: midjourney page task and image list component is ready

This commit is contained in:
RockYang 2023-09-15 17:40:39 +08:00
parent 638b399b31
commit 4112426848
15 changed files with 458 additions and 56 deletions

View File

@ -34,4 +34,5 @@ const (
OkMsg = "Success" OkMsg = "Success"
ErrorMsg = "系统开小差了" ErrorMsg = "系统开小差了"
InvalidArgs = "非法参数或参数解析失败" InvalidArgs = "非法参数或参数解析失败"
NoData = "No Data"
) )

View File

@ -6,6 +6,7 @@ import (
"chatplus/service" "chatplus/service"
"chatplus/service/oss" "chatplus/service/oss"
"chatplus/store/model" "chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"encoding/base64" "encoding/base64"
@ -23,6 +24,7 @@ type TaskStatus string
const ( const (
Stopped = TaskStatus("Stopped") Stopped = TaskStatus("Stopped")
Finished = TaskStatus("Finished") Finished = TaskStatus("Finished")
Running = TaskStatus("Running")
) )
type Image struct { type Image struct {
@ -190,6 +192,7 @@ func (h *MidJourneyHandler) notifyHandler(c *gin.Context, data notifyData) (erro
// save the job // save the job
job.UserId = task.UserId job.UserId = task.UserId
job.Type = task.Type.String()
job.MessageId = data.MessageId job.MessageId = data.MessageId
job.ReferenceId = data.ReferenceId job.ReferenceId = data.ReferenceId
job.Prompt = data.Prompt job.Prompt = data.Prompt
@ -242,6 +245,7 @@ func (h *MidJourneyHandler) Proxy(c *gin.Context) {
} }
type reqVo struct { type reqVo struct {
Src string `json:"src"`
Index int32 `json:"index"` Index int32 `json:"index"`
MessageId string `json:"message_id"` MessageId string `json:"message_id"`
MessageHash string `json:"message_hash"` MessageHash string `json:"message_hash"`
@ -259,15 +263,11 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs) resp.ERROR(c, types.InvalidArgs)
return return
} }
wsClient := h.App.ChatClients.Get(data.SessionId)
if wsClient == nil {
resp.ERROR(c, "No Websocket client online")
return
}
userId, _ := c.Get(types.LoginUserID) userId, _ := c.Get(types.LoginUserID)
h.mjService.PushTask(service.MjTask{ h.mjService.PushTask(service.MjTask{
Id: data.SessionId, Id: data.SessionId,
Src: service.TaskSrcChat, Src: service.TaskSrc(data.Src),
Type: service.Upscale, Type: service.Upscale,
Prompt: data.Prompt, Prompt: data.Prompt,
UserId: utils.IntValue(utils.InterfaceToString(userId), 0), UserId: utils.IntValue(utils.InterfaceToString(userId), 0),
@ -279,30 +279,29 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
MessageHash: data.MessageHash, MessageHash: data.MessageHash,
}) })
content := fmt.Sprintf("**%s** 已推送 upscale 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt) wsClient := h.App.ChatClients.Get(data.SessionId)
utils.ReplyMessage(wsClient, content) if wsClient != nil {
if h.App.MjTaskClients.Get(data.SessionId) == nil { content := fmt.Sprintf("**%s** 已推送 upscale 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
h.App.MjTaskClients.Put(data.SessionId, wsClient) utils.ReplyMessage(wsClient, content)
if h.App.MjTaskClients.Get(data.SessionId) == nil {
h.App.MjTaskClients.Put(data.SessionId, wsClient)
}
} }
resp.SUCCESS(c) resp.SUCCESS(c)
} }
// Variation send variation command to MidJourney Bot
func (h *MidJourneyHandler) Variation(c *gin.Context) { func (h *MidJourneyHandler) Variation(c *gin.Context) {
var data reqVo var data reqVo
if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" { if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
resp.ERROR(c, types.InvalidArgs) resp.ERROR(c, types.InvalidArgs)
return return
} }
wsClient := h.App.ChatClients.Get(data.SessionId)
if wsClient == nil {
resp.ERROR(c, "No Websocket client online")
return
}
userId, _ := c.Get(types.LoginUserID) userId, _ := c.Get(types.LoginUserID)
h.mjService.PushTask(service.MjTask{ h.mjService.PushTask(service.MjTask{
Id: data.SessionId, Id: data.SessionId,
Src: service.TaskSrcChat, Src: service.TaskSrc(data.Src),
Type: service.Variation, Type: service.Variation,
Prompt: data.Prompt, Prompt: data.Prompt,
UserId: utils.IntValue(utils.InterfaceToString(userId), 0), UserId: utils.IntValue(utils.InterfaceToString(userId), 0),
@ -313,10 +312,43 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
MessageId: data.MessageId, MessageId: data.MessageId,
MessageHash: data.MessageHash, MessageHash: data.MessageHash,
}) })
content := fmt.Sprintf("**%s** 已推送 variation 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
utils.ReplyMessage(wsClient, content) // 从聊天窗口发送的请求,记录客户端信息
if h.App.MjTaskClients.Get(data.SessionId) == nil { wsClient := h.App.ChatClients.Get(data.SessionId)
h.App.MjTaskClients.Put(data.SessionId, wsClient) 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) 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)
}

View File

@ -193,6 +193,7 @@ func main() {
group.POST("notify", h.Notify) group.POST("notify", h.Notify)
group.POST("upscale", h.Upscale) group.POST("upscale", h.Upscale)
group.POST("variation", h.Variation) group.POST("variation", h.Variation)
group.GET("jobs", h.JobList)
group.GET("proxy", h.Proxy) group.GET("proxy", h.Proxy)
}), }),

View File

@ -21,6 +21,10 @@ const MjRunningJobKey = "MidJourney_Running_Job"
type TaskType string type TaskType string
func (t TaskType) String() string {
return string(t)
}
const ( const (
Image = TaskType("image") Image = TaskType("image")
Upscale = TaskType("upscale") Upscale = TaskType("upscale")
@ -100,9 +104,9 @@ func (s *MjService) Run() {
} }
if err != nil { if err != nil {
logger.Error("绘画任务执行失败:", err) logger.Error("绘画任务执行失败:", err)
if task.RetryCount > 5 { //if task.RetryCount > 5 {
continue // continue
} //}
task.RetryCount += 1 task.RetryCount += 1
s.taskQueue.RPush(task) s.taskQueue.RPush(task)
// TODO: 执行失败通知聊天客户端 // TODO: 执行失败通知聊天客户端

View File

@ -4,6 +4,7 @@ import "time"
type MidJourneyJob struct { type MidJourneyJob struct {
Id uint `gorm:"primarykey;column:id"` Id uint `gorm:"primarykey;column:id"`
Type string
UserId int UserId int
MessageId string MessageId string
ReferenceId string ReferenceId string

16
api/store/vo/mj_job.go Normal file
View 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"`
}

View File

@ -5,3 +5,6 @@ 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 `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 `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`;

View File

@ -3,6 +3,10 @@
} }
.page-mj .inner { .page-mj .inner {
display: flex; display: flex;
/* 修改滚动条的颜色 */
/* 修改滚动条轨道的背景颜色 */
/* 修改滚动条的滑块颜色 */
/* 修改滚动条的滑块的悬停颜色 */
} }
.page-mj .inner .mj-box { .page-mj .inner .mj-box {
margin: 10px; margin: 10px;
@ -143,27 +147,60 @@
.page-mj .inner .el-form .el-slider { .page-mj .inner .el-form .el-slider {
width: 180px; 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 { .page-mj .inner .task-list-box {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
color: #fff; color: #fff;
overflow-x: hidden; 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; 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; 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; display: flex;
flex-flow: row; 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; 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; padding: 3px 0;
width: 50px; width: 50px;
text-align: center; text-align: center;
@ -173,6 +210,12 @@
background-color: #4e5058; background-color: #4e5058;
color: #fff; 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; background-color: #6d6f78;
} }
.mj-list-item-prompt .el-icon {
margin-left: 10px;
cursor: pointer;
position: relative;
top: 2px;
}

View File

@ -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 { .task-list-box {
width 100% width 100%
padding 10px padding 10px
color #ffffff color #ffffff
overflow-x hidden 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 { .task-list-inner {
.grid-content { .job-item {
margin-bottom 20px margin-bottom 20px
.opt { .opt {
@ -232,3 +283,12 @@
} }
} }
.mj-list-item-prompt {
.el-icon {
margin-left 10px
cursor pointer
position relative
top 2px
}
}

View File

@ -109,6 +109,7 @@ const send = (url, index) => {
emits('disable-input') emits('disable-input')
httpPost(url, { httpPost(url, {
index: index, index: index,
src: "chat",
message_id: data.value?.["message_id"], message_id: data.value?.["message_id"],
message_hash: data.value?.["image"]?.hash, message_hash: data.value?.["image"]?.hash,
session_id: getSessionId(), session_id: getSessionId(),

View 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>

View File

@ -69,6 +69,8 @@ import {showNotify} from "vant";
const props = defineProps({ const props = defineProps({
content: Object, content: Object,
icon: String, icon: String,
chatId: String,
roleId: Number,
createdAt: String createdAt: String
}); });
@ -104,11 +106,15 @@ const send = (url, index) => {
emits('disable-input') emits('disable-input')
httpPost(url, { httpPost(url, {
index: index, index: index,
src: "chat",
message_id: data.value?.["message_id"], message_id: data.value?.["message_id"],
message_hash: data.value?.["image"]?.hash, message_hash: data.value?.["image"]?.hash,
session_id: getSessionId(), session_id: getSessionId(),
key: data.value?.["key"], key: data.value?.["key"],
prompt: data.value?.["prompt"], prompt: data.value?.["prompt"],
chat_id: props.chatId,
role_id: props.roleId,
icon: props.icon,
}).then(() => { }).then(() => {
showNotify({type: "success", message: "任务推送成功,请耐心等待任务执行..."}) showNotify({type: "success", message: "任务推送成功,请耐心等待任务执行..."})
loading.value = false loading.value = false

View File

@ -118,7 +118,6 @@ const changeNav = (item) => {
.content { .content {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
overflow-y: scroll;
box-sizing: border-box; box-sizing: border-box;
} }

View File

@ -120,7 +120,7 @@
<el-form-item label="原始模式"> <el-form-item label="原始模式">
<template #default> <template #default>
<div class="form-item-inner"> <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 <el-tooltip
effect="light" effect="light"
content="启用新的RAW模式以“不带偏见”的方式生成图像。<br/> 同时也意味着您需要添加更长的提示。" content="启用新的RAW模式以“不带偏见”的方式生成图像。<br/> 同时也意味着您需要添加更长的提示。"
@ -166,6 +166,7 @@
:auto-upload="true" :auto-upload="true"
:show-file-list="false" :show-file-list="false"
:http-request="afterRead" :http-request="afterRead"
style="--el-color-primary:#47fff1"
> >
<el-image v-if="params.img !== ''" :src="params.img" fit="cover"/> <el-image v-if="params.img !== ''" :src="params.img" fit="cover"/>
<el-icon v-else class="uploader-icon"> <el-icon v-else class="uploader-icon">
@ -178,7 +179,8 @@
<el-form-item label="图像权重"> <el-form-item label="图像权重">
<template #default> <template #default>
<div class="form-item-inner"> <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 <el-tooltip
effect="light" effect="light"
content="使用图像权重参数--iw来调整图像 URL 与文本的重要性 <br/>权重较高时意味着图像提示将对完成的作业产生更大的影响" content="使用图像权重参数--iw来调整图像 URL 与文本的重要性 <br/>权重较高时意味着图像提示将对完成的作业产生更大的影响"
@ -209,24 +211,107 @@
<el-button color="#47fff1" :dark="false" round @click="generate">立即生成</el-button> <el-button color="#47fff1" :dark="false" round @click="generate">立即生成</el-button>
</div> </div>
</div> </div>
<div class="task-list-box" :style="{ height: listBoxHeight + 'px' }"> <div class="task-list-box">
<div class="task-list-inner"> <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
<h2>任务列表</h2> <h2>任务列表</h2>
<div class="running-job-list"> <div class="running-job-list">
<el-row :gutter="20"> <waterfall :items="runningJobs" v-if="runningJobs.length > 0">
<el-col :span="4" v-for="n in 10"> <template #default="scope">
<div class="grid-content"> <div class="job-item">
<el-image src="http://172.22.11.47:9010/chatgpt-plus/1694167591080692.png"/> <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> </div>
</el-col> </template>
</el-row> </waterfall>
<el-empty :image-size="100" v-else/>
</div> </div>
<h2>创作记录</h2> <h2>创作记录</h2>
<div class="finish-job-list"> <div class="finish-job-list">
<el-row :gutter="20"> <waterfall :items="finishedJobs" height="350" v-if="finishedJobs.length > 0">
<el-col :span="4" v-for="n in 100"> <template #default="scope">
<div class="grid-content"> <div class="job-item">
<el-image src="http://172.22.11.47:9010/chatgpt-plus/1694568531910050.png" fit="cover"/> <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">
<div class="opt-line"> <div class="opt-line">
<ul> <ul>
@ -247,23 +332,28 @@
</div> </div>
</div> </div>
</div> </div>
</el-col> </template>
</el-row> </waterfall>
</div> </div> <!-- end finish job list-->
</div> </div>
</div>
<el-backtop :right="100" :bottom="100"/>
</div><!-- end task list box -->
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import {ref} from "vue" import {onMounted, ref} from "vue"
import {DeleteFilled, InfoFilled, Plus} from "@element-plus/icons-vue"; import {DeleteFilled, DocumentCopy, InfoFilled, Picture, Plus} from "@element-plus/icons-vue";
import Compressor from "compressorjs"; import Compressor from "compressorjs";
import {httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus"; 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 mjBoxHeight = window.innerHeight - 150
const rates = [ const rates = [
{css: "horizontal", value: "16:9", text: "横图"}, {css: "horizontal", value: "16:9", text: "横图"},
@ -286,6 +376,43 @@ const params = ref({
prompt: "" 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) => { const changeRate = (item) => {
params.value.rate = item.value 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 = () => { const generate = () => {

View File

@ -49,6 +49,8 @@
<chat-mid-journey v-else-if="item.type==='mj'" <chat-mid-journey v-else-if="item.type==='mj'"
:content="item.content" :content="item.content"
:icon="item.icon" :icon="item.icon"
:role-id="role"
:chat-id="chatId"
@disable-input="disableInput(true)" @disable-input="disableInput(true)"
@enable-input="enableInput" @enable-input="enableInput"
:created-at="dateFormat(item['created_at'])"/> :created-at="dateFormat(item['created_at'])"/>