Browse Source

feat: 完善对话部分

zhupei@smartai.com 1 năm trước cách đây
mục cha
commit
052ed3dfd8

+ 6 - 0
public/config.js

@@ -11,6 +11,12 @@ window.API_BASE_URL = 'http://192.168.20.119:9301';
 
 window.WEBSOCKET_BASE_URL = '192.168.20.119:9301';
 
+/**
+ * 静态资源地址
+ * 图片、文档等类型的地址
+ */
+window.ASSETS_BASE_URL = 'http://192.168.20.119:9301';
+
 /**
  * rgflow 接口地址
  *

+ 10 - 0
src/modules/conversation-chat/DialogInput.vue

@@ -1,6 +1,8 @@
 <template>
   <div ref="input_area_container">
     <div class="dialog-input-area-container">
+      <slot name="top"></slot>
+
       <div class="textarea-container">
         <a-textarea
           class="textarea-input"
@@ -29,9 +31,13 @@
         @submit="handleSubmit"
       />
 
+      <slot name="bottom"></slot>
+
       <FileUploadPreviewV1
         v-if="input_file_list.length !== 0"
+        class="file-upload-preview-container"
         :file_list="input_file_list"
+        :enable_remove="true"
         @delete="handleFileDelete"
       />
     </div>
@@ -346,4 +352,8 @@ defineExpose({
 .file-input-btn {
   display: none;
 }
+
+.file-upload-preview-container {
+  margin-top: 5px;
+}
 </style>

+ 68 - 0
src/modules/conversation-chat/DialogInputTop.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="dialog-input-area-top-container">
+    <a-space size="large">
+      <a-tooltip :content="radio_qingxi_text">
+        <a-radio :value="1" :model-value="act_type" @click="handleRadioClick(1)">文档清洗</a-radio>
+      </a-tooltip>
+
+      <a-tooltip :content="radio_shengcheng_text">
+        <a-radio :value="2" :model-value="act_type" @click="handleRadioClick(2)">报告生成</a-radio>
+      </a-tooltip>
+    </a-space>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+
+/**
+ * 此组件用以处理对话的附加参数
+ * 父组件通过ref调用此组件expose的两个方法
+ */
+
+const props = defineProps({
+  disabled: Boolean
+});
+
+const act_type = ref(1);
+// const upload_warning_text = `支持上传文件pdf、docx文档(最多${MAX_ACCEPT_FILE}个,每个大小不超过10MB)`;
+const radio_qingxi_text = '对上传的文档进行清洗,输出生成清洗后的文档';
+const radio_shengcheng_text = '对上传的(经过清洗的)文档生成报告,输出生成的报告文档';
+// const emit = defineEmits(['change']);
+
+const handleRadioClick = (value) => {
+  act_type.value = value / 1;
+};
+
+/**
+ * 清空表单的内容
+ */
+const clear = () => {};
+
+/**
+ * 获取所有的数据
+ * 父组件应该使用 ref,调用此参数
+ */
+const getOptions = () => {
+  return {
+    workflow: act_type.value
+  };
+};
+
+defineExpose({
+  /**
+   * 清空表单里的内容
+   */
+  clear: clear,
+  /**
+   * 获取附件参数的键值对
+   */
+  getOptions: getOptions
+});
+</script>
+
+<style lang="css" scoped>
+.dialog-input-area-top-container {
+  margin-bottom: 10px;
+}
+</style>

+ 6 - 0
src/modules/conversation-chat/DialogItem.vue

@@ -68,6 +68,12 @@ const answerInfo = computed(() => {
   obj.client_custom_chat_status = chat_info.client_custom_chat_status;
   obj.client_custom_icon = chat_info.client_custom_icon;
 
+  // 附加数据
+  obj.client_custom_linked_sql = chat_info.client_custom_linked_sql || '';
+  obj.client_custom_linked_code = chat_info.client_custom_linked_code || '';
+  obj.client_custom_linked_refference_full_content = chat_info.client_custom_linked_refference_full_content || '';
+  obj.client_custom_linked_image = chat_info.client_custom_linked_image || '';
+
   return obj;
 });
 

+ 2 - 1
src/modules/conversation-chat/DialogList.vue

@@ -1,6 +1,6 @@
 <template>
   <a-scrollbar type="track" :style="container_style" ref="scroll_container">
-    <div class="dialog-list">
+    <div class="dialog-list dialog-markdown-container">
       <DialogItem
         v-for="(item, index) of list"
         :dialog="item"
@@ -63,6 +63,7 @@ defineExpose({
 });
 </script>
 
+<style src="./chat-content.css"></style>
 <style lang="css" scoped>
 .dialog-list {
   color: var(--color-text-1);

+ 38 - 0
src/modules/conversation-chat/chat-content.css

@@ -0,0 +1,38 @@
+/* 客户端自定义markdown元素 */
+.client-formated-markdown-element-item {
+}
+
+/**
+* 正文文档引用标记,点击此标记会弹出引用
+*/
+.client-formated-markdown-doc-refference-flag {
+  width: 13px;
+  height: 13px;
+  margin-left: 4px;
+  border-radius: 50%;
+  cursor: pointer;
+  display: inline-block;
+  border: 1px solid #888;
+}
+.client-formated-markdown-doc-refference-flag::before {
+  content: '!';
+  font-size: 12px;
+  color: #000;
+  line-height: 12px;
+  text-align: center;
+  display: block;
+}
+/* 正文底部文档引用列表 */
+.client-formated-markdown-doc-refference-container {
+}
+.client-formated-markdown-doc-refference-item {
+}
+
+.dialog-markdown-container {
+}
+.dialog-markdown-container code {
+  overflow-x: auto;
+}
+.dialog-markdown-container img {
+  max-width: 600px;
+}

+ 22 - 20
src/modules/conversation-dialog/format-anwser-string.js

@@ -1,4 +1,5 @@
 import marked from '../../3rd-libs/marked';
+import { formatStaticAssetsUrl } from '../../utils/utils';
 
 /**
  * 引用标记
@@ -42,25 +43,26 @@ export default function formatAnwserString(string) {
    */
   let parse_href = result.replace('<a href=', '<a target="_blank" href=');
 
-  // /**
-  //  * 替换图片字符串
-  //  * 将字符串中的图片数据替换掉
-  //  */
-  // var rep_img = parse_href.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, function (match, str) {
-  //   // match值类似于
-  //   //         <img src="https://res.stepfun.com/image_gen/20241125/01936254e2ad7aea9d3d371d9a376469.png?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYTJhYzZiM2JhZTU2N
-  //   // GMxMGFkNTkwNTk3OTVkJDQ%2F20241125%2Fcn-shanghai%2Ftos%2Frequest&X-Tos-Date=20241125T075910Z&X-Tos-Expires=3600&x-Tos-Signature=3fa70bdbcc1759ab45aaa1b8bd60eef886e55114c32a39f478ee85d4f262804e&x-Tos-SignededH
-  //   // eaders=host" alt="">
-
-  //   const box = document.createElement('div');
-  //   box.innerHTML = match;
-  //   const image_ele = box.querySelector('img');
-  //   const img_src = image_ele.src; // 图片链接
-  //   // const img_src = 'http://localhost/src/assets/slider/logo.jpg';
-  //   const img_alt = image_ele.alt; // 图片alt属性
-
-  //   return `<span class="xiaoshuhuitu-image-container" data-src="${img_src}" data-alt="${img_alt}"></span>`;
-  // });
+  /**
+   * 替换图片字符串
+   * 将字符串中的图片数据替换掉
+   */
+  var rep_img = parse_href.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, function (match, str) {
+    // match值类似于
+    //         <img src="https://res.stepfun.com/image_gen/20241125/01936254e2ad7aea9d3d371d9a376469.png?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYTJhYzZiM2JhZTU2N
+    // GMxMGFkNTkwNTk3OTVkJDQ%2F20241125%2Fcn-shanghai%2Ftos%2Frequest&X-Tos-Date=20241125T075910Z&X-Tos-Expires=3600&x-Tos-Signature=3fa70bdbcc1759ab45aaa1b8bd60eef886e55114c32a39f478ee85d4f262804e&x-Tos-SignededH
+    // eaders=host" alt="">
+
+    const box = document.createElement('div');
+    box.innerHTML = match;
+    const image_ele = box.querySelector('img');
+    const img_src = image_ele.src; // 图片链接
+    const real_src = formatStaticAssetsUrl(img_src);
+    // const img_src = 'http://localhost/src/assets/slider/logo.jpg';
+    const img_alt = image_ele.alt; // 图片alt属性
+
+    return `<img data-src="${real_src}" src="${real_src}" data-alt="${img_alt}"></img>`;
+  });
 
   /**
    * 文档引用效果
@@ -73,7 +75,7 @@ export default function formatAnwserString(string) {
    * 在界面中可以通过 refference-flag 获取到对应的元素
    */
   let idx = 0; // 记录当前是第几次匹配
-  const formated = parse_href.replaceAll(reg, function (match, index, full_str, c) {
+  const formated = rep_img.replaceAll(reg, function (match, index, full_str, c) {
     let match_final = match.replace('></span>', `data-flag-index="${idx}"></span>`);
 
     match_final = match_final.replace('class="circle"', `class="refflag-refflag-refflag ${REFFERENCE_FLAG}"`);

+ 4 - 2
src/modules/conversation-dialog/format-code-string.js

@@ -17,7 +17,8 @@ export const applyHighLight = (container_klass) => {
  * @param {string} code_string
  */
 export const formatCodePython = (code_string) => {
-  return `<pre><code class="hljs language-python">
+  return `
+<pre><code class="hljs language-python">
 ${code_string}</code></pre>`;
 };
 
@@ -28,6 +29,7 @@ ${code_string}</code></pre>`;
  * @returns
  */
 export const formatCodeSql = (code_string) => {
-  return `<pre><code class="hljs language-sql">
+  return `
+<pre><code class="hljs language-sql">
 ${code_string}</code></pre>`;
 };

+ 142 - 25
src/modules/conversation-dialog/format-dialog-answer-from-websocket_receive_message.js

@@ -4,6 +4,65 @@ import { formatCodePython, formatCodeSql } from './format-code-string';
 import generateClientUniqueId from '../../utils/generate-client-unique-id';
 import { getIcon4Answer } from './get-icon-4-dialog';
 
+import formatAnwserString from './format-anwser-string';
+
+/**
+ * 格式化文档引用的数据
+ * 将对象转换为字符串
+ * @param {object} refference
+ * @param {string} field_key - 数据中的相关字段
+ */
+function formatRefferenceString(refference, field_key) {
+  if (!refference) {
+    return '';
+  }
+
+  const doc_aggs = refference.doc_aggs;
+
+  if (!doc_aggs) {
+    return '';
+  }
+
+  const refs_markdown = [];
+  if (Array.isArray(doc_aggs)) {
+    doc_aggs.forEach((item, index) => {
+      refs_markdown.push(
+        `<li
+          class="client-formated-markdown-element-item client-formated-markdown-doc-refference-item"
+          data-doc-id="${item.doc_id}"
+          data-real-index="${index}"
+          data-ref-key="${field_key}"
+        >
+          <span class="refference-index">${index + 1}</span>
+          <span class="refference-label">${item.doc_name}</span>
+        </li>`
+      );
+    });
+  }
+
+  const li_str = refs_markdown.join('');
+
+  return `<ul class="client-formated-markdown-element-item client-formated-markdown-doc-refference-container" data-ref-key="${field_key}">${li_str}</ul>`;
+}
+
+function formatRefferenceFlag(answer) {
+  let cur_text = answer;
+
+  cur_text = cur_text.replace(/##\d\$\$/g, function (match, index, full) {
+    // console.log(match, index, full);
+    // match示例数据 ##1$$ ##2$$
+    // 通过两次替换,字符串只剩下序号
+    const match_v1 = match.replace('##', '');
+    const match_v2 = match_v1.replace('$$', '');
+
+    const ref_idx = match_v2;
+
+    return `<span class="client-formated-markdown-element-item client-formated-markdown-doc-refference-flag" data-ref-index="${ref_idx}"></span>`;
+  });
+
+  return cur_text;
+}
+
 /**
  * 格式化websocket发送消息的参数
  * 此方法返回的数据项,为智能体返回的回答的数据
@@ -101,15 +160,6 @@ export default function formatDialogAnswerFromWebsocketReceiveMessage(message_st
 
     currentMsg.answer = resMessage;
 
-    if (resImageUrl) {
-      // 智能数据可能会存在此值
-      const port = window.location.port ? `:${window.location.port}` : '';
-
-      const image_url = `http://${window.location.hostname}${port}${resImageUrl}`;
-
-      currentMsg.image_url = image_url;
-    }
-
     if (resExcelUrl && resExcelName) {
       // 智能数据需要用到的值
       const port = window.location.port ? `:${window.location.port}` : '';
@@ -136,36 +186,83 @@ export default function formatDialogAnswerFromWebsocketReceiveMessage(message_st
       currentMsg.file = file;
     }
 
+    if (resDownloadUrl) {
+      // 文档报告可能存在此数据(type document_report)
+      const file_url_arr = decodeURIComponent(resDownloadUrl).split('/');
+      const file_name = file_url_arr[file_url_arr.length - 1];
+
+      const file = {
+        file_type: 'word',
+        file_url: resDownloadUrl,
+        file_name: `${file_name}.docx` || '点击下载'
+      };
+
+      currentMsg.file = file;
+    }
+
+    // -------------------------------------------------------------
+    // -------------------------------------------------------------
+    // -------------------------------------------------------------
+    // -------------------------------------------------------------
+    // -------------------------------------------------------------
+    // -------------------------------------------------------------
+    // -------------------------------------------------------------
+    // 智能数据:代码和图片处理
+    // 上传一个excel,提问:生成一个饼状图
     if (resCode) {
       // 智能数据可能存在此字段
       const code_formated = formatCodePython(resCode);
 
-      currentMsg.code = code_formated;
+      currentMsg.client_custom_linked_code = code_formated;
     }
 
     if (resSQL) {
       // 智能数据可能存在此字段
+      // 代码数据不能使用markdown解析器进行解析,否则会乱码
       const sql_formated = formatCodeSql(resSQL);
 
-      currentMsg.sql = sql_formated;
+      currentMsg.client_custom_linked_sql = sql_formated;
     }
 
-    if (resDownloadUrl) {
-      // 文档报告可能存在此数据(type document_report)
-      const file_url_arr = decodeURIComponent(resDownloadUrl).split('/');
-      const file_name = file_url_arr[file_url_arr.length - 1];
+    if (resImageUrl) {
+      // 智能数据可能会存在此值
+      // const port = window.location.port ? `:${window.location.port}` : '';
 
-      const file = {
-        file_type: 'word',
-        file_url: resDownloadUrl,
-        file_name: `${file_name}.docx` || '点击下载'
-      };
+      // const image_url = `http://${window.location.hostname}${port}${resImageUrl}`;
 
-      currentMsg.file = file;
-    }
+      // currentMsg.image_url = image_url;
 
+      currentMsg.client_custom_linked_image = resImageUrl;
+    }
+    // ------------------------------------------------------
+    // ------------------------------------------------------
+    // ------------------------------------------------------
+    // ------------------------------------------------------
+    // ------------------------------------------------------
+    // 知识问答:文档引用
+
+    // 处理可能存在的文档引用效果
+    // 在知识问答板块中,正文存在文档引用效果
+    currentMsg.answer = formatRefferenceFlag(currentMsg.answer);
+
+    // 文档引用效果
     if (resRefference) {
-      // 文档引用
+      // 文档引用,在知识问答板块中,存在一些文档引用内容
+      // 需要在对话的答案底部显示文档引用的节点
+      // 将文档引用相关的数据拼接到答案的底部
+      const ref_str = formatRefferenceString(resRefference, 'client_custom_linked_refference_full_content');
+
+      if (ref_str) {
+        currentMsg.answer =
+          currentMsg.answer +
+          `
+        ${ref_str}
+      `;
+      }
+
+      // 添加字段:客户端自定义的相关字段,link完整数据
+      // 在答案展示部分,需要根据此数据展示文档引用效果
+      currentMsg.client_custom_linked_refference_full_content = resRefference;
     }
   }
 
@@ -208,6 +305,8 @@ export default function formatDialogAnswerFromWebsocketReceiveMessage(message_st
   if (resType === 'system') {
     // 系统消息
     chat_status = 1;
+
+    currentMsg.answer = resMessage;
   }
 
   if (resType === 'close') {
@@ -245,7 +344,7 @@ export default function formatDialogAnswerFromWebsocketReceiveMessage(message_st
   if (resType === 'error') {
     // 错误消息
     chat_status = 3;
-    currentMsg.client_custom_chat_status = 2; // 轮会话已结束
+    currentMsg.client_custom_chat_status = 2; // 轮会话已结束
     currentMsg.client_custom_chat_type = 1; // 修改消息类型: 改为 结束
 
     currentMsg.answer = resMessage;
@@ -257,8 +356,26 @@ export default function formatDialogAnswerFromWebsocketReceiveMessage(message_st
    */
   currentMsg.client_custom_unique_id = generateClientUniqueId(currentMsg.answer);
 
+  // if (currentMsg.answer) {
+  //   // 将markdown转换为html文件
+  //   currentMsg.answer = formatAnwserString(currentMsg.answer);
+
+  //   if (currentMsg.client_custom_linked_code) {
+  //     currentMsg.answer = currentMsg.answer + currentMsg.client_custom_linked_code;
+  //   }
+
+  //   // 由于SQL和代码不能通过markdown解析,所以此处进行字符串的拼接
+  //   if (currentMsg.client_custom_linked_sql) {
+  //     currentMsg.answer = currentMsg.answer + currentMsg.client_custom_linked_sql;
+  //   }
+
+  //   if (currentMsg.client_custom_linked_image) {
+  //     currentMsg.answer = currentMsg.answer + `<img src="${currentMsg.client_custom_linked_image}" />`;
+  //   }
+  // }
+
   return {
-    status: chat_status, // 0 未开始 1 进行中 2 已结束 3 错误
+    status: chat_status, // 本轮会话的状态:0 未开始 1 进行中 2 已结束 3 错误
     message: chat_msg,
     data: currentMsg
   };

+ 30 - 5
src/modules/conversation-dialog/format-dialog-send-options.js

@@ -74,6 +74,22 @@ export default function formatDialogSendOptions(text, file_list, agentInfo) {
       // 出题组卷
       break;
     case 9:
+      // 文档报告
+      send_data = {
+        title: text
+        // workflow: 1 // 1 文档清洗 2 文档生成
+        // upload_files: [] // 文件ID的列表
+      };
+
+      if (file_list && Array.isArray(file_list)) {
+        const upload_file_ids = [];
+        const len = file_list.length;
+        for (let i = 0; i < len; i++) {
+          const file_item = file_list[i];
+          upload_file_ids.push(file_item.id);
+        }
+        send_data.upload_files = upload_file_ids;
+      }
       break;
     default:
     // 默认处理
@@ -109,10 +125,19 @@ export default function formatDialogSendOptions(text, file_list, agentInfo) {
   //   doc_ids: []
   // };
 
-  /**
-   * 组装socket参数
-   */
-  const send_opts = jsonStringify(send_data);
+  // /**
+  //  * 在参数正式发送前,可以进行一些组装
+  //  */
+  // if (otherOptions && typeof otherOptions === 'object') {
+  //   for (let key in otherOptions) {
+  //     send_data[key] = otherOptions;
+  //   }
+  // }
+
+  // /**
+  //  * 组装socket参数
+  //  */
+  // const send_opts = jsonStringify(send_data);
 
-  return send_opts;
+  return send_data;
 }

+ 21 - 3
src/modules/conversation-item/DialogAnswer.vue

@@ -41,8 +41,8 @@ import BtnStopChat from './BtnStopChat.vue';
 import DialogContent from '../conversation-content/DialogContent.vue';
 
 import formatAnwserString from '../conversation-dialog/format-anwser-string';
-
 import { getIconIntelligent } from '../common/icon-map';
+import { formatStaticAssetsUrl } from '../../utils/utils';
 
 import copyText from '../../3rd-libs/clipboard';
 
@@ -90,9 +90,27 @@ const is_content_loading = computed(() => {
  * 需要使用markdown解析器进行解析
  */
 const answer_html = computed(() => {
-  const str = props.answer.content;
+  const answer = props.answer;
+  const str = answer.content;
+
+  // return str;
+
+  let answer_string = formatAnwserString(str);
+
+  // 由于SQL和代码不能通过markdown解析,所以此处进行字符串的拼接
+  // 在markdown转换结束后,添加拼接字符串
+  if (answer.client_custom_linked_code) {
+    answer_string = answer_string + answer.client_custom_linked_code;
+  }
+
+  if (answer.client_custom_linked_sql) {
+    answer_string = answer_string + answer.client_custom_linked_sql;
+  }
 
-  const answer_string = formatAnwserString(str);
+  if (answer.client_custom_linked_image) {
+    const img_url = formatStaticAssetsUrl(answer.client_custom_linked_image);
+    answer_string = answer_string + `<img src="${img_url}" />`;
+  }
 
   return answer_string;
 });

+ 0 - 2
src/modules/conversation-item/DialogQuestion.vue

@@ -44,8 +44,6 @@ const select_btn_visible = computed(() => {
 
 const question_attached_files = computed(() => {
   if (Array.isArray(props.question.client_custom_upload_file_list)) {
-    console.log(props.question.client_custom_upload_file_list, '所有的文件列表');
-
     return props.question.client_custom_upload_file_list;
   } else {
     return [];

+ 1 - 1
src/modules/file-icon/FileIcon.vue

@@ -36,7 +36,7 @@ const file_icon_image = computed(() => {
 
   if (end_fix === 'doc' || end_fix === 'docx') {
     icon_type = 'word';
-  } else if (end_fix === 'excel' || end_fix === 'xlsx') {
+  } else if (end_fix === 'excel' || end_fix === 'xlsx' || end_fix === 'xls') {
     icon_type = 'excel';
   } else if (end_fix === 'pdf') {
     icon_type = 'pdf';

+ 18 - 0
src/utils/utils.js

@@ -1,3 +1,5 @@
+import { getConfigField } from '../config/index';
+
 export const jsonStringify = (val) => {
   return JSON.stringify(val);
 };
@@ -41,3 +43,19 @@ export const funcDebounce = (func, wait, immediate) => {
     }, wait);
   };
 };
+
+/**
+ * 处理静态文件路径
+ * @param {string} file_href
+ */
+export const formatStaticAssetsUrl = (file_href) => {
+  console.log(file_href, '路径');
+
+  if (import.meta.env.MODE === 'production') {
+    const assets_base = getConfigField('ASSETS_BASE_URL');
+
+    return `${assets_base}${file_href}`;
+  } else {
+    return file_href;
+  }
+};

+ 1 - 1
src/views/Xiaoshu.vue

@@ -92,7 +92,7 @@ onMounted(() => {
     .xiaoshu-aside-left-menus-actions-container{
       display: none;
     }
-    .xiaoshu-aside-left-menus-pop-container>div.item {
+    .user-popover.xiaoshu-aside-left-menus-actions-container >div.item{
       display: none;
     }
   `;

+ 66 - 7
src/views/conversation/ConversationChatContainer.vue

@@ -34,7 +34,11 @@
           :disabled="input_disabled"
           @sizeChange="handleInputSizeChange"
           @submit="handleInputSubmit"
-        />
+        >
+          <template v-if="show_input_others_opts" v-slot:top>
+            <DialogInputTop :agent_info="CHAT_AGENT_INFOMATION" ref="dialog_input_top" />
+          </template>
+        </DialogInput>
       </div>
 
       <DialogFooter class="conversation-chat-footer" :style="{ height: footer_height + 'px' }" />
@@ -50,11 +54,14 @@
 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';
 
@@ -77,6 +84,7 @@ import formatDialogAnswerFromWebsocketReceiveMessage from '../../modules/convers
 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';
 
 /**
@@ -119,7 +127,9 @@ const input_height = ref(100);
 const { width: chat_width, height: chat_height } = useEleSizeV1('chat_main');
 // const { width: input_area_width, height: input_area_height } = useEleSizeV2('dialog_input');
 
-const inputClear = useInputClear('dialog_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(() => {
@@ -188,6 +198,30 @@ const chat_display_list = computed(() => {
  */
 const show_welcome = ref(true);
 
+/**
+ * 是否显示附件参数表单
+ * 文档报告场景下需要显示此组件
+ */
+const show_input_others_opts = computed(() => {
+  const agentInfo = CHAT_AGENT_INFOMATION.value;
+
+  if (!agentInfo) {
+    return false;
+  }
+
+  if (!agentInfo.agent) {
+    return false;
+  }
+
+  const agent = agentInfo.agent;
+  if (agent.menuId / 1 === 9) {
+    // 文档报告场景: 显示附加参数表单
+    return true;
+  }
+
+  return false;
+});
+
 /**
  * 分享
  * @param chat_id
@@ -270,6 +304,9 @@ const handleInputSubmit = async (options) => {
   // --------------------------------------------
   // --------------------------------------------
   // --------------------------------------------消息发送前的准备工作
+  // 保存附加参数数据,由于下方有一个 inputClearFn2() 方法的执行
+  // 会将所有数据清空,故附件参数需要在清理之前保存
+  const other_options = getInputTopOptions();
 
   /**
    * 当新消息发送时
@@ -295,7 +332,8 @@ const handleInputSubmit = async (options) => {
   show_welcome.value = false;
   // 清空文本数据和文件数据,避免下一次会话冲突
   // 清空表单里的数据
-  inputClear();
+  inputClearFn1();
+  inputClearFn2();
   // 清空可能存在的推荐项目
   chat_suggestion.value = [];
   // 清空已保存的提问数据(提问数据已经推入数组,此处无需继续保存)
@@ -405,13 +443,33 @@ const handleInputSubmit = async (options) => {
   }
 
   /**
-   * 获取文件参数
-   * 组装socket参数
+   * 组装参数
+   * 组装socket参数: 文本和文件
+   * 此方法会依据当前使用的agent,组装好传递给websocket的基础的文本和文件的参数
+   */
+  const msg_obj = formatDialogSendOptions(ask_text, files_response, CHAT_AGENT_INFOMATION.value);
+
+  /**
+   * 文本和文件的数据已经组装完成
+   * 在某些情况下,仅有文本和文件还不够,可能还有其他控制参数
+   * 在这种情况下,可以通过 DialogInputTop 获取到附加参数
+   * 添加附加参数
+   * 附加参数应该是一个键值对对象,最终和和已有的文本参数和文件参数合并成一个新的键值对对象发送给websocket
    */
-  const msg = formatDialogSendOptions(ask_text, files_response, CHAT_AGENT_INFOMATION.value);
+  // 获取附加表单的数据项目
+  // 如果
+  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);
+  handleWSSend(chat_new_id.value, msg_str);
 };
 
 /**
@@ -624,6 +682,7 @@ onUnmounted(() => {
 .conversation-chat-main {
   flex-grow: 1;
   height: 100%;
+  max-width: 960px;
   margin-left: auto;
   margin-right: auto;
 

+ 20 - 0
src/views/conversation/use-input-get-options.js

@@ -0,0 +1,20 @@
+import { ref, getCurrentInstance, onMounted, onUnmounted, computed } from 'vue';
+
+/**
+ * 获取input的 getOptions 方法
+ * @param {string} ref_name
+ * @returns
+ */
+export default function useInputGetOptions(ref_name) {
+  const { proxy } = getCurrentInstance();
+
+  return () => {
+    const target = proxy.$refs[ref_name];
+
+    if (target && target.getOptions && typeof target.getOptions === 'function') {
+      return target.getOptions();
+    }
+
+    return null;
+  };
+}