Browse Source

feat:溯源,查看详情页面,pdf定位高亮

yangfeng 1 năm trước cách đây
mục cha
commit
0eb5804292

+ 92 - 138
src/components/DialogWrap.vue

@@ -18,7 +18,7 @@
                 <div :class="['dialog-item', isSetting ? '' : 'dialog-absolute', 'dialog-item-' + key]"
                     v-for="(item, key) in dialogList" :key="item.client_gen_id" :data-item-index="key"
                     :data-item-key="item.client_gen_id">
-                    <div class="dialog-header" v-if="item?.question">
+                    <div class="dialog-header header-question" v-if="item?.question">
                         <!-- <div class="user-name" style="background-color: rgb(255, 110, 110)">
               {{ userName.slice(0, 1) }}
             </div> -->
@@ -31,21 +31,16 @@
                             :agentId="agentId" />
                     </div>
                     <!-- 如果是代码则不显示头像 -->
-                    <div class="dialog-header" v-if="item?.capacity">
+                    <div class="dialog-header  header-answer" :class="{ 'has-code': item.code || item.sql }"
+                        v-if="item?.capacity">
                         <img alt="avatar" src="/src/assets/slider/logo.jpg" width="28" />
                     </div>
 
-                    <!-- 没有处理答案则渲染代码和sql -->
+                    <!-- 处理python代码和SQL代码 -->
                     <div class="dialog-code-footer" v-if="item.code || item.sql">
-                        <div v-if="item.code">
-                            <p class="dialog-code-footer-title" v-if="item.code">Python:</p>
-                            <span v-html="item.code"></span>
-                        </div>
+                        <CodeDisplay v-if="item.code" type="python" :code="item.code" :index="key" />
 
-                        <div v-if="item.sql">
-                            <p class="dialog-code-footer-title" v-if="item.sql">SQL:</p>
-                            <span v-html="item.sql"></span>
-                        </div>
+                        <CodeDisplay v-if="item.sql" type="sql" :code="item.sql" :index="key" />
                     </div>
 
                     <div class="dialog-footer" v-else-if="item?.capacity"
@@ -146,33 +141,7 @@
 
                         <section>
                             <DocRefItem v-for="(knowledge, knowledgeIndex) in item.reference" :key="knowledgeIndex"
-                                :knowledge="knowledge" :knowledgeIndex="knowledgeIndex" @go-document="goKnowledge" />
-
-                            <!-- <div v-for="(knowledge, knowledgeIndex) in item.reference" :key="knowledgeIndex">
-                <a-popover :title="knowledge.doc_name" position="left">
-                  <div @click="openDocuementPage(knowledge)">
-                    <span>{{ knowledgeIndex + 1 }}</span>
-                    <span>{{ knowledge.doc_name }}</span>
-                  </div>
-                  <template #content>
-                    <div class="knowledge-detail">
-                      <div class="knowledge-content">
-                        {{ knowledge.content_ltks }}
-                      </div>
-                      <div class="knowledge-footer">
-                        <p>检索片段</p>
-                        <p>
-                          <span v-for="(content, contentIndex) in knowledge.content_ltks" :key="contentIndex"
-                            :class="{ active: knowledge.page === contentIndex }" @click="() => {
-                              knowledge.page = contentIndex;
-                            }
-                              ">{{ contentIndex + 1 }}</span>
-                        </p>
-                      </div>
-                    </div>
-                  </template>
-                </a-popover>
-              </div> -->
+                                :knowledge="knowledge" :knowledgeIndex="knowledgeIndex" />
                         </section>
                     </div>
 
@@ -261,9 +230,12 @@ import formatDialogItem from './dialog-wrap/format-dialog-item';
 
 import DocRefItem from './dialog-wrap/DocRefItem.vue';
 
+import CodeDisplay from './dialog-wrap/CodeDisplay.vue';
+
 import { useImageUploadStatus } from '../store/index';
 
-import { applyHighLight } from './answer-effect/marked-lib';
+import { formatCodePython, formatCodeSql } from './dialog-wrap/format-code-string';
+
 
 export default defineComponent({
     components: {
@@ -275,7 +247,8 @@ export default defineComponent({
         Question,
         Answer,
 
-        DocRefItem
+        DocRefItem,
+        CodeDisplay
     },
     props: [
         "open",
@@ -324,50 +297,7 @@ export default defineComponent({
         const tempHistory = initTempHistory();
 
         const list = props.list.map((item, index) => {
-            // const client_id = generateClientUniqueId(JSON.stringify(item));
-
             return formatDialogItem(item, index);
-
-            // return {
-            //   /**
-            //    * 客户端生成的ID
-            //    * 此数据作为列表唯一的key
-            //    */
-            //   client_gen_id: client_id,
-
-            //   answer: item.answer && replaceCode(item.answer, index),
-            //   question: item.question,
-            //   // 如果有引用
-            //   reference: (item.reference && item.reference.chunks && item.reference.doc_aggs) ? item.reference.doc_aggs.map(reference => {
-            //     return {
-            //       id: reference.doc_id,
-            //       doc_name: reference.doc_name,
-            //       page: 0,
-            //       content_ltks: [item.reference.chunks.find(chunk => chunk.doc_id == reference.doc_id).content_ltks]
-            //     }
-            //   }) : [],
-
-            //   // 完整的引用数据: pdf文档引用效果需要使用此数据
-            //   fullRefference: item.reference,
-            //   // 合法可用的图片地址
-            //   fullImageUrls: (() => {
-            //     if(item.images){
-            //       return item.images;
-            //     }else{
-            //       return [];
-            //     }
-            //   })(),
-
-            //   // 如果有文件处理完拿第一个,后续有变动再改
-            //   file: (item.files && item.files.length > 0) ? item.files.map(item => {
-            //     const port = window.location.port ? `:${window.location.port}` : '';
-            //     return {
-            //       file_type: 'word',
-            //       file_name: item.file_name,
-            //       file_url: `http://${window.location.hostname}${port}${item.file_url}`,
-            //     }
-            //   })[0] : null,
-            // }
         });
 
         const dialogList = reactive(list || []);
@@ -552,18 +482,6 @@ export default defineComponent({
         onMounted(() => {
             scrollToEnd();
 
-            // 应用代码高亮效果
-            setTimeout(() => {
-                applyHighLight();
-            });
-
-            /**
-             * 应用引用效果
-             * 答案中 引用图标(感叹号)点击时,浮层显示引用内容
-             */
-            // applyRefferencePopover(props.list);
-            // applyImageDisplay(props.list);
-
             /**
              * TODO: 只允许保存一条消息,避免出现多条消息同时发送的情况
              * 当网络状况不佳,用户发送第一条数据时,socket还未连接成功,紧接着又发送第二条数据,第三条数据....
@@ -625,7 +543,16 @@ export default defineComponent({
             // 创建socket链接
             createSocket();
 
-            let temp_step_msg = '';
+            // let temp_step_msg = '';
+
+            /**
+             * 当 websocket 返回的消息 type=stream 时
+             * 消息内容是一个字一个字返回的,此时,前端需要将返回的消息进行拼接,以显示完整的消息内容
+             * 当 type=stream 消息开始时,temp_stream_msg = temp_stream_msg + new_message,
+             * 并在界面上显示 temp_stream_msg 对应的值
+             * 当 type=stream 类型的消息结束时,应清空 temp_stream_msg 数据内容
+             * 以便下一次 type=stream 的消息来临时继续重新拼接
+             */
             let temp_stream_msg = '';
 
             /**
@@ -695,13 +622,13 @@ export default defineComponent({
                         // }
 
                         if (step_message) {
-                            temp_step_msg = temp_step_msg + step_message
-                            temp_stream_msg = '';
+                            temp_stream_msg = temp_stream_msg + step_message
+                            // temp_stream_msg = '';
 
-                            answerData.value = getHTML(temp_step_msg);
+                            answerData.value = getHTML(temp_stream_msg);
                         } else {
                             temp_stream_msg = temp_stream_msg + message;
-                            temp_step_msg = '';
+                            // temp_step_msg = '';
 
                             answerData.value = getHTML(temp_stream_msg);
                         }
@@ -714,6 +641,10 @@ export default defineComponent({
 
                         addAnswer(loadingData.value);
                     }
+                } else {
+                    // 当 type 不是 stream 时,及时清理此值
+                    // 避免下一次拼接时,与前一次拼接冲突
+                    temp_stream_msg = ''
                 }
 
                 if (msg_type === "message") {
@@ -761,25 +692,13 @@ export default defineComponent({
                         }
 
                         if (code) {
-                            const python_code = `
-              <pre>
-              <code class="hljs language-python">
-${code}
-              </code>
-              </pre>
-              `
+                            const python_code = formatCodePython(code);
 
                             answerCode.value = python_code;
                         }
 
                         if (sql) {
-                            const sql_code = `
-              <pre>
-              <code class="hljs language-sql">
-${sql}
-              </code>
-              </pre>
-              `
+                            const sql_code = formatCodeSql(sql);
 
                             answerSql.value = sql_code;
                         }
@@ -806,10 +725,10 @@ ${sql}
 
                     addAnswer(loadingData.value);
 
-                    // 应用代码高亮效果
-                    setTimeout(() => {
-                        applyHighLight();
-                    });
+                    // // 应用代码高亮效果
+                    // setTimeout(() => {
+                    //   applyHighLight();
+                    // });
                 }
 
                 if (msg_type === "close") {
@@ -893,6 +812,13 @@ ${sql}
              * 通过websocket进行发送
              */
             helper.$bus.on("send-message", (value) => {
+                /**
+                 * 当消息发送时,及时清理stream的拼接值
+                 * 避免本次发送,返回的stream数据与前一次返回的stream数据冲突
+                 */
+                temp_stream_msg = '';
+
+
                 dialogConfig.value = helper.dialogConfig;
 
                 const chat_type = props.type; // 对话的类型
@@ -963,6 +889,23 @@ ${sql}
             });
         });
 
+        /**
+         * 检查对话数据记录中是否存在代码(python或者SQL)
+         * 在 智能数据 显示代码时,不用显示用户头像
+         **/
+        const chatItemHasCode = (item) => {
+            if (item) {
+                if (item.code || item.sql) {
+                    console.log('有代码');
+                    return true;
+                }
+            }
+
+            console.log('无代码');
+
+            return false;
+        }
+
         return {
             dialogId: props.dialogId,
 
@@ -992,7 +935,8 @@ ${sql}
             // openDocuementPage,
 
             show_info_btns,
-            chat_type
+
+            chatItemHasCode
         };
     }
 });
@@ -1008,6 +952,11 @@ ${sql}
         font-weight: bold;
     }
 }
+
+/* 如果有代码,则不显示头像 */
+.dialog-header.header-answer.has-code {
+    display: none;
+}
 </style>
 <style scoped>
 /* .user-name {
@@ -1203,21 +1152,26 @@ ${sql}
 
     .dialog-code-footer {
         border-radius: 12px;
-        font-size: 14px;
-        background-color: #32333c;
-        color: #BCBDC3;
-        margin: 16px 0;
-        margin-left: 50px;
+        background-color: rgba(245, 247, 254, 1);
+        padding: 12px 16px;
+        /* margin: 16px 0; */
+        margin: 16px 0 16px 50px;
+    }
 
-        .dialog-code-footer-title {
-            font-size: 16px;
-            font-weight: 500;
-            /* margin: 12px 0; */
-            width: 100%;
-            /* padding: 4px 8px; */
-            /* background-color: #3D3F3F; */
-            color: #BCBDC3;
-        }
+    /* .dialog-code-footer {
+    border-radius: 12px;
+    font-size: 14px;
+    background-color: #32333c;
+    color: #BCBDC3;
+    margin: 16px 0;
+    margin-left: 50px;
+
+    .dialog-code-footer-title {
+      font-size: 16px;
+      font-weight: 500;
+      width: 100%;
+      color: #BCBDC3;
+    }
 
         &:nth-child(2) {
             background-color: rgba(0, 0, 0, 0);
@@ -1348,13 +1302,13 @@ ${sql}
                     display: flex;
                     align-items: center;
 
-                    &:nth-child(odd) {
-                        margin-right: 4%;
-                    }
-                }
-            }
+          &:nth-child(odd) {
+            margin-right: 4%;
+          }
         }
+      }
     }
+  } */
 }
 
 .dialog-flex-wrap {

+ 25 - 25
src/components/answer-effect/marked-lib.js

@@ -1,16 +1,16 @@
 import { Marked } from "marked";
-import { markedHighlight } from "marked-highlight";
-import hljs from 'highlight.js';
+// import { markedHighlight } from "marked-highlight";
+// import hljs from 'highlight.js';
 
-// import 'highlight.js/styles/github.css';
-import 'highlight.js/styles/monokai.css';
+// // import 'highlight.js/styles/github.css';
+// import 'highlight.js/styles/monokai.css';
 
-import javascript from 'highlight.js/lib/languages/javascript';
-import python from 'highlight.js/lib/languages/python';
-import sql from 'highlight.js/lib/languages/sql';
-hljs.registerLanguage('javascript', javascript);
-hljs.registerLanguage('python', python);
-hljs.registerLanguage('sql', sql);
+// import javascript from 'highlight.js/lib/languages/javascript';
+// import python from 'highlight.js/lib/languages/python';
+// import sql from 'highlight.js/lib/languages/sql';
+// hljs.registerLanguage('javascript', javascript);
+// hljs.registerLanguage('python', python);
+// hljs.registerLanguage('sql', sql);
 
 /**
  * @see https://www.npmjs.com/package/marked-highlight
@@ -22,22 +22,22 @@ hljs.registerLanguage('sql', sql);
 // const { Marked } = globalThis.marked;
 // const { markedHighlight } = globalThis.markedHighlight;
 const marked = new Marked(
-    markedHighlight({
-        emptyLangClass: 'hljs',
-        langPrefix: 'hljs language-',
-        highlight(code, lang, info) {
-            const language = hljs.getLanguage(lang) ? lang : 'plaintext';
-            return hljs.highlight(code, { language }).value;
-        }
-    })
+    // markedHighlight({
+    //     emptyLangClass: 'hljs',
+    //     langPrefix: 'hljs language-',
+    //     highlight(code, lang, info) {
+    //         const language = hljs.getLanguage(lang) ? lang : 'plaintext';
+    //         return hljs.highlight(code, { language }).value;
+    //     }
+    // })
 );
 
 export default marked;
 
-export const applyHighLight = () => {
-    // first, find all the div.code blocks
-    document.querySelectorAll('.dialog-item code').forEach(el => {
-        // then highlight each
-        hljs.highlightElement(el);
-    });
-}
+// export const applyHighLight = (container_klass) => {
+//     // first, find all the div.code blocks
+//     document.querySelectorAll(`${container_klass} code`).forEach(el => {
+//         // then highlight each
+//         hljs.highlightElement(el);
+//     });
+// }

+ 75 - 0
src/components/dialog-wrap/CodeDisplay.vue

@@ -0,0 +1,75 @@
+<template>
+<div :class="container_klass">
+    <p class="dialog-code-footer-title">{{cd_title}}:</p>
+    <div v-html="code" class="dialog-code-content"></div>
+</div>
+</template>
+<script setup>
+import { defineComponent, ref,  onMounted, computed } from "vue";
+
+import {applyHighLight} from './format-code-string';
+
+/**
+ * 此组件展示对话中的代码
+ * 例如python代码、SQL代码
+ */
+
+const props = defineProps({
+    type: String, // 代码类型:python/sql
+    code: String, // 代码内容
+    index: Number, // 对应了序号
+});
+
+/**
+ * 容器class
+ */
+const container_klass = computed(() => {
+    return `code-display-${props.type}-${props.index}`;
+});
+
+/**
+ * 代码类型展示
+ */
+const cd_title = computed(() => {
+    if(props.type === 'python'){
+        return 'Python'
+    }
+
+    if(props.type === 'sql'){
+        return 'SQL'
+    }
+
+    return '';
+});
+
+onMounted(() => {
+    applyHighLight(`.${container_klass.value}`);
+});
+</script>
+
+<style scoped>
+  .dialog-code-footer {
+    border-radius: 12px;
+    font-size: 14px;
+    background-color: #32333c;
+    color: #BCBDC3;
+    margin: 16px 0;
+    margin-left: 50px;
+  }
+
+    .dialog-code-footer-title {
+      font-size: 16px;
+      font-weight: 500;
+      /* margin: 12px 0; */
+      width: 100%;
+      /* padding: 4px 8px; */
+      /* background-color: #3D3F3F; */
+      /* color: #BCBDC3; */
+      color: #3D3F3F;
+    }
+
+    .dialog-code-content{
+        margin-top: 5px;
+        margin-bottom: 5px;
+    }
+</style>

+ 33 - 0
src/components/dialog-wrap/format-code-string.js

@@ -0,0 +1,33 @@
+import hljs from 'highlight.js';
+import 'highlight.js/styles/monokai.css';
+
+/**
+ * 应用代码高亮效果
+ * @param {string} container_klass - 代码所在容器的class值
+ */
+export const applyHighLight = (container_klass) => {
+    document.querySelectorAll(`${container_klass} code.hljs`).forEach(el => {
+        hljs.highlightElement(el);
+    });
+}
+
+/**
+ * 格式化对话返回的python代码
+ * 注意!!!此函数体内的代码格式切记注意,不要随意添加换行、空格等,否则会引起代码呈现样式异常
+ * @param {string} code_string 
+ */
+export const formatCodePython = (code_string) => {
+    return `<pre><code class="hljs language-python">
+${code_string}</code></pre>`;
+}
+
+/**
+ * 格式化对话返回的sql代码
+ * 注意!!!此函数体内的代码格式切记注意,不要随意添加换行、空格等,否则会引起代码呈现样式异常
+ * @param {string} code_string 
+ * @returns 
+ */
+export const formatCodeSql = (code_string) => {
+    return `<pre><code class="hljs language-sql">
+${code_string}</code></pre>`
+}

+ 4 - 14
src/components/dialog-wrap/format-dialog-item.js

@@ -1,5 +1,7 @@
 import generateClientUniqueId from './generate-client-unique-id';
 
+import { formatCodePython, formatCodeSql } from './format-code-string';
+
 const replaceCode = (text, index) => {
     const icon = `  <span class="circle" data-list-index="${index || 0}"></span>`
     return text.replace(/##\d\$\$/g, icon);
@@ -50,23 +52,11 @@ export default function formatDialogItem(item, index) {
     };
 
     if (item.code) {
-        result.code = `
-              <pre>
-              <code class="hljs language-python">
-${item.code}
-              </code>
-              </pre>
-        `
+        result.code = formatCodePython(item.code);
     }
 
     if (item.sql) {
-        result.sql = `
-              <pre>
-              <code class="hljs language-sql">
-${item.sql}
-              </code>
-              </pre>
-              `
+        result.sql = formatCodeSql(item.sql);
     }
 
     if (item.image_url) {