1
0
forked from Leaf/amber-ui

改进 项目结构

This commit is contained in:
ivamp 2024-09-12 21:50:39 +08:00
parent 48196a4942
commit 915f8ff59a
12 changed files with 481 additions and 111 deletions

View File

@ -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:

View File

@ -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",

View File

@ -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>

View File

@ -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}

View File

@ -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>

View File

@ -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>

View File

@ -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({

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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";

View File

@ -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;
}