Browse Source

Merge branch '2024-09-29/fetaure-toc' into 2024-10-25/fetaure-UpdateConfig

sunxiao 3 weeks ago
parent
commit
885549e62d

+ 1 - 0
components.d.ts

@@ -20,6 +20,7 @@ declare module 'vue' {
     ChatInput: typeof import('./src/components/Chat/ChatInput.vue')['default']
     ChatSite: typeof import('./src/components/Chat/ChatSite.vue')['default']
     ChatText: typeof import('./src/components/Chat/ChatText.vue')['default']
+    ChatTree: typeof import('./src/components/Chat/ChatTree.vue')['default']
     ChatWelcome: typeof import('./src/components/ChatWelcome/index.vue')['default']
     ContactUs: typeof import('./src/components/User/contactUs.vue')['default']
     EditPassword: typeof import('./src/components/Dialog/editPassword.vue')['default']

+ 38 - 0
package-lock.json

@@ -18,11 +18,13 @@
         "katex": "^0.16.10",
         "load-awesome": "^1.1.0",
         "markdown-it": "^14.1.0",
+        "markdown-it-anchor": "^9.2.0",
         "markdown-it-link-attributes": "^4.0.1",
         "markdown-it-math": "^4.1.1",
         "markdown-it-sub": "^2.0.0",
         "markdown-it-sup": "^2.0.0",
         "markdown-it-texmath": "^1.0.0",
+        "markdown-it-toc-done-right": "^4.2.0",
         "naive-ui": "^2.39.0",
         "pinia": "^2.1.7",
         "pinia-plugin-persistedstate": "^3.2.1",
@@ -1551,6 +1553,12 @@
       "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.7.tgz",
       "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="
     },
+    "node_modules/@types/linkify-it": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+      "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+      "peer": true
+    },
     "node_modules/@types/lodash": {
       "version": "4.17.4",
       "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.4.tgz",
@@ -1564,6 +1572,22 @@
         "@types/lodash": "*"
       }
     },
+    "node_modules/@types/markdown-it": {
+      "version": "14.1.2",
+      "resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+      "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+      "peer": true,
+      "dependencies": {
+        "@types/linkify-it": "^5",
+        "@types/mdurl": "^2"
+      }
+    },
+    "node_modules/@types/mdurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz",
+      "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+      "peer": true
+    },
     "node_modules/@types/node": {
       "version": "20.12.12",
       "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.12.12.tgz",
@@ -4941,6 +4965,15 @@
         "markdown-it": "bin/markdown-it.mjs"
       }
     },
+    "node_modules/markdown-it-anchor": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmmirror.com/markdown-it-anchor/-/markdown-it-anchor-9.2.0.tgz",
+      "integrity": "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==",
+      "peerDependencies": {
+        "@types/markdown-it": "*",
+        "markdown-it": "*"
+      }
+    },
     "node_modules/markdown-it-link-attributes": {
       "version": "4.0.1",
       "resolved": "https://registry.npmmirror.com/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz",
@@ -4969,6 +5002,11 @@
       "resolved": "https://registry.npmmirror.com/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz",
       "integrity": "sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg=="
     },
+    "node_modules/markdown-it-toc-done-right": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/markdown-it-toc-done-right/-/markdown-it-toc-done-right-4.2.0.tgz",
+      "integrity": "sha512-UB/IbzjWazwTlNAX0pvWNlJS8NKsOQ4syrXZQ/C72j+jirrsjVRT627lCaylrKJFBQWfRsPmIVQie8x38DEhAQ=="
+    },
     "node_modules/mdn-data": {
       "version": "2.0.14",
       "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz",

+ 2 - 0
package.json

@@ -25,11 +25,13 @@
     "katex": "^0.16.10",
     "load-awesome": "^1.1.0",
     "markdown-it": "^14.1.0",
+    "markdown-it-anchor": "^9.2.0",
     "markdown-it-link-attributes": "^4.0.1",
     "markdown-it-math": "^4.1.1",
     "markdown-it-sub": "^2.0.0",
     "markdown-it-sup": "^2.0.0",
     "markdown-it-texmath": "^1.0.0",
+    "markdown-it-toc-done-right": "^4.2.0",
     "naive-ui": "^2.39.0",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",

+ 36 - 1
src/components/Chat/ChatText.vue

@@ -1,5 +1,5 @@
 <script setup lang="jsx">
-import { computed, ref, watchEffect } from 'vue';
+import { computed, ref, inject, watchEffect } from 'vue';
 import MarkdownIt from 'markdown-it';
 import hljs from 'highlight.js';
 import mila from 'markdown-it-link-attributes';
@@ -8,6 +8,10 @@ import katex from 'katex';
 import * as echarts from 'echarts';
 import markdownItSub from 'markdown-it-sub';
 import markdownItSup from 'markdown-it-sup';
+import anchor from 'markdown-it-anchor';
+import toc from 'markdown-it-toc-done-right';
+
+const updateCatalog = inject('updateCatalog');
 
 const props = defineProps({
   content: {
@@ -52,6 +56,34 @@ mdi.renderer.rules.table_close = function () { return '</table></div>'; };
 mdi.use(markdownItSub);
 mdi.use(markdownItSup);
 
+mdi.use(anchor, {
+  level: [1, 2, 3, 4, 5, 6],
+  permalink: false,
+  permalinkBefore: false,
+  permalinkSymbol: '',
+})
+
+mdi.use(toc, {
+  includeLevel: [1, 2, 3, 4],
+  listClass: 'custom-toc',
+  format: (x, htmlencode) => {
+    return `<span>${htmlencode(x)}</span>`
+  },
+  callback: (_, ast) => {
+    setTimeout(_ => {
+      const domExists = document.querySelector('.table-of-contents');
+      if ( updateCatalog ) {
+        if ( domExists ) {
+          const { c:content } = ast;
+          updateCatalog(content);
+        } else {
+          updateCatalog([]);
+        }
+      }
+    }, 100);
+  }
+})
+
 const transformText = (text) => {
   return text.replace(/\b\w*_\w*\/\w*\W*\w*\b/g, function (match) {
     return `$${match}$`;
@@ -199,5 +231,8 @@ const text = computed(() => {
     margin-bottom: 0;
   }
 
+  .table-of-contents {
+    display: none;
+  }
 }
 </style>

+ 107 - 0
src/components/Chat/ChatTree.vue

@@ -0,0 +1,107 @@
+<script setup name="ChatTree">
+import { ref } from 'vue';
+import { NScrollbar, NTooltip } from 'naive-ui';
+
+const porps = defineProps({
+  data: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const isOpen = ref(true);
+
+const handleOpen = () => {
+  isOpen.value = !isOpen.value;
+}
+
+const getHrefValue = (val) => {
+  return val ? '#' + encodeURIComponent(String(val).trim().toLowerCase().replace(/\s+/g, '-')) : '';
+}
+</script>
+
+<template>
+  <div class="chat-tree-wrapper" :style="{ width: isOpen ? '200px' : '0px' }">
+    <n-tooltip trigger="hover">
+      <template #trigger>
+        <i :class="['icon', isOpen ? '' : 'arrow-left']" @click="handleOpen">
+          <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path
+              d="M5.64645 3.14645C5.45118 3.34171 5.45118 3.65829 5.64645 3.85355L9.79289 8L5.64645 12.1464C5.45118 12.3417 5.45118 12.6583 5.64645 12.8536C5.84171 13.0488 6.15829 13.0488 6.35355 12.8536L10.8536 8.35355C11.0488 8.15829 11.0488 7.84171 10.8536 7.64645L6.35355 3.14645C6.15829 2.95118 5.84171 2.95118 5.64645 3.14645Z"
+              fill="currentColor"></path>
+          </svg>
+        </i>
+      </template>
+      目录
+    </n-tooltip>
+    <NScrollbar style="height: 100%;" class="markdown-body chat-scroll">
+      <ol class="chat-tree-inner" style="list-style: none;">
+        <template v-for="item in data">
+          <li class="">
+            <NTooltip>
+              <template #trigger>
+                <a :href="getHrefValue(item.n)">{{ item.n }}</a>
+              </template>
+              {{ item.n }}
+            </NTooltip>
+            <ChatTree v-if="item.c.length" :data="item.c" />
+          </li>
+        </template>
+      </ol>
+    </NScrollbar>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.chat-tree-wrapper {
+    display: flex;
+    justify-content: flex-end;
+    position: absolute;
+    top: 102px;
+    right: 30px;
+    width: 200px;
+    z-index: 100;
+    border-radius: 5px;
+    box-shadow: 0 2px 4px 0px rgba(0, 0, 0, .06);
+    transition: all 0.3s;
+
+  .icon {
+    position: absolute;
+    top: 50%;
+    left: 0;
+    transform: translate(-50%, -50%);
+    display: inline-block;
+    width: 22px;
+    height: 22px;
+    border-radius: 50%;
+    border: 1px solid #efeff5;
+    fill: currentColor;
+    z-index: 10;
+    background: #fff;
+    cursor: pointer;
+  }
+
+  .arrow-left {
+    transform: rotate(180deg);
+  }
+
+  .chat-scroll {
+    border-radius: 5px;
+    border: 1px solid red;
+  }
+
+  .chat-tree-inner {
+    padding-top: 10px;
+    list-style: none;
+    font-size: 12px;
+    border-radius: 5px;
+
+    li {
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      -o-text-overflow: ellipsis;
+    }
+  }
+}
+</style>

+ 3 - 1
src/components/Chat/index.js

@@ -3,6 +3,7 @@ import ChatAnswer from './ChatAnswer';
 import ChatInput from './ChatInput';
 import ChatBaseCard from './ChatBaseCard';
 import ChatAgentInput from './ChatAgentInput';
+import ChatTree from './ChatTree';
 
 
 export { 
@@ -10,5 +11,6 @@ export {
   ChatAnswer,
   ChatInput,
   ChatBaseCard,
-  ChatAgentInput
+  ChatAgentInput,
+  ChatTree
 };

+ 13 - 1
src/components/Layout/TheChatView.vue

@@ -1,6 +1,5 @@
 <script setup>
 import { ref, unref, computed } from 'vue';
-
 import UserTop from './userTop.vue';
 
 defineProps({
@@ -19,6 +18,10 @@ defineProps({
   isChatSlot: {
     type: Boolean,
     default: true
+  },
+  catalogData: {
+    type: Array,
+    default: () => []
   }
 })
 
@@ -42,6 +45,9 @@ defineExpose({ targetScrollDom });
 
 <template>
   <div class="flex-1 h-full chat-container">
+    <div class="catalog-wrapper" v-if="catalogData.length">
+      <slot name="catalog"></slot>
+    </div>
     <div class="chat-wrapper w-full h-full flex flex-col rounded-[20px]">
       <div class="chat-header flex items-center justify-between py-[24px] px-[18px] ">
         <div class="left_inner" @click="handleClickBack">
@@ -71,8 +77,12 @@ defineExpose({ targetScrollDom });
 
 <style scoped lang="scss">
 .chat-container {
+  position: relative;
   padding: 20px 20px 20px 0;
   overflow: hidden;
+  
+  .catalog-wrapper {
+  }
 
   .chat-header {
     .left_inner {
@@ -112,12 +122,14 @@ defineExpose({ targetScrollDom });
     }
 
     .chat-main {
+      position: relative;
       min-height: calc(100% - 310px);
       color: #1A2029;
 
       .chat-scroll {
         overflow-x: hidden;
         overflow-y: auto;
+        scroll-behavior: smooth;
 
         &::-webkit-scrollbar-thumb,
         &::-webkit-scrollbar-track {

+ 21 - 5
src/views/analyse/WorkOrder.vue

@@ -1,5 +1,5 @@
 <script setup>
-import { ref, unref, computed, onUnmounted, h } from 'vue';
+import { ref, unref, computed, provide, onUnmounted } from 'vue';
 import { useMessage, NDatePicker, NTabs, NTab, NRadioGroup, NRadio, NCheckboxGroup, NCheckbox, NDataTable } from 'naive-ui';
 import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome, SvgIcon } from '@/components';
 import { ChatAsk, ChatAnswer } from '@/components/Chat';
@@ -14,7 +14,7 @@ import dayjs from 'dayjs';
 import { useInfinite, useScroll, useChat } from '@/composables';
 
 const { recordList, isFetching, onScrolltolower, onReset } = useInfinite('/front/bigModel/qa/pageList', { module: 1 });
-const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll();
+const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom, scrollToTop } = useScroll();
 const { chatDataSource, addChat, updateChat, clearChat, updateById } = useChat();
 
 let controller = new AbortController();
@@ -42,10 +42,17 @@ const recordActive = ref(null);
 
 const currenSessionId = ref(null);
 
+const catalogData = ref([]);
+
 const isExistInHistory = computed(() => (recordList.value.findIndex(({ sessionId: sId }) => sId === unref(currenSessionId)) === -1));
 const isChart = computed(() => tabActive.value == 'customDaily');
 const chatDataSourceItem = computed(() => unref(chatDataSource)[chatDataSource.value.length - 1]);
 
+// 目录版块
+provide('updateCatalog', (val) => {
+  catalogData.value = val;
+})
+
 const resetFormData = () => {
   workOrderParams.value = {
     timeBegin: null,
@@ -80,6 +87,8 @@ const handleCreateDialog = async () => {
   if (!unref(chatDataSource).length) {
     return message.info('已切换最新会话');
   }
+  
+  catalogData.value = [];
 
   resetState();
 }
@@ -114,7 +123,7 @@ const handleChatDetail = async ({ sessionId }) => {
 
   resetFormData();
 
-  scrollToBottom();
+  scrollToTop();
 }
 
 // 请求
@@ -330,6 +339,7 @@ const formatData = (data) => {
 
 // 返回
 const handleback = async () => {
+  catalogData.value = [];
   clearInterval(timer);
   await chatApi.getStopChatStream(currenSessionId.value);
   resetState();
@@ -338,6 +348,7 @@ const handleback = async () => {
 // 删除历史对话
 const handeChatDelete = async (id) => {
   await chatApi.deleteHistory(id);
+  catalogData.value = [];
   onReset();
   clearChat();
   message.success('删除成功');
@@ -390,12 +401,17 @@ onUnmounted(() => {
       </div>
     </TheSubMenu>
 
-    <TheChatView ref="scrollRef" :is-footer="false" :is-back-btn="!!chatDataSource.length" @on-click-back="handleback">
+    <TheChatView ref="scrollRef" :is-footer="false" :is-back-btn="!!chatDataSource.length" @on-click-back="handleback" :catalog-data="catalogData">
+      
+      <template #catalog>
+        <ChatTree :data="catalogData"></ChatTree>
+      </template>
+
       <ChatWelcome title="您好,我是LibraAI智慧工单助手" :sub-title="[
         '基于大语言模型的智能工单分析助手,可以为您实现数据分析及数据解读',
         '选择日期并为您生成日报分析'
       ]" v-if="!chatDataSource.length" />
-
+  
       <div class="conversation-item" v-show="chatDataSource.length">
         <div v-for="item in chatDataSource" :key="item.sessionId">
           <ChatAsk :content="item.showVal" :sessionId="item.sessionId"></ChatAsk>

+ 6 - 3
src/views/work/WorkView.vue

@@ -1,8 +1,8 @@
 <script setup>
 import { ref, unref, computed, onMounted, onUnmounted } from 'vue';
 import { useMessage } from 'naive-ui';
-import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome, SvgIcon } from '@/components';
-import { ChatAsk, ChatAnswer, ChatAgentInput } from '@/components/Chat';
+import { BaseButton, RecodeCardItem, TheSubMenu, TheChatView, ChatWelcome } from '@/components';
+import { ChatAsk, ChatAnswer, ChatAgentInput, ChatTree } from '@/components/Chat';
 import { getFormatYesterDay } from '@/utils/format';
 import { chatApi } from '@/api/chat';
 import { helperApi } from '@/api/helper';
@@ -32,6 +32,8 @@ const currenSessionId = ref(null);
 
 const isExistInHistory = computed(() => (recordList.value.findIndex(({ sessionId: sId }) => sId === unref(currenSessionId)) === -1));
 
+
+
 // 新建对话
 const handleCreateDialog = async () => {
   message.destroyAll();
@@ -243,6 +245,7 @@ onUnmounted(() => {
     </TheSubMenu>
 
     <TheChatView ref="scrollRef" :is-back-btn="!!chatDataSource.length" @on-click-back="handleback">
+
       <div v-show="!chatDataSource.length">
         <ChatWelcome title="您好,我是LibraAI智能助手" :sub-title="[
           'LibarAI智能助手模块提供撰写文章、生成报告等服务',
@@ -342,4 +345,4 @@ onUnmounted(() => {
     }
   }
 }
-</style>@/api/order
+</style>