forked from Leaf/amber-ui
改进 项目结构
This commit is contained in:
parent
48196a4942
commit
915f8ff59a
@ -335,7 +335,6 @@ definitions:
|
|||||||
maxLength: 30
|
maxLength: 30
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- assistant_id
|
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
schema.ChatMessageAddRequest:
|
schema.ChatMessageAddRequest:
|
||||||
|
@ -11,15 +11,14 @@
|
|||||||
"gen": "openapi-generator-cli generate -i ./api/swagger.yaml -g typescript-axios -o ./src/api"
|
"gen": "openapi-generator-cli generate -i ./api/swagger.yaml -g typescript-axios -o ./src/api"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@f3ve/vue-markdown-it": "^0.2.2",
|
|
||||||
"@kangc/v-md-editor": "^2.3.18",
|
"@kangc/v-md-editor": "^2.3.18",
|
||||||
"@traptitech/markdown-it-katex": "^3.6.0",
|
"@traptitech/markdown-it-katex": "^3.6.0",
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"katex": "^0.16.11",
|
"katex": "^0.16.11",
|
||||||
"lottie-web": "^5.12.2",
|
"lottie-web": "^5.12.2",
|
||||||
"markdown-it-latex2img": "^0.0.6",
|
|
||||||
"pinia": "^2.2.2",
|
"pinia": "^2.2.2",
|
||||||
"pinia-plugin-persistedstate": "^4.0.0",
|
"pinia-plugin-persistedstate": "^4.0.0",
|
||||||
"vooks": "^0.2.12",
|
"vooks": "^0.2.12",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
:locale="zhCN"
|
:locale="zhCN"
|
||||||
:theme="theme"
|
:theme="theme"
|
||||||
:theme-overrides="themeOverrides"
|
:theme-overrides="themeOverrides"
|
||||||
preflight-style-disabled
|
|
||||||
>
|
>
|
||||||
<n-global-style />
|
<n-global-style />
|
||||||
<n-loading-bar-provider>
|
<n-loading-bar-provider>
|
||||||
|
@ -1399,7 +1399,7 @@ export interface SchemaChatCreateRequest {
|
|||||||
* @type {number}
|
* @type {number}
|
||||||
* @memberof SchemaChatCreateRequest
|
* @memberof SchemaChatCreateRequest
|
||||||
*/
|
*/
|
||||||
'assistant_id': number;
|
'assistant_id'?: number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {SchemaCustomTime}
|
* @type {SchemaCustomTime}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
// import router from '../plugins/router';
|
|
||||||
import Header from "../layouts/Header.vue";
|
|
||||||
import { useUserStore } from "../stores/user";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
const route = useRoute();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
// const currentRoute = computed(() => router.currentRoute.value.name)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Header
|
|
||||||
v-show="userStore.logined"
|
|
||||||
style="min-height: var(--header-height)"
|
|
||||||
></Header>
|
|
||||||
|
|
||||||
<div class="p-4">
|
|
||||||
<n-scrollbar >
|
|
||||||
<router-view v-slot="{ Component }" :key="route.path">
|
|
||||||
<transition mode="out-in" name="fade">
|
|
||||||
<div>
|
|
||||||
<component :is="Component" />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</router-view>
|
|
||||||
</n-scrollbar>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <router-view></router-view> -->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
@ -1,18 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-tabs type="segment" animated class="select-none">
|
<n-tabs type="segment" animated class="select-none">
|
||||||
<n-tab-pane name="chap1" tab="对话">
|
<n-tab-pane name="chap1" tab="对话">
|
||||||
<n-list hoverable clickable>
|
<n-list hoverable clickable v-if="chatStore.chats?.length">
|
||||||
<n-list-item
|
<n-list-item
|
||||||
v-for="c in chatStore.chats"
|
v-for="c in chatStore.chats"
|
||||||
:key="c.id"
|
:key="c.id"
|
||||||
:class="
|
:class="
|
||||||
c.id === chatStore.currentChatId ? ' bg-gray-100' : 'text-red-50'
|
c.id === chatStore.currentChatId
|
||||||
|
? ' bg-gray-100 dark:bg-gray-700'
|
||||||
|
: ''
|
||||||
"
|
"
|
||||||
@click="viewChat(c.id ?? 0)"
|
@click="viewChat(c.id ?? 0)"
|
||||||
>
|
>
|
||||||
<n-thing> {{ c.name }} </n-thing>
|
<n-thing> {{ c.name }} </n-thing>
|
||||||
</n-list-item>
|
</n-list-item>
|
||||||
</n-list>
|
</n-list>
|
||||||
|
<div v-else>
|
||||||
|
<n-result
|
||||||
|
status="404"
|
||||||
|
title="你还没有对话"
|
||||||
|
description="不如现在就开始?"
|
||||||
|
>
|
||||||
|
</n-result>
|
||||||
|
</div>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="chap2" tab="助理">
|
<n-tab-pane name="chap2" tab="助理">
|
||||||
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
@ -20,6 +30,141 @@
|
|||||||
我的脑海中忽然出现了 Amazon
|
我的脑海中忽然出现了 Amazon
|
||||||
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
debug 这个线上问题。
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-base">
|
<div class="text-base">
|
||||||
<n-divider> <small>这是聊天的开头</small> </n-divider>
|
<n-divider> <small class="select-none">这是聊天的开头</small> </n-divider>
|
||||||
|
|
||||||
<div v-for="(message, index) in chat_messages" :key="index">
|
<div v-for="(message, index) in chat_messages" :key="index">
|
||||||
<div v-if="message.role === 'user' && message.content">
|
<div v-if="message.role === 'user' && message.content">
|
||||||
@ -18,12 +18,15 @@
|
|||||||
<n-avatar round size="large" :src="userStore.user.avatar" />
|
<n-avatar round size="large" :src="userStore.user.avatar" />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="message.role === 'assistant' && message.content" class="mt-3">
|
<div
|
||||||
|
v-else-if="message.role === 'assistant' && message.content"
|
||||||
|
class="mt-10 mb-10"
|
||||||
|
>
|
||||||
<!-- 助理消息 -->
|
<!-- 助理消息 -->
|
||||||
<n-flex justify="start">
|
<n-flex justify="start">
|
||||||
<n-avatar round size="large" :src="leaflowPng" />
|
<n-avatar round size="large" :src="leaflowPng" />
|
||||||
|
|
||||||
<div class="align-middle">
|
<div class="flex items-center">
|
||||||
<!-- <vue-markdown-it
|
<!-- <vue-markdown-it
|
||||||
:source="message.content"
|
:source="message.content"
|
||||||
:options="markdownOptions"
|
:options="markdownOptions"
|
||||||
@ -51,12 +54,9 @@ import { useUserStore } from "@/stores/user";
|
|||||||
import leaflowPng from "@/assets/images/leaflow.png";
|
import leaflowPng from "@/assets/images/leaflow.png";
|
||||||
import markdownKatex from "@traptitech/markdown-it-katex";
|
import markdownKatex from "@traptitech/markdown-it-katex";
|
||||||
import markdownIt from "markdown-it";
|
import markdownIt from "markdown-it";
|
||||||
import markdownLatex from "markdown-it-latex2img";
|
|
||||||
// highlightjs
|
// highlightjs
|
||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
|
|
||||||
import { VueMarkdownIt } from "@f3ve/vue-markdown-it";
|
|
||||||
|
|
||||||
const mdIt = markdownIt();
|
const mdIt = markdownIt();
|
||||||
|
|
||||||
// set options
|
// set options
|
||||||
@ -73,15 +73,6 @@ mdIt.use(markdownKatex, {
|
|||||||
output: "html",
|
output: "html",
|
||||||
});
|
});
|
||||||
|
|
||||||
mdIt.use(markdownKatex)
|
|
||||||
mdIt.use(markdownLatex);
|
|
||||||
|
|
||||||
const markdownOptions = {
|
|
||||||
html: true,
|
|
||||||
linkify: true,
|
|
||||||
};
|
|
||||||
const markdownPlugins = [markdownKatex];
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="relative flex flex-col lg:items-center">
|
||||||
class="relative flex flex-col h-[calc(100vh-var(--header-height)*1.9)] lg:items-center"
|
|
||||||
>
|
|
||||||
<div class="w-4/5">
|
<div class="w-4/5">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex-grow mt-3 mb-1 text-5xl" v-if="!chatMessages?.length">
|
<div
|
||||||
|
class="flex-grow mt-3 mb-1 text-5xl select-none"
|
||||||
|
v-if="!chatMessages?.length"
|
||||||
|
>
|
||||||
<n-gradient-text type="info" class="pr-3 pb-2 pt-2">
|
<n-gradient-text type="info" class="pr-3 pb-2 pt-2">
|
||||||
你好,{{ userStore.user.name }}
|
你好,{{ userStore.user.name }}
|
||||||
</n-gradient-text>
|
</n-gradient-text>
|
||||||
@ -20,6 +21,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fixed bottom-0 left-0 right-0 pb-10">
|
<div class="fixed bottom-0 left-0 right-0 pb-10">
|
||||||
|
<div
|
||||||
|
class="mx-auto w-2xl max-w-2xl text-center mb-3 animate__animated animate__pulse text-lg"
|
||||||
|
v-if="toolCalling"
|
||||||
|
>
|
||||||
|
<n-gradient-text type="info">
|
||||||
|
{{ toolName }}
|
||||||
|
</n-gradient-text>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="inputContainer"
|
ref="inputContainer"
|
||||||
class="mx-auto w-2xl max-w-2xl outline-none input-color input-bg rounded-full flex pl-5 pr-5 bg-white shadow-lg items-center p-4 pb-4 transition-all"
|
class="mx-auto w-2xl max-w-2xl outline-none input-color input-bg rounded-full flex pl-5 pr-5 bg-white shadow-lg items-center p-4 pb-4 transition-all"
|
||||||
@ -39,43 +48,86 @@
|
|||||||
></div>
|
></div>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<n-spin :show="processing">
|
||||||
ref="actionContainer"
|
<div
|
||||||
class="flex [&>button]:ml-2 pr-4 justify-end"
|
ref="actionContainer"
|
||||||
>
|
class="flex [&>button]:ml-2 pr-4 justify-end"
|
||||||
<n-button tertiary circle size="large">
|
>
|
||||||
<template #icon>
|
<n-tooltip trigger="hover">
|
||||||
<n-icon><DocumentAttachOutline /></n-icon>
|
<template #trigger>
|
||||||
</template>
|
<n-button tertiary circle size="large">
|
||||||
</n-button>
|
<template #icon>
|
||||||
<n-button tertiary circle size="large">
|
<n-icon><DocumentAttachOutline /></n-icon>
|
||||||
<template #icon>
|
</template>
|
||||||
<n-icon><MicOutline /></n-icon>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-button>
|
<span> 在做了在做了 </span>
|
||||||
<n-button tertiary circle size="large" v-show="showSendBtn">
|
</n-tooltip>
|
||||||
<template #icon>
|
|
||||||
<n-icon><SendOutline /></n-icon>
|
<n-tooltip trigger="hover">
|
||||||
</template>
|
<template #trigger>
|
||||||
</n-button>
|
<n-button tertiary circle size="large">
|
||||||
</div>
|
<template #icon>
|
||||||
|
<n-icon><MicOutline /></n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<span> 在做了在做了 </span>
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button
|
||||||
|
tertiary
|
||||||
|
circle
|
||||||
|
size="large"
|
||||||
|
v-show="chatMessages?.length"
|
||||||
|
@click="clearChatHistory"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><TrashBinOutline /></n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<span> 清空历史 </span>
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button
|
||||||
|
tertiary
|
||||||
|
circle
|
||||||
|
size="large"
|
||||||
|
v-show="showSendBtn"
|
||||||
|
@click="sendText"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><SendOutline /></n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<span> 发送 </span>
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
|
</n-spin>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMessage } from "naive-ui";
|
|
||||||
import { useUserStore } from "../../stores/user";
|
import { useUserStore } from "../../stores/user";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import {
|
import {
|
||||||
SendOutline,
|
SendOutline,
|
||||||
MicOutline,
|
MicOutline,
|
||||||
DocumentAttachOutline,
|
DocumentAttachOutline,
|
||||||
|
TrashBinOutline,
|
||||||
} from "@vicons/ionicons5";
|
} from "@vicons/ionicons5";
|
||||||
import { EntityChatMessage } from "@/api";
|
import { EntityChatMessage, SchemaChatMessageAddRequestRoleEnum } from "@/api";
|
||||||
import getApi from "@/plugins/api";
|
import getApi from "@/plugins/api";
|
||||||
import MessageList from "./MessageList.vue";
|
import MessageList from "./MessageList.vue";
|
||||||
|
import { useChatStore } from "@/stores/chat";
|
||||||
|
|
||||||
// 获取组件传入的 chatId
|
// 获取组件传入的 chatId
|
||||||
const chatId: Ref<string | number | undefined | null> = ref(null);
|
const chatId: Ref<string | number | undefined | null> = ref(null);
|
||||||
@ -93,11 +145,8 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
chatId.value = props.chatId;
|
|
||||||
});
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const chatStore = useChatStore();
|
||||||
const compositionStart = ref(false);
|
const compositionStart = ref(false);
|
||||||
const inputContainer: any = ref(null);
|
const inputContainer: any = ref(null);
|
||||||
const inputText: any = ref(null);
|
const inputText: any = ref(null);
|
||||||
@ -108,6 +157,13 @@ const showSendBtn = ref(false);
|
|||||||
const content = ref("");
|
const content = ref("");
|
||||||
const inputExpanded = ref(false);
|
const inputExpanded = ref(false);
|
||||||
const chatMessages: Ref<EntityChatMessage[] | undefined> = ref([]);
|
const chatMessages: Ref<EntityChatMessage[] | undefined> = ref([]);
|
||||||
|
const processing = ref(false);
|
||||||
|
const toolName = ref("");
|
||||||
|
const toolError = ref(false);
|
||||||
|
const toolCalling = ref(false);
|
||||||
|
const fileUpload = ref();
|
||||||
|
const uploading = ref(false);
|
||||||
|
const autoScroll = ref(true);
|
||||||
|
|
||||||
function onKeydown(e: KeyboardEvent) {
|
function onKeydown(e: KeyboardEvent) {
|
||||||
// 带 shift 不触发
|
// 带 shift 不触发
|
||||||
@ -215,24 +271,79 @@ function sendText() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 发送文本到服务器
|
// 发送文本到服务器
|
||||||
sendMessage(textContent);
|
sendMessage("user", textContent);
|
||||||
|
|
||||||
// 清空输入框
|
// 清空输入框
|
||||||
input.innerText = "";
|
input.innerText = "";
|
||||||
|
|
||||||
updateInputHeight();
|
updateInputHeight();
|
||||||
|
|
||||||
chatMessages.value?.push({
|
|
||||||
content: textContent,
|
|
||||||
role: "user",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendMessage(text: string) {
|
async function sendMessage(
|
||||||
console.log("发送文本:", text);
|
role: SchemaChatMessageAddRequestRoleEnum,
|
||||||
|
text: string
|
||||||
|
) {
|
||||||
|
if (processing.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (role.trim() === "") {
|
||||||
|
role = "user";
|
||||||
|
}
|
||||||
|
if (text.trim() === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
chatMessages.value?.push({ content: text, role: "user" });
|
if (!chatId.value) {
|
||||||
// 实际发送文本到服务器的逻辑
|
getApi()
|
||||||
|
.Chat.apiV1ChatsPost({
|
||||||
|
name: text.slice(0, 10),
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
chatId.value = res.data.data?.id;
|
||||||
|
await getChatMessages();
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toolError.value = false;
|
||||||
|
getApi()
|
||||||
|
.ChatMessage.apiV1ChatsIdMessagesPost(Number(chatId.value), {
|
||||||
|
message: text,
|
||||||
|
role: role,
|
||||||
|
})
|
||||||
|
.then(async (res) => {
|
||||||
|
// const newMessage = {
|
||||||
|
// content: text,
|
||||||
|
// role: role,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// if (chatMessages.value) {
|
||||||
|
// chatMessages.value = [newMessage];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (chatMessages.value && chatMessages.value.length) {
|
||||||
|
// chatMessages.value?.push(newMessage);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const streamId = res.data.data?.stream_id;
|
||||||
|
|
||||||
|
if (streamId) {
|
||||||
|
await getChatMessages();
|
||||||
|
streamChat(streamId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(async (err) => {
|
||||||
|
// if 409
|
||||||
|
if (err.response.status === 409) {
|
||||||
|
const streamId = err.response.data.data?.stream_id;
|
||||||
|
|
||||||
|
if (streamId) {
|
||||||
|
await getChatMessages();
|
||||||
|
streamChat(streamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFocused() {
|
function onFocused() {
|
||||||
@ -263,13 +374,112 @@ async function getChatMessages() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
chatMessages.value = cm.data.data;
|
chatMessages.value = cm.data.data;
|
||||||
|
|
||||||
|
// 完成
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function streamChat(streamId: String) {
|
||||||
|
const url = getApi().conf.basePath + "/api/v1/stream/" + streamId;
|
||||||
|
|
||||||
|
const evtSource = new EventSource(url);
|
||||||
|
|
||||||
|
let messageAdded = false;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
processing.value = true;
|
||||||
|
|
||||||
|
evtSource.addEventListener("data", (e) => {
|
||||||
|
if (e.data === "[DONE]") {
|
||||||
|
evtSource.close();
|
||||||
|
processing.value = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
|
||||||
|
let append = true;
|
||||||
|
|
||||||
|
switch (data.state) {
|
||||||
|
case "tool_calling":
|
||||||
|
toolCalling.value = true;
|
||||||
|
toolName.value =
|
||||||
|
data.tool_call_message.tool_name +
|
||||||
|
" 中的 " +
|
||||||
|
data.tool_call_message.function_name;
|
||||||
|
break;
|
||||||
|
case "tool_response":
|
||||||
|
setTimeout(() => {
|
||||||
|
toolName.value = "";
|
||||||
|
toolCalling.value = false;
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
case "tool_failed":
|
||||||
|
toolName.value =
|
||||||
|
data.tool_response_message.tool_name +
|
||||||
|
" 中的 " +
|
||||||
|
data.tool_response_message.function_name;
|
||||||
|
toolError.value = true;
|
||||||
|
append = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
toolCalling.value = false;
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
case "chunk":
|
||||||
|
if (!messageAdded) {
|
||||||
|
const newMessage = {
|
||||||
|
content: "",
|
||||||
|
role: "assistant",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!chatMessages.value) {
|
||||||
|
chatMessages.value = [newMessage];
|
||||||
|
} else {
|
||||||
|
chatMessages.value?.push(newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageAdded = true;
|
||||||
|
append = true;
|
||||||
|
i = (chatMessages.value?.length ?? 1) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoScroll.value) {
|
||||||
|
// 滚动到
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (append && messageAdded && chatMessages.value?.length) {
|
||||||
|
chatMessages.value[i].content += data.content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// close
|
||||||
|
evtSource.addEventListener("close", () => {
|
||||||
|
evtSource.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearChatHistory = async () => {
|
||||||
|
processing.value = true;
|
||||||
|
await getApi().ChatMessage.apiV1ChatsIdClearPost(chatStore.currentChatId);
|
||||||
|
chatMessages.value = [];
|
||||||
|
processing.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
chatId.value = props.chatId;
|
||||||
|
chatStore.currentChatId = Number(chatId.value);
|
||||||
updateInputHeight();
|
updateInputHeight();
|
||||||
getChatMessages();
|
getChatMessages();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
chatStore.currentChatId = 0;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -10,7 +10,7 @@ import Header from "./Header.vue";
|
|||||||
const currentRoute = computed(() => router.currentRoute.value.name);
|
const currentRoute = computed(() => router.currentRoute.value.name);
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const route = useRoute();
|
||||||
// import Header from './Header.vue'
|
// import Header from './Header.vue'
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
// const isTablet = useIsTablet()
|
// const isTablet = useIsTablet()
|
||||||
@ -20,7 +20,22 @@ const menuCollapsed = ref({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-layout position="absolute" :has-sider="false">
|
<Header
|
||||||
|
v-show="userStore.logined"
|
||||||
|
style="
|
||||||
|
min-height: var(--header-height);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
"
|
||||||
|
></Header>
|
||||||
|
|
||||||
|
<n-layout
|
||||||
|
:native-scrollbar="false"
|
||||||
|
position="absolute"
|
||||||
|
style="margin-top: var(--header-height)"
|
||||||
|
>
|
||||||
<!-- <n-layout-sider
|
<!-- <n-layout-sider
|
||||||
v-if="userStore.logined && !isMobile"
|
v-if="userStore.logined && !isMobile"
|
||||||
:collapsed-width="0"
|
:collapsed-width="0"
|
||||||
@ -36,11 +51,25 @@ const menuCollapsed = ref({
|
|||||||
>
|
>
|
||||||
<Menu v-show="!isMobile"></Menu>
|
<Menu v-show="!isMobile"></Menu>
|
||||||
</n-layout-sider> -->
|
</n-layout-sider> -->
|
||||||
<n-layout-content :native-scrollbar="false">
|
|
||||||
|
<n-layout :native-scrollbar="false">
|
||||||
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
|
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
|
||||||
<Container v-else /> -->
|
<Container v-else /> -->
|
||||||
<Guest v-if="!userStore.logined && !currentRoute?.startsWith('/auth')" />
|
<Guest v-if="!userStore.logined && !currentRoute?.startsWith('/auth')" />
|
||||||
<Container v-else />
|
<div v-else>
|
||||||
</n-layout-content>
|
<!-- <div style="height: calc(var(--header-height)*2)"></div> -->
|
||||||
|
|
||||||
|
<router-view :key="route.path"> </router-view>
|
||||||
|
|
||||||
|
<!-- <Container /> -->
|
||||||
|
<!-- <div class="p-4 pt-0 pb-0 mb-0 h-screen">
|
||||||
|
<router-view v-slot="{ Component }" :key="route.path">
|
||||||
|
<transition mode="out-in" name="fade">
|
||||||
|
<component :is="Component" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</n-layout>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
</template>
|
</template>
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
resizable
|
resizable
|
||||||
:default-width="width"
|
:default-width="width"
|
||||||
placement="left"
|
placement="left"
|
||||||
|
class="select-none"
|
||||||
>
|
>
|
||||||
<n-drawer-content title="有什么我可以帮您的吗" closable>
|
<n-drawer-content title="有什么我可以帮您的吗" closable :native-scrollbar="false">
|
||||||
<LeftSettings></LeftSettings>
|
<LeftSettings></LeftSettings>
|
||||||
</n-drawer-content>
|
</n-drawer-content>
|
||||||
</n-drawer>
|
</n-drawer>
|
||||||
@ -44,9 +45,15 @@
|
|||||||
<n-grid-item class="flex items-center justify-end mr-1.5">
|
<n-grid-item class="flex items-center justify-end mr-1.5">
|
||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<!-- 新对话 -->
|
<!-- 新对话 -->
|
||||||
<n-icon class="text-2xl mr-4 cursor-pointer" @click="backToHome">
|
<n-tooltip trigger="hover">
|
||||||
<AddOutline />
|
<template #trigger>
|
||||||
</n-icon>
|
<n-icon class="text-2xl mr-4 cursor-pointer" @click="backToHome">
|
||||||
|
<AddOutline />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
<span> 新对话 </span>
|
||||||
|
</n-tooltip>
|
||||||
|
|
||||||
<!-- 助理选择 -->
|
<!-- 助理选择 -->
|
||||||
<n-popover
|
<n-popover
|
||||||
:placement="userPlacement"
|
:placement="userPlacement"
|
||||||
@ -73,7 +80,7 @@
|
|||||||
round
|
round
|
||||||
size="medium"
|
size="medium"
|
||||||
:src="userStore.user.avatar"
|
:src="userStore.user.avatar"
|
||||||
class="mr-2"
|
class="mr-2 cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<UserMenu class="select-none" />
|
<UserMenu class="select-none" />
|
||||||
@ -89,17 +96,24 @@ import UserMenu from "../components/UserMenu.vue";
|
|||||||
import AssistantMenu from "../components/AssistantMenu.vue";
|
import AssistantMenu from "../components/AssistantMenu.vue";
|
||||||
import { useAppStore } from "../stores/app";
|
import { useAppStore } from "../stores/app";
|
||||||
import { useUserStore } from "../stores/user";
|
import { useUserStore } from "../stores/user";
|
||||||
import { useIsMobile, useIsTablet } from "../utils/composables";
|
import { useIsMobile } from "../utils/composables";
|
||||||
import { MenuOutline, PersonOutline, AddOutline } from "@vicons/ionicons5";
|
import {
|
||||||
|
MenuOutline,
|
||||||
|
PersonOutline,
|
||||||
|
AddOutline,
|
||||||
|
TrashOutline,
|
||||||
|
} from "@vicons/ionicons5";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
import { useChatStore } from "@/stores/chat";
|
||||||
|
import getApi from "@/plugins/api";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const isTablet = useIsTablet();
|
// const isTablet = useIsTablet();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const showDrawer = ref(false);
|
const showDrawer = ref(false);
|
||||||
const width = ref(200);
|
const width = ref(200);
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
// 如果是手机,则 width 为全屏
|
// 如果是手机,则 width 为全屏
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
@ -115,9 +129,11 @@ if (isMobile.value) {
|
|||||||
userPlacement.value = "bottom";
|
userPlacement.value = "bottom";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearChatHistory = async () => {
|
||||||
|
await getApi().ChatMessage.apiV1ChatsIdClearPost(chatStore.currentChatId);
|
||||||
|
};
|
||||||
|
|
||||||
const backToHome = () => {
|
const backToHome = () => {
|
||||||
router.push("/");
|
router.push("/");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,6 +3,7 @@ meta.name = "naive-ui-style";
|
|||||||
document.head.appendChild(meta);
|
document.head.appendChild(meta);
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
import "animate.css";
|
||||||
import { registerPlugins } from "./plugins";
|
import { registerPlugins } from "./plugins";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
|
||||||
|
@ -6,6 +6,20 @@
|
|||||||
--header-height: 48px;
|
--header-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.header-height {
|
.header-height {
|
||||||
min-height: var(--header-height);
|
min-height: var(--header-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user