mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-18 01:06:39 +08:00
add back-to-top component for all list page
This commit is contained in:
parent
088a614160
commit
95457b7dcd
@ -456,15 +456,44 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
|
|||||||
resp.ERROR(c, "记录不存在")
|
resp.ERROR(c, "记录不存在")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove job recode
|
// remove job recode
|
||||||
res := h.DB.Delete(&job)
|
tx := h.DB.Begin()
|
||||||
if res.Error != nil {
|
if err := tx.Delete(&job).Error; err != nil {
|
||||||
resp.ERROR(c, res.Error.Error())
|
tx.Rollback()
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refund power
|
||||||
|
err := tx.Model(&model.User{}).Where("id = ?", job.UserId).UpdateColumn("power", gorm.Expr("power + ?", job.Power)).Error
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var user model.User
|
||||||
|
h.DB.Where("id = ?", job.UserId).First(&user)
|
||||||
|
err = tx.Create(&model.PowerLog{
|
||||||
|
UserId: user.Id,
|
||||||
|
Username: user.Username,
|
||||||
|
Type: types.PowerConsume,
|
||||||
|
Amount: job.Power,
|
||||||
|
Balance: user.Power + job.Power,
|
||||||
|
Mark: types.PowerAdd,
|
||||||
|
Model: "mid-journey",
|
||||||
|
Remark: fmt.Sprintf("绘画任务失败,退回算力。任务ID:%s", job.TaskId),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
// remove image
|
// remove image
|
||||||
err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
|
err = h.uploader.GetUploadHandler().Delete(job.ImgURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("remove image failed: ", err)
|
logger.Error("remove image failed: ", err)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ package mj
|
|||||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"geekai/core/types"
|
"geekai/core/types"
|
||||||
logger2 "geekai/logger"
|
logger2 "geekai/logger"
|
||||||
"geekai/service"
|
"geekai/service"
|
||||||
@ -188,28 +187,6 @@ func (p *ServicePool) SyncTaskProgress() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
// 失败或者 30 分钟还没完成的任务删除并退回算力
|
|
||||||
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))
|
|
||||||
if tx.Error == nil && tx.RowsAffected > 0 {
|
|
||||||
var user model.User
|
|
||||||
p.db.Where("id = ?", job.UserId).First(&user)
|
|
||||||
p.db.Create(&model.PowerLog{
|
|
||||||
UserId: user.Id,
|
|
||||||
Username: user.Username,
|
|
||||||
Type: types.PowerConsume,
|
|
||||||
Amount: job.Power,
|
|
||||||
Balance: user.Power + job.Power,
|
|
||||||
Mark: types.PowerAdd,
|
|
||||||
Model: "mid-journey",
|
|
||||||
Remark: fmt.Sprintf("绘画任务失败,退回算力。任务ID:%s", job.TaskId),
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
|
if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
|
||||||
_ = servicePlus.Notify(job)
|
_ = servicePlus.Notify(job)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
background-color #383838
|
background-color #383838
|
||||||
border 1px solid #454545
|
border 1px solid #454545
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
padding 10px
|
padding 5px
|
||||||
margin-bottom 10px
|
margin-bottom 10px
|
||||||
display flex
|
display flex
|
||||||
flex-flow column
|
flex-flow column
|
||||||
@ -91,12 +91,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-image {
|
.el-image {
|
||||||
height 60px
|
height 30px
|
||||||
width 100%
|
width 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
margin-top 6px
|
margin-top 4px
|
||||||
|
font-size 12px
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
77
web/src/components/BackTop.vue
Normal file
77
web/src/components/BackTop.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<button v-if="showButton" @click="scrollToTop" class="scroll-to-top" :style="{bottom: bottom + 'px', right: right + 'px', backgroundColor: bgColor}">
|
||||||
|
<el-icon><ArrowUpBold /></el-icon>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {ArrowUpBold} from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BackTop',
|
||||||
|
components: {ArrowUpBold},
|
||||||
|
props: {
|
||||||
|
bottom: {
|
||||||
|
type: Number,
|
||||||
|
default: 30
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
type: Number,
|
||||||
|
default: 30
|
||||||
|
},
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#007bff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showButton: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.checkScroll();
|
||||||
|
window.addEventListener('resize', this.checkScroll);
|
||||||
|
this.$el.parentElement.addEventListener('scroll', this.checkScroll);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('resize', this.checkScroll);
|
||||||
|
this.$el.parentElement.removeEventListener('scroll', this.checkScroll);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
scrollToTop() {
|
||||||
|
const container = this.$el.parentElement;
|
||||||
|
container.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkScroll() {
|
||||||
|
const container = this.$el.parentElement;
|
||||||
|
this.showButton = container.scrollTop > 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="stylus">
|
||||||
|
.scroll-to-top {
|
||||||
|
position: fixed;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
width 40px
|
||||||
|
height 40px
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
align-items center
|
||||||
|
font-size 20px
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -118,6 +118,8 @@
|
|||||||
v-if="item.type==='prompt'" :data="item" :list-style="listStyle"/>
|
v-if="item.type==='prompt'" :data="item" :list-style="listStyle"/>
|
||||||
<chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false" :list-style="listStyle"/>
|
<chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false" :list-style="listStyle"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<back-top :right="30" :bottom="100" bg-color="#19C27D"/>
|
||||||
</div><!-- end chat box -->
|
</div><!-- end chat box -->
|
||||||
|
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
@ -220,6 +222,8 @@ import FileSelect from "@/components/FileSelect.vue";
|
|||||||
import FileList from "@/components/FileList.vue";
|
import FileList from "@/components/FileList.vue";
|
||||||
import ChatSetting from "@/components/ChatSetting.vue";
|
import ChatSetting from "@/components/ChatSetting.vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import BackTop from "@/components/BackTop.vue";
|
||||||
|
import {showMessageError} from "@/utils/dialog";
|
||||||
|
|
||||||
const title = ref('ChatGPT-智能助手');
|
const title = ref('ChatGPT-智能助手');
|
||||||
const models = ref([])
|
const models = ref([])
|
||||||
@ -603,18 +607,6 @@ const connect = function (chat_id, role_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 心跳函数
|
|
||||||
const sendHeartbeat = () => {
|
|
||||||
clearTimeout(heartbeatHandle.value)
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
if (socket.value !== null) {
|
|
||||||
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
|
|
||||||
}
|
|
||||||
resolve("success")
|
|
||||||
}).then(() => {
|
|
||||||
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
||||||
_socket.addEventListener('open', () => {
|
_socket.addEventListener('open', () => {
|
||||||
chatData.value = []; // 初始化聊天数据
|
chatData.value = []; // 初始化聊天数据
|
||||||
@ -636,8 +628,6 @@ const connect = function (chat_id, role_id) {
|
|||||||
} else { // 加载聊天记录
|
} else { // 加载聊天记录
|
||||||
loadChatHistory(chat_id);
|
loadChatHistory(chat_id);
|
||||||
}
|
}
|
||||||
// 发送心跳消息
|
|
||||||
sendHeartbeat()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_socket.addEventListener('message', event => {
|
_socket.addEventListener('message', event => {
|
||||||
@ -648,7 +638,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const data = JSON.parse(String(reader.result));
|
const data = JSON.parse(String(reader.result));
|
||||||
if (data.type === 'start') {
|
if (data.type === 'start') {
|
||||||
const prePrompt = chatData.value[chatData.value.length-1].content
|
const prePrompt = chatData.value[chatData.value.length-1]?.content
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
type: "reply",
|
type: "reply",
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
@ -689,9 +679,11 @@ const connect = function (chat_id, role_id) {
|
|||||||
} else {
|
} else {
|
||||||
lineBuffer.value += data.content;
|
lineBuffer.value += data.content;
|
||||||
const reply = chatData.value[chatData.value.length - 1]
|
const reply = chatData.value[chatData.value.length - 1]
|
||||||
|
if (reply) {
|
||||||
reply['orgContent'] = lineBuffer.value;
|
reply['orgContent'] = lineBuffer.value;
|
||||||
reply['content'] = md.render(processContent(lineBuffer.value));
|
reply['content'] = md.render(processContent(lineBuffer.value));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 将聊天框的滚动条滑动到最底部
|
// 将聊天框的滚动条滑动到最底部
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||||
@ -716,7 +708,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
connect(chat_id, role_id)
|
connect(chat_id, role_id)
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
setTimeout(() => connect(chat_id, role_id), 3000)
|
showMessageError("会话已断开,刷新页面...")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@
|
|||||||
</div> <!-- end finish job list-->
|
</div> <!-- end finish job list-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
||||||
</div><!-- end task list box -->
|
</div><!-- end task list box -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -193,6 +193,7 @@ import Clipboard from "clipboard";
|
|||||||
import {checkSession} from "@/action/session";
|
import {checkSession} from "@/action/session";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
import TaskList from "@/components/TaskList.vue";
|
import TaskList from "@/components/TaskList.vue";
|
||||||
|
import BackTop from "@/components/BackTop.vue";
|
||||||
|
|
||||||
const listBoxHeight = ref(0)
|
const listBoxHeight = ref(0)
|
||||||
// const paramBoxHeight = ref(0)
|
// const paramBoxHeight = ref(0)
|
||||||
|
@ -593,6 +593,7 @@
|
|||||||
</div> <!-- end finish job list-->
|
</div> <!-- end finish job list-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
||||||
</div><!-- end task list box -->
|
</div><!-- end task list box -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -613,6 +614,7 @@ import {getSessionId} from "@/store/session";
|
|||||||
import {copyObj, removeArrayItem} from "@/utils/libs";
|
import {copyObj, removeArrayItem} from "@/utils/libs";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
import TaskList from "@/components/TaskList.vue";
|
import TaskList from "@/components/TaskList.vue";
|
||||||
|
import BackTop from "@/components/BackTop.vue";
|
||||||
|
|
||||||
const listBoxHeight = ref(0)
|
const listBoxHeight = ref(0)
|
||||||
const paramBoxHeight = ref(0)
|
const paramBoxHeight = ref(0)
|
||||||
@ -1014,7 +1016,7 @@ const publishImage = (item, action) => {
|
|||||||
item.publish = action
|
item.publish = action
|
||||||
page.value = 0
|
page.value = 0
|
||||||
isOver.value = false
|
isOver.value = false
|
||||||
fetchFinishJobs()
|
item.publish = action
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error(text + "失败:" + e.message)
|
ElMessage.error(text + "失败:" + e.message)
|
||||||
})
|
})
|
||||||
|
@ -345,7 +345,7 @@
|
|||||||
</div> <!-- end finish job list-->
|
</div> <!-- end finish job list-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
||||||
</div><!-- end task list box -->
|
</div><!-- end task list box -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -476,6 +476,7 @@ import {useRouter} from "vue-router";
|
|||||||
import {getSessionId} from "@/store/session";
|
import {getSessionId} from "@/store/session";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
import TaskList from "@/components/TaskList.vue";
|
import TaskList from "@/components/TaskList.vue";
|
||||||
|
import BackTop from "@/components/BackTop.vue";
|
||||||
|
|
||||||
const listBoxHeight = ref(0)
|
const listBoxHeight = ref(0)
|
||||||
// const paramBoxHeight = ref(0)
|
// const paramBoxHeight = ref(0)
|
||||||
@ -755,7 +756,7 @@ const publishImage = (event, item, action) => {
|
|||||||
item.publish = action
|
item.publish = action
|
||||||
page.value = 0
|
page.value = 0
|
||||||
isOver.value = false
|
isOver.value = false
|
||||||
fetchFinishJobs()
|
item.publish = action
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error(text + "失败:" + e.message)
|
ElMessage.error(text + "失败:" + e.message)
|
||||||
})
|
})
|
||||||
|
@ -163,7 +163,10 @@
|
|||||||
<i class="iconfont icon-face"></i>
|
<i class="iconfont icon-face"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
||||||
|
|
||||||
|
</div><!-- end of waterfall -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 任务详情弹框 -->
|
<!-- 任务详情弹框 -->
|
||||||
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
|
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
|
||||||
@ -301,6 +304,7 @@ import {httpGet} from "@/utils/http";
|
|||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
|
import BackTop from "@/components/BackTop.vue";
|
||||||
|
|
||||||
const data = ref({
|
const data = ref({
|
||||||
"mj": [],
|
"mj": [],
|
||||||
|
Loading…
Reference in New Issue
Block a user