From c123adf7cb8caa3a92f1143755092371ef4a0658 Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期一, 22 七月 2024 13:51:37 +0800 Subject: [PATCH] 整理 chat.vue --- vite.config.ts | 2 src/components/chat/Chat.vue | 209 ++++--------------------- src/components/chat/hooks/useScrollToBottom.ts | 37 ++++ src/components/chat/hooks/useQueryProcess.ts | 41 +++++ src/components/chat/hooks/useAssistantContentOpt.ts | 136 +++++++++++++++++ src/components/chat/model/types.ts | 2 6 files changed, 253 insertions(+), 174 deletions(-) diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue index 6eb98d6..154e00e 100644 --- a/src/components/chat/Chat.vue +++ b/src/components/chat/Chat.vue @@ -27,21 +27,18 @@ <p class="text-red-500"> {{ item.content.msg }} </p> - <div - class="mt-5 flex items-center" - v-if="showFixQuestion" - > + <div class="mt-5 flex items-center" v-if="showFixQuestion(item)"> <div class="text-gray-600 flex-0"> - {{ computedMessageList.at(-1).content.origin.err_json.fix_question.title + '锛�' }} + {{ item.content.origin.err_json.fix_question.title + '锛�' }} </div> <div class="ml-1 space-x-2 inline-flex flex-wrap"> <div - v-for="item in computedMessageList.at(-1).content.origin.err_json.fix_question?.values" - :key="item" + v-for="fixItem in item.content.origin.err_json.fix_question?.values" + :key="fixItem" class="bg-gray-200 p-3 hover:bg-[#c5e0ff] hover:text-[#1c86ff] cursor-pointer rounded-lg" - @click="fixQuestionClick(item)" + @click="fixQuestionClick(fixItem)" > - {{ item.title }} + {{ fixItem.title }} </div> </div> </div> @@ -142,16 +139,17 @@ import { ElMessage } from 'element-plus'; import _ from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { computed, nextTick, onActivated, onMounted, ref, watch } from 'vue'; -import useClipboard from 'vue-clipboard3'; +import { computed, onMounted, ref } from 'vue'; import FeedbackPanel from './components/FeedbackPanel.vue'; import Loding from './components/Loding.vue'; +import { useAssistantContentOpt } from './hooks/useAssistantContentOpt'; +import { useQueryProcess } from './hooks/useQueryProcess'; +import { useScrollToBottom } from './hooks/useScrollToBottom'; import type { ChatContent } from './model/types'; import { AnswerState, AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage } from './model/types'; -import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi, SetHistoryAnswerState, getQuestionProcess } from '/@/api/ai/chat'; +import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi } from '/@/api/ai/chat'; import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue'; import CustomDrawer from '/@/components/drawer/CustomDrawer.vue'; -import { useClickOther } from '/@/hooks/useClickOther'; import router from '/@/router'; import { activeChatRoom, activeLLMId, activeSampleId, activeSectionAId, getRoomConfig, roomConfig } from '/@/stores/chatRoom'; import { ErrorCode } from '/@/utils/request'; @@ -168,22 +166,9 @@ const chatListDom = ref<HTMLDivElement>(); const messageList = ref<ChatMessage[]>([]); const computedMessageList = computed(() => { - return messageList.value.filter((v) => v && v.role !== RoleEnum.system); + return messageList.value.filter((v) => !!v); }); -const showAskMore = computed(() => { - if (!computedMessageList.value || computedMessageList.value.length === 0) return false; - const last = computedMessageList.value.at(-1); - const isShow = last?.role === RoleEnum.assistant && last?.content?.values && last.content?.askMoreList?.length > 0; - return isShow; -}); - -const showFixQuestion = computed(() => { - if (!computedMessageList.value || computedMessageList.value.length === 0) return false; - const last = computedMessageList.value.at(-1); - const isShow = last?.role === RoleEnum.assistant && last?.content?.values && last.content?.origin?.err_json?.fix_question; - return isShow; -}); const parseContent = (res) => { if (!res) return null; let content: ChatContent = { @@ -243,54 +228,23 @@ content.origin = res; return content; }; -//#region ====================== 鏌ヨ杩涘害 ====================== -let processId = ''; -const QUERY_PROCESS_INTERVAL = 1000; -const process = ref(''); -let processTimer = null; -let finishProcess = true; -const queryProcessApi = async () => { - const res = await getQuestionProcess({ - process_id: processId, - }).catch((err) => { - process.value = err; - }); +const { clearQueryProcess, process, processId, queryProcess } = useQueryProcess(); - process.value = res.process; - finishProcess = true; -}; - -const queryProcess = () => { - processTimer = setInterval(() => { - if (!finishProcess) return; - finishProcess = false; - queryProcessApi(); - }, QUERY_PROCESS_INTERVAL); -}; - -const clearQueryProcess = () => { - process.value = ''; - clearInterval(processTimer); -}; - -//#endregion - -let isNextChat = false; let questionRes = null; const questionAi = async (text) => { if (!currentSectionId) { ElMessage.warning('鍙戦�佸け璐ワ紝鏈‘瀹氬簲鐢ㄥ満鏅紒'); } - processId = uuidv4(); + processId.value = uuidv4(); const params = { - process_id: processId, + process_id: processId.value, question: text, // FIXME: 鏆傛椂杩欐牱 section_a_id: currentSectionId, history_group_id: currentRouteId, raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false, - next_chat: isNextChat, + next_chat: isNextChat.value, } as any; if (currentSampleId) { @@ -316,10 +270,6 @@ values: '', }); -const scrollToBottom = () => { - if (!chatListDom.value) return; - chatListDom.value.lastElementChild?.scrollIntoView(); -}; let currentSectionId = null; let currentSampleId = null; @@ -390,24 +340,6 @@ } }; -const askMoreClick = (item) => { - if (!item.question) return; - sendChatMessage({ type: AnswerType.Text, values: item.question }); -}; - -const fixQuestionClick = (item) => { - if (!item.question) return; - isNextChat = true; - try { - sendChatMessage({ - type: AnswerType.Text, - values: item.question, - }); - } finally { - isNextChat = false; - } -}; - onMounted(async () => { const res = await QueryHistoryDetail({ history_group_id: currentRouteId, @@ -454,97 +386,32 @@ sendChatMessage(); } }); -let forbidScroll = false; -watch( - messageList, - () => { - if (forbidScroll) return; - nextTick(() => scrollToBottom()); - }, - { - deep: true, - } -); -onActivated(() => { - if (forbidScroll) return; - nextTick(() => scrollToBottom()); +const { forbidScroll } = useScrollToBottom({ + chatListDom: chatListDom, + displayMessageList: computedMessageList, }); -//#region ====================== 鑱婂ぉ鍐呭鎿嶄綔 ====================== - -const { toClipboard } = useClipboard(); - -const copyClick = (item) => { - const type = item.content.type; - let text = ''; - if (type === AnswerType.Knowledge) { - text = item.content.values?.map((item) => item.answer).join('\n\n') ?? ''; - } else { - text = item.content.values; - } - ElMessage.success('澶嶅埗鎴愬姛'); - toClipboard(text); -}; - -const likeClick = async (item) => { - const toSetState = item.state === AnswerState.Like ? AnswerState.Null : AnswerState.Like; - const res = await SetHistoryAnswerState({ - history_id: item.historyId, - answer_state: toSetState, - }); - item.state = toSetState; - forbidScroll = true; - nextTick(() => { - forbidScroll = false; - }); -}; - -const unLikeClick = async (item) => { - const toSetState = item.state === AnswerState.Unlike ? AnswerState.Null : AnswerState.Unlike; - const res = await SetHistoryAnswerState({ - history_id: item.historyId, - answer_state: toSetState, - }); - item.state = toSetState; - - forbidScroll = true; - nextTick(() => { - forbidScroll = false; - }); -}; -const feedbackPosition = ref({ - x: 0, - y: 0, -}); - -const feedbackIsShow = ref(false); -const feedbackContent = ref(''); -const feedbackPanelRef = ref<HTMLDivElement>(null); -const currentFeedbackMapItem = ref(null); -const curFeedbackIndex = ref(0); -const feedbackClick = async (e, item, index) => { - currentFeedbackMapItem.value = item; - curFeedbackIndex.value = index; - const offsetX = -4; - const offsetY = -8; - feedbackIsShow.value = true; - nextTick(() => { - feedbackPosition.value = { - x: -feedbackPanelRef.value[index].$el.clientWidth + offsetX, - y: -feedbackPanelRef.value[index].$el.clientHeight + offsetY, - }; - }); -}; -useClickOther( - computed(() => feedbackPanelRef.value[curFeedbackIndex.value]), +const { + copyClick, + likeClick, + unLikeClick, + feedbackPosition, feedbackIsShow, - () => { - feedbackIsShow.value = false; - feedbackContent.value = ''; - } -); -//#endregion + feedbackContent, + feedbackPanelRef, + currentFeedbackMapItem, + feedbackClick, + askMoreClick, + fixQuestionClick, + isNextChat, + showFixQuestion, + showAskMore, +} = useAssistantContentOpt({ + forbidScroll, + sendChatMessage, + displayMessageList: computedMessageList, +}); //#region ====================== 渚ц竟鏍廳rawer ====================== const drawerIsShow = ref(false); diff --git a/src/components/chat/hooks/useAssistantContentOpt.ts b/src/components/chat/hooks/useAssistantContentOpt.ts new file mode 100644 index 0000000..6141114 --- /dev/null +++ b/src/components/chat/hooks/useAssistantContentOpt.ts @@ -0,0 +1,136 @@ +import { ElMessage } from 'element-plus'; +import type { ComputedRef, Ref } from 'vue'; +import { computed, nextTick, ref } from 'vue'; +import useClipboard from 'vue-clipboard3'; +import type { ChatMessage } from '../model/types'; +import { AnswerState, AnswerType, RoleEnum } from '../model/types'; +import { SetHistoryAnswerState } from '/@/api/ai/chat'; +import { useClickOther } from '/@/hooks/useClickOther'; + +export type AssistantContentOptOption = { + forbidScroll: Ref<boolean>; + sendChatMessage: any; + displayMessageList: ComputedRef<ChatMessage[]>; +}; + +export const useAssistantContentOpt = (option: AssistantContentOptOption) => { + const { forbidScroll, sendChatMessage, displayMessageList } = option; + const { toClipboard } = useClipboard(); + const isNextChat = ref(false); + + const copyClick = (item) => { + const type = item.content.type; + let text = ''; + if (type === AnswerType.Knowledge) { + text = item.content.values?.map((item) => item.answer).join('\n\n') ?? ''; + } else { + text = item.content.values; + } + ElMessage.success('澶嶅埗鎴愬姛'); + toClipboard(text); + }; + + const likeClick = async (item) => { + const toSetState = item.state === AnswerState.Like ? AnswerState.Null : AnswerState.Like; + const res = await SetHistoryAnswerState({ + history_id: item.historyId, + answer_state: toSetState, + }); + item.state = toSetState; + forbidScroll.value = true; + nextTick(() => { + forbidScroll.value = false; + }); + }; + + const unLikeClick = async (item) => { + const toSetState = item.state === AnswerState.Unlike ? AnswerState.Null : AnswerState.Unlike; + const res = await SetHistoryAnswerState({ + history_id: item.historyId, + answer_state: toSetState, + }); + item.state = toSetState; + + forbidScroll.value = true; + nextTick(() => { + forbidScroll.value = false; + }); + }; + const feedbackPosition = ref({ + x: 0, + y: 0, + }); + + const feedbackIsShow = ref(false); + const feedbackContent = ref(''); + const feedbackPanelRef = ref<HTMLDivElement>(null); + const currentFeedbackMapItem = ref(null); + const curFeedbackIndex = ref(0); + const feedbackClick = async (e, item, index) => { + currentFeedbackMapItem.value = item; + curFeedbackIndex.value = index; + const offsetX = -4; + const offsetY = -8; + feedbackIsShow.value = true; + nextTick(() => { + feedbackPosition.value = { + x: -feedbackPanelRef.value[index].$el.clientWidth + offsetX, + y: -feedbackPanelRef.value[index].$el.clientHeight + offsetY, + }; + }); + }; + useClickOther( + computed(() => feedbackPanelRef.value[curFeedbackIndex.value]), + feedbackIsShow, + () => { + feedbackIsShow.value = false; + feedbackContent.value = ''; + } + ); + const showAskMore = computed(() => { + if (!displayMessageList.value || displayMessageList.value.length === 0) return false; + const last = displayMessageList.value.at(-1); + const isShow = last?.role === RoleEnum.assistant && last?.content?.values && last.content?.askMoreList?.length > 0; + return isShow; + }); + + const showFixQuestion = (item) => { + const isShow = item?.role === RoleEnum.assistant && item?.content?.values && item.content?.origin?.err_json?.fix_question; + return isShow; + }; + const askMoreClick = (item) => { + if (!item.question) return; + sendChatMessage({ type: AnswerType.Text, values: item.question }); + }; + + const fixQuestionClick = (item) => { + if (!item.question) return; + isNextChat.value = true; + try { + sendChatMessage({ + type: AnswerType.Text, + values: item.question, + }); + } finally { + isNextChat.value = false; + } + }; + + return { + copyClick, + likeClick, + unLikeClick, + feedbackPosition, + feedbackIsShow, + feedbackContent, + feedbackPanelRef, + currentFeedbackMapItem, + curFeedbackIndex, + feedbackClick, + askMoreClick, + fixQuestionClick, + isNextChat, + showAskMore, + showFixQuestion, + }; +}; diff --git a/src/components/chat/hooks/useQueryProcess.ts b/src/components/chat/hooks/useQueryProcess.ts new file mode 100644 index 0000000..00d970d --- /dev/null +++ b/src/components/chat/hooks/useQueryProcess.ts @@ -0,0 +1,41 @@ +import { ref } from 'vue'; +import { getQuestionProcess } from '/@/api/ai/chat'; + +export const useQueryProcess = () => { + const processId = ref(''); + const QUERY_PROCESS_INTERVAL = 1000; + const process = ref(''); + let processTimer = null; + let finishProcess = true; + + const queryProcessApi = async () => { + const res = await getQuestionProcess({ + process_id: processId.value, + }).catch((err) => { + process.value = err; + }); + + process.value = res.process; + finishProcess = true; + }; + + const queryProcess = () => { + processTimer = setInterval(() => { + if (!finishProcess) return; + finishProcess = false; + queryProcessApi(); + }, QUERY_PROCESS_INTERVAL); + }; + + const clearQueryProcess = () => { + process.value = ''; + clearInterval(processTimer); + }; + + return { + processId, + process, + queryProcess, + clearQueryProcess + }; +}; diff --git a/src/components/chat/hooks/useScrollToBottom.ts b/src/components/chat/hooks/useScrollToBottom.ts new file mode 100644 index 0000000..b23e8a4 --- /dev/null +++ b/src/components/chat/hooks/useScrollToBottom.ts @@ -0,0 +1,37 @@ +import type { ComputedRef, Ref } from 'vue'; +import { nextTick, onActivated, ref, watch } from 'vue'; +import type { ChatMessage } from '../model/types'; + +export type UseScrollToBottomOption = { + chatListDom: Ref<HTMLDivElement>; + displayMessageList: ComputedRef<ChatMessage[]>; +}; + +export const useScrollToBottom = (option:UseScrollToBottomOption) => { + const {chatListDom,displayMessageList} = option; + + const scrollToBottom = () => { + if (!chatListDom.value) return; + chatListDom.value.lastElementChild?.scrollIntoView(); + }; + const forbidScroll = ref(false); + watch( + displayMessageList, + () => { + if (forbidScroll.value) return; + nextTick(() => scrollToBottom()); + }, + { + deep: true, + } + ); + + onActivated(() => { + if (forbidScroll.value) return; + nextTick(() => scrollToBottom()); + }); + + return { + forbidScroll + }; +}; diff --git a/src/components/chat/model/types.ts b/src/components/chat/model/types.ts index ce80274..3a3555a 100644 --- a/src/components/chat/model/types.ts +++ b/src/components/chat/model/types.ts @@ -24,7 +24,6 @@ export const enum RoleEnum { user = 'user', assistant = 'assistant', - system = 'system', } export const AnswerState = { Null: null, @@ -58,5 +57,4 @@ export const roleImageMap = { [RoleEnum.user]: userPic, [RoleEnum.assistant]: assistantPic, - [RoleEnum.system]: userPic, }; diff --git a/vite.config.ts b/vite.config.ts index e9c23f4..3cdb41a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -35,7 +35,7 @@ host: '0.0.0.0', port: env.VITE_PORT as unknown as number, open: JSON.parse(env.VITE_OPEN), - hmr: true, + hmr: false, }, build: { // outDir: 'dist/' + mode.mode, -- Gitblit v1.9.3