mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-19 01:36:38 +08:00
optimize the vue component communication, replace event listening with share data
This commit is contained in:
parent
1a1734abf0
commit
8923e938d2
@ -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",
|
||||
|
BIN
web/public/images/img-holder.png
Normal file
BIN
web/public/images/img-holder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
});
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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({
|
||||
|
@ -1,6 +0,0 @@
|
||||
// 导入mitt包
|
||||
import mitt from 'mitt'
|
||||
// 创建EventBus实例对象
|
||||
const bus = mitt()
|
||||
// 共享出eventbus的实例对象
|
||||
export default bus
|
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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) { // 加载打招呼信息
|
||||
|
@ -287,6 +287,7 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
store.removeMessageHandler("dall")
|
||||
})
|
||||
|
||||
const initData = () => {
|
||||
|
@ -738,6 +738,7 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
store.removeMessageHandler("mj")
|
||||
})
|
||||
|
||||
// 初始化数据
|
||||
|
@ -586,6 +586,7 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
store.removeMessageHandler("sd")
|
||||
})
|
||||
|
||||
|
||||
|
@ -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}`
|
||||
|
@ -386,6 +386,7 @@ onMounted(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
store.removeMessageHandler("suno")
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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']]);
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user