mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
feat: midjourney page task and image list component is ready
This commit is contained in:
parent
638b399b31
commit
4112426848
@ -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,
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
// 从聊天窗口发送的请求,记录客户端信息
|
||||
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"`
|
||||
}
|
@ -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 `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`;
|
||||
|
||||
-- 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 {
|
||||
@ -232,3 +283,12 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.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>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
</waterfall>
|
||||
</div> <!-- end finish job list-->
|
||||
</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'])"/>
|
||||
|
Loading…
Reference in New Issue
Block a user