From a166dd543a10a1092528e7f27b680f35330f02c6 Mon Sep 17 00:00:00 2001 From: gerson <1405270578@qq.com> Date: 星期六, 06 七月 2024 16:14:56 +0800 Subject: [PATCH] 单击提问示例输入;聊天室搜索;text回答支持,text,knowledge支持复制;question 进度查询 --- src/components/chat/Chat.vue | 127 +++++++++++++++++++++++++++++++++++++---- 1 files changed, 113 insertions(+), 14 deletions(-) diff --git a/src/components/chat/Chat.vue b/src/components/chat/Chat.vue index 930ab85..0be8fda 100644 --- a/src/components/chat/Chat.vue +++ b/src/components/chat/Chat.vue @@ -11,14 +11,31 @@ <component class="max-w-[100ch]" :is="answerTypeMapCom[item.content.type]" :data="item.content.values" /> </div> - <!-- <div v-if="item.role === RoleEnum.assistant" class="absolute flex items-center right-0 space-x-2 mr-2 mt-2"> - <SvgIcon class="cursor-pointer" name="ele-CopyDocument" @click="copyClick(item.content)" /> - <SvgIcon class="cursor-pointer" name="ywicon icon-dianzan" /> - <SvgIcon class="cursor-pointer" :size="12" name="ywicon icon-buzan" /> - </div> --> + <div v-if="item.role === RoleEnum.assistant" class="absolute flex items-center right-0 mr-2 mt-2 space-x-2"> + <div + class="flex items-center justify-center size-[15px]" + v-if="item.content?.type === AnswerType.Text || item.content?.type === AnswerType.Knowledge" + > + <i class="p-2 ywicon icon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]" @click="copyClick(item)" /> + </div> + <div class="flex items-center justify-center size-[15px]"> + <i + :class="{ 'text-[#0284ff]': item.state === AnswerState.Like }" + class="p-2 ywicon icon-dianzan cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]" + @click="likeClick(item)" + /> + </div> + <div class="flex items-center justify-center size-[15px]"> + <i + :class="{ 'text-[#0284ff]': item.state === AnswerState.Unlike }" + class="p-2 ywicon icon-buzan cursor-pointer hover:text-[#0284ff] !text-[13px] hover:!text-[15px]" + @click="unLikeClick(item)" + /> + </div> + </div> </div> - <Loding v-else /> + <Loding v-else :process="process" /> </div> </div> </div> @@ -36,11 +53,12 @@ import useClipboard from 'vue-clipboard3'; import Loding from './components/Loding.vue'; import type { ChatContent } from './model/types'; -import { AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage } from './model/types'; -import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi } from '/@/api/ai/chat'; +import { AnswerType, RoleEnum, answerTypeMapCom, roleImageMap, type ChatMessage, AnswerState } from './model/types'; +import { GetHistoryAnswer, QueryHistoryDetail, QuestionAi, SetHistoryAnswerState, getQuestionProcess } from '/@/api/ai/chat'; import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue'; import router from '/@/router'; import { activeChatRoom, activeLLMId, activeRoomId, activeSampleId, activeSectionAId } from '/@/stores/chatRoom'; +import { v4 as uuidv4 } from 'uuid'; let isTalking = ref(false); let messageContent = ref<ChatContent>({ @@ -94,13 +112,47 @@ } return content; }; +//#region ====================== 鏌ヨ杩涘害 ====================== +let processId = ''; +const QUERY_PROCESS_INTERVAL = 300; +const process = ref(''); +let processTimer = null; +let finishProcess = true; +const queryProcessApi = async () => { + const res = await getQuestionProcess({ + process_id: processId, + }).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); +}; + +//#endregion + +let questionRes = null; const questionAi = async (text) => { if (!currentSectionId) { ElMessage.warning('鍙戦�佸け璐ワ紝鏈‘瀹氬簲鐢ㄥ満鏅紒'); } - + processId = uuidv4(); const params = { + process_id: processId, question: text, // FIXME: 鏆傛椂杩欐牱 section_a_id: currentSectionId, @@ -114,8 +166,11 @@ if (currentLLMId) { params.llm_id = currentLLMId; } - + clearQueryProcess(); + queryProcess(); const res = await QuestionAi(params); + clearQueryProcess(); + questionRes = res; // const res = { // json_ok: true, // question: '鏄ㄦ棩浜斾竴骞垮満鍘嬪姏', @@ -173,15 +228,20 @@ } try { isTalking.value = true; + const userItem: ChatMessage = { role: RoleEnum.user, content } as any; + const assistantItem: ChatMessage = { role: RoleEnum.assistant, content: null, state: AnswerState.Null } as any; // 鍙戦�佸綋鍓� - messageList.value.push({ role: RoleEnum.user, content }); + messageList.value.push(userItem); // 娓呯┖杈撳叆妗� clearMessageContent(); + // 鍑虹幇鍥炲锛岀疆绌哄嚭鐜扮瓑寰呭姩鐢� - messageList.value.push({ role: RoleEnum.assistant, content: null }); + messageList.value.push(assistantItem); let resMsgContent: ChatContent = null; resMsgContent = await questionAi(content.values); + userItem.historyId = questionRes.history_id; + assistantItem.historyId = questionRes.history_id; appendLastMessageContent(resMsgContent); } catch (error: any) { appendLastMessageContent({ @@ -219,6 +279,7 @@ }); messageList.value = (res.details ?? []).map((item) => { return { + historyId: item.history_id, role: RoleEnum.user, content: { type: AnswerType.Text, @@ -234,8 +295,10 @@ resList.map((item, index) => { const insertIndex = index + 1 + i; messageList.value.splice(insertIndex, 0, { + historyId: item.answer.history_id, role: RoleEnum.assistant, content: parseContent(item.answer), + state: item.answer_state, }); i++; }); @@ -246,9 +309,11 @@ } ); +let forbidScroll = false; watch( messageList, () => { + if (forbidScroll) return; nextTick(() => scrollToBottom()); }, { @@ -260,9 +325,43 @@ const { toClipboard } = useClipboard(); -const copyClick = (content) => { +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(content); + 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; + }); }; //#endregion </script> -- Gitblit v1.9.3