mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
show error message for Midjourney task list page
This commit is contained in:
parent
42bc23cacf
commit
a6b9f57a50
@ -406,7 +406,7 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
||||
func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize int, publish bool) (error, []vo.MidJourneyJob) {
|
||||
session := h.DB.Session(&gorm.Session{})
|
||||
if finish {
|
||||
session = session.Where("progress = ?", 100).Order("id DESC")
|
||||
session = session.Where("progress >= ?", 100).Order("id DESC")
|
||||
} else {
|
||||
session = session.Where("progress < ?", 100).Order("id ASC")
|
||||
}
|
||||
|
@ -67,25 +67,7 @@ func (c *PlusClient) Imagine(task types.MjTask) (ImageRes, error) {
|
||||
}
|
||||
|
||||
}
|
||||
logger.Info("API URL: ", apiURL)
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
r, err := c.client.R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
return ImageRes{}, fmt.Errorf("请求 API %s 出错:%v", apiURL, err)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
errStr, _ := io.ReadAll(r.Body)
|
||||
return ImageRes{}, fmt.Errorf("API 返回错误:%s,%v", errRes.Error.Message, string(errStr))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return c.doRequest(body, apiURL)
|
||||
}
|
||||
|
||||
// Blend 融图
|
||||
@ -112,23 +94,7 @@ func (c *PlusClient) Blend(task types.MjTask) (ImageRes, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
r, err := c.client.R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
return ImageRes{}, fmt.Errorf("请求 API %s 出错:%v", apiURL, err)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return ImageRes{}, fmt.Errorf("API 返回错误:%s", errRes.Error.Message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return c.doRequest(body, apiURL)
|
||||
}
|
||||
|
||||
// SwapFace 换脸
|
||||
@ -165,23 +131,7 @@ func (c *PlusClient) SwapFace(task types.MjTask) (ImageRes, error) {
|
||||
},
|
||||
"state": "",
|
||||
}
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
r, err := c.client.SetTimeout(time.Minute).R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
return ImageRes{}, fmt.Errorf("请求 API %s 出错:%v", apiURL, err)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return ImageRes{}, fmt.Errorf("API 返回错误:%s", errRes.Error.Message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return c.doRequest(body, apiURL)
|
||||
}
|
||||
|
||||
// Upscale 放大指定的图片
|
||||
@ -195,24 +145,7 @@ func (c *PlusClient) Upscale(task types.MjTask) (ImageRes, error) {
|
||||
"taskId": task.MessageId,
|
||||
}
|
||||
apiURL := fmt.Sprintf("%s/mj-%s/mj/submit/action", c.apiURL, c.Config.Mode)
|
||||
logger.Info("API URL: ", apiURL)
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
r, err := c.client.R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
return ImageRes{}, fmt.Errorf("请求 API 出错:%v", err)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return ImageRes{}, fmt.Errorf("API 返回错误:%s", errRes.Error.Message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
return c.doRequest(body, apiURL)
|
||||
}
|
||||
|
||||
// Variation 以指定的图片的视角进行变换再创作,注意需要在对应的频道中关闭 Remix 变换,否则 Variation 指令将不会生效
|
||||
@ -226,9 +159,14 @@ func (c *PlusClient) Variation(task types.MjTask) (ImageRes, error) {
|
||||
"taskId": task.MessageId,
|
||||
}
|
||||
apiURL := fmt.Sprintf("%s/mj-%s/mj/submit/action", c.apiURL, c.Config.Mode)
|
||||
logger.Info("API URL: ", apiURL)
|
||||
|
||||
return c.doRequest(body, apiURL)
|
||||
}
|
||||
|
||||
func (c *PlusClient) doRequest(body interface{}, apiURL string) (ImageRes, error) {
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
logger.Info("API URL: ", apiURL)
|
||||
r, err := req.C().R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
@ -236,7 +174,13 @@ func (c *PlusClient) Variation(task types.MjTask) (ImageRes, error) {
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
return ImageRes{}, fmt.Errorf("请求 API 出错:%v", err)
|
||||
errMsg := err.Error()
|
||||
if r != nil {
|
||||
errStr, _ := io.ReadAll(r.Body)
|
||||
logger.Error("请求 API 出错:", string(errStr))
|
||||
errMsg = errMsg + " " + string(errStr)
|
||||
}
|
||||
return ImageRes{}, fmt.Errorf("请求 API 出错:%v", errMsg)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
|
@ -189,7 +189,7 @@ func (p *ServicePool) SyncTaskProgress() {
|
||||
|
||||
for _, job := range jobs {
|
||||
// 失败或者 30 分钟还没完成的任务删除并退回算力
|
||||
if time.Now().Sub(job.CreatedAt) > time.Minute*30 || job.Progress == -1 {
|
||||
if time.Now().Sub(job.CreatedAt) > time.Minute*30 {
|
||||
p.db.Delete(&job)
|
||||
// 退回算力
|
||||
tx := p.db.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power))
|
||||
|
@ -42,6 +42,8 @@ func NewService(name string, taskQueue *store.RedisQueue, notifyQueue *store.Red
|
||||
}
|
||||
}
|
||||
|
||||
const failedProgress = 101
|
||||
|
||||
func (s *Service) Run() {
|
||||
logger.Infof("Starting MidJourney job consumer for %s", s.Name)
|
||||
for s.running {
|
||||
@ -116,7 +118,7 @@ func (s *Service) Run() {
|
||||
}
|
||||
|
||||
logger.Error("绘画任务执行失败:", errMsg)
|
||||
job.Progress = -1
|
||||
job.Progress = failedProgress
|
||||
job.ErrMsg = errMsg
|
||||
// update the task progress
|
||||
s.db.Updates(&job)
|
||||
@ -164,7 +166,7 @@ func (s *Service) Notify(job model.MidJourneyJob) error {
|
||||
// 任务执行失败了
|
||||
if task.FailReason != "" {
|
||||
s.db.Model(&model.MidJourneyJob{Id: job.Id}).UpdateColumns(map[string]interface{}{
|
||||
"progress": -1,
|
||||
"progress": failedProgress,
|
||||
"err_msg": task.FailReason,
|
||||
})
|
||||
s.notifyQueue.RPush(sd.NotifyMessage{UserId: job.UserId, JobId: int(job.Id), Message: sd.Failed})
|
||||
|
@ -420,9 +420,31 @@
|
||||
flex-flow column
|
||||
justify-content center
|
||||
align-items center
|
||||
min-height 200px
|
||||
min-height 220px
|
||||
color #ffffff
|
||||
overflow hidden
|
||||
|
||||
.err-msg-container {
|
||||
overflow hidden
|
||||
word-break break-all
|
||||
padding 0 10px 20px 10px
|
||||
.title {
|
||||
font-size 16px
|
||||
text-align center
|
||||
font-weight bold
|
||||
color #f56c6c
|
||||
margin-bottom 20px
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size 14px
|
||||
color #E9F1F6
|
||||
line-height 1.5
|
||||
text-overflow ellipsis
|
||||
height 100px
|
||||
overflow hidden
|
||||
}
|
||||
}
|
||||
.iconfont {
|
||||
font-size 50px
|
||||
margin-bottom 10px
|
||||
|
@ -1,244 +0,0 @@
|
||||
<template>
|
||||
<div class="chat-line chat-line-mj" v-loading="loading">
|
||||
<div class="chat-line-inner">
|
||||
<div class="chat-icon">
|
||||
<img :src="icon" alt="User"/>
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<div class="content">
|
||||
<div class="text" v-html="data.html"></div>
|
||||
<div class="images" v-if="data.image?.url !== ''">
|
||||
<el-image :src="data.image?.url"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[data.image?.url]"
|
||||
fit="cover"
|
||||
:initial-index="0" loading="lazy">
|
||||
<template #placeholder>
|
||||
<div class="image-slot"
|
||||
:style="{height: height+'px', lineHeight:height+'px'}">
|
||||
正在加载图片<span class="dot">...</span></div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="opt" v-if="data.showOpt &&data.image?.hash !== ''">
|
||||
<div class="opt-line">
|
||||
<ul>
|
||||
<li><a @click="upscale(1)">U1</a></li>
|
||||
<li><a @click="upscale(2)">U2</a></li>
|
||||
<li><a @click="upscale(3)">U3</a></li>
|
||||
<li><a @click="upscale(4)">U4</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="opt-line">
|
||||
<ul>
|
||||
<li><a @click="variation(1)">V1</a></li>
|
||||
<li><a @click="variation(2)">V2</a></li>
|
||||
<li><a @click="variation(3)">V3</a></li>
|
||||
<li><a @click="variation(4)">V4</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bar" v-if="createdAt !== ''">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
||||
<span class="bar-item">tokens: {{ tokens }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, watch} from "vue";
|
||||
import {Clock, Picture} from "@element-plus/icons-vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {httpPost} from "@/utils/http";
|
||||
import {getSessionId} from "@/store/session";
|
||||
|
||||
const props = defineProps({
|
||||
content: Object,
|
||||
icon: String,
|
||||
chatId: String,
|
||||
roleId: Number,
|
||||
createdAt: String
|
||||
});
|
||||
|
||||
const data = ref(props.content)
|
||||
const tokens = ref(0)
|
||||
const cacheKey = "img_placeholder_height"
|
||||
const item = localStorage.getItem(cacheKey);
|
||||
const loading = ref(false)
|
||||
const height = ref(0)
|
||||
if (item) {
|
||||
height.value = parseInt(item)
|
||||
}
|
||||
if (data.value["image"]?.width > 0) {
|
||||
height.value = 350 * data.value["image"]?.height / data.value["image"]?.width
|
||||
localStorage.setItem(cacheKey, height.value)
|
||||
}
|
||||
data.value["showOpt"] = data.value["content"]?.indexOf("- Image #") === -1;
|
||||
// console.log(data.value)
|
||||
|
||||
watch(() => props.content, (newVal) => {
|
||||
data.value = newVal;
|
||||
});
|
||||
const emits = defineEmits(['disable-input', 'disable-input']);
|
||||
const upscale = (index) => {
|
||||
send('/api/mj/upscale', index)
|
||||
}
|
||||
|
||||
const variation = (index) => {
|
||||
send('/api/mj/variation', index)
|
||||
}
|
||||
|
||||
const send = (url, index) => {
|
||||
loading.value = true
|
||||
emits('disable-input')
|
||||
httpPost(url, {
|
||||
index: index,
|
||||
src: "chat",
|
||||
message_id: data.value?.["message_id"],
|
||||
message_hash: data.value?.["image"]?.hash,
|
||||
session_id: getSessionId(),
|
||||
prompt: data.value?.["prompt"],
|
||||
chat_id: props.chatId,
|
||||
role_id: props.roleId,
|
||||
icon: props.icon,
|
||||
}).then(() => {
|
||||
ElMessage.success("任务推送成功,请耐心等待任务执行...")
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("任务推送失败:" + e.message)
|
||||
emits('disable-input')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.chat-line-mj {
|
||||
background-color #ffffff;
|
||||
justify-content: center;
|
||||
width 100%
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-bottom: 1px solid #d9d9e3;
|
||||
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
width 100%;
|
||||
max-width 900px;
|
||||
padding-left 10px;
|
||||
|
||||
.chat-icon {
|
||||
margin-right 20px;
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
position: relative;
|
||||
padding: 0 5px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
color #374151;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow: auto;
|
||||
|
||||
.text {
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
}
|
||||
}
|
||||
|
||||
.images {
|
||||
max-width 350px;
|
||||
|
||||
.el-image {
|
||||
border-radius 10px;
|
||||
|
||||
.image-slot {
|
||||
color #c1c1c1
|
||||
width 350px
|
||||
text-align center
|
||||
border-radius 10px;
|
||||
border 1px solid #e1e1e1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.opt {
|
||||
.opt-line {
|
||||
margin 6px 0
|
||||
|
||||
ul {
|
||||
display flex
|
||||
flex-flow row
|
||||
padding-left 10px
|
||||
|
||||
li {
|
||||
margin-right 10px
|
||||
|
||||
a {
|
||||
padding 6px 0
|
||||
width 64px
|
||||
text-align center
|
||||
border-radius 5px
|
||||
display block
|
||||
cursor pointer
|
||||
background-color #4E5058
|
||||
color #ffffff
|
||||
|
||||
&:hover {
|
||||
background-color #6D6F78
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
padding 10px;
|
||||
|
||||
.bar-item {
|
||||
background-color #f7f7f8;
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
|
||||
.el-icon {
|
||||
position relative
|
||||
top 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
@ -144,15 +144,26 @@ onMounted(() => {
|
||||
if (links) {
|
||||
httpPost("/api/upload/list", {urls: links}).then(res => {
|
||||
files.value = res.data
|
||||
|
||||
for (let link of links) {
|
||||
if (isExternalImg(link, files.value)) {
|
||||
files.value.push({url:link, ext: ".png"})
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
for (let link of links) {
|
||||
content.value = content.value.replace(link,"")
|
||||
}
|
||||
|
||||
}
|
||||
content.value = md.render(content.value.trim())
|
||||
})
|
||||
|
||||
const isExternalImg = (link, files) => {
|
||||
return isImage(link) && !files.find(file => file.url === link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -487,6 +487,17 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<el-image v-else-if="slotProp.item['err_msg'] !== ''">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<div class="err-msg-container">
|
||||
<div class="title">任务失败</div>
|
||||
<div class="text">{{ slotProp.item['err_msg'] }}</div>
|
||||
</div>
|
||||
<el-button type="danger" @click="removeImage(slotProp.item)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<el-image v-else>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
@ -536,16 +547,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="remove">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
|
||||
<el-button type="warning" v-if="slotProp.item.publish"
|
||||
@click="publishImage(slotProp.item, false)"
|
||||
circle>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
<el-button type="success" v-else @click="publishImage(slotProp.item, true)" circle>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
<div class="remove" v-if="slotProp.item.progress === 100">
|
||||
<el-tooltip effect="light" content="删除任务" placement="top">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="取消发布" placement="top" v-if="slotProp.item.publish">
|
||||
<el-button type="warning"
|
||||
@click="publishImage(slotProp.item, false)"
|
||||
circle>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="发布图片" placement="top" v-else>
|
||||
<el-button type="success" @click="publishImage(slotProp.item, true)" circle>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="light" content="复制提示词" placement="top">
|
||||
<el-button type="success" class="copy-prompt-mj"
|
||||
:data-clipboard-text="slotProp.item.prompt" circle>
|
||||
<el-icon><DocumentCopy/></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -714,7 +739,7 @@ const connect = () => {
|
||||
reader.readAsText(event.data, "UTF-8")
|
||||
reader.onload = () => {
|
||||
const message = String(reader.result)
|
||||
if (message === "FINISH") {
|
||||
if (message === "FINISH" || message === "FAIL") {
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
fetchFinishJobs(page.value)
|
||||
@ -786,7 +811,7 @@ const fetchRunningJobs = () => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
if (jobs[i].progress === -1) {
|
||||
if (jobs[i].progress === 101) {
|
||||
ElNotification({
|
||||
title: '任务执行失败',
|
||||
dangerouslyUseHTMLString: true,
|
||||
@ -799,7 +824,6 @@ const fetchRunningJobs = () => {
|
||||
} else {
|
||||
power.value += mjActionPower.value
|
||||
}
|
||||
continue
|
||||
}
|
||||
_jobs.push(jobs[i])
|
||||
}
|
||||
@ -833,7 +857,7 @@ const fetchFinishJobs = () => {
|
||||
jobs[i]['thumb_url'] = '/images/img-placeholder.jpg'
|
||||
}
|
||||
|
||||
if (jobs[i].type === 'image' || jobs[i].type === 'variation') {
|
||||
if ((jobs[i].type === 'image' || jobs[i].type === 'variation') && jobs[i].progress === 100) {
|
||||
jobs[i]['can_opt'] = true
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user