改进 聊天和左侧菜单
This commit is contained in:
parent
92da9181e1
commit
086dd9979a
@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
out
|
|
||||||
.gitignore
|
|
@ -1,17 +1,16 @@
|
|||||||
/* eslint-env node */
|
/* eslint-env node */
|
||||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
require("@rushstack/eslint-patch/modern-module-resolution");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
"eslint:recommended",
|
||||||
'plugin:vue/vue3-recommended',
|
"plugin:vue/vue3-recommended",
|
||||||
'@electron-toolkit',
|
"@vue/eslint-config-typescript/recommended",
|
||||||
'@electron-toolkit/eslint-config-ts/eslint-recommended',
|
"@vue/eslint-config-prettier",
|
||||||
'@vue/eslint-config-typescript/recommended',
|
|
||||||
'@vue/eslint-config-prettier'
|
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/require-default-prop': 'off',
|
"vue/require-default-prop": "off",
|
||||||
'vue/multi-word-component-names': 'off'
|
"vue/multi-word-component-names": "off",
|
||||||
}
|
},
|
||||||
}
|
ignores: ["src/typed-router.d.ts", "node_modules", "dist", "out", ".gitignore"],
|
||||||
|
};
|
||||||
|
12
package.json
12
package.json
@ -31,11 +31,17 @@
|
|||||||
"@vue/eslint-config-prettier": "^9.0.0",
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.10.0",
|
||||||
"eslint-plugin-vue": "^9.26.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-config-standard": "^17.1.0",
|
||||||
|
"eslint-plugin-import": "^2.30.0",
|
||||||
|
"eslint-plugin-n": "^17.10.2",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-promise": "^7.1.0",
|
||||||
|
"eslint-plugin-vue": "^9.28.0",
|
||||||
"naive-ui": "^2.39.0",
|
"naive-ui": "^2.39.0",
|
||||||
"postcss": "^8.4.45",
|
"postcss": "^8.4.45",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.3",
|
||||||
"tailwindcss": "^3.4.10",
|
"tailwindcss": "^3.4.10",
|
||||||
"typescript": "^5.5.2",
|
"typescript": "^5.5.2",
|
||||||
"unplugin-auto-import": "^0.18.2",
|
"unplugin-auto-import": "^0.18.2",
|
||||||
|
2
src/components.d.ts
vendored
2
src/components.d.ts
vendored
@ -12,6 +12,8 @@ declare module 'vue' {
|
|||||||
ChatMenu: typeof import('./components/ChatMenu.vue')['default']
|
ChatMenu: typeof import('./components/ChatMenu.vue')['default']
|
||||||
Container: typeof import('./components/Container.vue')['default']
|
Container: typeof import('./components/Container.vue')['default']
|
||||||
copy: typeof import('./components/AssistantMenu.vue')['default']
|
copy: typeof import('./components/AssistantMenu.vue')['default']
|
||||||
|
LeftSetting: typeof import('./components/LeftSetting.vue')['default']
|
||||||
|
LeftSettings: typeof import('./components/LeftSettings.vue')['default']
|
||||||
Menu: typeof import('./components/Menu.vue')['default']
|
Menu: typeof import('./components/Menu.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-list hoverable clickable>
|
<div v-show="!loaded">正在载入</div>
|
||||||
|
<n-list hoverable clickable v-show="loaded" class="select-none">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xl">切换助理</span>
|
<span class="text-xl">切换助理</span>
|
||||||
@ -14,14 +15,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import { useUserStore } from "../stores/user";
|
import { useUserStore } from "../stores/user";
|
||||||
import { updateAll } from "../plugins/update/update";
|
import { updateAll } from "../plugins/update/update";
|
||||||
import { useAssistantStore } from "../stores/assistants";
|
import { useAssistantStore } from "../stores/assistants";
|
||||||
|
|
||||||
const route = useRoute();
|
const loaded = ref(false);
|
||||||
const currentRoute: any = computed(() => route.name);
|
|
||||||
const collapsed = ref(false);
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const assistantStore = useAssistantStore();
|
const assistantStore = useAssistantStore();
|
||||||
|
|
||||||
@ -35,6 +33,7 @@ watch(
|
|||||||
);
|
);
|
||||||
function update() {
|
function update() {
|
||||||
updateAll();
|
updateAll();
|
||||||
|
loaded.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
// import router from '../plugins/router';
|
// import router from '../plugins/router';
|
||||||
import Header from "../layouts/Header.vue";
|
import Header from "../layouts/Header.vue";
|
||||||
import { useUserStore } from "../stores/user";
|
import { useUserStore } from "../stores/user";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
const route = useRoute();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
// const currentRoute = computed(() => router.currentRoute.value.name)
|
// const currentRoute = computed(() => router.currentRoute.value.name)
|
||||||
</script>
|
</script>
|
||||||
@ -14,7 +15,7 @@ const userStore = useUserStore();
|
|||||||
></Header>
|
></Header>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }" :key="route.path">
|
||||||
<transition mode="out-in" name="fade">
|
<transition mode="out-in" name="fade">
|
||||||
<div>
|
<div>
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
|
53
src/components/LeftSettings.vue
Normal file
53
src/components/LeftSettings.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<n-tabs type="segment" animated>
|
||||||
|
<n-tab-pane name="chap1" tab="对话">
|
||||||
|
<n-list hoverable clickable>
|
||||||
|
<n-list-item
|
||||||
|
v-for="c in chatStore.chats"
|
||||||
|
:key="c.id"
|
||||||
|
:class="c.id == chatStore.currentChatId ? 'text-primary' : ''"
|
||||||
|
@click="viewChat(c.id ?? 0)"
|
||||||
|
>
|
||||||
|
<n-thing> {{ c.name }} </n-thing>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="chap2" tab="助理">
|
||||||
|
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的
|
||||||
|
Amazon 服务,还是救公寓的火。<br /><br />
|
||||||
|
我的脑海中忽然出现了 Amazon
|
||||||
|
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
|
debug 这个线上问题。
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NMenu } from "naive-ui";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import { leftMenuOptions } from "../plugins/menus/left";
|
||||||
|
import { ChatboxOutline } from "@vicons/ionicons5";
|
||||||
|
import getApi from "../plugins/api";
|
||||||
|
import { useChatStore } from "../stores/chat";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
// @ts-ignore
|
||||||
|
const chatId = route.params.id as number;
|
||||||
|
|
||||||
|
const currentRoute: any = computed(() => route.name);
|
||||||
|
|
||||||
|
const collapsed = ref(false);
|
||||||
|
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
|
async function getChats() {
|
||||||
|
chatStore.chats = (await getApi().Chat.apiV1ChatsGet()).data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewChat = (chatId: number) => {
|
||||||
|
router.push("/chat/" + chatId);
|
||||||
|
};
|
||||||
|
|
||||||
|
getChats();
|
||||||
|
</script>
|
@ -13,8 +13,8 @@
|
|||||||
<span class="font-xl ml-2">对话列表</span>
|
<span class="font-xl ml-2">对话列表</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<n-list-item v-for="i in 10" :key="i">
|
<n-list-item v-for="c in chatStore.chats" :key="c.id">
|
||||||
<n-thing> 对话 </n-thing>
|
<n-thing> {{ c.name }} </n-thing>
|
||||||
</n-list-item>
|
</n-list-item>
|
||||||
</n-list>
|
</n-list>
|
||||||
</template>
|
</template>
|
||||||
@ -24,10 +24,20 @@ import { NMenu } from "naive-ui";
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { leftMenuOptions } from "../plugins/menus/left";
|
import { leftMenuOptions } from "../plugins/menus/left";
|
||||||
import { ChatboxOutline } from "@vicons/ionicons5";
|
import { ChatboxOutline } from "@vicons/ionicons5";
|
||||||
|
import getApi from "../plugins/api";
|
||||||
|
import { useChatStore } from "../stores/chat";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const currentRoute: any = computed(() => route.name);
|
const currentRoute: any = computed(() => route.name);
|
||||||
|
|
||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
|
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
|
async function getChats() {
|
||||||
|
chatStore.chats = (await getApi().Chat.apiV1ChatsGet()).data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChats();
|
||||||
</script>
|
</script>
|
||||||
|
@ -20,7 +20,7 @@ const menuCollapsed = ref({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-layout position="absolute" :has-sider="true">
|
<n-layout position="absolute" :has-sider="true">
|
||||||
<n-layout-sider
|
<!-- <n-layout-sider
|
||||||
v-if="userStore.logined && !isMobile"
|
v-if="userStore.logined && !isMobile"
|
||||||
:collapsed-width="0"
|
:collapsed-width="0"
|
||||||
:native-scrollbar="false"
|
:native-scrollbar="false"
|
||||||
@ -34,7 +34,7 @@ const menuCollapsed = ref({
|
|||||||
@expand="menuCollapsed.left = false"
|
@expand="menuCollapsed.left = false"
|
||||||
>
|
>
|
||||||
<Menu v-show="!isMobile"></Menu>
|
<Menu v-show="!isMobile"></Menu>
|
||||||
</n-layout-sider>
|
</n-layout-sider> -->
|
||||||
<n-layout-content>
|
<n-layout-content>
|
||||||
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
|
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
|
||||||
<Container v-else /> -->
|
<Container v-else /> -->
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<n-grid cols="2" class="header-height">
|
<n-grid cols="2" class="header-height">
|
||||||
<n-grid-item class="flex items-center justify-start mr-1.5">
|
<n-grid-item class="flex items-center justify-start mr-1.5">
|
||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<n-popover
|
<!-- <n-popover
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
style="padding: 0; width: 288px"
|
style="padding: 0; width: 288px"
|
||||||
@ -16,7 +16,25 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
<Menu></Menu>
|
<Menu></Menu>
|
||||||
</n-popover>
|
</n-popover> -->
|
||||||
|
|
||||||
|
<n-drawer
|
||||||
|
v-model:show="showDrawer"
|
||||||
|
resizable
|
||||||
|
:default-width="width"
|
||||||
|
placement="left"
|
||||||
|
>
|
||||||
|
<n-drawer-content>
|
||||||
|
<LeftSettings></LeftSettings>
|
||||||
|
</n-drawer-content>
|
||||||
|
</n-drawer>
|
||||||
|
<n-icon
|
||||||
|
size="20"
|
||||||
|
style="margin-left: 12px"
|
||||||
|
@click="showDrawer = true"
|
||||||
|
>
|
||||||
|
<menu-outline />
|
||||||
|
</n-icon>
|
||||||
|
|
||||||
<!-- 更新状态 -->
|
<!-- 更新状态 -->
|
||||||
<div v-show="appStore.updating">正在更新数据</div>
|
<div v-show="appStore.updating">正在更新数据</div>
|
||||||
@ -72,6 +90,16 @@ 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 width = ref(200);
|
||||||
|
|
||||||
|
// 如果是手机,则 width 为全屏
|
||||||
|
if (isMobile.value) {
|
||||||
|
// 获取屏幕宽度
|
||||||
|
width.value = window.innerWidth - 100;
|
||||||
|
} else {
|
||||||
|
width.value = "40%";
|
||||||
|
}
|
||||||
|
|
||||||
const userPlacement = ref("bottom");
|
const userPlacement = ref("bottom");
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
|
322
src/pages/chat/[id]/index copy.vue
Normal file
322
src/pages/chat/[id]/index copy.vue
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3 class="mb-3">聊天记录</h3>
|
||||||
|
|
||||||
|
<v-card
|
||||||
|
v-for="message in messages.data"
|
||||||
|
:key="message.id"
|
||||||
|
class="mx-auto mt-3"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="font-weight-black">
|
||||||
|
<div v-if="message.role == 'assistant'">AI</div>
|
||||||
|
<div v-else-if="message.role == 'system'">系统</div>
|
||||||
|
|
||||||
|
<div v-else-if="message.role == 'file'" class="text-right">文件</div>
|
||||||
|
<div v-else-if="message.role == 'image'" class="text-right">图片</div>
|
||||||
|
<div v-else-if="message.role == 'user'" class="text-right">用户</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
{{ message.role }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card-text class="bg-surface-light pt-4">
|
||||||
|
<div
|
||||||
|
v-if="message.role == 'assistant' || message.role == 'system'"
|
||||||
|
class="text-left"
|
||||||
|
>
|
||||||
|
<vue-markdown :source="message.content" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="message.role == 'user'" class="text-right">
|
||||||
|
<vue-markdown :source="message.content" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="message.role == 'image'" class="text-right">
|
||||||
|
<img
|
||||||
|
:src="fileBaseUrl + '/' + message.content + '/download'"
|
||||||
|
width="30%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="message.role == 'file'" class="text-right">
|
||||||
|
其他文件:<a :href="fileBaseUrl + '/' + message.content + '/download'"
|
||||||
|
>下载</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ message.content }}
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<div v-if="toolError" class="mb-3">
|
||||||
|
<v-alert
|
||||||
|
density="compact"
|
||||||
|
text="这个工具出现了异常,这应该不是我们的问题,如果你是此工具的开发者,请打开开发者控制台查看具体错误。"
|
||||||
|
:title="'工具 ' + toolName + ' 出现异常'"
|
||||||
|
type="warning"
|
||||||
|
></v-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="toolCalling">
|
||||||
|
<v-progress-circular
|
||||||
|
color="primary"
|
||||||
|
indeterminate
|
||||||
|
:size="16"
|
||||||
|
></v-progress-circular>
|
||||||
|
正在执行 {{ toolName }}
|
||||||
|
</div>
|
||||||
|
<v-text-field
|
||||||
|
v-model="input"
|
||||||
|
label="输入消息"
|
||||||
|
@keyup.enter="sendMessage"
|
||||||
|
></v-text-field>
|
||||||
|
<v-file-input v-model="fileUpload" label="选择文件"></v-file-input>
|
||||||
|
|
||||||
|
<v-btn color="primary" @click="sendMessage">发送</v-btn>
|
||||||
|
<v-btn
|
||||||
|
class="ml-2"
|
||||||
|
color="primary"
|
||||||
|
:loading="uploading"
|
||||||
|
@click="uploadFile"
|
||||||
|
>上传文件</v-btn
|
||||||
|
>
|
||||||
|
|
||||||
|
<v-btn class="ml-2" color="primary" @click="clearMessages">清空</v-btn>
|
||||||
|
<v-btn class="ml-2" color="primary" @click="deleteChat">删除</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import useApi from "@/plugins/api";
|
||||||
|
import router from "@/router";
|
||||||
|
import { useChatStore } from "@/stores/chat";
|
||||||
|
import config from "@/config/config";
|
||||||
|
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const chatId = useRoute().params.id as number;
|
||||||
|
chatStore.currentChatId = chatId;
|
||||||
|
|
||||||
|
const messages: Ref<any> = ref({
|
||||||
|
data: [],
|
||||||
|
});
|
||||||
|
const input = ref("");
|
||||||
|
const toolName = ref("");
|
||||||
|
const toolError = ref(false);
|
||||||
|
const toolCalling = ref(false);
|
||||||
|
const fileUpload = ref();
|
||||||
|
const uploading = ref(false);
|
||||||
|
const fileBaseUrl = config.backend + "/api/v1/files";
|
||||||
|
|
||||||
|
document.addEventListener("paste", function (event) {
|
||||||
|
const items = event.clipboardData && event.clipboardData.items;
|
||||||
|
if (items && items.length) {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].type.indexOf("image") !== -1) {
|
||||||
|
fileUpload.value = items[i].getAsFile();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
if (input.value !== "") {
|
||||||
|
toolError.value = false;
|
||||||
|
api.ChatMessage.apiV1ChatsIdMessagesPost(chatId, {
|
||||||
|
message: input.value,
|
||||||
|
role: "user",
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
const newMessage = {
|
||||||
|
content: input.value,
|
||||||
|
role: "user",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (messages.value.data == null) {
|
||||||
|
messages.value.data = [newMessage];
|
||||||
|
} else {
|
||||||
|
messages.value.data?.push(newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const streamId = res.data.data?.stream_id;
|
||||||
|
|
||||||
|
if (streamId) {
|
||||||
|
streamChat(streamId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// if 409
|
||||||
|
if (err.response.status === 409) {
|
||||||
|
const streamId = err.response.data.data?.stream_id;
|
||||||
|
|
||||||
|
if (streamId) {
|
||||||
|
streamChat(streamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function streamChat(streamId: String) {
|
||||||
|
const url = api.conf.basePath + "/api/v1/stream/" + streamId;
|
||||||
|
|
||||||
|
const evtSource = new EventSource(url);
|
||||||
|
|
||||||
|
let messageAdded = false;
|
||||||
|
|
||||||
|
// 滚动页面到最底部
|
||||||
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
evtSource.addEventListener("data", (e) => {
|
||||||
|
if (e.data === "[DONE]") {
|
||||||
|
evtSource.close();
|
||||||
|
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 (messages.value.data == null) {
|
||||||
|
messages.value.data = [newMessage];
|
||||||
|
} else {
|
||||||
|
// add to messages
|
||||||
|
messages.value.data?.push(newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageAdded = true;
|
||||||
|
append = true;
|
||||||
|
// set index
|
||||||
|
i = (messages.value.data?.length ?? 1) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (append && messageAdded) {
|
||||||
|
// @ts-ignore
|
||||||
|
messages.value.data[i].content += data.content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// close
|
||||||
|
evtSource.addEventListener("close", () => {
|
||||||
|
evtSource.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMessages = () => {
|
||||||
|
api.ChatMessage.apiV1ChatsIdMessagesGet(chatId).then((res) => {
|
||||||
|
messages.value.data = [];
|
||||||
|
res.data.data?.forEach((message: any) => {
|
||||||
|
if (message.role === "file") {
|
||||||
|
// 如果 mime_type 是 image/
|
||||||
|
if (message.user_file) {
|
||||||
|
if (message.user_file.file.mime_type.startsWith("image/")) {
|
||||||
|
message.role = "image";
|
||||||
|
message.content = message.user_file.file.id;
|
||||||
|
}
|
||||||
|
} else if (message.file.mime_type.startsWith("image/")) {
|
||||||
|
message.role = "image";
|
||||||
|
message.content = message.file.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.value.data?.push(message);
|
||||||
|
} else if (message.role === "assistant" || message.role === "user") {
|
||||||
|
messages.value.data?.push(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearMessages = () => {
|
||||||
|
api.ChatMessage.apiV1ChatsIdClearPost(chatId).then(() => {
|
||||||
|
getMessages();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteChat = () => {
|
||||||
|
api.Chat.apiV1ChatsIdDelete(chatId).then(() => {
|
||||||
|
api.Chat.apiV1ChatsGet().then((r) => {
|
||||||
|
chatStore.chats = r.data.data;
|
||||||
|
});
|
||||||
|
router.push("/assistants");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFile = () => {
|
||||||
|
if (!fileUpload.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = true;
|
||||||
|
api.ChatMessage.apiV1ChatsIdFilesPost(
|
||||||
|
chatId,
|
||||||
|
{
|
||||||
|
file: fileUpload.value,
|
||||||
|
url: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((r) => {
|
||||||
|
// 如果成功
|
||||||
|
if (r.status === 200 || r.status === 201) {
|
||||||
|
fileUpload.value = null;
|
||||||
|
getMessages();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert(err.response.data.message);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
uploading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getMessages();
|
||||||
|
</script>
|
9
src/pages/chat/[id]/index.vue
Normal file
9
src/pages/chat/[id]/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div>聊天记录 {{ chatId }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// @ts-ignore
|
||||||
|
const chatId = useRoute().params.id as number;
|
||||||
|
// chatStore.currentChatId = chatId;
|
||||||
|
</script>
|
@ -20,6 +20,7 @@ interface Api {
|
|||||||
Tool: ToolApi;
|
Tool: ToolApi;
|
||||||
ChatMessage: ChatMessageApi;
|
ChatMessage: ChatMessageApi;
|
||||||
ChatPublic: ChatPublicApi;
|
ChatPublic: ChatPublicApi;
|
||||||
|
conf: Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
let api: Api | null = null; // 使用联合类型来表示初始状态可能是 null
|
let api: Api | null = null; // 使用联合类型来表示初始状态可能是 null
|
||||||
@ -49,6 +50,7 @@ const getApi = () => {
|
|||||||
Tool: new ToolApi(conf, undefined, axios),
|
Tool: new ToolApi(conf, undefined, axios),
|
||||||
ChatMessage: new ChatMessageApi(conf, undefined, axios),
|
ChatMessage: new ChatMessageApi(conf, undefined, axios),
|
||||||
ChatPublic: new ChatPublicApi(conf, undefined, axios),
|
ChatPublic: new ChatPublicApi(conf, undefined, axios),
|
||||||
|
conf: conf
|
||||||
};
|
};
|
||||||
|
|
||||||
return api;
|
return api;
|
||||||
|
@ -4,6 +4,7 @@ import { EntityChat } from "../api";
|
|||||||
export const useChatStore = defineStore("chats", {
|
export const useChatStore = defineStore("chats", {
|
||||||
persist: false,
|
persist: false,
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
currentChatId: 0,
|
||||||
chats: <EntityChat[] | undefined>[],
|
chats: <EntityChat[] | undefined>[],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
2
src/typed-router.d.ts
vendored
2
src/typed-router.d.ts
vendored
@ -23,6 +23,8 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'/auth/continue': RouteRecordInfo<'/auth/continue', '/auth/continue', Record<never, never>, Record<never, never>>,
|
'/auth/continue': RouteRecordInfo<'/auth/continue', '/auth/continue', Record<never, never>, Record<never, never>>,
|
||||||
'/auth/login': RouteRecordInfo<'/auth/login', '/auth/login', Record<never, never>, Record<never, never>>,
|
'/auth/login': RouteRecordInfo<'/auth/login', '/auth/login', Record<never, never>, Record<never, never>>,
|
||||||
'/auth/logout': RouteRecordInfo<'/auth/logout', '/auth/logout', Record<never, never>, Record<never, never>>,
|
'/auth/logout': RouteRecordInfo<'/auth/logout', '/auth/logout', Record<never, never>, Record<never, never>>,
|
||||||
|
'/chat/[id]/': RouteRecordInfo<'/chat/[id]/', '/chat/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||||
|
'/chat/[id]/index copy': RouteRecordInfo<'/chat/[id]/index copy', '/chat/:id/index copy', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||||
'/guest/': RouteRecordInfo<'/guest/', '/guest', Record<never, never>, Record<never, never>>,
|
'/guest/': RouteRecordInfo<'/guest/', '/guest', Record<never, never>, Record<never, never>>,
|
||||||
'/test': RouteRecordInfo<'/test', '/test', Record<never, never>, Record<never, never>>,
|
'/test': RouteRecordInfo<'/test', '/test', Record<never, never>, Record<never, never>>,
|
||||||
'/test2': RouteRecordInfo<'/test2', '/test2', Record<never, never>, Record<never, never>>,
|
'/test2': RouteRecordInfo<'/test2', '/test2', Record<never, never>, Record<never, never>>,
|
||||||
|
@ -17,7 +17,16 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": [
|
"types": [
|
||||||
"unplugin-vue-router/client"
|
"unplugin-vue-router/client"
|
||||||
]
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allowJs": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
@ -30,4 +39,4 @@
|
|||||||
"path": "./tsconfig.node.json"
|
"path": "./tsconfig.node.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite";
|
||||||
import vue from "@vitejs/plugin-vue"
|
import vue from "@vitejs/plugin-vue";
|
||||||
import VueRouter from "unplugin-vue-router/vite"
|
import VueRouter from "unplugin-vue-router/vite";
|
||||||
import Layouts from "vite-plugin-vue-layouts"
|
import Layouts from "vite-plugin-vue-layouts";
|
||||||
import Components from "unplugin-vue-components/vite"
|
import Components from "unplugin-vue-components/vite";
|
||||||
import AutoImport from "unplugin-auto-import/vite"
|
import AutoImport from "unplugin-auto-import/vite";
|
||||||
// import { resolve } from "path";
|
// import { resolve } from "path";
|
||||||
// const rootPath = new URL(".", import.meta.url).pathname;
|
// const rootPath = new URL(".", import.meta.url).pathname;
|
||||||
|
|
||||||
import { fileURLToPath, URL } from "node:url"
|
import { fileURLToPath, URL } from "node:url";
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -54,4 +54,4 @@ export default defineConfig({
|
|||||||
port: 5173,
|
port: 5173,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user