From a861b2a6357039aa8f46cdb9c7956a62948a8519 Mon Sep 17 00:00:00 2001 From: Lex Lim Date: Wed, 28 Jun 2023 20:18:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9C=BA=E5=99=A8=E4=BA=BA?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/chatcomplete.ts | 35 +++++++++- src/components.d.ts | 5 +- src/components/ChatMessage.vue | 3 +- src/components/SelectBotPersonaModal.vue | 69 +++++++++++++++++++ src/components/TypeaheadModal.vue | 3 + src/layouts/ToolboxLayout.vue | 65 +++++++----------- src/types/base.ts | 24 +++++-- src/utils/other.ts | 6 ++ src/views/ChatComplete.vue | 85 +++++++++++++++++++----- src/views/ToolboxIndex.vue | 62 +++++++++++++++-- 10 files changed, 282 insertions(+), 75 deletions(-) create mode 100644 src/components/SelectBotPersonaModal.vue diff --git a/src/api/chatcomplete.ts b/src/api/chatcomplete.ts index 7387ba0..8fe01a5 100644 --- a/src/api/chatcomplete.ts +++ b/src/api/chatcomplete.ts @@ -1,7 +1,7 @@ import { RemoteError } from "@/errors/remoteError" import { toolkitApi } from "@/request/toolkit-api"; import { useUserStore } from "@/stores" -import { ChatCompleteChunkInfo, ExtractDocInfo } from "@/types/base"; +import { BotPersonaInfo, ChatCompleteChunkInfo, ExtractDocInfo } from "@/types/base"; import { getWebSocketApiUrl } from "@/utils/request"; export type IndexPageParams = { @@ -67,6 +67,23 @@ export type GetPointCostParams = { extract_limit?: number, } +export type GetBotPersonaListParams = { + page?: number, + category_id?: number, +} + +export type GetBotPersonaListResponse = { + list: BotPersonaInfo[], + page_count: number, +} + +export type GetBotPersonaInfoParams = { + id?: number, + bot_id?: string, +} + +export type GetBotPersonaInfoResponse = BotPersonaInfo + export const chatCompleteApi = { indexPage(params: IndexPageParams, onProgress: IndexPageOnProgress): Promise { return new Promise((resolve, reject) => { @@ -120,6 +137,16 @@ export const chatCompleteApi = { return data.data?.point_cost ?? -1 }, + async getBotPersonaList(params: GetBotPersonaListParams): Promise { + const { data } = await toolkitApi.get(`/chatcomplete/persona/list`, { params }) + return data.data + }, + + async getBotPersonaInfo(params: GetBotPersonaInfoParams): Promise { + const { data } = await toolkitApi.get(`/chatcomplete/persona/info`, { params }) + return data.data + }, + async forkConversation(params: ForkConversationParams): Promise { const { data } = await toolkitApi.post(`/chatcomplete/conversation/fork`, params) return data.data @@ -129,7 +156,8 @@ export const chatCompleteApi = { conversationId?: number, extractLimit?: number, inCollection?: boolean, - editMessageId?: [number, string] + editMessageId?: [number, string], + botId?: string, } = {}): Promise { let params: any = { title: title, @@ -147,6 +175,9 @@ export const chatCompleteApi = { if (args.editMessageId) { params.edit_message_id = args.editMessageId.join(',') } + if (args.botId) { + params.bot_id = args.botId + } const { data } = await toolkitApi.post('/chatcomplete/message', params) return data.data }, diff --git a/src/components.d.ts b/src/components.d.ts index 05f6941..2d3379a 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -9,6 +9,7 @@ export {} declare module '@vue/runtime-core' { export interface GlobalComponents { + BotPersonaList: typeof import('./components/BotPersonaList.vue')['default'] ChatCompleteSettingsModal: typeof import('./components/ChatCompleteSettingsModal.vue')['default'] ChatMessage: typeof import('./components/ChatMessage.vue')['default'] IonApp: typeof import('@ionic/vue')['IonApp'] @@ -31,9 +32,6 @@ declare module '@vue/runtime-core' { IonInfiniteScroll: typeof import('@ionic/vue')['IonInfiniteScroll'] IonInfiniteScrollContent: typeof import('@ionic/vue')['IonInfiniteScrollContent'] IonItem: typeof import('@ionic/vue')['IonItem'] - IonItemOption: typeof import('@ionic/vue')['IonItemOption'] - IonItemOptions: typeof import('@ionic/vue')['IonItemOptions'] - IonItemSliding: typeof import('@ionic/vue')['IonItemSliding'] IonLabel: typeof import('@ionic/vue')['IonLabel'] IonList: typeof import('@ionic/vue')['IonList'] IonListHeader: typeof import('@ionic/vue')['IonListHeader'] @@ -58,6 +56,7 @@ declare module '@vue/runtime-core' { MarkdownParser: typeof import('./components/MarkdownParser.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SelectBotPersonaModal: typeof import('./components/SelectBotPersonaModal.vue')['default'] TypeaheadModal: typeof import('./components/TypeaheadModal.vue')['default'] } } diff --git a/src/components/ChatMessage.vue b/src/components/ChatMessage.vue index 8badc84..6d959d2 100644 --- a/src/components/ChatMessage.vue +++ b/src/components/ChatMessage.vue @@ -8,6 +8,7 @@ export default { import { createOutline, reloadOutline, gitNetworkOutline } from 'ionicons/icons' const props = defineProps<{ + msgId?: string, sender: string, avatar: string, content: string, @@ -54,7 +55,7 @@ const displayTime = computed(() => { {{ props.sender }} {{ displayTime }} -
+
编辑 diff --git a/src/components/SelectBotPersonaModal.vue b/src/components/SelectBotPersonaModal.vue new file mode 100644 index 0000000..567ad08 --- /dev/null +++ b/src/components/SelectBotPersonaModal.vue @@ -0,0 +1,69 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/TypeaheadModal.vue b/src/components/TypeaheadModal.vue index 1df40c4..3530a13 100644 --- a/src/components/TypeaheadModal.vue +++ b/src/components/TypeaheadModal.vue @@ -160,6 +160,9 @@ watch(() => props.visible, (value, oldValue) => { + + +

{{ item.title }}

{{ item.subtitle }}

diff --git a/src/layouts/ToolboxLayout.vue b/src/layouts/ToolboxLayout.vue index dbeafea..06e6713 100644 --- a/src/layouts/ToolboxLayout.vue +++ b/src/layouts/ToolboxLayout.vue @@ -352,48 +352,29 @@ onMounted(async () => { 这里还什么都没有呢 - - - - - - - - - - - - - -

{{ p.page_title || '' }}

-
- -

{{ p.title || '无标题' }}

-
-

{{ p.description || ' ' }}

-
-
- - - -
- - - - 更改标题 - - - - - -
+ + + + +

{{ p.page_title || '' }}

+
+ +

{{ p.title || '无标题' }}

+
+

{{ p.description || ' ' }}

+
+
+ + + +
diff --git a/src/types/base.ts b/src/types/base.ts index d6795ea..3cd9de4 100644 --- a/src/types/base.ts +++ b/src/types/base.ts @@ -33,9 +33,23 @@ export type ChatCompleteMessage = { export type ChatCompleteChunkInfo = ChatCompleteMessage[] +export type BotPersonaInfo = { + id: number, + bot_id: string, + bot_name: string, + bot_avatar?: string, + bot_description?: string, + category_id: number, + system_prompt: string, + message_log: ChatCompleteMessage[], + default_question?: string, + updated_at: number, +} + export type TypeaheadItem = { - key: any; - title: string; - subtitle?: string; - value: any; -}; \ No newline at end of file + key: any, + title: string, + subtitle?: string, + icon?: string, + value: any, +} \ No newline at end of file diff --git a/src/utils/other.ts b/src/utils/other.ts index 7ca12f0..8cbb4b5 100644 --- a/src/utils/other.ts +++ b/src/utils/other.ts @@ -1,3 +1,5 @@ +export const DEFAULT_BOT_AVATAR = '/images/assistant-avatar.png' + export function setPageTitle(...title: string[]) { let newTitle = title.join(' - ') if (newTitle === '') { @@ -5,4 +7,8 @@ export function setPageTitle(...title: string[]) { } else { document.title = newTitle + ' - ' + import.meta.env.VITE_APP_NAME } +} + +export function secondTimestamp() { + return Math.floor(Date.now() / 1000) } \ No newline at end of file diff --git a/src/views/ChatComplete.vue b/src/views/ChatComplete.vue index a858a01..b1edd0f 100644 --- a/src/views/ChatComplete.vue +++ b/src/views/ChatComplete.vue @@ -12,7 +12,7 @@ import { } from '@ionic/vue' import { showConfirm, showError, showHelpToast } from '@/utils/dialog' import { ChatCompleteChunkInfo } from '@/types/base' -import { setPageTitle } from '@/utils/other' +import { DEFAULT_BOT_AVATAR, secondTimestamp, setPageTitle } from '@/utils/other' import { pinConversation } from '@/utils/actions' import { moduleConversationRoute } from '@/types/enum' import { IonRouterOutletInstance } from '@/types/instance' @@ -42,6 +42,11 @@ const state = reactive({ conversationTitle: '加载中...', conversationId: 0, pinned: false, + personaLoaded: false, + + botId: '', + botName: '', + botAvatar: '', conversationChunkIdList: [] as number[], messageChunkList: [] as ConversationChunkInfo[], @@ -73,6 +78,8 @@ if (route.params.id) { state.conversationId = parseInt(Array.isArray(route.params.id) ? route.params.id[0] : route.params.id) } +const isNewConversaion = computed(() => state.conversationId === 0) + // ============================================================================ // 帮助信息 // ============================================================================ @@ -100,12 +107,14 @@ const onSendMessage = async () => { contentEl?.scrollToBottom() ignoreAutoScroll = false + state.personaLoaded = true state.conversationLoading = true try { let question = state.formMessage const startRes = await api.chat.startChatComplete(pageStore.title, question, { conversationId: state.conversationId, editMessageId: state.editingId || undefined, + botId: state.botId, inCollection: settingsStore.collectionMode, extractLimit: settingsStore.docExtractLimit }) @@ -181,7 +190,7 @@ const onSendMessage = async () => { } if (startRes.conversation_id !== state.conversationId) { - if (state.conversationId === 0) { + if (isNewConversaion.value) { // 新建对话 conversationStore.list.push({ id: startRes.conversation_id, @@ -323,10 +332,14 @@ const loadPrevConversationChunk = (): Promise => { const loadConversation = async () => { try { - if (state.conversationId === 0) { + if (isNewConversaion.value) { // 新建对话 state.conversationTitle = '未命名对话' + state.conversationLoading = true + state.botId = route.query.botId as string ?? 'default' + await loadBotPersona() } else { + state.personaLoaded = true state.messageChunkList = [] state.errorMessage = null state.formMessage = '' @@ -346,6 +359,9 @@ const loadConversation = async () => { state.conversationTitle = conversationInfo.title ?? '未命名对话' state.pinned = conversationInfo.pinned + state.botId = conversationInfo.extra.bot_id ?? 'default' + + await loadBotPersona() state.conversationChunkIdList = await api.chat.getConversationChunkList({ id: state.conversationId @@ -387,6 +403,37 @@ const onInfiniteScrollLoad = async (event: InfiniteScrollCustomEvent) => { await event.target.complete() } +// ============================================================================ +// 加载机器人信息 +// ============================================================================ +const loadBotPersona = async () => { + if (state.personaLoaded) return + + state.personaLoaded = true + + try { + let personaInfo = await api.chat.getBotPersonaInfo({ + bot_id: state.botId + }) + if (isNewConversaion.value) { + // 新对话,加载机器人预设值 + state.messageChunkList.push({ + id: 0, + message_data: personaInfo.message_log?.map((message, index) => ({ + ...message, + id: '', + time: secondTimestamp() + })) ?? [] + }) + state.formMessage = personaInfo.default_question ?? '' + } + state.botName = personaInfo.bot_name + state.botAvatar = personaInfo.bot_avatar ?? DEFAULT_BOT_AVATAR + } catch (err: any) { + console.error(err) + } +} + // ============================================================================ // 修改消息、创建分支 // ============================================================================ @@ -753,19 +800,21 @@ watch(() => conversationInfo.value, (currentConversation) => { -
- -
+ +
+ +
+
@@ -799,13 +848,13 @@ watch(() => conversationInfo.value, (currentConversation) => { + @manual-update-index="updatePageIndex" :presenting-element="mainRouterOutlet?.$el">