| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768 |
- <template>
- <div class="conversation-chat-container">
- <div class="conversation-chat-left">
- <DialogAsideLeft />
- </div>
- <div class="conversation-chat-main" ref="chat_main">
- <DialogHeader
- class="conversation-chat-header"
- :agent_info="CHAT_AGENT_INFOMATION"
- :style="{ height: header_height + 'px' }"
- />
- <div class="conversation-chat-content">
- <DialogWelcome v-if="show_welcome" class="conversation-chat-area" :agent_info="CHAT_AGENT_INFOMATION" />
- <DialogList
- v-if="!show_welcome"
- ref="list_scroller"
- class="conversation-chat-area"
- :height="list_height"
- :width="chat_width"
- :agent_id="agent_id"
- :list="chat_display_list"
- :suggestion="chat_suggestion"
- @share="handleChatShare"
- @submit="handleSuggestSubmit"
- />
- <DialogInput
- ref="dialog_input"
- class="conversation-chat-input"
- :style="input_style"
- :agent_info="CHAT_AGENT_INFOMATION.agent"
- :agent_config="CHAT_AGENT_INFOMATION.config"
- :disabled="input_disabled"
- @sizeChange="handleInputSizeChange"
- @submit="handleInputSubmit"
- >
- <template v-if="show_input_others_opts" v-slot:top>
- <DialogInputTop :menu_id="current_menu_id" :agent_info="CHAT_AGENT_INFOMATION" ref="dialog_input_top" />
- </template>
- </DialogInput>
- </div>
- <DialogFooter class="conversation-chat-footer" :style="{ height: footer_height + 'px' }" />
- </div>
- <div class="conversation-chat-right">
- <DialogAsideRight />
- </div>
- </div>
- </template>
- <script setup>
- import { ref, getCurrentInstance, onMounted, onUnmounted, computed, nextTick } from 'vue';
- import { Message } from '@arco-design/web-vue';
- import { jsonParse, jsonStringify } from '../../utils/utils';
- import DialogAsideLeft from '../../modules/conversation-chat/DialogAsideLeft.vue';
- import DialogHeader from '../../modules/conversation-chat/DialogHeader.vue';
- import DialogWelcome from '../../modules/conversation-chat/DialogWelcome.vue';
- import DialogList from '../../modules/conversation-chat/DialogList.vue';
- import DialogInput from '../../modules/conversation-chat/DialogInput.vue';
- import DialogInputTop from '../../modules/conversation-chat/DialogInputTop.vue';
- import DialogFooter from '../../modules/conversation-chat/DialogFooter.vue';
- import DialogAsideRight from '../../modules/conversation-chat/DialogAsideRight.vue';
- // import { useAllAgentStore } from '../../base/store/use-all-agents';
- import getChatAgentInfomation from '../../modules/conversation-board/get-chat-agent-infomation';
- import useWebSocketChat from '../../modules/conversation-dialog/use-websocket-chat';
- import {
- getHistoryLogs,
- getDialogChatId,
- uploadFiles,
- uploadCombineExcelFiles,
- uploadFileByAgentInfo
- } from '../../modules/conversation-dialog/api-dialog';
- import { files2Formdata } from '../../modules/file-preview/files-2-formdata';
- import formatDialogSendOptions from '../../modules/conversation-dialog/format-dialog-send-options';
- import formatDialogHistory from '../../modules/conversation-dialog/format-dialog-history';
- import formatDialogQuestionByClient from '../../modules/conversation-dialog/format-dialog-question-by-client';
- import formatDialogAnswerForLoading from '../../modules/conversation-dialog/format-dialog-answer-for-loading';
- import formatDialogAnswerForException from '../../modules/conversation-dialog/format-dialog-answer-for-exception';
- import formatDialogAnswerFromWebsocketReceiveMessage from '../../modules/conversation-dialog/format-dialog-answer-from-websocket_receive_message';
- import useEleSizeV1 from '../../utils/use-ele-size-v1';
- import useEleSizeV2 from '../../utils/use-ele-size-v2';
- import useInputClear from './use-input-clear';
- import useInputGetOptions from './use-input-get-options';
- import useScroll2Bottom from './use-scroll-bottom';
- /**
- * 1. 历史会话进入此页面,展示历史会话内容
- * 2. 点击智能体,新建会话
- */
- const props = defineProps({
- /**
- * 智能体的ID
- * 此参数必须传递
- */
- agent_id: String,
- /**
- * 会话ID
- * 如果有此参数,则意味着获取该对话的记录内容
- */
- session_id: String,
- chat_type: String
- });
- // const allAgents = useAllAgentStore();
- /**
- * 对话使用到的agent信息
- * agent属性是由接口返回的数据
- * config属性是客户端自定义的数据,针对特定的agent的一些个性化配置,可由此进行配置
- * 二者结合,形成对话组件所需要的一些参数
- */
- const CHAT_AGENT_INFOMATION = ref({
- agent: {},
- config: {}
- });
- /**
- * input的一些操作方法
- */
- const inputClearFn1 = useInputClear('dialog_input'); // 主表单清空
- const inputClearFn2 = useInputClear('dialog_input_top'); // 顶部附加表单的数据
- const getInputTopOptions = useInputGetOptions('dialog_input_top'); // 获取附加表单参数的方法
- /**
- * 对话区域滚动到底部
- */
- const scroll2Bottom = useScroll2Bottom('list_scroller');
- const scroll2ListBottom = () => {
- nextTick(() => {
- scroll2Bottom();
- });
- };
- /**
- * 整个对话区域的一些尺寸定义
- */
- const header_height = 30;
- const footer_height = 30;
- const input_height = ref(100);
- /**
- * 获取到对话区域的宽度和高度
- */
- const { width: chat_width, height: chat_height } = useEleSizeV1('chat_main');
- /**
- * 对话列表区域的高度
- */
- const list_height = computed(() => {
- return chat_height.value - header_height - footer_height - input_height.value - 20;
- });
- const input_style = computed(() => {
- return {
- // height: input_height + 'px'
- };
- });
- // ------------------------------------------------------------------
- // ------------------------------------------------------------------
- // ------------------------------------------------------------------
- /**
- * 对话数据列表
- */
- const chat_log_list = ref([]); // 历史对话的数据列表
- const chat_new_list = ref([]); // 新对话的数据列表
- const chat_suggestion = ref([]); // 对话推荐项目
- /**
- * 当前新对话的ID
- * 当用户有输入后,点击提交,此时创建新对话,新对话的ID为此ID
- * 之后用户可以一直使用此ID进行对话。
- */
- const chat_new_id = ref(null);
- /**
- * 新对话,客户端的提问数据
- * 用以临时保存客户端的提问数据
- * 每当用户确认进行提问时,将新提问赋给此值
- * 一旦对话消息通过websocket发送到服务器,此值可清空
- */
- const chat_new_question = ref({});
- /**
- * 界面展示的对话列表
- * 当用户从历史会话进入时,历史会话记录有数据,当用户基于历史会话进行对话时,新会话也有数据,
- * 当用户从agent进入时,仅有新会话,没有历史会话
- *
- * 页面渲染的会话列表是 历史会话 + 新会话数据
- */
- const chat_display_list = computed(() => {
- return [].concat(chat_log_list.value, chat_new_list.value);
- });
- /**
- * 是否禁用输入组件
- */
- const input_disabled = ref(false);
- /**
- * 菜单ID编号
- * 1 报表合并
- * 2 知识问答
- * 3 文档智能
- * 4 智能问答
- * 5 智能数据
- * 6 小数绘图
- * 7 文档出卷
- * 8 出题组卷
- * 9 文档报告
- */
- const current_menu_id = computed(() => {
- const agent = CHAT_AGENT_INFOMATION.value.agent;
- const menu_id = agent.menuId / 1;
- return menu_id;
- });
- // /**
- // * 当前对话的类型
- // * 目前已知类型有量类: excel 和 其他类
- // * 当类别为excel时,文件上传接口和对话websocket链接都不一样
- // */
- // const current_chat_type = computed(() => {
- // const menu_id = current_menu_id.value;
- // const chat_type = menu_id === 1 ? 'excel' : '';
- // return chat_type;
- // });
- /**
- * 是否显示欢迎页
- * 当用户进入对话页面,还未正式开始对话时
- * 对话区域会显示欢迎内容
- * 当用户开始对话时,欢迎内容消失,对话列表开始展示
- */
- const show_welcome = ref(true);
- /**
- * 是否显示附件参数表单
- * 文档报告场景下需要显示此组件
- */
- const show_input_others_opts = computed(() => {
- const menu_id = current_menu_id.value;
- /**
- * 7 文档出卷
- * 9 文档报告
- */
- if (menu_id === 7 || menu_id === 9) {
- return true;
- }
- return false;
- });
- /**
- * 内容分享操作
- * @param chat_id
- */
- const handleChatShare = (chat_id) => {};
- /**
- * 在聊天界面点击推荐项
- * 继续对话
- */
- const handleSuggestSubmit = (suggest) => {};
- /**
- * 处理页面中的错误消息
- * @param msg
- */
- const handleErrorMessage = (msg) => {
- Message.error({ content: msg, position: 'top' });
- };
- // ---------------------------------------------
- /**
- * 异常处理
- * 异常处理过程是一个会话模拟的过程
- * 先在界面上显示加载动画,然后紧接着显示错误提示
- * 让用户感知到智能体有处理数据的过程
- * 所以在异常处理阶段,有一些需要处理的关键点,比如欢迎界面的显示(如果在现实,则需要隐藏,否则对话显示不了)
- * @param exceptionInfo
- */
- const handleChatRequestionException = (exceptionInfo) => {
- // 已结束
- const answerData = formatDialogAnswerForException(CHAT_AGENT_INFOMATION.value, exceptionInfo);
- // 原来的最后一项弹出
- chat_new_list.value.pop();
- // 添加新的最后一项
- chat_new_list.value.push(answerData);
- // 对话已结束:文件上传、文本输入区域放开使用
- input_disabled.value = false;
- // 对话区域滚动到底部
- scroll2ListBottom();
- };
- /**
- * 输入区域的尺寸发生了变化
- * 整个对话区域的尺寸也需要依据此变化而变化
- * @param size
- */
- const handleInputSizeChange = (size) => {
- const input_width = size.width;
- // const input_height = size.height;
- input_height.value = size.height;
- };
- /**
- * 输入已经确认要提交了
- *
- * 1. 创建会话 get-chat-id
- * 2. 文件上传
- * 3. 连接socket,socket 发送文本与文件数据内容
- * 4. 接收 socket 返回的数据项
- */
- const handleInputSubmit = async (options) => {
- const ask_text = options.text; // 文本
- const ask_files = options.files; // 文件列表
- // TODO: 内容检查,如果文本内容不合法,则不允许发送
- if (!ask_text) {
- handleErrorMessage('请输入文字');
- return;
- }
- // --------------------------------------------
- // --------------------------------------------
- // --------------------------------------------
- // --------------------------------------------
- // --------------------------------------------消息发送前的准备工作
- // 保存附加参数数据,由于下方有一个 inputClearFn2() 方法的执行
- // 会将所有数据清空,故附件参数需要在清理之前保存
- const other_options = getInputTopOptions();
- /**
- * 当新消息发送时
- * 意味着新一轮对话的开始
- * 此时应该将之前一轮对话的信息进行清空
- *
- * 整理客户端提问的数据
- * 组装Question数据
- */
- const client_question = formatDialogQuestionByClient(ask_text, ask_files);
- // 保存用户的提问信息
- // 当消息正式发出后,将提问数据放入列表中
- chat_new_question.value = client_question;
- // 消息即将发送,记录用户的提问数据
- chat_new_list.value.push(chat_new_question.value);
- // 客户端依据此数据展示加载中的动画
- const loadingMsg = formatDialogAnswerForLoading(CHAT_AGENT_INFOMATION.value);
- chat_new_list.value.push(loadingMsg);
- /**
- * 会话已确认发送
- * 隐藏欢迎内容,显示对话内容
- */
- show_welcome.value = false;
- // 清空文本数据和文件数据,避免下一次会话冲突
- // 清空表单里的数据
- inputClearFn1();
- inputClearFn2();
- // 清空可能存在的推荐项目
- chat_suggestion.value = [];
- // 清空已保存的提问数据(提问数据已经推入数组,此处无需继续保存)
- chat_new_question.value = null;
- // 文件上传、文本输入区域禁用
- input_disabled.value = true;
- // 对话区域滚动到底部
- scroll2ListBottom();
- // --------------------------------------------
- // --------------------------------------------
- // --------------------------------------------
- // --------------------------------------------
- // --------------------------------------------
- const agent_id = props.agent_id;
- // 对话分为三个步骤
- // 1. 创建会话,如果已经创建,则不需要再次创建
- // 2. 上传文件,如果不存在文件,则无需文件上传
- // 3. 连接websocket
- /**
- * 创建会话 get chat id
- * 判断对话是否已经创建
- * 如果对话已经创建,则使用已经创建的对话
- * 否则,创建新对话
- */
- if (chat_new_id.value === null) {
- // 未创建chat,开始创建
- try {
- const chatResult = await getDialogChatId(agent_id);
- if (chatResult.code / 1 === 200) {
- // 成功
- const res_data = chatResult.data;
- chat_new_id.value = res_data.chat_id;
- } else {
- // 失败
- // 处理异常:直接在界面上显示
- handleChatRequestionException({
- message: `code: ${chatResult.code} message: ${chatResult.msg}`
- });
- return;
- }
- } catch (err) {
- console.log('err-nfsdnakiuwirea7', err);
- // 处理错误
- handleChatRequestionException({
- message: err.message
- });
- return;
- }
- } else {
- // 已创建chat,使用已创建的会话ID,进入下一步流程
- }
- // 文件上传后的信息
- let files_response = null;
- /**
- * 如果存在文件数据,则进行文件上传
- */
- if (ask_files.length !== 0) {
- // 进行文件上传,获取文件信息
- const chat_id = chat_new_id.value;
- try {
- const menu_id = current_menu_id.value;
- const uploadResult = await uploadFileByAgentInfo(agent_id, chat_id, ask_files, menu_id);
- if (uploadResult.code / 1 === 200) {
- // 文件上传成功
- files_response = uploadResult.data;
- } else {
- // 文件上传异常
- handleChatRequestionException({
- message: `code: ${uploadResult.code} \n\n message: ${uploadResult.msg}`
- });
- return;
- }
- } catch (err) {
- console.log('err-fhskafhakfjslkfjl', err);
- handleChatRequestionException({
- message: err.message
- });
- return;
- }
- }
- /**
- * 组装参数
- * 组装socket参数: 文本和文件
- * 此方法会依据当前使用的agent,组装好传递给websocket的 基础的文本和文件的参数
- */
- const menu_id = current_menu_id.value;
- const msg_obj = formatDialogSendOptions(ask_text, files_response, menu_id);
- /**
- * 文本和文件的数据已经组装完成
- * 在某些情况下,仅有文本和文件还不够,可能还有其他控制参数
- * 在这种情况下,可以通过 DialogInputTop 获取到附加参数
- * 添加附加参数
- * 附加参数应该是一个键值对对象,最终和和已有的文本参数和文件参数合并成一个新的键值对对象发送给websocket
- */
- // 获取附加表单的数据项目
- if (other_options && typeof other_options === 'object') {
- for (let key in other_options) {
- msg_obj[key] = other_options[key];
- }
- }
- /**
- * 所有参数已经正式组装完成,将对象格式转换为字符串
- */
- const msg_str = jsonStringify(msg_obj);
- // 发送websocket消息
- handleWSSend(chat_new_id.value, msg_str);
- };
- /**
- * ------------------------------------------------------------
- * websocket定义以及初始化
- */
- const wsTrigger = {
- /**
- * 当websocket消息回来后
- * @param e
- */
- onMessage: handleWSMessage,
- /**
- * 当websocket消息出错时
- * @param e
- */
- onError: handleWSError,
- /**
- * 当websocket消息关闭时
- * @param e
- */
- onClose: handleWSClose
- };
- /**
- * 初始化websocket相关的一些方法
- */
- const { initWSConnection, sendWSMessage, getWSInstance, closeWSConnect } = useWebSocketChat(wsTrigger);
- /**
- * 发送websocket消息
- * @param chat_id - 对话记录的ID
- * @param value
- */
- function handleWSSend(chat_id, value) {
- /**
- * -----------------------------------------------------------
- * -----------------------------------------------------------
- * -----------------------------------------------------------
- * websocket消息正式发送,发送步骤:
- * 1. 初始化链接
- * 2. 发送消息
- */
- // 初始化websocket连接
- const menu_id = current_menu_id.value;
- initWSConnection(props.agent_id, chat_id, menu_id);
- // 发送socket连接
- sendWSMessage(value);
- /**
- * websocket消息发送流程完成
- * ------------------------------------------------------------
- * ------------------------------------------------------------
- * ------------------------------------------------------------
- */
- }
- /**
- * 接收websocket消息
- * @param e
- */
- function handleWSMessage(e) {
- const res_data = e.data;
- // 获取当前对话中最后一个对话数据
- // 最后一个对话数据为当前正在进行的对话
- const last_msg = chat_new_list.value[chat_new_list.value.length - 1];
- /**
- * 整理返回的数据
- */
- const currentChat = formatDialogAnswerFromWebsocketReceiveMessage(res_data, last_msg, CHAT_AGENT_INFOMATION.value);
- const chat_status = currentChat.status;
- const chat_message = currentChat.message;
- // 处理对话数据
- const handleChatData = () => {
- const chat_data = currentChat.data; // 对话相关的数据项
- // 原来的最后一项弹出
- chat_new_list.value.pop();
- // 添加新的最后一项
- chat_new_list.value.push(chat_data);
- // 滚动到底部
- scroll2ListBottom();
- };
- if (chat_status === 0) {
- // 未开始: 此状态不存在,无需处理
- }
- if (chat_status === 1) {
- // 此轮对话仍然在进行中
- handleChatData();
- }
- if (chat_status === 2) {
- // 此轮对话已结束
- handleChatData();
- // 对话已结束:检查是否存在推荐数据
- const chat_suggest = currentChat.suggest; // 对话相关的推荐数据
- if (chat_suggest) {
- chat_suggestion.value = chat_suggest;
- }
- // 对话已结束:文件上传、文本输入区域放开使用
- input_disabled.value = false;
- }
- if (chat_status === 3) {
- // 此轮对话出错了
- // TODO: 错误处理
- // 此轮对话已结束
- handleChatData();
- // 对话已结束:文件上传、文本输入区域放开使用
- input_disabled.value = false;
- }
- }
- /**
- * 处理websocket错误
- * @param e
- */
- function handleWSError(e) {
- console.log('socket-error-8jfdksagjsfdjaklnfsdajkl', e);
- handleChatRequestionException({
- message: `对话无法继续:WebSocket Error: ${e.target.url}`
- });
- }
- /**
- * 处理websocket关闭事件
- * @param e
- */
- function handleWSClose(e) {}
- onMounted(() => {
- const agent_id = props.agent_id;
- const session_id = props.session_id;
- /**
- * 根据agent_id 查询到agent的基本信息
- * 从而确定agent的相关功能支持,例如:是否需要上传文件,支持上传什么类型的文件、支持上传多少个文件?
- * 例如:主题、背景、等等
- */
- CHAT_AGENT_INFOMATION.value = getChatAgentInfomation(agent_id);
- // /**
- // * 设置页面标题
- // */
- // const agent = CHAT_AGENT_INFOMATION.value.agent;
- // const agent_name = agent.name;
- // document.title = document.title + '-' + agent_name;
- // 组件挂载
- if (session_id) {
- // 有历史记录:获取历史记录内容进行展示
- getHistoryLogs(agent_id, session_id)
- .then((res) => {
- if (res.code / 1 === 200) {
- // 成功
- const session_list = res.data;
- // 将列表数据进行转化
- const list = formatDialogHistory(session_list, CHAT_AGENT_INFOMATION.value);
- // 如果历史记录不为空
- // 则需要展示对话内容
- // 此时欢迎内容就不需要展示
- if (list.length !== 0) {
- show_welcome.value = false;
- // 滚动到底部
- scroll2ListBottom();
- }
- chat_log_list.value = list;
- } else {
- // 异常
- // TODO:处理异常
- show_welcome.value = false;
- // handleChatRequestionException({
- // message: res.msg
- // });
- // 使用消息提示,而非界面错误提示
- handleErrorMessage('历史会话异常:' + res.msg);
- }
- })
- .catch((err) => {
- console.log('err-mfsjakfjsdkalfjs', err);
- show_welcome.value = false;
- // handleChatRequestionException({
- // message: err.message
- // });
- handleErrorMessage('历史会话错误:' + err.message);
- });
- } else {
- // 无历史记录:等待创建会话内容(用户开始输入、提交完成),列表区域显示欢迎界面
- }
- });
- // ---------------------------------------------------------
- onUnmounted(() => {
- // 组件卸载,关闭组件内的websocket连接
- closeWSConnect();
- });
- </script>
- <style lang="css" scoped>
- .conversation-chat-container {
- display: flex;
- justify-content: center;
- margin-left: auto;
- margin-right: auto;
- height: 100%;
- }
- .conversation-chat-left {
- width: 25%;
- flex-shrink: 0;
- }
- .conversation-chat-right {
- width: 25%;
- flex-shrink: 0;
- }
- .conversation-chat-main {
- flex-grow: 1;
- height: 100%;
- max-width: 960px;
- margin-left: auto;
- margin-right: auto;
- display: flex;
- flex-direction: column;
- /* background-color: #aaa; */
- }
- .conversation-chat-content {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- .conversation-chat-area {
- flex-grow: 1;
- overflow: auto;
- }
- .conversation-chat-input {
- /* height: 100px; */
- /* background-color: #a67777; */
- flex-shrink: 0;
- margin-top: 5px;
- margin-bottom: 5px;
- padding: 5px;
- border-radius: 10px;
- display: flex;
- }
- </style>
|