瀏覽代碼

feat:新增问答推理思考

hanyang 1 年之前
父節點
當前提交
0258a2d845

+ 8 - 3
src/modules/conversation-aside/web-page/Web-page-list.vue

@@ -47,12 +47,14 @@ const toDetail = (item) => {
 </script>
 <style scoped>
 .page-list {
+  width: 100%;
   text-align: left;
   padding: 0 20px;
   overflow-y: auto;
   overflow-x: hidden;
 }
 .page-list-item {
+  width: 100%;
   display: flex;
   flex-direction: column;
   padding: 5px 0;
@@ -77,6 +79,7 @@ const toDetail = (item) => {
   text-overflow: ellipsis;
 
   overflow: hidden;
+  width: 100%;
 }
 .item-des,
 .item-bottom {
@@ -85,16 +88,18 @@ const toDetail = (item) => {
 }
 .item-des {
   line-height: 18px;
-  display: -webkit-box;
+  /* display: -webkit-box;
   -webkit-line-clamp: 3;
   -webkit-box-orient: vertical;
-  text-overflow: ellipsis;
-  overflow: hidden;
+  text-overflow: ellipsis; */
+  /* overflow: hidden; */
+  width: 100%;
 }
 .item-bottom {
   display: flex;
   align-items: center;
   gap: 10px;
+  width: 100%;
   padding-bottom: 10px;
 }
 .item-origin {

+ 3 - 0
src/modules/conversation-board/use-agent-list-4-history.js

@@ -60,6 +60,9 @@ export default function useAgentList4History(onSuccess) {
           }
 
           list_agents.value = res_list;
+
+          list_agents.value.unshift({ id: '', name: '全部' });
+          console.log(list_agents.value);
           // 在store中缓存数据
           allAgents.setList(res_list);
 

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

@@ -11,6 +11,7 @@
       @retry="handleRetryChat"
       @select="handleSelectChat"
       @edit="handleEditChat"
+      @toSearchPage="toSearchPage"
     />
   </div>
 </template>
@@ -96,9 +97,23 @@ const answerInfo = computed(() => {
   // 系统消息
   obj.client_custom_running_msg = chat_info.client_custom_running_msg || '';
 
+  // 联网消息
+  obj.client_custom_item_internet = chat_info.client_custom_item_internet || false;
+  obj.client_custom_item_internet_list = chat_info.client_custom_item_internet_list || [];
+  // 是否深度思考
+  obj.client_custom_item_think = chat_info.client_custom_item_think || false;
+  // 对话id
+  obj.client_custom_chat_message_id = chat_info.client_custom_chat_message_id || '';
+  // 思考内容
+  obj.deep_answer = chat_info.deep_answer || '';
+  // 思考状态
+  obj.client_custom_chat_deep_loading = chat_info.client_custom_chat_deep_loading || false;
   return obj;
 });
 
+const toSearchPage = () => {
+  emit('toSearchPage');
+};
 /**
  * 停止答案的生成
  */

+ 5 - 0
src/modules/conversation-chat/DialogList.vue

@@ -18,6 +18,7 @@
         :answer_action_list="answer_action_list"
         @select="handleSelectChat(item)"
         @edit="handleEditChat(item)"
+        @toSearchPage="toSearchPage(item)"
       />
 
       <DialogSuggestion v-if="suggestion.length !== 0" :suggestion="suggestion" />
@@ -87,6 +88,10 @@ const handleSelectChat = (item) => {};
 const handleEditChat = (item) => {
   emit('edit', item);
 };
+
+const toSearchPage = (item) => {
+  emit('toSearchPage', item);
+};
 defineExpose({
   scroll2Bottom: () => {
     scroll2Bottom(1000000);

+ 1 - 1
src/modules/conversation-chat/dialog-input/InputActions.vue

@@ -205,7 +205,7 @@ watch(
     emit('actionConfig', {
       isDeep: is_deep_think.value ? 2 : 1,
       knowledgeId: base_list.value,
-      chatMode: is_connect_internet.value ? 2 : 1
+      chatMode: base_list.value.length ? 3 : 1
     });
   },
   {

+ 27 - 22
src/modules/conversation-content/DialogContent.vue

@@ -1,11 +1,18 @@
 <template>
   <div class="dialog-chat-answer-content" :style="custom_style">
-    <!-- <div class="think-select" v-if="show_think">
+    <DialogWebPage
+      v-if="is_internet_search"
+      :internet_search="internet_search"
+      @toSearchPage="toSearchPage"
+    ></DialogWebPage>
+    <!-- <a-button class="think-select" v-if="deep_loading">
       <icon-robot class="robot" @click="handleThinkClick" />
       <span>思考中...</span>
       <icon-down v-if="true" class="down" />
       <icon-up v-else class="down" />
-    </div> -->
+    </a-button> -->
+    <DialogThinkContent v-if="is_think && deep_answer" :deep_loading="deep_loading" :deep_answer="deep_answer">
+    </DialogThinkContent>
     <div v-if="is_loading">
       <a-spin></a-spin>
     </div>
@@ -18,6 +25,8 @@
 
 <script setup>
 import { computed } from 'vue';
+import DialogWebPage from './DialogWebPage.vue';
+import DialogThinkContent from './DialogThinkContent.vue';
 
 // 接收markdown数据,进行一些转换
 const props = defineProps({
@@ -42,9 +51,21 @@ const props = defineProps({
   /**
    *  是否展示思考
    * */
-  show_think: Boolean
+  is_think: Boolean,
+  /**
+   * 是否展示联网搜索
+   * */
+  is_internet_search: Boolean,
+  /**
+   * 联网数据
+   * */
+  internet_search: Array,
+  deep_answer: String,
+  deep_loading: Boolean
 });
 
+const emit = defineEmits(['toSearchPage']);
+
 /**
  * 内容DOM节点的样式
  * 主要用于控制节点的宽度,避免对话内容中某些内容将对话区域撑开,造成样式异常
@@ -56,7 +77,9 @@ const custom_style = computed(() => {
     return '';
   }
 });
-
+const toSearchPage = () => {
+  emit('toSearchPage');
+};
 const handleThinkClick = () => {
   // 展开/收起
 };
@@ -81,22 +104,4 @@ const handleThinkClick = () => {
 .answer-content-text {
   line-height: 20px;
 }
-.think-select {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  gap: 5px;
-  height: 40px;
-  width: 130px;
-  border-radius: 12px;
-  background: var(--color-fill-2);
-  cursor: pointer;
-  .robot {
-    color: #668ff3;
-    font-size: 16px;
-  }
-  .down {
-    margin-left: 10px;
-  }
-}
 </style>

+ 87 - 0
src/modules/conversation-content/DialogThinkContent.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="think">
+    <a-button class="think-select" v-if="!show_think_page" @click="handleThinkClick">
+      <icon-robot class="robot" />
+      <span>{{ show_think_title }}</span>
+      <icon-down class="down" />
+    </a-button>
+
+    <div class="think_content" v-else>
+      <!-- <div class="content-title"><icon-check-circle class="icon" /></div> -->
+      <div class="content-title" @click="handleThinkClick">
+        <div class="content-title-left">
+          <icon-robot class="robot" />
+          <span>{{ show_think_title }}</span>
+        </div>
+        <icon-up class="down" />
+      </div>
+      <div class="content-text" v-html="deep_answer"></div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+const props = defineProps({
+  deep_loading: Boolean,
+  deep_answer: String
+});
+const show_think_page = ref(true);
+const show_think_title = computed(() => {
+  return props.deep_loading ? '思考中...' : '已完成推理';
+});
+const handleThinkClick = () => {
+  show_think_page.value = !show_think_page.value;
+};
+</script>
+<style scoped>
+.think-select {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 5px;
+  border-radius: 12px;
+  background: var(--color-fill-2);
+  cursor: pointer;
+  .robot {
+    color: #20b759;
+    font-size: 16px;
+  }
+  .down {
+    margin-left: 10px;
+  }
+}
+.think_content {
+  background: #f3f5fc;
+  transition: all 0.2s ease;
+  border-radius: 12px;
+}
+
+.content-title {
+  height: 30px;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 15px;
+  gap: 10px;
+  cursor: pointer;
+  color: var(--color-text-2);
+}
+.content-title:hover {
+  background-color: var(--color-secondary-hover);
+  color: var(--color-text-2);
+}
+.content-title-left {
+  .robot {
+    color: #20b759;
+    margin-right: 10px;
+    /* color: #668ff3; */
+  }
+}
+.content-text {
+  margin-left: 20px;
+  border-left: 1px solid #ccc;
+  padding: 10px 20px;
+}
+</style>

+ 75 - 0
src/modules/conversation-content/DialogWebPage.vue

@@ -0,0 +1,75 @@
+<template>
+  <a-button
+    class="btn"
+    @mouseenter="isHovered = true"
+    @mouseleave="isHovered = false"
+    @click="toSearchPage"
+    v-if="internet_search_len"
+  >
+    <icon-check-circle class="icon" />
+    <span class="btn-text"> {{ buttonText }}</span>
+  </a-button>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+const props = defineProps({
+  is_internet_search: Boolean,
+  internet_search: Array
+});
+const emit = defineEmits(['toSearchPage']);
+const internet_search_len = computed(() => {
+  return props.internet_search.length;
+});
+const isHovered = ref(false);
+const buttonText = computed(() => {
+  return isHovered.value ? '打开网页详情' : `已阅读${internet_search_len.value}个网页`;
+});
+const toSearchPage = () => {
+  emit('toSearchPage');
+};
+</script>
+<style scoped>
+.btn {
+  border-radius: 12px;
+}
+.icon {
+  color: #20b759;
+  font-size: 16px;
+  margin-right: 5px;
+}
+
+/* 基础按钮样式 */
+.btn {
+  position: relative;
+  overflow: hidden;
+  padding: 10px 15px;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10px;
+}
+
+.btn-text {
+  display: inline-block;
+  transition: transform 0.5s ease-in-out;
+}
+
+/* 鼠标悬停时的效果 */
+.btn:hover .btn-text {
+  animation: slide-up 0.5s forwards;
+  /* content: '打开网页详情';  */
+}
+
+@keyframes slide-up {
+  0% {
+    transform: translateY(100%);
+  }
+  100% {
+    transform: translateY(0);
+  }
+}
+</style>

+ 38 - 14
src/modules/conversation-dialog/format-dialog-answer-from-sse_receive_message.js

@@ -13,6 +13,7 @@ import {
 } from './format-dialog-answer-custom-markdown-str';
 
 import formatDialogAnswerAttachedHtmlBeforeRender from './format-dialog-answer-attached-html-before-render';
+import { isDate } from 'lodash-es';
 
 /**
  * 格式化sse发送消息的参数
@@ -25,10 +26,14 @@ import formatDialogAnswerAttachedHtmlBeforeRender from './format-dialog-answer-a
  * @param {object} preMsg - 前一次的消息,如果是流式消息,则需要上一次的Content进行拼接,从而形成新的消息
  * @returns
  */
+let fountThink = false;
+let message_id = '';
 export default function formatDialogAnswerFromSseReceiveMessage(message_obj, preMsg, agentInfo, mixAnswer) {
-  console.log(message_obj, 2222);
   const res_content = message_obj.data;
-
+  if (message_id !== message_obj?.message_id) {
+    fountThink = false;
+    message_id = message_obj?.message_id;
+  }
   // 复制一份原消息内容
   const copyPreMsg = jsonParse(jsonStringify(preMsg));
 
@@ -107,7 +112,8 @@ export default function formatDialogAnswerFromSseReceiveMessage(message_obj, pre
     // sql: '',
     // reference: ''
     // 会话的message_id
-    client_custom_chat_message_id: ''
+    client_custom_chat_message_id: '',
+    deep_answer: ''
     //   client_custom_item_think: false, // 是否有思考
     //   client_custom_item_think_content: '', //思考内容
     //   client_custom_item_internet: false, //是否联网
@@ -126,12 +132,9 @@ export default function formatDialogAnswerFromSseReceiveMessage(message_obj, pre
       currentMsg.client_custom_item_internet_list = res_content.outputs.json;
     }
   }
-  console.log(message_obj.message_id, 3333);
-  if (message_obj.message_id) {
-    currentMsg.client_custom_chat_message_id = message_obj.message_id;
-  }
-  currentMsg.client_custom_chat_message_id = message_obj.message_id || '';
-  console.log(currentMsg.client_custom_chat_message_id, 444);
+  // console.log(message_obj.message_id, 3333);
+
+  // console.log(currentMsg.client_custom_chat_message_id, 444);
 
   /**
    * 将前面websocket发来的数据,合并到当前的消息中
@@ -257,9 +260,26 @@ export default function formatDialogAnswerFromSseReceiveMessage(message_obj, pre
   if (resType === 'stream') {
     // 流消息
     chat_status = 1;
-    currentMsg.answer = currentMsg.answer += res_content.answer;
-
-    // // 拼接数据
+    if (message_obj.isDeep) {
+      if (!fountThink) {
+        currentMsg.client_custom_chat_deep_loading = true;
+        currentMsg.deep_answer = currentMsg.deep_answer += res_content.answer;
+        if (res_content.answer == '</think>') {
+          currentMsg.client_custom_chat_deep_loading = false;
+          fountThink = true;
+        }
+      } else {
+        currentMsg.answer = currentMsg.answer += res_content.answer;
+      }
+    } else {
+      currentMsg.answer = currentMsg.answer += res_content.answer;
+    }
+    // 测试代码
+    // if (!message_obj.isDeep) {
+    //   currentMsg.client_custom_chat_deep_loading = true;
+    //   currentMsg.deep_answer = currentMsg.deep_answer += res_content.answer;
+    // }
+    // // // 拼接数据
     // if (preMsg && preMsg.answer) {
     //   currentMsg.answer = preMsg.answer + image + resMessage;
     // } else {
@@ -447,9 +467,13 @@ export default function formatDialogAnswerFromSseReceiveMessage(message_obj, pre
   //  * 在answer字符串渲染到界面之前,在answer后拼接的html字符串
   //  */
   // currentMsg.client_custom_attached_html_before_render = temp_html_str;
-  console.log(currentMsg.client_custom_chat_message_id, 6666);
+
   formatDialogAnswerAttachedHtmlBeforeRender(currentMsg);
-  console.log(currentMsg.client_custom_chat_message_id, 8888);
+
+  if (message_obj.message_id) {
+    currentMsg.client_custom_chat_message_id = message_obj.message_id;
+  }
+  currentMsg.client_custom_chat_message_id = message_obj.message_id || '';
   return {
     status: chat_status, // 本轮会话的状态:0 未开始 1 进行中 2 已结束 3 错误
     message: chat_msg,

+ 3 - 3
src/modules/conversation-history/api-conversation-history.js

@@ -66,9 +66,9 @@ export const apiConversationHistoryList = (params) => {
 
   const agent_id = params.agent_id;
 
-  if (!agent_id) {
-    return Promise.reject(new Error('前端异常:Invalid Agent ID'));
-  }
+  // if (!agent_id) {
+  //   return Promise.reject(new Error('前端异常:Invalid Agent ID'));
+  // }
 
   const request_url = `/api/v1/chat/sessions`;
 

+ 45 - 8
src/modules/conversation-item/DialogAnswer.vue

@@ -19,6 +19,12 @@
             :is_loading="is_content_loading"
             :max_width="content_max_width"
             :show_think="show_think"
+            :is_internet_search="is_internet_search"
+            :internet_search="internet_search"
+            :is_think="is_think"
+            :deep_answer="deep_answer"
+            :deep_loading="deep_loading"
+            @toSearchPage="toSearchPage"
           >
             <DialogActions
               v-if="answer_status === 2"
@@ -100,7 +106,7 @@ const props = defineProps({
   answer_action_list: Array
 });
 
-const emit = defineEmits(['stop', 'retry', 'select', 'edit']);
+const emit = defineEmits(['stop', 'retry', 'select', 'edit', 'toSearchPage']);
 
 const select_btn_visible = computed(() => {
   return props.show_select === true;
@@ -210,6 +216,41 @@ const answer_html = computed(() => {
   }
 });
 
+/**
+ * 判断是否是联网搜索,
+ * */
+const is_internet_search = computed(() => {
+  const answer = props.answer;
+
+  return answer.client_custom_item_internet;
+});
+/**
+ * 联网搜索内容
+ * */
+const internet_search = computed(() => {
+  const answer = props.answer;
+  return answer.client_custom_item_internet_list;
+});
+// 判断是否是推理内容
+const is_think = computed(() => {
+  const answer = props.answer;
+
+  return answer.client_custom_item_think;
+  // return true;
+});
+
+// 思考内容
+const deep_answer = computed(() => {
+  const answer = props.answer;
+  const str = answer.deep_answer;
+  let answer_string = formatAnwserString(str);
+  return answer_string;
+});
+// 思考内容输出状态
+const deep_loading = computed(() => {
+  const answer = props.answer;
+  return answer.client_custom_chat_deep_loading;
+});
 /**
  * 智能体回答区域容器的class值
  * 通过唯一ID生成,可以保证界面中每一个答案都有一个唯一的容器类名
@@ -258,13 +299,6 @@ const has_refference = computed(() => {
 
   return true;
 });
-/**
- * 是否展示思考过程
- * */
-const show_think = computed(() => {
-  return true;
-});
-
 const handleSelectChange = () => {
   emit('select');
 };
@@ -301,6 +335,9 @@ const handleAnswerRetry = () => {
 const handleAnswerEdit = () => {
   emit('edit');
 };
+const toSearchPage = () => {
+  emit('toSearchPage');
+};
 
 onMounted(() => {
   const container_klass = anwser_klass.value;

+ 15 - 2
src/views/conversation/ConversationChatContainer.vue

@@ -38,6 +38,7 @@
               :is_show_right_page="is_show_right_page"
               @share="handleChatShare"
               @edit="handleEditChat"
+              @toSearchPage="toSearchPage"
               @submit="handleSuggestSubmit"
               :style="style_conversation_chat_list"
             >
@@ -305,8 +306,8 @@ const style_conversation_chat_main_container = computed(() => {
 const style_conversation_chat_list = computed(() => {
   if (is_show_right_page.value || show_word_viewer.value) {
     return {
-      paddingRight: 0 + '%',
-      paddingLeft: 0 + '%',
+      paddingRight: 3 + '%',
+      paddingLeft: 3 + '%',
       boxSizing: 'border-box'
     };
   } else {
@@ -1206,6 +1207,16 @@ const toSubmitNewChat = async (options) => {
  * */
 const search_list = ref([]);
 
+const toSearchPage = (item) => {
+  search_list.value = [...item.client_custom_item_internet_list];
+  if (!is_show_right_page.value) {
+    is_show_right_page.value = true;
+  }
+  if (!show_web_page.value) {
+    show_web_page.value = true;
+  }
+};
+
 /**
  * sseOnMessage处理函数
  * */
@@ -1463,6 +1474,7 @@ defineExpose({
 }
 .conversation-chat-main {
   flex-grow: 1;
+  width: 100%;
   height: 100%;
   /* max-width: 960px; */
   margin-left: auto;
@@ -1477,6 +1489,7 @@ defineExpose({
 .conversation-chat-main-left {
   /* flex-grow: 1; */
   /* flex-basis: 10%; */
+  width: 10%;
   flex-direction: column;
   overflow: hidden;
   height: 100%;