Parcourir la source

政策对比和政策解读功能优化

songxy il y a 2 mois
Parent
commit
ef4cd2f4ae

+ 0 - 1
ais_search_zj/web/public/config.js

@@ -56,7 +56,6 @@
     authServer: '/auth',
     policyServer: 'https://zjugpt.com/server',
     chatServer: '/chat',
-    policyChat: '/aisChat',
     aiServer: '/aiServer',  //省厅
     knowledgeServer: '/aisChat',  //温州
     knowledgeServer2: 'https://natureai.zjugis.com/liqchat',

+ 0 - 172
ais_search_zj/web/src/components/pdf2/PDFReader.vue

@@ -1,172 +0,0 @@
-<template>
-  <div style="width: 100%; height: 100%; position: relative; overflow: hidden">
-    <iframe
-      :src="src"
-      width="100%"
-      height="100%"
-      style="position: relative; border: none"
-      ref="iframeRef"
-      @load="onLoad"
-    ></iframe>
-  </div>
-</template>
-
-<script setup>
-import { message } from 'ant-design-vue';
-import { ref, computed } from 'vue';
-const emits = defineEmits(['match']);
-const iframeRef = ref(null);
-const tryIndex = ref(0);
-const props = defineProps({
-  src: String
-});
-
-// 计算 iframe 的源 URL
-const src = computed(() => {
-  let sanitizedSrc = props.src.replaceAll('&', '%26');
-  return `/lib/pdfjs/web/viewer.html?file=${sanitizedSrc}&t=${new Date().getTime()}`;
-});
-
-// iframe 加载完成后的处理
-const onLoad = () => {
-  setTimeout(() => {
-    const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-    // 确保 PDF.js 的对象已加载
-    if (!doc || !doc.defaultView.PDFViewerApplication) {
-      console.error('PDF.js 未正确加载');
-      return;
-    }
-    const style = doc.createElement('style');
-    style.textContent = `
-   #viewerContainer{
-     overflow-x: hidden;
-   }
-    ::-webkit-scrollbar {
-    /*width: 0;宽度为0隐藏*/
-    width: 6px;
-  }
-  ::-webkit-scrollbar-thumb {
-    border-radius: 6px;
-    height: 60px;
-    background-color: darkgrey;
-  }
-  ::-webkit-scrollbar-track {
-    background: transparent;
-  }
-    `;
-    doc.head.appendChild(style);
-  }, 500);
-};
-
-// 调用 PDF.js 的查找方法
-const searchText = (txt) => {
-  const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-  if (!doc || !doc.defaultView.PDFViewerApplication) {
-    console.error('PDF 未正确加载');
-    return;
-  }
-
-  const PDFViewerApplication = doc.defaultView.PDFViewerApplication;
-
-  // 确保文本查找控件可用
-  if (!PDFViewerApplication.findBar || !PDFViewerApplication.findController) {
-    console.error('PDF 查找功能未正确初始化');
-    return;
-  }
-  // 设置搜索关键字并触发搜索
-  // PDFViewerApplication.findBar.open(); // 打开查找工具栏(可选)
-
-  // 确保 findBar 存在
-  const findBar = PDFViewerApplication.findBar;
-  if (!findBar) {
-    // console.error("findBar 未找到");
-    return;
-  }
-  PDFViewerApplication.findBar.open(); // 打开查找工具栏
-  // 隐藏工具条但不关闭查找功能
-  findBar.bar.classList.add('hidden');
-
-  // 填充搜索框并触发搜索
-  const findInput = findBar.findField; // 获取搜索输入框
-  findInput.value = txt;
-
-  // 手动触发输入事件,模拟用户操作
-  const event = new Event('input', { bubbles: true, cancelable: true });
-  findInput.dispatchEvent(event);
-
-  // 监听查找结果
-  const findController = PDFViewerApplication.findController;
-  const checkMatch = setTimeout(() => {
-    // 检查当前匹配数量
-    const matchCount = findController?._matchesCountTotal;
-
-    if (matchCount === 0) {
-      clearTimeout(checkMatch);
-      // 弹框提示
-      if (tryIndex.value == 0) {
-        tryIndex.value = 1;
-        searchText(txt.substring(5));
-      } else {
-        if (txt) {
-          message.info('未匹配到相关内容!');
-        }
-      }
-      return;
-    }
-
-    if (matchCount > 0) {
-      emits('match', matchCount);
-      clearTimeout(checkMatch);
-    }
-  }, 500);
-};
-
-// 移除高亮(可选功能)
-const removeSourceHighlight = () => {
-  const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-  if (!doc || !doc.defaultView.PDFViewerApplication) {
-    // console.error("PDF.js 未正确加载");
-    return;
-  }
-
-  const PDFViewerApplication = doc.defaultView.PDFViewerApplication;
-
-  // 清空查找结果
-  PDFViewerApplication.findController.executeCommand('find', {
-    query: ''
-  });
-};
-const findNextBtn = () => {
-  const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-  if (!doc || !doc.defaultView.PDFViewerApplication) {
-    console.error('PDF 未正确加载');
-    return;
-  }
-  const PDFViewerApplication = doc.defaultView.PDFViewerApplication;
-  const findBar = PDFViewerApplication.findBar;
-  findBar.findNextButton.click();
-};
-const findPreBtn = () => {
-  const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-  if (!doc || !doc.defaultView.PDFViewerApplication) {
-    console.error('PDF 未正确加载');
-    return;
-  }
-  const PDFViewerApplication = doc.defaultView.PDFViewerApplication;
-  const findBar = PDFViewerApplication.findBar;
-  findBar.findPreviousButton.click();
-};
-
-defineExpose({
-  searchText,
-  removeSourceHighlight,
-  findNextBtn,
-  findPreBtn
-});
-</script>
-

+ 0 - 46
ais_search_zj/web/src/components/pdf2/PDFViewer.vue

@@ -1,46 +0,0 @@
-<template>
-<div style="width: 100%; height: 100%; position: relative;">
-  <iframe v-if="type === 'pdf'" :src="src" width="100%" height="100%" style="position: relative;border: medium none;">
-
-  </iframe>
-
-  <div class="close-icon" @click=" emits('close')">
-    <CloseOutlined />
-  </div>
-</div>
-</template>
-<script setup>
-import { CloseOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
-const emits = defineEmits(['close'])
-const props = defineProps({
-  src: String,
-})
-const type = ref('pdf')
-
-watch(props.src, v => {
-  if(v) {
-    if(v.endsWith('.pdf')) {
-      type.value = 'pdf'
-    } else if(v.endsWith('.txt')) {
-      type.value = 'txt'
-    }
-  }
-})
-
-
-const src = computed(() => {
-  return `/lib/pdfjs/web/viewer.html?file=${props.src}&t=`+ new Date().getTime();
-  // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
-})
-
-</script>
-
-<style scoped lang="scss">
-.close-icon {
-  position: absolute;
-
-  top: 8px;
-  right: 12px;
-  cursor: pointer;
-}
-</style>

+ 0 - 764
ais_search_zj/web/src/components/pdf2/PDFViewerSearch.vue

@@ -1,764 +0,0 @@
-<template>
-  <div style="width: 100%; height: 100%; position: relative; overflow: hidden">
-    <iframe
-      v-if="type === 'pdf'"
-      :src="src"
-      width="100%"
-      scrolling="no"
-      height="100%"
-      style="position: relative; border: medium none"
-      ref="iframeRef"
-      @load="onLoad"
-    >
-    </iframe>
-    <div class="close-icon" @click="emits('close')" v-if="false">
-      <CloseOutlined />
-    </div>
-  </div>
-</template>
-<script setup>
-import { CloseOutlined } from '@ant-design/icons-vue';
-import { message } from 'ant-design-vue';
-import { nextTick } from 'vue';
-const iframeRef = ref(null);
-const emits = defineEmits(['close', 'outline', 'search', 'load']);
-const searchList = ref([]);
-const pageContent = ref([]);
-const pageText = ref([]);
-const props = defineProps({
-  src: String
-});
-const type = ref('pdf');
-
-watch(
-  () => props.src,
-  (v) => {
-    if (v.endsWith('.pdf')) {
-      type.value = 'pdf';
-    } else if (v.endsWith('.txt')) {
-      type.value = 'txt';
-    }
-  },
-  { immediate: true }
-);
-
-const onLoad = () => {
-  const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-  //待定
-  // var toolbar = doc.getElementsByClassName('toolbar');
-  // if (toolbar && toolbar.length > 0) {
-  // toolbar[0].style.display = 'none';
-  // }
-  setTimeout(() => {
-    const script = doc.createElement('script');
-    script.textContent = `
-  window.addEventListener('message', (event) => {
-  const message = event.data;
-  if (message.action === 'locateElement') {
-    const element= document.getElementById('hightlight-'+message.index);
-    debugger;
-    if(element!=null&&element.offsetParent){
-    setTimeout(() => {
-        element.scrollIntoView({ behavior: 'smooth', block: 'center' })
-    }, 10);
-    }
-  }
-  if (message.action === 'mlLocateElement') {
-
-    const elements= document.getElementsByClassName(message.className);
-    if(elements!=null){
-     setTimeout(() => {
-        elements[message.index].scrollIntoView({ behavior: 'smooth', block: 'center' })
-      }, 50);
-    }
-  }
-  });`;
-    doc.body.appendChild(script);
-    const style = doc.createElement('style');
-    style.textContent = `
-  #outerContainer{
-   overflow: hidden;
-  }
-   #page-container{
-  overflow: hidden;
-   }
-   #viewerContainer{
-     overflow: hidden;
-   }
-  .canvasWrapper {
-  position: relative;
-  /* display: flex;
-  justify-content: center; */
-}
-
-.textLayer {
-  text-align: initial;
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-  opacity: 1;
-  line-height: 1;
-  text-size-adjust: none;
-  forced-color-adjust: none;
-}
-.textLayer span,
-.textLayer br {
-  color: transparent;
-  position: absolute;
-  white-space: pre;
-  cursor: text;
-  transform-origin: 0% 0%;
-}
-.highlight-layer {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  pointer-events: none;
-  opacity: 0.15;
-  overflow: hidden;
-}
-.flash {
-  animation: flashing 0.6s;
-  animation-iteration-count: 2;
-}
-@keyframes flashing {
-  0% {
-    opacity: 0;
-  }
-  100% {
-    opacity: 0.15;
-  }
-}
-.highlight {
-  background-color: #0076fa;
-  position: absolute;
-}
-  `;
-    doc.head.appendChild(style);
-  }, 400);
-  setTimeout(() => {
-    var el = doc.getElementsByClassName('page');
-    // var singleHeight = 1263;
-       var singleHeight = 1000;
-    if (iframeRef.value.height != singleHeight * el.length) {
-      iframeRef.value.height = singleHeight * el.length;
-    }
-    setTimeout(() => {
-      pageText.value = [];
-      pageContent.value = [];
-      //获取每页数据
-      var textDivs = doc.getElementsByClassName('textLayer');
-      if (textDivs) {
-        for (var i = 0; i < textDivs.length; i++) {
-          var item = { page: i + 1, txt: textDivs[i].innerText };
-          var items = [];
-          for (var j = 0; j < textDivs[i].children.length; j++) {
-            var citem = {
-              str: textDivs[i].children[j].innerText,
-              width: textDivs[i].children[j].getBoundingClientRect().width,
-              top: textDivs[i].children[j].getBoundingClientRect().top,
-              height: textDivs[i].children[j].getBoundingClientRect().height
-            };
-            items.push(citem);
-          }
-          pageContent.value.push({ page: i + 1, items: items });
-          pageText.value.push(item);
-        }
-      }
-    }, 1000);
-  }, 1000);
-};
-const src = computed(() => {
-  // return `/lib/pdfjs/web/viewer.html?file=${props.src}&t=` + new Date().getTime();
-  return `/lib/pdfjs/web/viewer.html?file=${props.src}&t=` + new Date().getTime();
-
-  // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
-});
-const scrollTo = (item) => {
-  nextTick(() => {
-    setTimeout(() => {
-      if (item.top) {
-        locateElement(item.cs, item.index, 1);
-      } else {
-        //搜索
-        locateElement(item.cs, item.index);
-        //改变选中颜色 其他黄色
-        searchList.value.forEach((citem, cindex) => {
-          const iframeDocument =
-            iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-          const highlightedSpan = iframeDocument.getElementById('hightlight-' + cindex);
-          if (cindex == item.index) {
-            highlightedSpan.parentNode.innerHTML = highlightedSpan.parentNode.innerHTML.replace(
-              'lightblue',
-              'yellow'
-            );
-          } else {
-            highlightedSpan.parentNode.innerHTML = highlightedSpan.parentNode.innerHTML.replace(
-              'yellow',
-              'lightblue'
-            );
-          }
-        });
-      }
-    }, 10);
-  });
-};
-const locateElement = (className, index, type) => {
-  const message = {
-    action: type ? 'mlLocateElement' : 'locateElement',
-    className: className,
-    index: index
-  };
-  iframeRef.value.contentWindow.postMessage(message, '*');
-};
-const gotoElement = (element) => {
-  nextTick(() => {
-    element.scrollIntoView({ behavior: 'smooth', block: 'center' });
-  });
-};
-const highlightText = (str) => {
-  setTimeout(() => {
-    removeHighlight();
-    heightSingle(str);
-  }, 100);
-};
-const goLocation = (txt, flag) => {
-  removeHighlight();
-  searchList.value = [];
-  heightSingle(txt, flag);
-};
-//ai 问答
-const goSourceLocation = (content, type) => {
-  //获取页面
-  removeSourceHightLight();
-  const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-  var pageNumber = 0;
-  if (content.indexOf('-') > -1 && content.indexOf('-') < 3) {
-    content = content.substring(content.indexOf('-') + 1);
-  }
-  var text = content;
-  var ptText = text.replaceAll('\n', '');
-  ptText = ptText.replaceAll(' ', '');
-  console.log(ptText);
-  pageText.value.forEach((item) => {
-    item.txt = item.txt.replaceAll('\n', '');
-    item.txt = item.txt.replaceAll(' ', '');
-    console.log('==页=' + item.txt);
-    console.log('==t=' + ptText);
-    if (ptText.length > 30) {
-      if (item.txt.indexOf(ptText.substring(30, 48)) > -1) {
-        pageNumber = item.page;
-      }
-    } else {
-      if (item.txt.indexOf(ptText.substring(0, 18)) > -1) {
-        pageNumber = item.page;
-      }
-    }
-  });
-  text = text.replaceAll('\n', '');
-  text = text.replaceAll(' ', '');
-  if (text.endsWith('。') || text.endsWith(',') || text.endsWith('、')) {
-    text = text.substring(0, text.length - 1);
-  }
-  if (pageNumber == 0) {
-    message.info('未匹配到相关内容!');
-    return;
-  }
-  //'left: 133.17px; top: 152.408px; font-size: 31.3597px; font-family: sans-serif; transform: scaleX(1.01878);'
-  var arr = doc.getElementsByClassName('textLayer')[pageNumber - 1].getElementsByTagName('span');
-  if (arr) {
-    var hightList = [];
-    var startIndex = 0;
-    var endIndex = 0;
-    var thirdIndex = 0;
-    startIndex = getStartIndex(arr, text, text.length);
-    endIndex = getEndIndex(arr, text, text.length, startIndex);
-    if (startIndex && endIndex && endIndex < startIndex) {
-      endIndex = startIndex + text.length;
-    }
-    var currentPageIndex = 0;
-    var nextArr = [];
-    var thirdArr = [];
-    if (endIndex == undefined) {
-      currentPageIndex = arr.length;
-      if (doc.getElementsByClassName('textLayer')[pageNumber]) {
-        nextArr = doc.getElementsByClassName('textLayer')[pageNumber].getElementsByTagName('span');
-        endIndex = getEndIndex(nextArr, text, text.length, startIndex);
-        if (endIndex == undefined && doc.getElementsByClassName('textLayer')[pageNumber + 1]) {
-          thirdArr = doc
-            .getElementsByClassName('textLayer')
-            [pageNumber + 1].getElementsByTagName('span');
-          thirdIndex = getEndIndex(thirdArr, text, text.length, 0);
-        }
-      } else {
-        endIndex = undefined;
-      }
-    } else {
-      currentPageIndex = endIndex;
-      if (type == 1) {
-        if (endIndex && startIndex && endIndex > startIndex + content.length) {
-          currentPageIndex = startIndex + content.length;
-        }
-        //中间标点匹配问题
-        // var len = startIndex + content.length + 5;
-        // if (startIndex && endIndex && endIndex > (startIndex + content.length + 5)) {
-        //   currentPageIndex = len;
-        // }
-      }
-    }
-    if (!startIndex) {
-      startIndex = 0;
-    }
-    for (var i = startIndex; i < currentPageIndex + 1; i++) {
-      var element = arr[i];
-      if (!element) {
-        break;
-      }
-      var hItem = {};
-      var cssText = element.style.cssText;
-
-      var spanText = element.innerText;
-      var s = spanText.replaceAll(' ', '');
-      var d = spanText.endsWith('。') ? spanText.replaceAll('。', '') : spanText;
-
-      console.log(spanText);
-      if (
-        text.includes(spanText) ||
-        (s && text.includes(s)) ||
-        (d && text.includes(d)) ||
-        (spanText.substring(2) && text.includes(spanText.substring(2)))
-      ) {
-        var topLabel = cssText.split('top: ')[1].split(';')[0];
-        var leftLabel = cssText.split('left: ')[1].split(';')[0];
-        var currentItems = [];
-        pageContent.value.forEach((item) => {
-          if (item.page == pageNumber) {
-            currentItems = item.items;
-          }
-        });
-        for (var m = 0; m < currentItems.length; m++) {
-          //  citem.str=citem.str.replaceAll(' ','');
-          var citem = currentItems[m];
-          if (citem.str == spanText) {
-            hItem.width = parseFloat(citem.width);
-            hItem.height = parseFloat(citem.height);
-            hItem.text = spanText;
-            hItem.left = leftLabel;
-            hItem.top = topLabel;
-            hightList.push(hItem);
-            if (hightList.length == 1) {
-              gotoElement(element);
-            }
-            if (text.endsWith(citem.str)) {
-              break;
-            }
-          }
-        }
-      }
-    }
-    if (type == 1 && hightList.length == 0) {
-      message.info('未匹配到相关内容!');
-    }
-    // if (type == 2) {
-    var nextHightList = [];
-    //跨页 目前只考虑跨1页
-    if (endIndex != undefined) {
-      for (var i = 0; i < nextArr.length; i++) {
-        var element = nextArr[i];
-        var hItem = {};
-        var cssText = element.style.cssText;
-        var spanText = element.innerText;
-        console.log(spanText);
-        if (text.includes(spanText)) {
-          var topLabel = cssText.split('top: ')[1].split(';')[0];
-          var leftLabel = cssText.split('left: ')[1].split(';')[0];
-          var currentItems = [];
-          pageContent.value.forEach((item) => {
-            if (item.page == pageNumber + 1) {
-              currentItems = item.items;
-            }
-          });
-          for (var l = 0; l < currentItems.length; l++) {
-            var citem = currentItems[l];
-            if (citem.str == spanText) {
-              hItem.width = parseFloat(citem.width);
-              hItem.height = parseFloat(citem.height);
-              hItem.text = spanText;
-              hItem.left = leftLabel;
-              hItem.top = topLabel;
-              nextHightList.push(hItem);
-              if (text.endsWith(citem.str)) {
-                break;
-              }
-            }
-          }
-        }
-        if (text.endsWith(spanText)) {
-          break;
-        }
-      }
-    } else {
-      //跨页 目前只考虑跨2页
-      for (var i = 0; i < nextArr.length; i++) {
-        var element = nextArr[i];
-        var hItem = {};
-        var cssText = element.style.cssText;
-        var spanText = element.innerText;
-        console.log(spanText);
-
-        var topLabel = cssText.split('top: ')[1].split(';')[0];
-        var leftLabel = cssText.split('left: ')[1].split(';')[0];
-        var currentItems = [];
-        pageContent.value.forEach((item) => {
-          if (item.page == pageNumber + 1) {
-            currentItems = item.items;
-          }
-        });
-        for (var l = 0; l < currentItems.length; l++) {
-          var citem = currentItems[l];
-          if (citem.str == spanText) {
-            hItem.width = parseFloat(citem.width);
-            hItem.height = parseFloat(citem.height);
-            hItem.text = spanText;
-            hItem.left = leftLabel;
-            hItem.top = topLabel;
-            nextHightList.push(hItem);
-            if (text.endsWith(citem.str)) {
-              break;
-            }
-          }
-        }
-      }
-      var thirdHightList = [];
-      if (thirdIndex != 0 && thirdIndex != undefined) {
-        for (var z = 0; z < thirdIndex + 1; z++) {
-          var element = thirdArr[z];
-          var hItem = {};
-          var cssText = element.style.cssText;
-          var spanText = element.innerText;
-          console.log(spanText);
-          if (text.includes(spanText)) {
-            var topLabel = cssText.split('top: ')[1].split(';')[0];
-            var leftLabel = cssText.split('left: ')[1].split(';')[0];
-            var currentItems = [];
-            pageContent.value.forEach((item) => {
-              if (item.page == pageNumber + 2) {
-                currentItems = item.items;
-              }
-            });
-            for (var l = 0; l < currentItems.length; l++) {
-              var citem = currentItems[l];
-              if (citem.str == spanText) {
-                hItem.width = parseFloat(citem.width);
-                hItem.height = parseFloat(citem.height);
-                hItem.text = spanText;
-                hItem.left = leftLabel;
-                hItem.top = topLabel;
-                thirdHightList.push(hItem);
-                if (text.endsWith(citem.str)) {
-                  break;
-                }
-              }
-            }
-          }
-          if (text.endsWith(spanText)) {
-            break;
-          }
-        }
-      }
-    }
-
-    //渲染
-    var canvas = doc.getElementsByClassName(`page`)[pageNumber - 1];
-    const hightDiv = document.createElement('div');
-    hightDiv.className = 'highlight-layer flash';
-    hightList.forEach((it) => {
-      const hDiv = document.createElement('div');
-      hDiv.className = 'highlight';
-      // 设置内联样式
-      hDiv.style.width = it.width + 'px';
-      hDiv.style.height = it.height * 1.4 + 'px';
-      hDiv.style.left = it.left;
-      hDiv.style.top = parseFloat(it.top.split('%')[0]) - 0.5 + '%';
-      hightDiv.appendChild(hDiv);
-    });
-    canvas.appendChild(hightDiv);
-    if (nextHightList && nextHightList.length > 0) {
-      //渲染
-      var nextCanvas = doc.getElementsByClassName(`page`)[pageNumber];
-      const hightDiv = document.createElement('div');
-      hightDiv.className = 'highlight-layer flash';
-      nextHightList.forEach((it) => {
-        const hDiv = document.createElement('div');
-        hDiv.className = 'highlight';
-        // 设置内联样式
-        hDiv.style.width = it.width + 'px';
-        hDiv.style.height = it.height * 1.4 + 'px';
-        hDiv.style.left = it.left;
-        hDiv.style.top = parseFloat(it.top.split('%')[0]) - 0.5 + '%';
-        hightDiv.appendChild(hDiv);
-      });
-      nextCanvas.appendChild(hightDiv);
-    }
-    if (thirdHightList && thirdHightList.length > 0) {
-      //渲染
-      var thirdCanvas = doc.getElementsByClassName(`page`)[pageNumber + 1];
-      const hightDiv = document.createElement('div');
-      hightDiv.className = 'highlight-layer flash';
-      thirdHightList.forEach((it) => {
-        const hDiv = document.createElement('div');
-        hDiv.className = 'highlight';
-        // 设置内联样式
-        hDiv.style.width = it.width + 'px';
-        hDiv.style.height = it.height * 1.4 + 'px';
-        hDiv.style.left = it.left;
-        hDiv.style.top = parseFloat(it.top.split('%')[0]) - 0.5 + '%';
-        hightDiv.appendChild(hDiv);
-      });
-      thirdCanvas.appendChild(hightDiv);
-    }
-    // }
-  }
-};
-//交集
-function commonContent(str1, str2) {
-  let result = '';
-  for (let i = 0; i < str1.length; i++) {
-    if (str2.includes(str1[i])) {
-      result += str1[i];
-    }
-  }
-  return result;
-}
-const getStartIndex = (arr, text, total) => {
-  var indexs = [];
-  for (var i = 0; i < arr.length; i++) {
-    var element = arr[i];
-    var spanText = element.innerText;
-    console.log('===' + spanText);
-
-    if (
-      (spanText && text.startsWith(spanText)) ||
-      (spanText && spanText.substring(2) && spanText && text.startsWith(spanText.substring(2))) ||
-      (spanText.indexOf('。') > -1 &&
-        spanText.split('。')[1] &&
-        text.startsWith(spanText.split('。')[1])) ||
-      (spanText.endsWith('。') &&
-        spanText.split('。')[0] &&
-        text.startsWith(spanText.split('。')[0]))
-    ) {
-      indexs.push(i);
-      //   return i;
-    }
-  }
-  if (indexs.length == 0) {
-    return 0;
-  } else if (indexs.length == 2) {
-    console.log(JSON.stringify(indexs));
-    return indexs[0] == 0 ? indexs[1] : indexs[0];
-  } else {
-    return indexs[0];
-  }
-  //todo 有问题
-  // if (count == arr.length) {
-  //   start.value = start.value + 1;
-  //   text = text.substring(start.value, total);
-  //   getStartIndex(arr, text, total);
-  // }
-};
-const getEndIndex = (arr, text, total, startIndex) => {
-  for (var i = 0; i < arr.length; i++) {
-    var element = arr[i];
-    var spanText = element.innerText;
-    var e = '';
-    if (spanText.endsWith('。')) {
-      spanText = spanText.substring(0, spanText.lastIndexOf('。'));
-    }
-    if (spanText.indexOf('。') > -1) {
-      e = spanText.substring(0, spanText.lastIndexOf('。'));
-    }
-    console.log('===' + spanText);
-    if (
-      (spanText && text.endsWith(spanText) && spanText.length > 0) ||
-      (spanText && spanText.indexOf(text) > -1) ||
-      (e && text.endsWith(e))
-    ) {
-      return i;
-    }
-  }
-};
-const heightSingle = (str, flag) => {
-  const iframeDocument = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-  const searchText = str;
-  var highlightColor = 'lightblue';
-
-  // 使用DOM操作在iframe中搜索并高亮文本
-  const textNodes = iframeDocument.evaluate(
-    ".//text()[contains(., '" + searchText + "')]",
-    iframeDocument,
-    null,
-    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
-    null
-  );
-  var firstFlag = false;
-  for (let i = 0, length = textNodes.snapshotLength; i < length; i++) {
-    const content = textNodes.snapshotItem(i);
-    const textNode = content.textContent;
-
-    const regex = new RegExp(searchText, 'gi');
-    var newContent = textNode.replace(regex, (match) => {
-      if (match == searchText) {
-        if (!firstFlag) {
-          firstFlag = true;
-          highlightColor = 'yellow';
-        } else {
-          highlightColor = 'lightblue';
-        }
-        return `<span  id='hightlight-${searchList.value.length}' style="background-color: ${highlightColor};color: #333;margin-top:-3px;border-radius: 3px;">${match}</span>`;
-      }
-    });
-    if (content.parentNode) {
-      var n = content.parentNode;
-      console.log('====' + n.offsetTop);
-      newContent = '<' + newContent.split('<')[1] + '</span>';
-      var h = content.parentNode.innerHTML;
-      if (!flag) {
-        content.parentNode.innerHTML = h.replace(searchText, newContent);
-      }
-      if (n) {
-        searchList.value.push({
-          cs: n.parentNode.className,
-          index: searchList.value.length,
-          n: n.parentNode
-        });
-        if (searchList.value.length == 1) {
-          // if (!flag) {
-          //   locateElement(searchList.value[0].cs, 0);
-          // }
-          gotoElement(n);
-        }
-        //大纲只定位一次
-        if (flag) {
-          break;
-        }
-      }
-    }
-    if (!flag) {
-      if (i == textNodes.snapshotLength - 1) {
-        //查询结束
-        emits('search', searchList.value);
-      }
-    }
-  }
-};
-const removeSourceHightLight = () => {
-  //获取页面 ai定位移除
-  const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-
-  const arr = doc.getElementsByClassName('highlight-layer');
-  const l = arr.length;
-  for (let i = l - 1; i >= 0; i--) {
-    if (arr[i] != null) {
-      arr[i].parentNode.removeChild(arr[i]);
-    }
-  }
-};
-const removeHighlight = () => {
-  removeSourceHightLight();
-  if (searchList.value) {
-    searchList.value.forEach((item, index) => {
-      const iframeDocument =
-        iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
-      const highlightedSpan = iframeDocument.getElementById('hightlight-' + index);
-      if (highlightedSpan != null) {
-        const span = highlightedSpan;
-        console.log(span.innerHTML);
-        setTimeout(() => {
-          span.parentNode.replaceChild(document.createTextNode(span.textContent), span);
-        }, 10);
-      }
-    });
-  }
-  searchList.value = [];
-  emits('search', searchList.value);
-};
-defineExpose({ highlightText, removeHighlight, scrollTo, goLocation, goSourceLocation, location });
-</script>
-
-<style scoped lang="scss">
-.close-icon {
-  position: absolute;
-
-  top: 8px;
-  right: 12px;
-  cursor: pointer;
-}
-</style>
-<style>
-/* 不能用scoped,因为动态创建的元素不会被编译带上hash */
-/* 不加上这些css修饰,元素会错位,样式来自vue-pdf-embed这个库 */
-.canvasWrapper {
-  position: relative;
-  /* display: flex;
-  justify-content: center; */
-}
-.page {
-  position: relative;
-  box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.08);
-  margin: 30px auto;
-}
-.textLayer {
-  text-align: initial;
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-  opacity: 0.2;
-  line-height: 1;
-  text-size-adjust: none;
-  forced-color-adjust: none;
-}
-.textLayer span,
-.textLayer br {
-  color: transparent;
-  position: absolute;
-  white-space: pre;
-  cursor: text;
-  transform-origin: 0% 0%;
-}
-.highlight-layer {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  pointer-events: none;
-  opacity: 0.15;
-  overflow: hidden;
-}
-.flash {
-  animation: flashing 0.6s;
-  animation-iteration-count: 2;
-}
-@keyframes flashing {
-  0% {
-    opacity: 0;
-  }
-  100% {
-    opacity: 0.15;
-  }
-}
-.highlight {
-  background-color: #0076fa;
-  position: absolute;
-}
-</style>

+ 0 - 403
ais_search_zj/web/src/components/pdf2/PdfCanvas.vue

@@ -1,403 +0,0 @@
-<template>
-  <div class="pdf-container">
-    <div class="header">
-      <div class="title">{{ title }}</div>
-      <div class="full-screen" @click="fullScreen">
-        <FullscreenOutlined color="red" />
-      </div>
-      <div class="close-icon" @click="emits('close')">
-        <CloseOutlined />
-      </div>
-    </div>
-    <div ref="pdfRef" class="pdf-viewer"></div>
-  </div>
-</template>
-<script setup>
-import { CloseOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
-import { ref, watch } from 'vue';
-import PdfjsWorker from 'pdfjs-dist/build/pdf.worker.js?worker';
-const emits = defineEmits(['close']);
-const props = defineProps({
-  src: { type: String, required: true },
-  content: { type: String, required: true },
-  num: { type: Number, required: false }
-});
-const pdfRef = ref(null);
-const height = ref(0);
-const start = ref(0);
-const pageContent = ref([]);
-const pageText = ref([]);
-// 侦听props的src改变,则立即切换渲染pdf
-watch(
-  () => props.src,
-  () => {
-    setTimeout(() => init(), 1);
-  },
-  { immediate: true }
-);
-const title = computed(() => {
-  return window.formatDocName(decodeURI(decodeURI(props.src)))
-  // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
-});
-// 渲染pdf
-async function init() {
-  var pdfDom = document.getElementsByClassName('pdf-viewer')[0];
-  while (pdfDom.firstChild) {
-    pdfDom.removeChild(pdfDom.firstChild);
-  }
-  // 异步加载pdf.js
-  const PDFJS = await import('pdfjs-dist/build/pdf.js');
-  if (typeof window !== 'undefined' && 'Worker' in window) {
-    PDFJS.GlobalWorkerOptions.workerPort = new PdfjsWorker();
-  }
-  // 加载文档
-  let loadingTask = PDFJS.getDocument({ url: props.src });
-  loadingTask.__PDFDocumentLoadingTask = true;
-  pageContent.value = [];
-  const pdf = await loadingTask.promise; // 使用await等待加载完毕
-  // 循环渲染每一页
-  for (let i = 1; i <= pdf.numPages; i++) {
-    const page = await pdf.getPage(i);
-    await renderPage(page, PDFJS, i);
-  }
-  console.log('pdf页面全部渲染完毕', pdf.numPages, pdf);
-  pageText.value = [];
-  //获取每页数据
-  var textDivs = document.getElementsByClassName('textLayer');
-  if (textDivs) {
-    for (var i = 0; i < textDivs.length; i++) {
-      var item = { page: i + 1, txt: textDivs[i].innerText };
-      pageText.value.push(item);
-    }
-  }
-  if (props.content) {
-    goLocation();
-  }
-}
-
-const fullScreen = () => {
-  const pdfSrc = props.src.replace(window.AppGlobalConfig.knowledgeDocUrlProxy, window.AppGlobalConfig.knowledgeDocUrlProxy2)
-  window.open(`/aisearch/lib/pdfjs/web/viewer.html?file=${decodeURI(pdfSrc)}&t=` + new Date().getTime(), '_blank')
-}
-
-
-const goLocation = () => {
-  //获取页面
-  var pageNumber = 0;
-  var text = props.content;
-
-  var ptText = text.replaceAll('\n', '');
-  ptText = ptText.replaceAll(' ', '');
-  console.log(ptText);
-  pageText.value.forEach((item) => {
-    item.txt = item.txt.replaceAll('\n', '');
-    item.txt = item.txt.replaceAll(' ', '');
-    console.log(item.txt);
-    if (item.txt.indexOf(ptText.substring(0, 18)) > -1) {
-      pageNumber = item.page;
-    }
-  });
-  text = text.replaceAll('\n', '');
-  text = text.replaceAll(' ', '');
-  if (text.endsWith('。') || text.endsWith(',') || text.endsWith('、')) {
-    text = text.substring(0, text.length - 1);
-  }
-  //'left: 133.17px; top: 152.408px; font-size: 31.3597px; font-family: sans-serif; transform: scaleX(1.01878);'
-  var arr = document
-    .getElementsByClassName('textLayer')
-    [pageNumber - 1].getElementsByTagName('span');
-  if (arr) {
-    var hightList = [];
-    var startIndex = 0;
-    var endIndex = 0;
-    startIndex = getStartIndex(arr, text, text.length);
-    endIndex = getEndIndex(arr, text, text.length, startIndex);
-    var currentPageIndex = 0;
-    var nextArr = [];
-    if (endIndex == undefined) {
-      currentPageIndex = arr.length;
-      nextArr = document
-        .getElementsByClassName('textLayer')
-        [pageNumber].getElementsByTagName('span');
-      endIndex = getEndIndex(nextArr, text, text.length, startIndex);
-    } else {
-      currentPageIndex = endIndex;
-    }
-    for (var i = startIndex; i < currentPageIndex; i++) {
-      var element = arr[i];
-      var hItem = {};
-      var cssText = element.style.cssText;
-      var spanText = element.innerText;
-      console.log(spanText);
-      if (text.includes(spanText)) {
-        var topLabel = cssText.split('top: ')[1].split(';')[0];
-        topLabel = topLabel.substring(0, topLabel.length - 2);
-        var currentItems = [];
-        pageContent.value.forEach((item) => {
-          if (item.page == pageNumber) {
-            currentItems = item.items;
-          }
-        });
-        for (var m = 0; m < currentItems.length; m++) {
-          var citem = currentItems[m];
-          if (citem.str == spanText) {
-            hItem.width = parseFloat(citem.width);
-            hItem.height = parseFloat(citem.height);
-            hItem.text = spanText;
-            hItem.left = parseFloat(cssText.substring(6, cssText.indexOf('px')));
-            hItem.top = parseFloat(topLabel);
-            hightList.push(hItem);
-            if (text.endsWith(citem.str)) {
-              break;
-            }
-          }
-        }
-      }
-    }
-    var nextHightList = [];
-    //跨页 目前只考虑跨1页
-    if (endIndex != undefined) {
-      for (var i = 0; i < nextArr.length; i++) {
-        var element = nextArr[i];
-        var hItem = {};
-        var cssText = element.style.cssText;
-        var spanText = element.innerText;
-        console.log(spanText);
-        if (text.includes(spanText)) {
-          var topLabel = cssText.split('top: ')[1].split(';')[0];
-          topLabel = topLabel.substring(0, topLabel.length - 2);
-          var currentItems = [];
-          pageContent.value.forEach((item) => {
-            if (item.page == pageNumber + 1) {
-              currentItems = item.items;
-            }
-          });
-          for (var l = 0; l < currentItems.length; l++) {
-            var citem = currentItems[l];
-            if (citem.str == spanText) {
-              hItem.width = parseFloat(citem.width);
-              hItem.height = parseFloat(citem.height);
-              hItem.text = spanText;
-              hItem.left = parseFloat(cssText.substring(6, cssText.indexOf('px')));
-              hItem.top = parseFloat(topLabel);
-              nextHightList.push(hItem);
-              if (text.endsWith(citem.str)) {
-                break;
-              }
-            }
-          }
-        }
-        if (text.endsWith(spanText)) {
-          break;
-        }
-      }
-    }
-    //定位
-    pdfRef.value.scrollTop = height.value * (pageNumber - 1) + hightList[0]['top'];
-    //渲染
-    var canvas = document.getElementsByClassName(`page`)[pageNumber - 1];
-    const hightDiv = document.createElement('div');
-    hightDiv.className = 'highlight-layer flash';
-    hightList.forEach((it) => {
-      const hDiv = document.createElement('div');
-      hDiv.className = 'highlight';
-      // 设置内联样式
-      hDiv.style.width = it.width * 2 + 'px';
-      hDiv.style.height = it.height * 2 + 'px';
-      hDiv.style.left = it.left + 'px';
-      hDiv.style.top = it.top + 'px';
-      hightDiv.appendChild(hDiv);
-    });
-    canvas.appendChild(hightDiv);
-    if (nextHightList && nextHightList.length > 0) {
-      //渲染
-      var nextCanvas = document.getElementsByClassName(`page`)[pageNumber];
-      const hightDiv = document.createElement('div');
-      hightDiv.className = 'highlight-layer flash';
-      nextHightList.forEach((it) => {
-        const hDiv = document.createElement('div');
-        hDiv.className = 'highlight';
-        // 设置内联样式
-        hDiv.style.width = it.width * 2 + 'px';
-        hDiv.style.height = it.height * 2 + 'px';
-        hDiv.style.left = it.left + 'px';
-        hDiv.style.top = it.top + 'px';
-        hightDiv.appendChild(hDiv);
-      });
-      nextCanvas.appendChild(hightDiv);
-    }
-  }
-};
-const getStartIndex = (arr, text, total) => {
-  var count = 0;
-  for (var i = 0; i < arr.length; i++) {
-    var element = arr[i];
-    var spanText = element.innerText;
-    console.log('===' + spanText);
-    if (text.startsWith(spanText)) {
-      return i;
-    } else {
-      count++;
-    }
-  }
-  if (count == arr.length) {
-    start.value = start.value + 1;
-    text = text.substring(start.value, total);
-    getStartIndex(arr, text, total);
-  }
-};
-const getEndIndex = (arr, text, total, startIndex) => {
-  for (var i = 0; i < arr.length; i++) {
-    var element = arr[i];
-    var spanText = element.innerText;
-    console.log('===' + spanText);
-    if (text.endsWith(spanText) && spanText.length > 1) {
-      return i;
-    }
-  }
-};
-const renderPage = (page, PDFJS, num) => {
-  return new Promise(async (resolve, reject) => {
-    // 先渲染图片
-    let pixelRatio = 1;
-    const viewport = page.getViewport({ scale: 2 });
-    const canvas = document.createElement('canvas');
-    const context = canvas.getContext('2d');
-    canvas.id = `page-${num}`;
-    canvas.height = viewport.height * pixelRatio;
-    console.log('==height', canvas.height);
-    height.value = canvas.height;
-    canvas.width = viewport.width * pixelRatio;
-    const renderContext = {
-      canvasContext: context,
-      viewport: viewport,
-      transform: [pixelRatio, 0, 0, pixelRatio, 0, 0]
-    };
-    await page.render(renderContext).promise;
-    // 再渲染文字
-    const textContent = await page.getTextContent();
-    pageContent.value.push({ page: num, items: textContent.items });
-    const textLayer = document.createElement('div');
-    PDFJS.renderTextLayer({
-      textContent: textContent,
-      container: textLayer,
-      viewport: viewport,
-      transform: [pixelRatio, 0, 0, pixelRatio, 0, 0],
-      textDivs: []
-    });
-    textLayer.className = 'textLayer';
-    // 将文字覆盖在图片上
-    const canvasWrapper = document.createElement('div');
-    canvasWrapper.className = 'canvasWrapper';
-    const pageDiv = document.createElement('div');
-    pageDiv.className = 'page';
-    canvasWrapper.appendChild(canvas);
-    pageDiv.appendChild(canvasWrapper);
-    pageDiv.appendChild(textLayer);
-    pdfRef.value.appendChild(pageDiv);
-    resolve();
-  });
-};
-</script>
-<style scoped lang="scss">
-.pdf-container {
-  width: 100%;
-  height: 100%;
-  position: relative;
-  .header {
-    height: 40px;
-    position: relative;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    border-bottom: 1px solid #eee;
-
-    .title {
-      font-size: 18px;
-      font-weight: bolder;
-    }
-    .close-icon {
-      position: absolute;
-      top: 10px;
-      right: 21px;
-      cursor: pointer;
-    }
-
-    .full-screen {
-      position: absolute;
-      top: 10px;
-      right: 51px;
-      cursor: pointer;
-      color: #0C6FF7;
-    }
-  }
-}
-.pdf-viewer {
-  width: 100%;
-  height: calc(100% - 40px);
-  position: relative;
-  border: 0px solid #ccc;
-  overflow-y: auto;
-}
-</style>
-<style>
-/* 不能用scoped,因为动态创建的元素不会被编译带上hash */
-/* 不加上这些css修饰,元素会错位,样式来自vue-pdf-embed这个库 */
-.canvasWrapper {
-  position: relative;
-  /* display: flex;
-  justify-content: center; */
-}
-.page {
-  position: relative;
-  box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.08);
-  margin: 30px auto;
-}
-.textLayer {
-  text-align: initial;
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-  opacity: 0.2;
-  line-height: 1;
-  text-size-adjust: none;
-  forced-color-adjust: none;
-}
-.textLayer span,
-.textLayer br {
-  color: transparent;
-  position: absolute;
-  white-space: pre;
-  cursor: text;
-  transform-origin: 0% 0%;
-}
-.highlight-layer {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  pointer-events: none;
-  opacity: 0.15;
-  overflow: hidden;
-}
-.flash {
-  animation: flashing 0.6s;
-  animation-iteration-count: 2;
-}
-@keyframes flashing {
-  0% {
-    opacity: 0;
-  }
-  100% {
-    opacity: 0.15;
-  }
-}
-.highlight {
-  background-color: #0076fa;
-  position: absolute;
-}
-</style>

+ 0 - 151
ais_search_zj/web/src/components/pdf2/TxtViewer.vue

@@ -1,151 +0,0 @@
-
-
-<template>
-  <div class="header">
-    <div class="title" :title="title">{{ title }}</div>
-    <div class="full-screen" @click="fullScreen">
-      <FullscreenOutlined color="red" />
-    </div>
-    <div class="close-icon" @click="emits('close')">
-      <CloseOutlined />
-    </div>
-  </div>
-  <div :text-src="src" class="text">
-    <p style="white-space: pre-wrap" v-html="content"></p>
-  </div>
-</template>
-<script setup>
-import axios from 'axios';
-import { CloseOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
-const emits = defineEmits(['close']);
-
-const props = defineProps({
-  src: String,
-  txt: String
-});
-
-const content = ref('');
-
-watch(props.src, (v) => {
-  if (v) {
-    getContent(v);
-  }
-});
-
-const src = computed(() => {
-  getContent(props.src);
-  return `${props.src}`;
-  // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
-});
-
-const fullScreen = () => {
-  const pdfSrc = props.src.replace(window.AppGlobalConfig.knowledgeDocUrlProxy, window.AppGlobalConfig.knowledgeDocUrlProxy2)
-  window.open(`/aisearch/#/viewer/txt?file=${encodeURIComponent(pdfSrc)}`, '_blank')
-}
-
-const title = computed(() => {
-  return window.formatDocName(decodeURI(decodeURI(props.src)))
-  // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
-});
-//高亮+定位
-const goLocation = () => {
-  var text = props.txt.replaceAll('\n\n', '\r\n');
-  text = text.replaceAll('\n', '\r\n');
-  text = text.replaceAll('\r\r', '\r');
-  if (text.startsWith(' \r\n')) {
-    text = text.replace(' \r\n', '');
-  }
-  if (text.endsWith('\r\n')) {
-    text = text.substring(0, text.length - 2);
-  }
-  if (text && content.value.indexOf(text) > -1) {
-    var pre = content.value.substring(0, content.value.indexOf(text));
-    var end = content.value.substring(
-      content.value.indexOf(text) + text.length,
-      content.value.length
-    );
-    var middle = "<span class='heightlight flash' id='hightlight'>" + text + '</span>';
-    content.value = pre + middle + end;
-    setTimeout(() => {
-       var hightlightDiv = document.getElementById('hightlight');
-       hightlightDiv.scrollIntoView({ behavior: "smooth" } );
-    }, 1500);
-  }
-};
-const getContent = (_src) => {
-  content.value = '';
-  axios
-    .get(_src, {
-      responseType: 'text'
-    })
-    .then((res) => {
-      content.value = res.data;
-      if (props.txt) {
-        goLocation();
-      }
-    });
-};
-</script>
-<style scoped lang="scss">
-.header {
-  height: 40px;
-  position: relative;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  border-bottom: 1px solid #eee;
-
-  .title {
-    margin-left: 20px;
-    font-size: 18px;
-    font-weight: bolder;
-    max-width: 90%;
-    white-space: nowrap;
-    /*超出的空白区域不换行*/
-    overflow: hidden;
-    /*超出隐藏*/
-    text-overflow: ellipsis;
-  }
-  .close-icon {
-    position: absolute;
-    top: 10px;
-    right: 21px;
-    cursor: pointer;
-  }
-
-  .full-screen {
-    position: absolute;
-    top: 10px;
-    right: 51px;
-    cursor: pointer;
-    color: #0C6FF7;
-  }
-}
-.text {
-  width: calc(100% - 30px);
-  margin-left: 20px;
-  overflow-y: auto;
-  padding: 20px;
-  max-height: calc(100vh - 310px);
-  border: 1px solid #ccc;
-}
-</style>
-<style >
-.heightlight {
-  background-color: #0076fa;
-  color: white;
-  line-height: 30px;
-}
-.flash {
-  animation: flashing 0.6s;
-  animation-iteration-count: 2;
-}
-@keyframes flashing {
-  0% {
-    opacity: 0.8;
-  }
-  100% {
-    opacity: 0.15;
-  }
-}
-</style>

+ 0 - 498
ais_search_zj/web/src/components/pdf2/WordViewer.vue

@@ -1,498 +0,0 @@
-<template>
-  <div class="word-container">
-    <!-- <button @click="ints">点点就会高亮</button> -->
-
-      <div class="header">
-      <div class="title">{{ title }}</div>
-        <div class="full-screen" @click="fullScreen">
-          <FullscreenOutlined color="red" />
-        </div>
-      <div class="close-icon" @click="emits('close')">
-        <CloseOutlined />
-      </div>
-    </div>
-    <div ref="word" id="fileShow" class="words"></div>
-    <div class="spin" v-if="loading">
-      <a-spin></a-spin>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import axios from 'axios';
-import { CloseOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
-import { defineProps, ref, onMounted, watch, toRefs } from 'vue';
-import { renderAsync } from 'docx-preview'; //要安装 npm install docx-preview
-import JSZip from 'jszip'; //要安装 npm install jszip
-import { number } from 'echarts';
-window.JSZip = JSZip;
-const emits = defineEmits(['close']);
-const loading = ref(true);
-const props = defineProps({
-  src: { type: String, required: true },
-  content: { type: String, required: true },
-  num: { type: Number, required: false },
-  index: { type: Number, required: true }
-});
-const pageContent = ref([]);
-const pageText = ref([]);
-watch(
-  () => props.index,
-  () => {
-    setTimeout(() => inti(), 1);
-  },
-  { immediate: true }
-);
-
-watch(
-  () => props.src,
-  () => {
-    setTimeout(() => inti(), 1);
-  },
-  { immediate: true }
-);
-
-watch(
-  () => props.num,
-  () => {
-    setTimeout(() => inti(), 1);
-  },
-  { immediate: true }
-);
-const title = computed(() => {
-  return window.formatDocName(decodeURI(decodeURI(props.src)))
-  // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
-});
-// word预览
-const inti = () => {
-  axios
-    .get(props.src, { responseType: 'blob' })
-    .then((response) => {
-      const url = window.URL.createObjectURL(new Blob([response.data]));
-      const previewContainer = document.getElementById('fileShow');
-      renderAsync(new Blob([response.data]), previewContainer); //渲染
-      setTimeout(() => {
-        pageText.value = [];
-        pageContent.value = [];
-        //获取每页数据
-        var textDivs = document.getElementsByClassName('docx');
-        if (textDivs) {
-          for (var i = 0; i < textDivs.length; i++) {
-            var item = { page: i + 1, txt: textDivs[i].innerText };
-            var items = [];
-            for (var j = 0; j < textDivs[i].children.length; j++) {
-              var citem = {
-                str: textDivs[i].children[j].innerText,
-                width: textDivs[i].children[j].getBoundingClientRect().width,
-                top: textDivs[i].children[j].getBoundingClientRect().top,
-                height: textDivs[i].children[j].getBoundingClientRect().height
-              };
-              items.push(citem);
-            }
-            pageContent.value.push({ page: i + 1, items: items });
-            pageText.value.push(item);
-          }
-          loading.value = false;
-          setTimeout(() => {
-            if (props.content) {
-              goSourceLocation(props.content);
-              //   ints();
-            }
-          }, 100);
-        }
-      }, 2000);
-    })
-    .catch((error) => {
-      console.error(error);
-    });
-};
-//
-const searchNum = ref(0);
-const indexNum = ref(0);
-const ints = () => {
-  // 多个关键词高亮,循环调用高亮方法
-  let data = [
-    '图1-1国土部门的土地利用规划体系资料来源:张占录.土地利用规划学[M].北京:中国人民大学出版社,2006.(一)土地利用总体规划土地利用总体规划是指人民政府依照法律规定在一定的规划区域内,根据国民经济和第一章导论9',
-    '划定城市增长边界。明确生态环境、土地、水'
-  ];
-  goSourceLocation(data[0]);
-};
-//ai 问答
-const goSourceLocation = (content) => {
-  //获取页面
-  removeSourceHightLight();
-  var list = [];
-  var pageNumber = 0;
-  if (content.indexOf('-') > -1 && content.indexOf('-') < 3) {
-    content = content.substring(content.indexOf('-') + 1);
-  }
-  if (content.startsWith(' ')) {
-    content = content.substring(1);
-  }
-  //   if (content.endsWith('\n\n') && content.replaceAll('\n\n', '').lastIndexOf('\n') != -1) {
-  //     content = content
-  //       .replaceAll('\n\n', '')
-  //       .substring(0, content.replaceAll('\n\n', '').lastIndexOf('\n'));
-  //   }
-  var text = content;
-
-  var ptText = text.replaceAll('\n', '');
-  ptText = ptText.replaceAll(' ', '');
-  console.log(ptText);
-  for (var i = 0; i < pageText.value.length; i++) {
-    var item = pageText.value[i];
-    item.txt = item.txt.replaceAll('\n', '');
-    item.txt = item.txt.replaceAll(' ', '');
-    console.log('==页=' + item.txt);
-    console.log('==t=' + ptText);
-    if (ptText.length > 30) {
-      if (item.txt.indexOf(ptText.substring(30, 48)) > -1) {
-        pageNumber = item.page;
-        break;
-      }
-    } else {
-      if (item.txt.indexOf(ptText.substring(0, 18)) > -1) {
-        pageNumber = item.page;
-        break;
-      }
-    }
-  }
-  text = text.replaceAll('\n', '');
-  text = text.replaceAll(' ', '');
-  if (text.endsWith('。') || text.endsWith(',') || text.endsWith('、')) {
-    text = text.substring(0, text.length - 1);
-  }
-  //'left: 133.17px; top: 152.408px; font-size: 31.3597px; font-family: sans-serif; transform: scaleX(1.01878);'
-  var arr = document.getElementsByClassName('docx')[pageNumber - 1].getElementsByTagName('span');
-  if (arr) {
-    var hightList = [];
-    var startIndex = 0;
-    var endIndex = 0;
-    var thirdIndex = 0;
-    startIndex = getStartIndex(arr, text, text.length);
-    endIndex = getEndIndex(arr, text, text.length, startIndex) + 1;
-    if (endIndex + '' == 'NaN') {
-      endIndex = undefined;
-    }
-    if (startIndex && endIndex && endIndex < startIndex) {
-      endIndex = startIndex + text.length;
-    }
-    var currentPageIndex = 0;
-    var nextArr = [];
-    var thirdArr = [];
-    if (endIndex == undefined) {
-      currentPageIndex = arr.length;
-      if (document.getElementsByClassName('docx')[pageNumber]) {
-        nextArr = document.getElementsByClassName('docx')[pageNumber].getElementsByTagName('span');
-        endIndex = getEndIndex(nextArr, text, text.length, startIndex);
-        if (endIndex == undefined && document.getElementsByClassName('docx')[pageNumber + 1]) {
-          thirdArr = document
-            .getElementsByClassName('docx')
-            [pageNumber + 1].getElementsByTagName('span');
-          thirdIndex = getEndIndex(thirdArr, text, text.length, 0);
-        }
-      } else {
-        endIndex = undefined;
-      }
-    } else {
-      currentPageIndex = endIndex;
-      if (endIndex && startIndex && endIndex > startIndex + content.length) {
-        currentPageIndex = startIndex + content.length;
-      }
-    }
-    if (!startIndex) {
-      startIndex = 0;
-    }
-    for (var i = startIndex; i < currentPageIndex + 1; i++) {
-      var element = arr[i];
-      if (!element) {
-        break;
-      }
-      var hItem = {};
-      var cssText = element.style.cssText;
-
-      var spanText = element.innerText;
-      if (!spanText) {
-        continue;
-      }
-      var d = spanText.endsWith('。') ? spanText.replaceAll('。', '') : spanText;
-      var s = spanText.replaceAll(' ', '');
-      console.log(spanText);
-      if (
-        text.includes(spanText) ||
-        (s && text.includes(s)) ||
-        (d && text.includes(d)) ||
-        spanText.includes(text) ||
-        (spanText.substring(2) && text.includes(spanText.substring(2)))
-      ) {
-        element.classList.add('searchWord');
-        list.push(spanText);
-        if (list.length == 1) {
-          element.scrollIntoView({ behavior: 'smooth', block: 'center' });
-        }
-      }
-    }
-    // if (type == 2) {
-    var nextHightList = [];
-    //跨页 目前只考虑跨1页
-    if (endIndex != undefined) {
-      for (var i = 0; i < nextArr.length; i++) {
-        var element = nextArr[i];
-        var hItem = {};
-        var cssText = element.style.cssText;
-        var spanText = element.innerText;
-        console.log(spanText);
-        var d = spanText.endsWith('。') ? spanText.replaceAll('。', '') : spanText;
-        var s = spanText.replaceAll(' ', '');
-        console.log(spanText);
-        if (
-          text.includes(spanText) ||
-          (s && text.includes(s)) ||
-          (d && text.includes(d)) ||
-          spanText.includes(text) ||
-          (spanText.substring(2) && text.includes(spanText.substring(2)))
-        ) {
-          list.push(spanText);
-        }
-        if (text.endsWith(spanText)) {
-          break;
-        }
-      }
-    } else {
-      //跨页 目前只考虑跨2页
-      for (var i = 0; i < nextArr.length; i++) {
-        var element = nextArr[i];
-        var hItem = {};
-        var cssText = element.style.cssText;
-        var spanText = element.innerText;
-        console.log(spanText);
-
-        list.push(spanText);
-      }
-      var thirdHightList = [];
-      if (thirdIndex != 0 && thirdIndex != undefined) {
-        for (var z = 0; z < thirdIndex + 1; z++) {
-          var element = thirdArr[z];
-          var hItem = {};
-          var cssText = element.style.cssText;
-          var spanText = element.innerText;
-          console.log(spanText);
-          var d = spanText.endsWith('。') ? spanText.replaceAll('。', '') : spanText;
-          var s = spanText.replaceAll(' ', '');
-          console.log(spanText);
-          if (
-            text.includes(spanText) ||
-            (s && text.includes(s)) ||
-            (d && text.includes(d)) ||
-            spanText.includes(text) ||
-            (spanText.substring(2) && text.includes(spanText.substring(2)))
-          ) {
-            list.push(spanText);
-          }
-          if (text.endsWith(spanText)) {
-            break;
-          }
-        }
-      }
-    }
-    // list.forEach((item) => {
-    //   handleSearch(item);
-    // });
-  }
-};
-const getStartIndex = (arr, text, total) => {
-  var indexs = [];
-  for (var i = 0; i < arr.length; i++) {
-    var element = arr[i];
-    var spanText = element.innerText;
-    console.log('===' + spanText);
-    if (
-      (spanText && text.startsWith(spanText)) ||
-      (spanText && spanText.substring(2) && spanText && text.startsWith(spanText.substring(2))) ||
-      (spanText.indexOf('。') > -1 &&
-        spanText.split('。')[1] &&
-        text.startsWith(spanText.split('。')[1]))
-    ) {
-      indexs.push(i);
-      //   return i;
-    }
-  }
-  if (indexs.length == 0) {
-    return 0;
-  } else if (indexs.length == 2) {
-    console.log(JSON.stringify(indexs));
-    return indexs[0] == 0 ? indexs[1] : indexs[0];
-  } else {
-    return indexs[0];
-  }
-  //todo 有问题
-  // if (count == arr.length) {
-  //   start.value = start.value + 1;
-  //   text = text.substring(start.value, total);
-  //   getStartIndex(arr, text, total);
-  // }
-};
-const getEndIndex = (arr, text, total, startIndex) => {
-  for (var i = 0; i < arr.length; i++) {
-    var element = arr[i];
-    var spanText = element.innerText;
-    if(spanText.includes('的名著《政治经济学与赋税原理》中指出:地租仅只是为了使用土地而付给地主的金额') ){
-      debugger;
-    }
-    var e='';
-    if (spanText.endsWith('。')) {
-      spanText = spanText.substring(0, spanText.lastIndexOf('。'));
-    }
-     if (spanText.indexOf('。')>-1) {
-      e = spanText.substring(0, spanText.lastIndexOf('。'));
-    }
-    console.log('===' + spanText);
-    if (
-      (spanText && text.endsWith(spanText) && spanText.length > 0) ||
-      (spanText&&spanText.indexOf(text) > -1)||(e && text.endsWith(e))
-    ) {
-      return i;
-    }
-  }
-};
-const removeSourceHightLight = () => {
-  //获取页面 ai定位移除
-
-  const arr = document.getElementsByClassName('searchWord');
-  const l = arr.length;
-  for (let i = l - 1; i >= 0; i--) {
-    if (arr[i] != null) {
-      arr[i].classList.remove('searchWord');
-    }
-  }
-};
-// 高亮方法,可以使用vue所有页面
-const handleSearch = (e: any) => {
-  searchNum.value = 0; //本页有多少个关键词
-  indexNum.value = 0; //当前关键词序号
-  let searchVal = e;
-  //words是内容区域的类名,我这个是word文档的类名
-  let main = <any>document.querySelector('.words');
-  let innerHTML = main.innerHTML;
-
-  // 每次搜索之前都需要将em替换回来,不然就会出现em里面套em的情况而导致em数量一直叠加,
-  //多个关键词这点可以去掉,不然只显示最后一个关键词高亮
-  //   let emReg1 = new RegExp('<em class="searchWord">', 'g');
-  //   innerHTML = innerHTML.replace(emReg1, '');
-  //   let emReg2 = new RegExp('</em>', 'g');
-  //   innerHTML = innerHTML.replace(emReg2, '');
-  //
-  //删除当前关键词的背景色
-  //多个关键词这点可以去掉,不然只显示最后一个关键词高亮
-  //   let emReg3 = new RegExp('<em class="currentSearchWord">', 'g');
-  //   innerHTML = innerHTML.replace(emReg3, '');
-  //   let emReg4 = new RegExp('</em>', 'g');
-  //   innerHTML = innerHTML.replace(emReg4, '');
-  try {
-    if (searchVal) {
-      // 设置本次搜索背景色
-      let reg = new RegExp(searchVal, 'i');
-      innerHTML = innerHTML.replace(reg, '<em class="searchWord">' + searchVal + '</em>');
-    }
-    main.innerHTML = innerHTML;
-  } catch (e) {
-    console.log(e);
-  }
-};
-const searchGaoLian = (e: any) => {
-  let searchVal = e;
-  searchNum.value = document.getElementsByTagName('em').length;
-  if (searchNum.value != 0) {
-    // 使用.style.background会出现em删不掉会一直叠加的情况 因此只能替换innerHTML
-    document.getElementsByTagName('em')[0].innerHTML =
-      '<em class="currentSearchWord">' + searchVal + '</em>';
-    // 滚动到第一个关键字位置
-    document.getElementsByTagName('em')[0].scrollIntoView({
-      block: 'start',
-      behavior: 'smooth'
-    });
-  }
-};
-
-const fullScreen = () => {
-  const pdfSrc = props.src.replace(window.AppGlobalConfig.knowledgeDocUrlProxy, window.AppGlobalConfig.knowledgeDocUrlProxy2)
-  window.open(`/aisearch/#/viewer/word?file=${encodeURIComponent(pdfSrc)}`, '_blank')
-}
-//生命周期显示word
-onMounted(() => {
-  inti();
-});
-</script>
-<style lang="scss" scoped>
-.word-container {
-  width: 100%;
-  height: 100%;
-
-  //position: relative;
-
-  .spin {
-    width: 100%;
-    display: flex;
-    margin-top: 200px;
-    justify-content: center;
-    align-items: center;
-  }
-  .header {
-    height: 40px;
-    position: absolute;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    border-bottom: 1px solid #eee;
-    top:0;
-    z-index: 10001;
-    background: #fff;
-    width: 100%;
-
-    .title {
-      font-size: 18px;
-      font-weight: bolder;
-    }
-    .close-icon {
-      position: absolute;
-      top: 10px;
-      right: 21px;
-      cursor: pointer;
-    }
-    .full-screen {
-      position: absolute;
-      top: 10px;
-      right: 51px;
-      cursor: pointer;
-      color: #0C6FF7;
-    }
-  }
-  .words {
-    overflow: auto;
-    margin-top: 40px;
-    max-height: 1200px;
-  }
-}
-</style>
-<style s>
-.searchWord {
-  background-color: #0076fa;
-  line-height: 40px;
-  opacity: 0.8;
-  color: white !important;
-}
-.docx-wrapper {
-  background: white !important;
-}
-.docx-wrapper .docx {
-  width: 100% !important;
-
-  padding: 50px !important;
-}
-.docx-wrapper .docx span {
-  font-size: 20px !important;
-}
-.docx-wrapper > section.docx {
-  box-shadow: unset !important;
-}
-</style>

+ 6 - 0
ais_search_zj/web/src/utils/common.js

@@ -98,4 +98,10 @@ export function findPath (tree, targetId) {
   }
 
   return null;
+}
+
+export const getNumAll = (str) => {
+  return Array.from(str.matchAll(/\[\[(\d+)\]\]/g), match => {
+      return parseInt(match[1])
+  });
 }

+ 1 - 8
ais_search_zj/web/src/views/ai-home/index.vue

@@ -389,6 +389,7 @@ import { message } from 'ant-design-vue';
 import { h, ref, reactive, watch } from 'vue';
 import ManagerAPI from '@/api/manager';
 import PubsubService from '@/utils/PubsubService';
+import { getNumAll } from '@/utils/common';
 import { useUserStore } from '@/stores';
 import DraggableSplitter from "./components/DraggableSplitter.vue";
 
@@ -1024,9 +1025,6 @@ const openDocByIndex = (ind, id) => {
       '=policy&',
       activeTab.value === 'paper' ? '=compose_paper_material_total&' : '=policy&'
   )
-  console.log("knowledgeDocUrl-------------------------")
-  console.log(knowledgeDocUrl)
-  console.log(link)
   if(link.indexOf(knowledgeDocUrl) != -1) {
     pdfSrc.value = link.replace(knowledgeDocUrl,
       window.AppGlobalConfig.knowledgeDocUrlProxy.replace(
@@ -1059,11 +1057,6 @@ const getNum = (str) => {
     return null;
   }
 };
-const getNumAll = (str) => {
-  return Array.from(str.matchAll(/\[\[(\d+)\]\]/g), match => {
-      return parseInt(match[1])
-  });
-}
 
 const sessionId = ref('');
 const sessionCreate = () => {

+ 91 - 87
ais_search_zj/web/src/views/zcdb/components/aiAssistant.vue

@@ -4,61 +4,62 @@
       <div class="name"><img src="/images/zczk/icon-ai-g.gif" /> AI智能助手</div>
     </div>
     <div class="question">
-      <div class="item">
-        <div class="item-title">
-          <div class="name">
-            文件内容太多,读起来太累了?可以试着让我来帮您解读。<span>你可以试着这样问我:</span>
+      <div class="chat_box" id="scrollArea">
+        <div class="item">
+          <div class="item-title">
+            <div class="name">
+              文件内容太多,读起来太累了?可以试着让我来帮您解读。<span>你可以试着这样问我:</span>
+            </div>
           </div>
-        </div>
-        <div class="value-panel">
-          <div
-            class="citem"
-            v-for="(item, index) in question"
-            :key="index"
-            @click="toggleQuestion(index)"
-          >
-            <div class="value">{{ item }}</div>
+          <div class="value-panel">
+            <div
+              class="citem"
+              v-for="(item, index) in question"
+              :key="index"
+              @click="toggleQuestion(index)"
+            >
+              <div class="value">{{ item }}</div>
+            </div>
           </div>
         </div>
-      </div>
-      <div class="chat">
-        <div class="item_box">
-          <div class="item" v-for="(item, index) in form.chatDesc" :key="index">
-            <div class="user-panel">
-              <div class="name">{{ item.name }}</div>
-              <img src="/images/zczk/user.png" />
-            </div>
-            <div class="ai-panel">
-              <img src="/images/zczk/icon-ai-title.png" />
-              <div class="desc" v-if="item.content" id="scrollArea">
-                <vue-markdown-it
-                  :source="item.content"
-                  :options="{
-                    html: true,
-                    linkify: true
-                  }"
-                />
+        <div class="chat">
+          <div class="item_box">
+            <div class="item" v-for="(item, index) in form.chatDesc" :key="index">
+              <div class="user-panel">
+                <div class="name">{{ item.name }}</div>
+                <img src="/images/zczk/user.png" />
+              </div>
+              <div class="ai-panel">
+                <img src="/images/zczk/icon-ai-title.png" />
+                <div class="desc" v-if="item.content">
+                  <vue-markdown-it
+                    :source="item.content"
+                    :options="{
+                      html: true,
+                      linkify: true
+                    }"
+                  />
+                </div>
+                <div class="desc animation" v-else>正在思考中<span class="dots"></span></div>
               </div>
-              <div class="desc animation" v-else>正在思考中<span class="dots"></span></div>
             </div>
           </div>
         </div>
-
-        <div class="input-panel">
-          <a-textarea
-            v-model:value="form.keyoword"
-            placeholder="请输入"
-            :auto-size="{ minRows: 3, maxRows: 3 }"
-            @keydown.enter="keydownEnter"
-          >
-          </a-textarea>
-          <div class="send" @click="send">发送<img src="/images/zczk/icon-white-send.png" /></div>
-        </div>
+      </div>
+      <div class="input-panel">
+        <a-textarea
+          v-model:value="form.keyoword"
+          placeholder="请输入"
+          :auto-size="{ minRows: 3, maxRows: 3 }"
+          @keydown.enter="keydownEnter"
+        >
+        </a-textarea>
+        <div class="send" @click="send">发送<img src="/images/zczk/icon-white-send.png" /></div>
       </div>
     </div>
   </div>
 </template>
-<script lang="ts" setup>
+<script setup>
 /**
  * @description ai对比助手
  */
@@ -67,11 +68,13 @@ import { fetchEventSource } from '@microsoft/fetch-event-source';
 import { message } from 'ant-design-vue';
 import { VueMarkdownIt } from '@f3ve/vue-markdown-it';
 import PubsubService from '@/utils/PubsubService';
-const zoom = ref(false);
-interface IProps {
-  data: [];
-}
-const props = defineProps<IProps>();
+import { getNumAll } from '@/utils/common';
+const props = defineProps({
+  data: {
+    type: Array,
+    default: ()=>[]
+  }
+});
 const { data } = toRefs(props);
 onMounted(() => {});
 
@@ -82,7 +85,7 @@ const question = [
   '请帮我总结下每份文件中的关于XXX的主要做法,并分析各自特点?'
 ];
 const docs = ref([]);
-const form = ref<any>({
+const form = ref({
   url: '',
   loading: false,
   activeIndex: -1,
@@ -108,10 +111,6 @@ const keydownEnter = (e) => {
     e.preventDefault();
   }
 };
-const toggleZoom = () => {
-  zoom.value = !zoom.value;
-  PubsubService.publish('change-view', { value: zoom.value });
-};
 const send = () => {
   if (!form.value.keyoword) {
     message.info('请输入!');
@@ -123,23 +122,27 @@ const send = () => {
   form.value.chatDesc.push(item);
   query();
 };
+const timers = ref([])  //缓存定时器
+const timerIndex = ref(0)
 const query = async () => {
   scrollToBottom();
   form.value.aiLoading = true;
-  const params: any = new FormData();
-  data.value.forEach((t: any) => {
+  const params = new FormData();
+  data.value.forEach((t) => {
     params.append('files', t.file);
   });
   params.append('stream', true);
   params.append('query', form.value.query);
+  timers.value = []
+  timerIndex.value = 0
   await fetchEventSource(
-    (window as any)?.AppGlobalConfig?.knowledgeServer + '/chat/complete_file_chat',
+    (window)?.AppGlobalConfig?.knowledgeServer + '/chat/complete_file_chat',
     {
       method: 'POST',
       openWhenHidden: true,
       body: params,
       signal: ctr.signal,
-      async onmessage(msg: any) {
+      async onmessage(msg) {
         if (msg) {
           if (msg.code && msg.code == 101) {
             message.info(msg.msg);
@@ -154,28 +157,31 @@ const query = async () => {
                 form.value.aiLoading = false;
               }, 2000);
               var con = rData.choices[0].delta.content;
-              if (rData.status == 3) {
+              if (rData.status == 2) {
+                let timer = setTimeout(() => {
+                  form.value.chatDesc[form.value.chatDesc.length - 1].content += con;
+                  scrollToBottom();
+                  clearTimeout(timer)
+                }, timerIndex.value * 15)
+                timers.value.push(timer)
+                timerIndex.value++;
+              }else if (rData.status == 3) {
+                timers.value.forEach((timer) => {
+                  clearTimeout(timer);
+                })
+                let nums = getNumAll(con);
+                nums.forEach(num => {
+                  con = con.replace(
+                        `[[${num}]]`,
+                        `<span onclick="window.openDocByIndex(${num})" class="poi" style="cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 14px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 4px;background: #fff;color:#435C80;border: 1px solid #BACAE3">${num}</span>`
+                    )
+                })
                 form.value.chatDesc[form.value.chatDesc.length - 1].content = con;
-              } else {
-                form.value.chatDesc[form.value.chatDesc.length - 1].content += con;
-              }
-              if (rData.status == 3) {
-                let num = getNum(form.value.chatDesc[form.value.chatDesc.length - 1].content);
-                while (num) {
-                  form.value.chatDesc[form.value.chatDesc.length - 1].content = form.value.chatDesc[
-                    form.value.chatDesc.length - 1
-                  ].content.replace(
-                    `[[${num}]]`,
-                    `<span onclick="window.openDocByIndex(${num}, ${form.value.activeIndex})" class="poi" style="    cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 14px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 4px;background: #fff;color:#435C80;border: 1px solid #BACAE3">${num}</span>`
-                  );
-
-                  num = getNum(form.value.chatDesc[form.value.chatDesc.length - 1].content);
-                }
+                scrollToBottom();
                 if (rData.docs) {
                   handleDocs(rData.docs);
                 }
               }
-              scrollToBottom();
             }
           } catch (e) {
             console.log('===' + e.message);
@@ -229,19 +235,9 @@ const handleDocs = (data) => {
     });
   }
 };
-const getNum = (str) => {
-  const matches = str.match(/\[\[(\d+)\]\]/);
-
-  if (matches) {
-    return matches[1]; // 输出: 2
-  } else {
-    return null;
-  }
-};
 
 const scrollToBottom = async () => {
   // 滚动到底部
-
   await nextTick(() => {
     const scrollEle = document.getElementById('scrollArea');
 
@@ -304,7 +300,17 @@ const scrollToBottom = async () => {
     position: relative;
     padding: 10px;
     height: calc(100% - 60px);
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    .chat_box {
+      flex: 1;
+      height: 0px;
+      overflow-y: auto;
+    }
     .item {
+      display: flex;
+      flex-direction: column;
       // display: flex;
       // width: 521px;
       border-radius: 8px 8px 8px 8px;
@@ -353,8 +359,6 @@ const scrollToBottom = async () => {
       }
     }
     .chat {
-      height: calc(100% - 170px);
-
       font-family: PingFang SC;
       font-weight: 500;
       margin: 7px 0px 0px 0px;

+ 89 - 122
ais_search_zj/web/src/views/zjjd/index.vue

@@ -37,7 +37,7 @@
         </div>
       </div>
       <div class="affix_box" :offsetTop="20">
-        <div class="right-panel" :class="{ zm: zoomFlag }">
+        <div class="right-panel">
           <div class="ai-interpretation">
             <div class="check-type">
               <div class="check">
@@ -48,10 +48,10 @@
               </div>
             </div>
             <div class="result">
-              <div class="content" v-if="checkType == 'interpretation'">
+              <div class="content" id="scrollArea" v-if="checkType == 'interpretation'">
                 <a-spin class="spin" v-if="aiLoading"></a-spin>
                 <template v-else>
-                  <div class="desc" id="scrollArea">
+                  <div class="desc">
                     <vue-markdown-it
                       :source="content"
                       v-if="content"
@@ -64,45 +64,43 @@
                 </template>
               </div>
               <div class="question" v-else>
-                <div class="item">
-                  <div class="value-panel">
-                    <div class="name">
-                      文件内容太多,读起来太累了?可以试着让我来帮您解读。<span
-                        >你可以试着这样问我:</span
+                <div class="chat_box" id="chat_box">
+                  <div class="item">
+                    <div class="value-panel">
+                      <div class="name">
+                        文件内容太多,读起来太累了?可以试着让我来帮您解读。<span
+                          >你可以试着这样问我:</span
+                        >
+                      </div>
+                      <div
+                        class="citem"
+                        v-for="(item, index) in question"
+                        :key="index"
+                        @click="toggleQuestion(index)"
                       >
-                    </div>
-                    <div
-                      class="citem"
-                      v-for="(item, index) in question"
-                      :key="index"
-                      @click="toggleQuestion(index)"
-                    >
-                      <div class="value">{{ item }}</div>
+                        <div class="value">{{ item }}</div>
+                      </div>
                     </div>
                   </div>
-                </div>
-                <div class="scale-panel" @click="toggleZoom" v-if="!zoomFlag">
-                  视窗太小,点击<span>放大</span>
-                </div>
-                <div class="scale-panel" @click="toggleZoom" v-else><span> 返回小视窗</span></div>
-                <div class="chat">
-                  <div class="item" v-for="(item, index) in chatDesc" :key="index">
-                    <div class="user-panel">
-                      <div class="name">{{ item.name }}</div>
-                      <img src="/images/zczk/user.png" />
-                    </div>
-                    <div class="ai-panel">
-                      <img src="/images/zczk/icon-ai-title.png" />
-                      <div class="desc" v-if="item.content" id="scrollArea-1">
-                        <vue-markdown-it
-                          :source="item.content"
-                          :options="{
-                            html: true,
-                            linkify: true
-                          }"
-                        />
+                  <div class="chat">
+                    <div class="item" v-for="(item, index) in chatDesc" :key="index">
+                      <div class="user-panel">
+                        <div class="name">{{ item.name }}</div>
+                        <img src="/images/zczk/user.png" />
+                      </div>
+                      <div class="ai-panel">
+                        <img src="/images/zczk/icon-ai-title.png" />
+                        <div class="desc" v-if="item.content" id="scrollArea-1">
+                          <vue-markdown-it
+                            :source="item.content"
+                            :options="{
+                              html: true,
+                              linkify: true
+                            }"
+                          />
+                        </div>
+                        <div class="desc animation" v-else>正在思考中<span class="dots"></span></div>
                       </div>
-                      <div class="desc animation" v-else>正在思考中<span class="dots"></span></div>
                     </div>
                   </div>
                 </div>
@@ -137,6 +135,7 @@ import HomeHeader from '@/views/home/components/HomeHeader.vue';
 import PDFViewer from '@/components/pdf/PDFViewerSearch.vue';
 import ProgressBar from '@/components/ProgressBar.vue';
 import api from '@/utils/policy-api';
+import { getNumAll } from '@/utils/common';
 import { message } from 'ant-design-vue';
 import { fetchEventSource } from '@microsoft/fetch-event-source';
 import { VueMarkdownIt } from '@f3ve/vue-markdown-it';
@@ -150,7 +149,6 @@ const aiLoading = ref(false);
 const queryText = ref('分别给出该文章的全文综述和要点总结');
 const content = ref('');
 const loading = ref(false);
-const zoomFlag = ref(false);
 const ifRef = ref(null);
 const docs = ref([]);
 const question = [
@@ -177,7 +175,6 @@ onMounted(async () => {
         query();
       }, 100);
     }
-    console.log('--url-');
   }
 });
 const getFileByID = (id) => {
@@ -236,13 +233,11 @@ const send = () => {
   var item = { name: queryText.value, content: '' };
   chatDesc.value.push(item);
   query();
+  scrollToBottom('chat_box')
 };
 const toggleQuestion = (index) => {
   keyword.value = question[index];
 };
-const toggleZoom = () => {
-  zoomFlag.value = !zoomFlag.value;
-};
 const keydownEnter = () => {
   send();
 };
@@ -277,25 +272,11 @@ const uploadFile = () => {
   queryText.value = '分别给出该文章的全文综述和要点总结';
   query();
 };
-const scrollToBottom = async () => {
+const scrollToBottom = async (idDom) => {
   // 滚动到底部
-
+  if (!idDom) return;
   await nextTick(() => {
-    const scrollEle = document.getElementById('scrollArea');
-
-    if (scrollEle != null) {
-      scrollEle.scrollTo({
-        top: scrollEle.scrollHeight - scrollEle.clientHeight + 50,
-        behavior: 'smooth'
-      });
-    }
-  });
-};
-const scrollToBottom1 = async () => {
-  // 滚动到底部
-
-  await nextTick(() => {
-    const scrollEle = document.getElementById('scrollArea-1');
+    const scrollEle = document.getElementById(idDom);
 
     if (scrollEle != null) {
       scrollEle.scrollTo({
@@ -348,22 +329,17 @@ const handleDocs = (data) => {
     });
   }
 };
-const getNum = (str) => {
-  const matches = str.match(/\[\[(\d+)\]\]/);
-
-  if (matches) {
-    return matches[1]; // 输出: 2
-  } else {
-    return null;
-  }
-};
+const timers = ref([])  //缓存定时器
+const timerIndex = ref(0)
 const query = async () => {
   var params = new FormData();
   aiLoading.value = true;
   params.append('files', fileDetail.value);
   params.append('stream', true);
   params.append('query', queryText.value);
-  await fetchEventSource(window.AppGlobalConfig.policyChat + '/chat/complete_file_chat', {
+  timers.value = []
+  timerIndex.value = 0
+  await fetchEventSource(window.AppGlobalConfig.aiServer + '/chat/complete_file_chat', {
     method: 'POST',
     openWhenHidden: true,
     body: params,
@@ -373,59 +349,46 @@ const query = async () => {
         try {
           aiLoading.value = false;
           const rData = JSON.parse(msg.data);
-          console.log(msg.data);
           if (rData) {
             var con = rData.choices[0].delta.content;
-            if (checkType.value == 'interpretation') {
-              if (rData.status == 3) {
+            if (rData.status == 3) {
+              timers.value.forEach((timer) => {
+                clearTimeout(timer);
+              })
+              let nums = getNumAll(con);
+              nums.forEach(num => {
+                con = con.replace(
+                      `[[${num}]]`,
+                      `<span onclick="window.openDocByIndex(${num})" class="poi" style="    cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 14px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 4px;background: #fff;color:#435C80;border: 1px solid #BACAE3">${num}</span>`
+                  )
+              })
+              if (checkType.value == 'interpretation') {
                 content.value = con;
-                let num = getNum(content.value);
-                while (num) {
-                  const docsNum = rData.docs.length;
-                  if (docsNum && num > docsNum + 1) {
-                    // currentResponse.value.msg = currentResponse.value.msg.replace(`[[${num}]]`, ``)
-                  }
-                  content.value = content.value.replace(
-                    `[[${num}]]`,
-                    `<span onclick="window.openDocByIndex(${num})" class="poi" style="    cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 14px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 4px;background: #fff;color:#435C80;border: 1px solid #BACAE3">${num}</span>`
-                  );
-
-                  num = getNum(content.value);
-                }
-                if (rData.docs) {
-                  handleDocs(rData.docs);
-                }
+                scrollToBottom('scrollArea');
               } else {
-                content.value += con;
+                chatDesc.value[chatDesc.value.length - 1].content = con + '\n\n #### 以上是我思考的内容!\n\n';
+                scrollToBottom('chat_box');
               }
-              scrollToBottom();
-            } else {
-              if (rData.status == 3) {
-                chatDesc.value[chatDesc.value.length - 1].content = con;
-              } else {
-                chatDesc.value[chatDesc.value.length - 1].content += con;
+              if (rData.docs) {
+                handleDocs(rData.docs);
               }
-              if (rData.status == 3) {
-                let num = getNum(chatDesc.value[chatDesc.value.length - 1].content);
-                while (num) {
-                  const docsNum = rData.docs.length;
-                  if (docsNum && num > docsNum + 1) {
-                    // currentResponse.value.msg = currentResponse.value.msg.replace(`[[${num}]]`, ``)
-                  }
-                  chatDesc.value[chatDesc.value.length - 1].content = chatDesc.value[
-                    chatDesc.value.length - 1
-                  ].content.replace(
-                    `[[${num}]]`,
-                    `<span onclick="window.openDocByIndex(${num})" class="poi" style="    cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 14px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 4px;background: #fff;color:#435C80;border: 1px solid #BACAE3">${num}</span>`
-                  );
-
-                  num = getNum(chatDesc.value[chatDesc.value.length - 1].content);
-                }
-                if (rData.docs) {
-                  handleDocs(rData.docs);
-                }
+            } else if (rData.status == 2) {
+              let timer = null;
+              if (checkType.value == 'interpretation') {
+                timer = setTimeout(() => {
+                  content.value += con;
+                  scrollToBottom('scrollArea');
+                  clearTimeout(timer)
+                }, timerIndex.value * 15)
+              } else {
+                timer = setTimeout(() => {
+                  chatDesc.value[chatDesc.value.length - 1].content += con;
+                  scrollToBottom('chat_box');
+                  clearTimeout(timer)
+                }, timerIndex.value * 15)
               }
-              scrollToBottom1();
+              timers.value.push(timer)
+              timerIndex.value++;
             }
           }
         } catch (e) {
@@ -463,7 +426,7 @@ const query = async () => {
       border-radius: 6px;
       padding: 10px 0px 0px 0px;
       background-color: #fff;
-      width: calc(100% - 500px);
+      width: calc(100% - 600px);
     }
     .affix_box {
       position: fixed;
@@ -471,7 +434,7 @@ const query = async () => {
       top: 75px;
     }
     .right-panel {
-      width: 480px;
+      width: 580px;
       height: calc(100vh - 86px) !important;
       border-radius: 6px;
       background-color: #fff;
@@ -544,6 +507,8 @@ const query = async () => {
             height: 100%;
             padding: 0px 0px 6px 0px;
             position: relative;
+            display: flex;
+            flex-direction: column;
             .item {
               display: flex;
               width: 100%;
@@ -606,9 +571,12 @@ const query = async () => {
                 color: #3c7bff;
               }
             }
-            .chat {
-              height: calc(100% - 320px);
+            .chat_box {
+              flex: 1;
+              height: 0px;
               overflow-y: auto;
+            }
+            .chat {
               font-family: PingFang SC;
               font-weight: 500;
               margin: 10px 0px;
@@ -655,7 +623,6 @@ const query = async () => {
                     border-radius: 6px 6px 6px 6px;
                     padding: 10px 18px 10px 13px;
                     font-weight: 500;
-                    max-height: 460px;
                     overflow-y: auto;
                     font-size: 14px;
                     color: #333333;
@@ -688,7 +655,7 @@ const query = async () => {
               }
             }
             .input-panel {
-              position: absolute;
+              position: relative;
               bottom: 0px;
               background: #f5f8fc;
               border-radius: 6px;

+ 6 - 11
ais_search_zj/web/vite.config.js

@@ -41,22 +41,22 @@ export default defineConfig({
     cors: true,
     proxy: {
       '/server': {
-        target: 'http://10.10.8.238:9999/',
-          // target: 'http://10.249.6.132:8528/aisKnowledge',
+        // target: 'http://10.10.8.238:9999/',
+          target: 'https://ai.zrzyt.zj.gov.cn/aisKnowledge',
           // target: 'https://zdzy.zrzyt.zj.gov.cn/aisKnowledge',
           changeOrigin: true,
           rewrite: function (path) { return path.replace(/^\/server/, ''); }
       },
       '/aisKnowledge': {
-        target: 'http://10.10.8.238:9999/',
-          // target: 'http://10.249.6.132:8528/aisKnowledge',
+        // target: 'http://10.10.8.238:9999/',
+          target: 'https://ai.zrzyt.zj.gov.cn/aisKnowledge',
           // target: 'https://zdzy.zrzyt.zj.gov.cn/aisKnowledge',
           changeOrigin: true,
           rewrite: function (path) { return path.replace(/^\/aisKnowledge/, ''); }
       },
       '/aisChat': {
-        // target: 'http://localhost:9999/',
-          target: 'https://zjugpt.com/llm',
+        // target: 'http://10.10.8.231:20331/',
+          target: 'https://ai.zrzyt.zj.gov.cn/aisChat',
           // target: 'https://zdzy.zrzyt.zj.gov.cn/aisKnowledge',
           changeOrigin: true,
           rewrite: function (path) { return path.replace(/^\/aisChat/, ''); }
@@ -80,11 +80,6 @@ export default defineConfig({
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/chat/, '')
       },
-      '/policyChat': {
-        target: 'https://ai.zrzyt.zj.gov.cn/policyChat',
-        changeOrigin: true,
-        rewrite: (path) => path.replace(/^\/policyChat/, '')
-      },
       '/lianqiai2': {
         target: 'http://121.40.148.47:8530',
         changeOrigin: true,