optimize the vue component communication, replace event listening with share data

This commit is contained in:
RockYang 2024-09-30 14:20:59 +08:00
parent 1a1734abf0
commit 8923e938d2
23 changed files with 473 additions and 480 deletions

View File

@ -27,7 +27,6 @@
"markmap-view": "^0.16.0",
"md-editor-v3": "^2.2.1",
"memfs": "^4.9.3",
"mitt": "^3.0.1",
"pinia": "^2.1.4",
"qrcode": "^1.5.3",
"qs": "^6.11.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -191,7 +191,25 @@
}
}
.failed {
display flex
flex-flow column
justify-content center
.title {
margin-bottom 20px
text-align center
color #ee0a24
font-size 18px
}
.opt {
display flex
justify-content center
.van-button {
margin 0 5px
}
}
}
}
}
}

View File

@ -179,6 +179,26 @@
}
.failed {
display flex
flex-flow column
justify-content center
.title {
margin-bottom 20px
text-align center
color #ee0a24
font-size 18px
}
.opt {
display flex
justify-content center
.van-button {
margin 0 5px
}
}
}
}
}
}

View File

@ -52,13 +52,14 @@
</div>
</template>
<script setup>
import {computed, onMounted, ref} from 'vue';
import {onMounted, ref, watch} from 'vue';
import {getMenuItems, useSidebarStore} from '@/store/sidebar';
import {useRouter} from "vue-router";
import {ArrowDown, ArrowRight, Expand, Fold, Moon, Sunny} from "@element-plus/icons-vue";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import {removeAdminToken} from "@/store/session";
import {useSharedStore} from "@/store/sharedata";
const version = ref(process.env.VUE_APP_VERSION)
const avatar = ref('/images/user-info.jpg')
@ -66,24 +67,18 @@ const sidebar = useSidebarStore();
const router = useRouter();
const breadcrumb = ref([])
// eslint-disable-next-line no-undef
const props = defineProps({
theme: String,
});
const theme = computed(() => {
return props.theme
const store = useSharedStore()
const dark = ref(store.adminTheme === 'dark')
const theme = ref(store.adminTheme)
watch(() => store.adminTheme, (val) => {
theme.value = val
})
const dark = ref(props.theme === 'dark' ? true : false)
// eslint-disable-next-line no-undef
const emits = defineEmits(['changeTheme']);
const changeTheme = () => {
emits('changeTheme', dark.value)
store.setAdminTheme(dark.value ? 'dark' : 'light')
}
router.afterEach((to, from) => {
router.afterEach((to) => {
initBreadCrumb(to.path)
});

View File

@ -52,11 +52,12 @@
</template>
<script setup>
import {computed, ref} from 'vue';
import {computed, ref, watch} from 'vue';
import {setMenuItems, useSidebarStore} from '@/store/sidebar';
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import {useRoute} from "vue-router";
import {useSharedStore} from "@/store/sharedata";
const title = ref('')
const logo = ref('')
@ -68,16 +69,11 @@ httpGet('/api/admin/config/get?key=system').then(res => {
}).catch(e => {
ElMessage.error("加载系统配置失败: " + e.message)
})
// eslint-disable-next-line no-undef
const props = defineProps({
theme: String,
});
const theme = computed(() => {
return props.theme
const store = useSharedStore()
const theme = ref(store.adminTheme)
watch(() => store.adminTheme, (val) => {
theme.value = val
})
const items = [
{
icon: 'home',

View File

@ -38,17 +38,14 @@ import {onBeforeRouteUpdate, useRoute, useRouter} from 'vue-router';
import {ArrowDown, Close} from "@element-plus/icons-vue";
import {checkAdminSession} from "@/store/cache";
import {ElMessageBox} from "element-plus";
import {computed} from "vue";
import {useSharedStore} from "@/store/sharedata";
import {ref, watch} from "vue";
// eslint-disable-next-line no-undef
const props = defineProps({
theme: String,
});
const theme = computed(() => {
return props.theme
const store = useSharedStore()
const theme = ref(store.adminTheme)
watch(() => store.adminTheme, (val) => {
theme.value = val
})
const router = useRouter();
checkAdminSession().catch(() => {
ElMessageBox({

View File

@ -1,6 +0,0 @@
// 导入mitt包
import mitt from 'mitt'
// 创建EventBus实例对象
const bus = mitt()
// 共享出eventbus的实例对象
export default bus

View File

@ -8,6 +8,8 @@ export const useSharedStore = defineStore('shared', {
chatStream: Storage.get("chat_stream",true),
socket: WebSocket,
messageHandlers:{},
mobileTheme: Storage.get("mobile_theme", "light"),
adminTheme: Storage.get("admin_theme", "light"),
}),
getters: {},
actions: {
@ -51,6 +53,17 @@ export const useSharedStore = defineStore('shared', {
this.setMessageHandler(callback)
}, 1000)
}
},
removeMessageHandler(key) {
delete this.messageHandlers[key]
},
setMobileTheme(theme) {
this.mobileTheme = theme
Storage.set("mobile_theme", theme)
},
setAdminTheme(theme) {
this.adminTheme = theme
Storage.set("admin_theme", theme)
}
}
},
});

View File

@ -7,25 +7,6 @@
import Storage from "good-storage";
const MOBILE_THEME = process.env.VUE_APP_KEY_PREFIX + "MOBILE_THEME"
const ADMIN_THEME = process.env.VUE_APP_KEY_PREFIX + "ADMIN_THEME"
export function getMobileTheme() {
return Storage.get(MOBILE_THEME) ? Storage.get(MOBILE_THEME) : 'light'
}
export function setMobileTheme(theme) {
Storage.set(MOBILE_THEME, theme)
}
export function getAdminTheme() {
return Storage.get(ADMIN_THEME) ? Storage.get(ADMIN_THEME) : 'light'
}
export function setAdminTheme(theme) {
Storage.set(ADMIN_THEME, theme)
}
export function GetFileIcon(ext) {
const files = {
".docx": "doc.png",

View File

@ -213,7 +213,7 @@
</template>
<script setup>
import {nextTick, onMounted, ref, watch} from 'vue'
import {nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue";
import {Delete, Edit, InfoFilled, More, Plus, Promotion, Search, Share, VideoPause} from '@element-plus/icons-vue'
@ -422,6 +422,10 @@ onMounted(() => {
})
})
});
onUnmounted(() => {
store.removeMessageHandler("chat")
})
//
const initData = () => {
@ -774,6 +778,7 @@ const clearAllChats = function () {
const loadChatHistory = function (chatId) {
chatData.value = []
loading.value = true
httpGet('/api/chat/history?chat_id=' + chatId).then(res => {
const data = res.data
if ((!data || data.length === 0) && chatData.value.length === 0) { //

View File

@ -287,6 +287,7 @@ onMounted(() => {
onUnmounted(() => {
clipboard.value.destroy()
store.removeMessageHandler("dall")
})
const initData = () => {

View File

@ -738,6 +738,7 @@ onMounted(() => {
onUnmounted(() => {
clipboard.value.destroy()
store.removeMessageHandler("mj")
})
//

View File

@ -586,6 +586,7 @@ onMounted(() => {
onUnmounted(() => {
clipboard.value.destroy()
store.removeMessageHandler("sd")
})

View File

@ -131,7 +131,7 @@
</template>
<script setup>
import {onMounted, reactive, ref} from "vue";
import {onMounted, onUnmounted, reactive, ref} from "vue";
import {CircleCloseFilled} from "@element-plus/icons-vue";
import {httpDownload, httpPost, httpGet} from "@/utils/http";
import {checkSession, getClientId} from "@/store/cache";
@ -175,6 +175,10 @@ onMounted(()=>{
})
})
onUnmounted(() => {
store.removeMessageHandler("luma")
})
const download = (item) => {
const url = replaceImg(item.video_url)
const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${url}`

View File

@ -386,6 +386,7 @@ onMounted(() => {
onUnmounted(() => {
clipboard.value.destroy()
store.removeMessageHandler("suno")
})
const page = ref(1)

View File

@ -1,9 +1,9 @@
<template>
<div class="admin-home" v-if="isLogin">
<admin-sidebar v-model:theme="theme"/>
<admin-sidebar/>
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<admin-header v-model:theme="theme" @changeTheme="changeTheme"/>
<admin-tags v-model:theme="theme"/>
<admin-header/>
<admin-tags/>
<div :class="'content '+theme" :style="{height:contentHeight+'px'}">
<router-view v-slot="{ Component }">
<transition name="move" mode="out-in">
@ -24,14 +24,15 @@ import AdminSidebar from "@/components/admin/AdminSidebar.vue";
import AdminTags from "@/components/admin/AdminTags.vue";
import {useRouter} from "vue-router";
import {checkAdminSession} from "@/store/cache";
import {ref} from "vue";
import {getAdminTheme, setAdminTheme} from "@/store/system";
import {ref, watch} from "vue";
import {useSharedStore} from "@/store/sharedata";
const sidebar = useSidebarStore();
const tags = useTagsStore();
const isLogin = ref(false)
const contentHeight = window.innerHeight - 80
const theme = ref(getAdminTheme())
const store = useSharedStore()
const theme = ref(store.adminTheme)
//
const router = useRouter();
@ -41,14 +42,10 @@ checkAdminSession().then(() => {
router.replace('/admin/login')
})
const changeTheme = (value) => {
if (value) {
theme.value = 'dark'
} else {
theme.value = 'light'
}
setAdminTheme(theme.value)
}
watch(() => store.adminTheme, (val) => {
theme.value = val
})
</script>

View File

@ -125,15 +125,14 @@
<script setup>
import {nextTick, onMounted, onUnmounted, ref, watch} from "vue";
import {showImagePreview, showNotify, showToast} from "vant";
import {onBeforeRouteLeave, useRouter} from "vue-router";
import {useRouter} from "vue-router";
import {processContent, randString, renderInputText, UUID} from "@/utils/libs";
import {httpGet} from "@/utils/http";
import hl from "highlight.js";
import 'highlight.js/styles/a11y-dark.css'
import ChatPrompt from "@/components/mobile/ChatPrompt.vue";
import ChatReply from "@/components/mobile/ChatReply.vue";
import {getSessionId, getUserToken} from "@/store/session";
import {checkSession} from "@/store/cache";
import {checkSession, getClientId} from "@/store/cache";
import Clipboard from "clipboard";
import { showMessageError} from "@/utils/dialog";
import {useSharedStore} from "@/store/sharedata";
@ -191,7 +190,7 @@ const loadModels = () => {
role.value = getRoleById(roleId.value)
columns.value = [roles.value, models.value]
selectedValues.value = [roleId.value, modelId.value]
connect()
loadChatHistory()
}).catch((e) => {
showNotify({type: "danger", message: '获取聊天角色失败: ' + e.messages})
})
@ -214,48 +213,12 @@ if (chatId.value) {
loadModels()
}
const url = ref(location.protocol + '//' + location.host + '/mobile/chat/export?chat_id=' + chatId.value)
onMounted(() => {
winHeight.value = window.innerHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight - 70
const clipboard = new Clipboard(".content-mobile,.copy-code-mobile,#copy-link-btn");
clipboard.on('success', (e) => {
e.clearSelection()
showNotify({type: 'success', message: '复制成功', duration: 1000})
})
clipboard.on('error', () => {
showNotify({type: 'danger', message: '复制失败', duration: 2000})
})
})
onUnmounted(() => {
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})
const newChat = (item) => {
showPicker.value = false
const options = item.selectedOptions
roleId.value = options[0].value
modelId.value = options[1].value
modelValue.value = getModelName(modelId.value)
chatId.value = UUID()
chatData.value = []
role.value = getRoleById(roleId.value)
title.value = "新建对话"
loadHistory.value = true
connect()
}
const chatData = ref([])
const loading = ref(false)
const finished = ref(false)
const error = ref(false)
const store = useSharedStore()
const url = ref(location.protocol + '//' + location.host + '/mobile/chat/export?chat_id=' + chatId.value)
const mathjaxPlugin = require('markdown-it-mathjax3')
const md = require('markdown-it')({
breaks: true,
@ -282,7 +245,91 @@ const md = require('markdown-it')({
}
});
md.use(mathjaxPlugin)
onMounted(() => {
winHeight.value = window.innerHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight - 70
const clipboard = new Clipboard(".content-mobile,.copy-code-mobile,#copy-link-btn");
clipboard.on('success', (e) => {
e.clearSelection()
showNotify({type: 'success', message: '复制成功', duration: 1000})
})
clipboard.on('error', () => {
showNotify({type: 'danger', message: '复制失败', duration: 2000})
})
store.addMessageHandler("chat",(data) => {
if (data.channel !== "chat" || data.clientId !== getClientId()) {
return
}
if (data.type === 'error') {
showMessageError(data.body)
return
}
if (isNewMsg.value) {
chatData.value.push({
type: "reply",
id: randString(32),
icon: role.value.icon,
content: data.body
});
if (!title.value) {
title.value = previousText.value
}
lineBuffer.value = data.body;
isNewMsg.value = false
} else if (data.type === 'end') { //
enableInput()
lineBuffer.value = ''; //
isNewMsg.value = true
} else {
lineBuffer.value += data.body;
const reply = chatData.value[chatData.value.length - 1]
reply['orgContent'] = lineBuffer.value;
reply['content'] = md.render(processContent(lineBuffer.value));
nextTick(() => {
hl.configure({ignoreUnescapedHTML: true})
const lines = document.querySelectorAll('.message-line');
const blocks = lines[lines.length - 1].querySelectorAll('pre code');
blocks.forEach((block) => {
hl.highlightElement(block)
})
scrollListBox()
const items = document.querySelectorAll('.message-line')
const imgs = items[items.length - 1].querySelectorAll('img')
for (let i = 0; i < imgs.length; i++) {
if (!imgs[i].src) {
continue
}
imgs[i].addEventListener('click', (e) => {
e.stopPropagation()
showImagePreview([imgs[i].src]);
})
}
})
}
})
})
onUnmounted(() => {
store.removeMessageHandler("chat")
})
const newChat = (item) => {
showPicker.value = false
const options = item.selectedOptions
roleId.value = options[0].value
modelId.value = options[1].value
modelValue.value = getModelName(modelId.value)
chatId.value = UUID()
chatData.value = []
role.value = getRoleById(roleId.value)
title.value = "新建对话"
loadChatHistory()
}
const onLoad = () => {
// checkSession().then(() => {
@ -334,121 +381,113 @@ const loadChatHistory = () => {
})
}
// websocket
onBeforeRouteLeave(() => {
if (socket.value !== null) {
socket.value.close();
}
})
// socket
const prompt = ref('');
const showStopGenerate = ref(false); //
const showReGenerate = ref(false); //
const previousText = ref(''); //
const lineBuffer = ref(''); //
const socket = ref(null);
const canSend = ref(true)
const isNewMsg = ref(true)
const loadHistory = ref(true)
const store = useSharedStore()
const stream = ref(store.chatStream)
watch(() => store.chatStream, (newValue) => {
stream.value = newValue
});
const connect = function () {
// WebSocket
const _sessionId = getSessionId();
let host = process.env.VUE_APP_WS_HOST
if (host === '') {
if (location.protocol === 'https:') {
host = 'wss://' + location.host;
} else {
host = 'ws://' + location.host;
}
}
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${roleId.value}&chat_id=${chatId.value}&model_id=${modelId.value}&token=${getUserToken()}`);
_socket.addEventListener('open', () => {
loading.value = false
previousText.value = '';
canSend.value = true;
// const connect = function () {
// // WebSocket
// const _sessionId = getSessionId();
// let host = process.env.VUE_APP_WS_HOST
// if (host === '') {
// if (location.protocol === 'https:') {
// host = 'wss://' + location.host;
// } else {
// host = 'ws://' + location.host;
// }
// }
// const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${roleId.value}&chat_id=${chatId.value}&model_id=${modelId.value}&token=${getUserToken()}`);
// _socket.addEventListener('open', () => {
// loading.value = false
// previousText.value = '';
// canSend.value = true;
//
// if (loadHistory.value) { //
// loadChatHistory()
// }
// });
//
// _socket.addEventListener('message', event => {
// if (event.data instanceof Blob) {
// const reader = new FileReader();
// reader.readAsText(event.data, "UTF-8");
// reader.onload = () => {
// const data = JSON.parse(String(reader.result));
// if (data.type === 'error') {
// showMessageError(data.message)
// return
// }
//
// if (isNewMsg.value && data.type !== 'end') {
// chatData.value.push({
// type: "reply",
// id: randString(32),
// icon: role.value.icon,
// content: data.content
// });
// if (!title.value) {
// title.value = previousText.value
// }
// lineBuffer.value = data.content;
// isNewMsg.value = false
// } else if (data.type === 'end') { //
// enableInput()
// lineBuffer.value = ''; //
// isNewMsg.value = true
// } else {
// lineBuffer.value += data.content;
// const reply = chatData.value[chatData.value.length - 1]
// reply['orgContent'] = lineBuffer.value;
// reply['content'] = md.render(processContent(lineBuffer.value));
//
// nextTick(() => {
// hl.configure({ignoreUnescapedHTML: true})
// const lines = document.querySelectorAll('.message-line');
// const blocks = lines[lines.length - 1].querySelectorAll('pre code');
// blocks.forEach((block) => {
// hl.highlightElement(block)
// })
// scrollListBox()
//
// const items = document.querySelectorAll('.message-line')
// const imgs = items[items.length - 1].querySelectorAll('img')
// for (let i = 0; i < imgs.length; i++) {
// if (!imgs[i].src) {
// continue
// }
// imgs[i].addEventListener('click', (e) => {
// e.stopPropagation()
// showImagePreview([imgs[i].src]);
// })
// }
// })
// }
//
// };
// }
//
// });
//
// _socket.addEventListener('close', () => {
// //
// canSend.value = true
// loadHistory.value = false
// //
// connect()
// });
//
// socket.value = _socket;
// }
if (loadHistory.value) { //
loadChatHistory()
}
});
_socket.addEventListener('message', event => {
if (event.data instanceof Blob) {
const reader = new FileReader();
reader.readAsText(event.data, "UTF-8");
reader.onload = () => {
const data = JSON.parse(String(reader.result));
if (data.type === 'error') {
showMessageError(data.message)
return
}
if (isNewMsg.value && data.type !== 'end') {
chatData.value.push({
type: "reply",
id: randString(32),
icon: role.value.icon,
content: data.content
});
if (!title.value) {
title.value = previousText.value
}
lineBuffer.value = data.content;
isNewMsg.value = false
} else if (data.type === 'end') { //
enableInput()
lineBuffer.value = ''; //
isNewMsg.value = true
} else {
lineBuffer.value += data.content;
const reply = chatData.value[chatData.value.length - 1]
reply['orgContent'] = lineBuffer.value;
reply['content'] = md.render(processContent(lineBuffer.value));
nextTick(() => {
hl.configure({ignoreUnescapedHTML: true})
const lines = document.querySelectorAll('.message-line');
const blocks = lines[lines.length - 1].querySelectorAll('pre code');
blocks.forEach((block) => {
hl.highlightElement(block)
})
scrollListBox()
const items = document.querySelectorAll('.message-line')
const imgs = items[items.length - 1].querySelectorAll('img')
for (let i = 0; i < imgs.length; i++) {
if (!imgs[i].src) {
continue
}
imgs[i].addEventListener('click', (e) => {
e.stopPropagation()
showImagePreview([imgs[i].src]);
})
}
})
}
};
}
});
_socket.addEventListener('close', () => {
//
canSend.value = true
loadHistory.value = false
//
connect()
});
socket.value = _socket;
}
const disableInput = (force) => {
canSend.value = false;
@ -473,6 +512,11 @@ const sendMessage = () => {
return
}
if (store.socket.readyState !== WebSocket.OPEN) {
showToast("连接断开,正在重连...");
return
}
if (prompt.value.trim().length === 0) {
showToast("请输入需要 AI 回答的问题")
return false;
@ -492,7 +536,17 @@ const sendMessage = () => {
})
disableInput(false)
socket.value.send(JSON.stringify({stream: stream.value, content: prompt.value}));
store.socket.send(JSON.stringify({
channel: 'chat',
type:'text',
body:{
role_id: roleId.value,
model_id: modelId.value,
chat_id: chatId.value,
content: prompt.value,
stream: stream.value
}
}));
previousText.value = prompt.value;
prompt.value = '';
return true;
@ -500,7 +554,7 @@ const sendMessage = () => {
const stopGenerate = () => {
showStopGenerate.value = false;
httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
httpGet("/api/chat/stop?session_id=" + getClientId()).then(() => {
enableInput()
})
}
@ -515,7 +569,17 @@ const reGenerate = () => {
icon: loginUser.value.avatar,
content: renderInputText(text)
});
socket.value.send(JSON.stringify({stream: stream.value, content: previousText.value}));
store.socket.send(JSON.stringify({
channel: 'chat',
type:'text',
body:{
role_id: roleId.value,
model_id: modelId.value,
chat_id: chatId.value,
content: previousText.value,
stream: stream.value
}
}));
}
const showShare = ref(false)

View File

@ -17,11 +17,10 @@
</template>
<script setup>
import {ref} from "vue";
import {getMobileTheme, setMobileTheme} from "@/store/system";
import {ref, watch} from "vue";
import {useRouter} from "vue-router";
import {isMobile} from "@/utils/libs";
import bus from '@/store/eventbus'
import {useSharedStore} from "@/store/sharedata";
const router = useRouter()
if (!isMobile()) {
@ -29,11 +28,11 @@ if (!isMobile()) {
}
const active = ref('home')
const theme = ref(getMobileTheme())
const store = useSharedStore()
const theme = ref(store.mobileTheme)
bus.on('changeTheme', (value) => {
theme.value = value
setMobileTheme(theme.value)
watch(() => store.mobileTheme, (val) => {
theme.value = val
})
</script>

View File

@ -125,7 +125,13 @@
<van-cell-group inset>
<van-field name="switch" label="暗黑主题">
<template #input>
<van-switch v-model="dark" @change="changeTheme"/>
<van-switch v-model="dark" @change="(val) => store.setMobileTheme(val?'dark':'light')"/>
</template>
</van-field>
<van-field name="switch" label="流式输出">
<template #input>
<van-switch v-model="stream" @change="(val) => store.setChatStream(val)"/>
</template>
</van-field>
<!-- <van-field-->
@ -158,9 +164,7 @@ import {ElMessage} from "element-plus";
import {checkSession, getSystemInfo} from "@/store/cache";
import {useRouter} from "vue-router";
import {removeUserToken} from "@/store/session";
import bus from '@/store/eventbus'
import {getMobileTheme} from "@/store/system";
import QRCode from "qrcode";
import {useSharedStore} from "@/store/sharedata";
const form = ref({
username: 'GeekMaster',
@ -183,6 +187,9 @@ const router = useRouter()
const userId = ref(0)
const isLogin = ref(false)
const showSettings = ref(false)
const store = useSharedStore()
const stream = ref(store.chatStream)
const dark = ref(store.mobileTheme === 'dark')
onMounted(() => {
checkSession().then(user => {
@ -322,12 +329,6 @@ const logout = function () {
})
}
const dark = ref(getMobileTheme() === 'dark')
const changeTheme = () => {
bus.emit('changeTheme', dark.value ? 'dark' : 'light')
}
</script>
<style lang="stylus">

View File

@ -81,11 +81,9 @@
description="暂无记录"
/>
<van-grid :gutter="10" :column-num="3" v-else>
<van-grid-item v-for="item in runningJobs">
<van-grid-item v-for="item in runningJobs" :key="item.id">
<div v-if="item.progress > 0">
<van-image :src="item['img_url']">
<template v-slot:error>加载失败</template>
</van-image>
<van-image src="/images/img-holder.png"></van-image>
<div class="progress">
<van-circle
v-model:current-rate="item.progress"
@ -124,8 +122,15 @@
@load="onLoad"
>
<van-grid :gutter="10" :column-num="2">
<van-grid-item v-for="item in finishedJobs">
<div class="job-item">
<van-grid-item v-for="item in finishedJobs" :key="item.id">
<div class="failed" v-if="item.progress === 101">
<div class="title">任务失败</div>
<div class="opt">
<van-button size="small" @click="showErrMsg(item)">详情</van-button>
<van-button type="danger" @click="removeImage($event,item)" size="small">删除</van-button>
</div>
</div>
<div class="job-item" v-else>
<van-image
:src="item['img_url']"
:class="item['can_opt'] ? '' : 'upscale'"
@ -165,7 +170,7 @@ import {onMounted, onUnmounted, ref} from "vue"
import {Delete} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http";
import Clipboard from "clipboard";
import {checkSession, getSystemInfo} from "@/store/cache";
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
import {useRouter} from "vue-router";
import {getSessionId} from "@/store/session";
import {
@ -178,10 +183,10 @@ import {
showToast
} from "vant";
import {showLoginDialog} from "@/utils/libs";
import {useSharedStore} from "@/store/sharedata";
const listBoxHeight = ref(window.innerHeight - 40)
const mjBoxHeight = ref(window.innerHeight - 150)
const item = ref({})
const isLogin = ref(false)
window.onresize = () => {
@ -203,6 +208,7 @@ const styles = [
{text: "自然", value: "natural"}
]
const params = ref({
client_id: getClientId(),
quality: qualities[0].value,
size: sizes[0].value,
style: styles[0].value,
@ -223,56 +229,8 @@ const router = useRouter()
const power = ref(0)
const dallPower = ref(0) // DALL
const socket = ref(null)
const userId = ref(0)
const heartbeatHandle = ref(null)
const connect = () => {
let host = process.env.VUE_APP_WS_HOST
if (host === '') {
if (location.protocol === 'https:') {
host = 'wss://' + location.host;
} else {
host = 'ws://' + location.host;
}
}
//
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/dall/client?user_id=${userId.value}`);
_socket.addEventListener('open', () => {
socket.value = _socket;
//
sendHeartbeat()
});
_socket.addEventListener('message', event => {
if (event.data instanceof Blob) {
fetchRunningJobs()
finished.value = false
page.value = 1
fetchFinishJobs(page.value)
}
});
_socket.addEventListener('close', () => {
if (socket.value !== null) {
connect()
}
});
}
const store = useSharedStore()
const clipboard = ref(null)
const prompt = ref('')
onMounted(() => {
@ -290,25 +248,32 @@ onMounted(() => {
}).catch(e => {
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
})
store.addMessageHandler("dall", (data) => {
if (data.channel !== "dall" || data.clientId !== getClientId()) {
return
}
if (data.body === "FINISH" || data.body === "FAIL") {
page.value = 1
fetchFinishJobs(1)
}
fetchRunningJobs()
})
})
onUnmounted(() => {
clipboard.value.destroy()
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
store.removeMessageHandler("dall")
})
const initData = () => {
checkSession().then(user => {
power.value = user['power']
userId.value = user.id
isLogin.value = true
fetchRunningJobs()
fetchFinishJobs(1)
connect()
}).catch(() => {
loading.value = false
});
@ -317,20 +282,7 @@ const initData = () => {
const fetchRunningJobs = () => {
//
httpGet(`/api/dall/jobs?finish=0`).then(res => {
const jobs = res.data.items
const _jobs = []
for (let i = 0; i < jobs.length; i++) {
if (jobs[i].progress === -1) {
showNotify({
message: `任务ID${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
type: 'danger',
})
power.value += dallPower.value
continue
}
_jobs.push(jobs[i])
}
runningJobs.value = _jobs
runningJobs.value = res.data.items
}).catch(e => {
showNotify({type: "danger", message: "获取任务失败:" + e.message})
})
@ -349,10 +301,17 @@ const fetchFinishJobs = (page) => {
if (jobs.length < pageSize.value) {
finished.value = true
}
const _jobs = []
for (let i = 0; i < jobs.length; i++) {
if (jobs[i].progress === -1) {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
}
_jobs.push(jobs[i])
}
if (page === 1) {
finishedJobs.value = jobs
finishedJobs.value = _jobs
} else {
finishedJobs.value = finishedJobs.value.concat(jobs)
finishedJobs.value = finishedJobs.value.concat(_jobs)
}
loading.value = false
}).catch(e => {
@ -385,6 +344,7 @@ const generate = () => {
httpPost("/api/dall/image", params.value).then(() => {
showSuccessToast("绘画任务推送成功,请耐心等待任务执行...")
power.value -= dallPower.value
fetchRunningJobs()
}).catch(e => {
showFailToast("任务推送失败:" + e.message)
})
@ -403,6 +363,15 @@ const showPrompt = (item) => {
});
}
const showErrMsg = (item) => {
showDialog({
title: '错误详情',
message: item['err_msg'],
}).then(() => {
// on close
});
}
const removeImage = (event, item) => {
event.stopPropagation()
showConfirmDialog({
@ -412,6 +381,7 @@ const removeImage = (event, item) => {
}).then(() => {
httpGet("/api/dall/remove", {id: item.id, user_id: item.user_id}).then(() => {
showSuccessToast("任务删除成功")
fetchFinishJobs(1)
}).catch(e => {
showFailToast("任务删除失败:" + e.message)
})
@ -458,14 +428,6 @@ const sizeConfirm =(item) => {
showSizePicker.value =false
}
const showInfo = (message) => {
showDialog({
title: "参数说明",
message: message,
}).then(() => {
// on close
});
}
</script>
<style lang="stylus">

View File

@ -180,11 +180,9 @@
description="暂无记录"
/>
<van-grid :gutter="10" :column-num="3" v-else>
<van-grid-item v-for="item in runningJobs">
<van-grid-item v-for="item in runningJobs" :key="item.id">
<div v-if="item.progress > 0">
<van-image :src="item['img_url']">
<template v-slot:error>加载失败</template>
</van-image>
<van-image src="/images/img-holder.png"></van-image>
<div class="progress">
<van-circle
v-model:current-rate="item.progress"
@ -223,8 +221,15 @@
@load="onLoad"
>
<van-grid :gutter="10" :column-num="2">
<van-grid-item v-for="item in finishedJobs">
<div class="job-item">
<van-grid-item v-for="item in finishedJobs" :key="item.id">
<div class="failed" v-if="item.progress === 101">
<div class="title">任务失败</div>
<div class="opt">
<van-button size="small" @click="showErrMsg(item)">详情</van-button>
<van-button type="danger" @click="removeImage(item)" size="small">删除</van-button>
</div>
</div>
<div class="job-item" v-else>
<van-image
:src="item['thumb_url']"
:class="item['can_opt'] ? '' : 'upscale'"
@ -234,6 +239,10 @@
<template v-slot:loading>
<van-loading type="spinner" size="20"/>
</template>
<template v-slot:error>
<span style="margin-bottom: 20px">正在下载图片</span>
<van-loading type="circular" color="#1989fa" size="40"/>
</template>
</van-image>
<div class="opt" v-if="item['can_opt']">
@ -276,15 +285,16 @@
<script setup>
import {nextTick, onMounted, onUnmounted, ref} from "vue";
import {showConfirmDialog, showFailToast, showImagePreview, showNotify, showSuccessToast, showToast} from "vant";
import {showConfirmDialog, showFailToast, showImagePreview, showNotify, showSuccessToast, showToast,showDialog } from "vant";
import {httpGet, httpPost} from "@/utils/http";
import Compressor from "compressorjs";
import {getSessionId} from "@/store/session";
import {checkSession, getSystemInfo} from "@/store/cache";
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
import {useRouter} from "vue-router";
import {Delete} from "@element-plus/icons-vue";
import {showLoginDialog} from "@/utils/libs";
import Clipboard from "clipboard";
import {useSharedStore} from "@/store/sharedata";
const activeColspan = ref([""])
@ -306,6 +316,7 @@ const models = [
]
const imgList = ref([])
const params = ref({
client_id: getClientId(),
task_type: "image",
rate: rates[0].value,
model: models[0].value,
@ -327,11 +338,11 @@ const userId = ref(0)
const router = useRouter()
const runningJobs = ref([])
const finishedJobs = ref([])
const socket = ref(null)
const power = ref(0)
const activeName = ref("txt2img")
const isLogin = ref(false)
const prompt = ref('')
const store = useSharedStore()
const clipboard = ref(null)
onMounted(() => {
@ -349,19 +360,26 @@ onMounted(() => {
isLogin.value = true
fetchRunningJobs()
fetchFinishJobs(1)
connect()
}).catch(() => {
// router.push('/login')
});
store.addMessageHandler("mj", (data) => {
if (data.channel !== "mj" || data.clientId !== getClientId()) {
return
}
if (data.body === "FINISH" || data.body === "FAIL") {
page.value = 1
fetchFinishJobs(1)
}
fetchRunningJobs()
})
})
onUnmounted(() => {
clipboard.value.destroy()
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
store.removeMessageHandler("mj")
})
const mjPower = ref(1)
@ -373,60 +391,6 @@ getSystemInfo().then(res => {
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
})
const heartbeatHandle = ref(null)
const connect = () => {
let host = process.env.VUE_APP_WS_HOST
if (host === '') {
if (location.protocol === 'https:') {
host = 'wss://' + location.host;
} else {
host = 'ws://' + location.host;
}
}
//
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/mj/client?user_id=${userId.value}`);
_socket.addEventListener('open', () => {
socket.value = _socket;
//
sendHeartbeat()
});
_socket.addEventListener('message', event => {
if (event.data instanceof Blob) {
const reader = new FileReader();
reader.readAsText(event.data, "UTF-8")
reader.onload = () => {
const message = String(reader.result)
if (message === "FINISH" || message === "FAIL") {
page.value = 1
fetchFinishJobs(1)
}
fetchRunningJobs()
}
}
});
_socket.addEventListener('close', () => {
if (socket.value !== null) {
connect()
}
});
}
//
const fetchRunningJobs = (userId) => {
httpGet(`/api/mj/jobs?finish=0&user_id=${userId}`).then(res => {
@ -464,27 +428,10 @@ const fetchFinishJobs = (page) => {
httpGet(`/api/mj/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
const jobs = res.data.items
for (let i = 0; i < jobs.length; i++) {
if (jobs[i].progress === 101) {
showNotify({
message: `任务ID${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
type: 'danger',
})
if (jobs[i].type === 'image') {
power.value += mjPower.value
} else {
power.value += mjActionPower.value
}
continue
}
if (jobs[i]['use_proxy']) {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?x-oss-process=image/quality,q_60&format=webp'
if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
} else {
if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
} else {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
}
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
}
if ((jobs[i].type === 'image' || jobs[i].type === 'variation') && jobs[i].progress === 100){
@ -557,6 +504,7 @@ const uploadImg = (file) => {
const send = (url, index, item) => {
httpPost(url, {
client_id: getClientId(),
index: index,
channel_id: item.channel_id,
message_id: item.message_id,
@ -566,6 +514,7 @@ const send = (url, index, item) => {
}).then(() => {
showSuccessToast("任务推送成功,请耐心等待任务执行...")
power.value -= mjActionPower.value
fetchRunningJobs()
}).catch(e => {
showFailToast("任务推送失败:" + e.message)
})
@ -597,6 +546,7 @@ const generate = () => {
httpPost("/api/mj/image", params.value).then(() => {
showToast("绘画任务推送成功,请耐心等待任务执行")
power.value -= mjPower.value
fetchRunningJobs()
}).catch(e => {
showFailToast("任务推送失败:" + e.message)
})
@ -604,12 +554,13 @@ const generate = () => {
const removeImage = (item) => {
showConfirmDialog({
title: '标题',
title: '删除提示',
message:
'此操作将会删除任务和图片,继续操作码?',
}).then(() => {
httpGet("/api/mj/remove", {id: item.id, user_id: item.user_id}).then(() => {
showSuccessToast("任务删除成功")
fetchFinishJobs(1)
}).catch(e => {
showFailToast("任务删除失败:" + e.message)
})
@ -644,6 +595,15 @@ const showPrompt = (item) => {
});
}
const showErrMsg = (item) => {
showDialog({
title: '错误详情',
message: item['err_msg'],
}).then(() => {
// on close
});
}
const imageView = (item) => {
showImagePreview([item['img_url']]);
}

View File

@ -133,11 +133,9 @@
description="暂无记录"
/>
<van-grid :gutter="10" :column-num="3" v-else>
<van-grid-item v-for="item in runningJobs">
<van-grid-item v-for="item in runningJobs" :key="item.id">
<div v-if="item.progress > 0">
<van-image :src="item['img_url']">
<template v-slot:error>加载失败</template>
</van-image>
<van-image src="/images/img-holder.png"></van-image>
<div class="progress">
<van-circle
v-model:current-rate="item.progress"
@ -176,8 +174,15 @@
@load="onLoad"
>
<van-grid :gutter="10" :column-num="2">
<van-grid-item v-for="item in finishedJobs">
<div class="job-item">
<van-grid-item v-for="item in finishedJobs" :key="item.id">
<div class="failed" v-if="item.progress === 101">
<div class="title">任务失败</div>
<div class="opt">
<van-button size="small" @click="showErrMsg(item)">详情</van-button>
<van-button type="danger" @click="removeImage($event,item)" size="small">删除</van-button>
</div>
</div>
<div class="job-item" v-else>
<van-image
:src="item['img_url']"
:class="item['can_opt'] ? '' : 'upscale'"
@ -217,7 +222,7 @@ import {onMounted, onUnmounted, ref} from "vue"
import {Delete} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http";
import Clipboard from "clipboard";
import {checkSession, getSystemInfo} from "@/store/cache";
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
import {useRouter} from "vue-router";
import {getSessionId} from "@/store/session";
import {
@ -230,10 +235,10 @@ import {
showToast
} from "vant";
import {showLoginDialog} from "@/utils/libs";
import {useSharedStore} from "@/store/sharedata";
const listBoxHeight = ref(window.innerHeight - 40)
const mjBoxHeight = ref(window.innerHeight - 150)
const item = ref({})
const isLogin = ref(false)
const activeColspan = ref([""])
@ -261,6 +266,7 @@ const upscaleAlgArr = ref([
const showUpscalePicker = ref(false)
const params = ref({
client_id: getClientId(),
width: 1024,
height: 1024,
sampler: samplers.value[0].value,
@ -287,56 +293,8 @@ if (_params) {
const power = ref(0)
const sdPower = ref(0) // SD
const socket = ref(null)
const userId = ref(0)
const heartbeatHandle = ref(null)
const connect = () => {
let host = process.env.VUE_APP_WS_HOST
if (host === '') {
if (location.protocol === 'https:') {
host = 'wss://' + location.host;
} else {
host = 'ws://' + location.host;
}
}
//
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/sd/client?user_id=${userId.value}`);
_socket.addEventListener('open', () => {
socket.value = _socket;
//
sendHeartbeat()
});
_socket.addEventListener('message', event => {
if (event.data instanceof Blob) {
fetchRunningJobs()
finished.value = false
page.value = 1
fetchFinishJobs(page.value)
}
});
_socket.addEventListener('close', () => {
if (socket.value !== null) {
connect()
}
});
}
const store = useSharedStore()
const clipboard = ref(null)
const prompt = ref('')
onMounted(() => {
@ -355,14 +313,23 @@ onMounted(() => {
}).catch(e => {
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
})
store.addMessageHandler("sd", (data) => {
if (data.channel !== "sd" || data.clientId !== getClientId()) {
return
}
if (data.body === "FINISH" || data.body === "FAIL") {
page.value = 1
fetchFinishJobs(1)
}
fetchRunningJobs()
})
})
onUnmounted(() => {
clipboard.value.destroy()
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
store.removeMessageHandler("sd")
})
@ -373,7 +340,6 @@ const initData = () => {
isLogin.value = true
fetchRunningJobs()
fetchFinishJobs(1)
connect()
}).catch(() => {
loading.value = false
});
@ -414,10 +380,17 @@ const fetchFinishJobs = (page) => {
if (jobs.length < pageSize.value) {
finished.value = true
}
const _jobs = []
for (let i = 0; i < jobs.length; i++) {
if (jobs[i].progress === -1) {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
}
_jobs.push(jobs[i])
}
if (page === 1) {
finishedJobs.value = jobs
finishedJobs.value = _jobs
} else {
finishedJobs.value = finishedJobs.value.concat(jobs)
finishedJobs.value = finishedJobs.value.concat(_jobs)
}
loading.value = false
}).catch(e => {
@ -450,6 +423,7 @@ const generate = () => {
httpPost("/api/sd/image", params.value).then(() => {
showSuccessToast("绘画任务推送成功,请耐心等待任务执行...")
power.value -= sdPower.value
fetchRunningJobs()
}).catch(e => {
showFailToast("任务推送失败:" + e.message)
})
@ -468,6 +442,15 @@ const showPrompt = (item) => {
});
}
const showErrMsg = (item) => {
showDialog({
title: '错误详情',
message: item['err_msg'],
}).then(() => {
// on close
});
}
const removeImage = (event, item) => {
event.stopPropagation()
showConfirmDialog({
@ -477,6 +460,7 @@ const removeImage = (event, item) => {
}).then(() => {
httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
showSuccessToast("任务删除成功")
fetchFinishJobs(1)
}).catch(e => {
showFailToast("任务删除失败:" + e.message)
})