123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203 |
- <template>
- <div class="home_box">
- <draggable-splitter rightWidht="680px">
- <template #left>
- <div class="boxs">
- <div class="left_box">
- <button @click="startNewSessionHandle">
- <span class="icon"></span>
- <span>开启新对话</span>
- </button>
- <div class="history">
- <div class="menu_down">
- <span class="title">历史问答</span>
- <span class="icon" @click="toggleCardHandle('history')">
- <UpOutlined v-if="visibleMap['history']" />
- <DownOutlined v-else />
- </span>
- </div>
- <div class="history_box" v-show="visibleMap['history']">
- <ul>
- <template v-for="(session, index) in sessionLists">
- <li :key="index" v-if="session['datas'].length > 0">
- <span class="title">{{session['title']}}</span>
- <ul>
- <li
- v-for="(item, cindex) in session['datas']"
- :key="cindex"
- :class="{'active': item['id'] === cSessionId}"
- @mouseenter="sessionDelId = item['id']"
- @mouseleave="sessionDelId = ''">
- <span @click="switchSession(item)">{{item['tittle']}}</span>
- <span v-if="sessionDelId === item['id']" @click="onSessionDeleteHandle(item['id'])">
- <DeleteOutlined />
- </span>
- </li>
- </ul>
- </li>
- </template>
- </ul>
- </div>
- </div>
- <div class="ai_tools">
- <div class="menu_down">
- <span class="title">AI工具</span>
- <span class="icon" @click="toggleCardHandle('tool')">
- <UpOutlined v-if="visibleMap['tool']" />
- <DownOutlined v-else />
- </span>
- </div>
- <ul v-show="visibleMap['tool']">
- <li @click="toToolPage('./#/policy/interpret')">
- <span class="icon">
- <span class="iconfont icon-a-lujing8796"></span>
- </span>
- <span class="txt">政策解读</span>
- </li>
- <li @click="toToolPage('./#/policy/smart')">
- <span class="icon">
- <span class="iconfont icon-a-lujing8794"></span>
- </span>
- <span class="txt">政策比对</span>
- </li>
- </ul>
- </div>
- </div>
- <div class="right_box">
- <div class="chat-container">
- <div v-if="historys.length === 0" class="messages-container">
- <p class="n_tips">Hi~,我是自然资源大模型,您身边的智能助手!</p>
- </div>
- <div v-else class="messages-container" ref="msgContainer">
- <template v-for="(history,index) in historys" :key="index">
- <div class="message user">
- {{ history.question }}
- </div>
- <div class="message assistant">
- <div class="ai-search-detail">
- <div class="search-panel" id="pageContainer">
- <div :class="`search-detail search-detail-${askType}`">
- <div
- class="search-result"
- id="searchResult"
- style="overflow-x: hidden; height: auto"
- ref="messageContainer"
- >
- <div :class="`result-panel result-panel-${askType}`">
- <div class="result">
- <template v-if="askType === 'zcfg'">
- <div class="result-view">
- <div class="q-r">
- <div class="ds-content-box">
- <div class="icon">
- <img src="/images/icon-ds.png" />
- </div>
- <div class="ds-panel">
- <div class="ds-loading" v-if="history.currentResponse.hintTxt" @click="history.currentResponse.showChain = !history.currentResponse.showChain">
- {{ history.currentResponse.hintTxt }}
- <DownOutlined class="icon-arrow" :class="{ rotate: history.currentResponse.showChain }" />
- </div>
- <div class="ds-con" v-if="history.currentResponse.showChain">
- <vue-markdown-it
- id="dsMarkdown"
- :source="
- history.currentResponse.streamMock
- ? history.currentResponse.streamMsg.indexOf('###') > -1
- ? history.currentResponse.streamMsg.substring(
- 0,
- history.currentResponse.streamMsg.indexOf('###')
- )
- : history.currentResponse.streamMsg
- : history.currentResponse.msg.indexOf('</think>') > -1
- ? history.currentResponse.msg.substring(
- 0,
- history.currentResponse.msg.indexOf('</think>')
- )
- : history.currentResponse.msg
- "
- :options="{
- html: true,
- linkify: true
- }"
- />
- </div>
- <a-spin :indicator="indicator" v-if="history.currentResponse.loading" />
- </div>
- </div>
- <vue-markdown-it
- id="resMarkdown"
- :source="
- history.currentResponse.streamMock
- ? history.currentResponse.streamMsg.indexOf('###') > -1
- ? history.currentResponse.streamMsg.substring(
- history.currentResponse.streamMsg.indexOf('###')
- )
- : ''
- : history.dsChecked
- ? history.currentResponse.msg.indexOf('</think>') > -1
- ? history.currentResponse.msg.substring(history.currentResponse.msg.indexOf('</think>'))
- : ''
- : history.currentResponse.msg
- "
- :options="{
- html: true,
- linkify: true
- }"
- />
- </div>
- <div class="source" v-if="activeTab !== 'original' && history.currentResponse.docs.length > 0" v-show="activeIndex === 5">
- <div class="title">
- <span>基于{{ history.currentResponse.docs.length }}个参考来源</span>
- <span class="icon" @click="history.sourceVisible = !history.sourceVisible">
- <UpOutlined v-if="history.sourceVisible" />
- <DownOutlined v-else />
- </span>
- </div>
- <div class="items" v-show="history.sourceVisible">
- <div
- class="item"
- v-if="activeTab !== 'net'"
- v-for="(doc, i) in history.currentResponse.docs"
- :key="'doc-' + i"
- >
- <div class="doc">
- <p>
- <span
- class="ma"
- style="font-weight: bolder; color: #000000; margin-right: 10px"
- >出处 [{{ i + 1 }}] </span
- ><span class="doc-link" @click="openDoc(doc, i)">{{ doc.doc }}</span>
- </p>
- <div
- :class="`doc-icon${
- !doc.showContent ? ' doc-icon-show' : ' doc-icon-hide'
- }`"
- @click="doc.showContent = !doc.showContent"
- ></div>
- </div>
- <div :class="`content${doc.showContent ? '' : ' content-hide'}`">
- <p>{{ doc.content }}</p>
- </div>
- </div>
- <div
- class="item item-url"
- v-if="activeTab === 'net'"
- v-for="(doc, i) in history.currentResponse.docs"
- :key="'doc-' + i"
- >
- <div class="doc">
- <p>
- <span class="doc-link" @click="openUrl(doc.link)">{{ doc.title }}</span>
- </p>
- </div>
- <div class="bottom">
- <div class="title-icon">
- <div class="icon"></div>
- <div class="title">{{ doc.doc }}</div>
- </div>
- <div class="index">{{ i + 1 }}</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <template v-else>
- <div v-if="activeIndex >= 1" class="map-answer">
- <div class="left-panel">
- <div class="content">
- <div class="summary-card">
- <div class="summary-title" id="tdscSummaryTitle">总结</div>
- <div class="summary-content">
- <a-skeleton active v-if="!history.currentResponse.msg" />
- <vue-markdown-it
- :source="
- history.currentResponse.streamMock
- ? history.currentResponse.streamMsg
- : history.currentResponse.msg
- "
- :options="{
- html: true,
- linkify: true
- }"
- />
- </div>
- </div>
- <div class="chart-card" v-if="history.currentResponse.hasChart" id="tdscChartCard">
- <div class="chart-title">生成图表</div>
- <a-skeleton
- active
- style="height: 100%"
- v-if="!history.currentResponse.chartOption"
- />
- <div v-else class="chart" id="summaryChart"></div>
- </div>
- </div>
- </div>
- </div>
- </template>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- </div>
-
- <div class="input-container">
- <textarea
- v-model="cQuestion"
- class="input-box"
- ref="msgInput"
- placeholder="请输入对话内容,换行请使用Shift+Enter"
- rows="5"
- @keydown="onKeydownHandle"
- ></textarea>
- <div class="bottom_box">
- <div class="tools_box">
- <div class="tool_box_1">
- <a-dropdown>
- <template #overlay>
- <a-menu @click="({key})=>onChange(key)">
- <a-menu-item key="0">
- <div :class="{tool_1: true, active: modelType === '0'}">
- DeepSeek
- </div>
- </a-menu-item>
- <a-menu-item key="1">
- <div :class="{tool_1: true, active: modelType === '1'}">
- 通义千问
- </div>
- </a-menu-item>
- </a-menu>
- </template>
- <a-button style="width: 120px">
- {{ modelType === '0' ? 'DeepSeek' : '通义千问'}}
- <DownOutlined />
- </a-button>
- </a-dropdown>
- </div>
- <div class="tool_box_2">
- <a-dropdown>
- <template #overlay>
- <a-menu @click="changeAnswerType">
- <a-menu-item key="0">
- <div :class="{tool_1: true, active: answerType === '0'}">
- 简洁
- </div>
- </a-menu-item>
- <a-menu-item key="1">
- <div :class="{tool_1: true, active: answerType === '1'}">
- 深入
- </div>
- </a-menu-item>
- <a-menu-item key="2">
- <div :class="{tool_1: true, active: answerType === '2'}">
- 研究
- </div>
- </a-menu-item>
- </a-menu>
- </template>
- <a-button style="width: 80px">
- {{ answerType === '0' ? '简洁' : answerType === '1' ? '深入' : '研究'}}
- <DownOutlined />
- </a-button>
- </a-dropdown>
- </div>
- <div class="tool_box_3">
- <a-dropdown>
- <template #overlay>
- <a-menu @click="({key})=>changeTab(key)">
- <a-menu-item
- v-for="t in tabs"
- :key="t.key"
- >
- <div :class="{tool_1: true, active: activeTab === t.key}">
- {{t.name}}
- </div>
- </a-menu-item>
- </a-menu>
- </template>
- <a-button style="width: 90px">
- {{ activeTab === 'knowledge' ? '知识库' : activeTab === 'net' ? '全网' : '原生'}}
- <DownOutlined />
- </a-button>
- </a-dropdown>
- </div>
- </div>
- <div class="send_btn">
- <div v-if="historys.length > 0 && historys[historyIndex].currentResponse.loading" @click="onSendHandle(false)">
- <i class="stop"></i>
- </div>
- <div v-else @click="onSendHandle">
- <i class="iconfont icon-a-lujing9250"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <template #right v-if="showDoc">
- <div
- class="docs_box"
- style="background-color: white;"
- >
- <p-d-f-viewer
- v-if="fileType === 'pdf'"
- :src="pdfSrc"
- @close="closeDoc"
- :content="pdfContent"
- :num="pdfNum"
- />
- <word-viewer
- v-if="fileType === 'docx'"
- :src="pdfSrc"
- @close="closeDoc"
- :content="pdfContent"
- :num="pdfNum"
- >
- </word-viewer>
- <txt-viewer v-if="fileType === 'txt'" :src="pdfSrc" @close="closeDoc" :txt="pdfContent" />
- </div>
- </template>
- </draggable-splitter>
- </div>
- </template>
- <script setup>
- import {
- LoadingOutlined,
- DeleteOutlined,
- UpOutlined,
- DownOutlined
- } from "@ant-design/icons-vue";
- import { Modal } from 'ant-design-vue';
- import dayjs from 'dayjs';
- import { fetchEventSource } from '@microsoft/fetch-event-source';
- import PDFViewer from '@/components/pdf/PdfCanvas.vue';
- import WordViewer from '@/components/pdf/WordViewer.vue';
- import { VueMarkdownIt } from '@f3ve/vue-markdown-it';
- import TxtViewer from '@/components/pdf/TxtViewer.vue';
- 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";
- let ctr = null;
- const userStore = useUserStore();
- const visibleMap = reactive({
- history: true,
- tool: true
- })
- const toggleCardHandle = (type) => {
- visibleMap[type] = !visibleMap[type]
- }
- const msgInput = ref(null)
- const msgContainer = ref(null)
- function scrollToBottom() {
- msgContainer.value.scrollTop = msgContainer.value.scrollHeight;
- }
- const historys = ref([])
- let historyIndex = -1;
- const cQuestion = ref('')
- const cSessionId = ref('')
- const sessionDelId = ref('')
- const isSessionNew = ref(true)
- const startNewSessionHandle = () => {
- cQuestion.value = ''
- historys.value = []
- historyIndex = -1
- isSessionNew.value = true
- cSessionId.value = ''
- }
- const onKeydownHandle = (e) => {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- if (historys.value.length > 0 && historys.value[historyIndex].currentResponse.loading) {
- message.error('回答输出中,请稍后操作或点击停止回答');
- return;
- }
- onSendHandle();
- }
- }
- const onSendHandle = (status = true) => {
- if (!cQuestion.value) {
- message.error('请输入问题');
- return;
- }
- const isFllow = historys.value.length > 0
- if (!status) {
- historys.value[historyIndex].currentResponse.loading = false;
- stopAI();
- return;
- }
- historyIndex++;
- historys.value.push({
- question: cQuestion.value,
- dsChecked: true,
- sourceVisible: false,
- currentResponse: {
- loading: true,
- hintTxt: '',
- streamMsg: '',
- streamMock: false,
- msg: '',
- originAnswer: '',
- showChain: true,
- oDocs: '',
- docs: []
- }
- })
- ask(decodeURIComponent(cQuestion.value), isFllow);
- cQuestion.value = '';
- const timer = setTimeout(() => {
- scrollToBottom()
- clearTimeout(timer)
- }, 50)
- }
- const toToolPage = (path) => {
- window.open(path, '_blank')
- }
- const aiLoading = ref(false);
- const indicator = h(LoadingOutlined, {
- style: {
- fontSize: '24px'
- },
- spin: true
- });
- const modelType = ref('0')
- const answerType = ref('0');
- const question = ref('国有土地的使用方式有哪些?');
- const statusText = ref('检索中');
- const activeIndex = ref(0);
- const activeTab = ref('knowledge');
- const evaluate = ref(null);
- const open = ref(true);
- const showDoc = ref(false);
- const pdfSrc = ref('');
- const pdfContent = ref('');
- const pdfNum = ref(1);
- const fileType = ref('pdf');
- const startTime = ref(0);
- const endTime = ref(0);
- const times = ref(0);
- const timers = ref([]);
- let streamMockInterval = null;
- const streamToAnswer = () => {
- historys.value[historyIndex].currentResponse.index = 0;
- streamMockInterval = setInterval(() => {
- const { originAnswer = '', msg, streamMsg = '', id, index = 0 } = historys.value[historyIndex].currentResponse;
- if (historys.value[historyIndex].currentResponse.mockStart || index <= originAnswer.length) {
- historys.value[historyIndex].currentResponse.streamMsg = originAnswer.substr(0, index + 2).replaceAll('\n', ' \n');
- if (originAnswer) {
- historys.value[historyIndex].currentResponse.index += 2;
- }
- let num = getNum(historys.value[historyIndex].currentResponse.streamMsg);
- while (num) {
- const docsNum = historys.value[historyIndex].currentResponse.docs.length;
- historys.value[historyIndex].currentResponse.streamMsg = historys.value[historyIndex].currentResponse.streamMsg.replace(
- `[[${num}]]`,
- `<span onclick="window.openDocByIndex(${num}, ${id}, ${historyIndex})" class="poi" style=" cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 12px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 10px;background: #d0d5dd; width: 20px;
- height: 20px;
- background: #FFFFFF;
- border-radius: 4px 4px 4px 4px;
- border: 1px solid #BACAE3;">${num}</span>`
- );
- num = getNum(historys.value[historyIndex].currentResponse.streamMsg);
- }
- } else {
- if (streamMockInterval) {
- clearInterval(streamMockInterval);
- streamMockInterval = null;
- }
- activeIndex.value = 5;
- }
- }, 50);
- };
- const tabs = [
- { key: 'knowledge', name: '知识库' },
- // { key: 'net', name: '全网' },
- { key: 'original', name: '原生' },
- ];
- const changeStatusText = () => {
- let i = 0;
- setInterval(() => {
- statusText.value = '检索中' + '.'.repeat(i);
- if (i === 3) {
- i = 0;
- }
- i++;
- }, 500);
- };
- changeStatusText();
- const askType = ref('zcfg');
- const ask = async (q, isFllow) => {
- if (ctr) {
- if (streamMockInterval) {
- clearInterval(streamMockInterval);
- streamMockInterval = null;
- }
- ctr.abort();
- }
- times.value = 0;
- if (timers.value) {
- timers.value.forEach((t) => {
- clearTimeout(t);
- t = null;
- });
- }
- timers.value = [];
- if (historys.value[historyIndex].dsChecked) {
- historys.value[historyIndex].currentResponse.hintTxt = '';
- historys.value[historyIndex].currentResponse.loading = true;
- }
- question.value = q;
- open.value = false;
- showDoc.value = false;
- askType.value = 'zcfg';
- quest(isFllow);
- };
- let questionUrl = '/chat/kb_chat';
- const changeTab = (tab) => {
- times.value = 0;
- if (timers.value) {
- timers.value.forEach((t) => {
- clearTimeout(t);
- t = null;
- });
- }
- timers.value = [];
- if (tab === 'net') {
- questionUrl = '/chat/bing_chat';
- } else if(tab === 'knowledge'){
- questionUrl = '/chat/kb_chat';
- }else if(tab === 'original') {
- questionUrl = '/chat/chat';
- }
- activeTab.value = tab;
- };
- const onChange = (type) => {
- modelType.value = type;
- dsChange(type);
- changeAnswerType({key: '0'});
- };
- //ds绑定用户改变
- const dsChange = (type) => {
- localStorage.setItem("_isDeepSeek", type);
- if (timers.value) {
- timers.value.forEach((t) => {
- clearTimeout(t);
- t = null;
- });
- timers.value = [];
- }
- times.value = 0;
- //打字机效果 切换会打印
- aiLoading.value = true;
- };
- const changeAnswerType = ({ key }) => {
- answerType.value = key
- };
- const questHistories = ref([]);
- let scb = null;
- const quest = async (isFllow) => {
- startTime.value = Date.now();
- window.scroll({ top: 0, behavior: 'smooth' });
- evaluate.value = null;
- activeIndex.value = 0;
- if (!isFllow) {
- showDoc.value = false;
- }
- if (isFllow) {
- const { id, question, msg, docs, originAnswer, keywords = [] } = historys.value[historyIndex].currentResponse;
- questHistories.value.push({ id, question, msg, docs: docs, originAnswer, keywords });
- } else {
- questHistories.value = [];
- }
- aiLoading.value = true;
- if (scb !== null) {
- clearInterval(scb);
- scb = null;
- } else {
- scb = setInterval(() => {
- }, 500);
- }
- ctr = new AbortController();
- const id = questHistories.value.length;
- historys.value[historyIndex].currentResponse = {
- loading: true,
- time: 0,
- hintTxt: '',
- question: question.value,
- id,
- msg: '',
- originAnswer: '',
- streamMock: false,
- streamMsg: '',
- oDocs: '',
- showChain: true,
- docs: []
- };
- activeIndex.value = 0;
- if (isSessionNew.value) {
- sessionCreate();
- isSessionNew.value = false
- }
- if (activeTab.value === 'net') {
- questionUrl = '/chat/bing_chat';
- } else if(activeTab.value === 'knowledge'){
- questionUrl = '/chat/kb_chat';
- }else if(activeTab.value === 'original') {
- questionUrl = '/chat/chat';
- }
- const topKs = window?.AppGlobalConfig?.topKs || {
- 0: 5,
- 1: 10,
- 2: 15
- };
- let body = null
- if (activeTab.value === 'net') {
- body = {
- query: question.value,
- stream: true,
- model: modelType.value === '0' ? 'deepseek-r1' : '',
- search_type: answerType.value
- }
- } else if(activeTab.value === 'knowledge'){
- body = {
- query: question.value,
- mode: 'local_kb',
- kb_name: activeTab.value === 'paper' ? 'compose_paper_material_total' : modelType.value === '1' ? window?.AppGlobalConfig?.llm?.kb_name : 'policy',
- top_k: topKs[answerType.value],
- search_type: answerType.value,
- score_threshold: 0.5,
- model: historys.value[historyIndex].dsChecked ? 'deepseek-r1' : '',
- history: [],
- stream: true,
- prompt_name: 'rag_context_qa.md',
- return_direct: false
- }
- } else if (activeTab.value === 'original') {
- body = {
- query: question.value,
- history: [],
- stream: true
- }
- }
- if (isFllow) {
- if (activeTab.value === 'knowledge') {
- if (questHistories.value.length > 0) {
- body.history_keyword = questHistories.value[questHistories.value.length - 1].keywords;
- const arrs = historys.value.map((history) => {
- return {
- "role": "user",
- "content": history.question
- }
- })
- body.history = arrs
- }
- } else if (activeTab.value === 'original') {
- for (let i = 0; i < historys.value.length; i++){
- const history = historys.value[i]
- if (history && history['currentResponse'] && history['currentResponse']['msg']) {
- body.history.push([
- history['currentResponse']['question'],
- history['currentResponse']['msg'].substring(history['currentResponse']['msg'].indexOf('</think>')+8)
- ])
- }
- }
- }
- }
- if (activeTab.value !== 'net' && answerType.value !== '0') {
- historys.value[historyIndex].currentResponse.streamMock = true;
- historys.value[historyIndex].currentResponse.mockStart = true;
- streamToAnswer();
- }
- const rootUrl = modelType.value === '1' ? window.AppGlobalConfig.aisChat : window.AppGlobalConfig.aiServer
- await fetchEventSource(rootUrl + questionUrl, {
- method: 'POST',
- openWhenHidden: true,
- timeout: 300000,
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(body),
- signal: ctr.signal,
- async onmessage(msg) {
- if (activeIndex.value !== 3) {
- activeIndex.value = 3;
- }
- activeTab.value === 'net' ? handleNetResponse(msg, id) : handleKnowledgeResponse(msg, id);
- },
- onclose () {
- if (scb !== null) {
- clearInterval(scb);
- scb = null;
- collectQuestion();
- }
- if (historys.value[historyIndex].currentResponse.streamMock) {
- historys.value[historyIndex].currentResponse.mockStart = false;
- } else {
- activeIndex.value = 5;
- }
- },
- onerror (err) {
- historys.value[historyIndex].currentResponse.loading = false
- throw err;
- }
- });
- };
- const handleKnowledgeResponse = (msg, id) => {
- if (!msg || !msg.data) {
- return;
- }
- const rData = JSON.parse(msg.data);
- if (rData?.choices && rData.choices.length > 0) {
- if (activeTab.value === 'net' && rData.status !== 2) {
- return;
- }
- if (rData.status == 3) {
- if (timers.value) {
- timers.value.forEach((t) => {
- clearTimeout(t);
- t = null;
- });
- timers.value = [];
- }
- endTime.value = Date.now();
- var time = ((endTime.value - startTime.value) / 1000).toFixed(0);
- historys.value[historyIndex].currentResponse.hintTxt = `已深度思考(用时 ${time} 秒)`;
- historys.value[historyIndex].currentResponse.time = time;
- historys.value[historyIndex].currentResponse.loading = false;
- aiLoading.value=true;
- historys.value[historyIndex].currentResponse.originAnswer = rData.choices[0]?.delta?.content.replaceAll(
- '\n',
- ` \n`
- );
- historys.value[historyIndex].currentResponse.msg = rData.choices[0]?.delta?.content.replaceAll('\n', ` \n`);
- let num = getNum(historys.value[historyIndex].currentResponse.msg);
- while (num) {
- const docsNum = historys.value[historyIndex].currentResponse.docs.length;
- historys.value[historyIndex].currentResponse.msg = historys.value[historyIndex].currentResponse.msg.replace(
- `[[${num}]]`,
- `<span onclick="window.openDocByIndex(${num}, ${id}, ${historyIndex})" class="poi" style=" cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 12px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 10px;background: #d0d5dd; width: 20px;
- height: 20px;
- background: #FFFFFF;
- border-radius: 4px 4px 4px 4px;
- border: 1px solid #BACAE3;">${num}</span>`
- );
- num = getNum(historys.value[historyIndex].currentResponse.msg);
- }
- } else {
-
- aiLoading.value = false;
- historys.value[historyIndex].currentResponse.hintTxt = '思考中...';
- //ds模式打字机效果输出
- const timer = setTimeout(() => {
- if (!aiLoading.value) {
- historys.value[historyIndex].currentResponse.originAnswer += rData.choices[0]?.delta?.content.replaceAll(
- '\n',
- ` \n`
- );
- historys.value[historyIndex].currentResponse.msg += rData.choices[0]?.delta?.content.replaceAll('\n', ` \n`);
- let num = getNum(historys.value[historyIndex].currentResponse.msg);
- while (num) {
- const docsNum = historys.value[historyIndex].currentResponse.docs.length;
- historys.value[historyIndex].currentResponse.msg = historys.value[historyIndex].currentResponse.msg.replace(
- `[[${num}]]`,
- `<span onclick="window.openDocByIndex(${num}, ${id}, ${historyIndex})" class="poi" style=" cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 12px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 10px;background: #d0d5dd; width: 20px;
- height: 20px;
- background: #FFFFFF;
- border-radius: 4px 4px 4px 4px;
- border: 1px solid #BACAE3;">${num}</span>`
- );
- num = getNum(historys.value[historyIndex].currentResponse.msg);
- }
- }
- clearTimeout(timer);
- timers.value.push(timer);
- }, times.value * 15);
- }
- }
- if (!historys.value[historyIndex].currentResponse.docs.length) {
- historys.value[historyIndex].currentResponse.oDocs = JSON.stringify(rData.docs)
- if (rData.docs && rData.docs.length) {
- handleDocs(rData.docs);
- }
- }
- times.value++;
- setTimeout(() => {
- scrollToBottom()
- }, 50)
- };
- const handleNetResponse = (msg, id) => {
- const rData = msg.data;
- if (!!rData && rData !== '[DONE]') {
- try {
- const res = JSON.parse(rData);
- if (res.error) {
- activeIndex.value = 4;
- message.error(res.error);
- historys.value[historyIndex].currentResponse.msg = res.error;
- return;
- }
- if (res.rag_finish) {
- if (activeIndex.value < 4) {
- activeIndex.value = 4;
- }
- if (historys.value[historyIndex].dsChecked) {
- endTime.value = Date.now();
- var time = ((endTime.value - startTime.value) / 1000).toFixed(0);
- historys.value[historyIndex].currentResponse.hintTxt = `已深度思考(用时 ${time} 秒)`;
- historys.value[historyIndex].currentResponse.loading = false;
- }
- } else {
- if (historys.value[historyIndex].dsChecked && historys.value[historyIndex].currentResponse.hintTxt != '思考中...') {
- historys.value[historyIndex].currentResponse.hintTxt = '思考中...';
- }
- }
- if (res.result) {
- historys.value[historyIndex].currentResponse.originAnswer = res.result;
- historys.value[historyIndex].currentResponse.msg = res.result.replaceAll('\n', ` \n`);
- let num = getNum(historys.value[historyIndex].currentResponse.msg);
- while (num) {
- const docsNum = historys.value[historyIndex].currentResponse.docs.length;
- if (docsNum && num > docsNum + 1) {
- historys.value[historyIndex].currentResponse.msg = historys.value[historyIndex].currentResponse.msg.replace(`[[${num}]]`, ``);
- }
- historys.value[historyIndex].currentResponse.msg = historys.value[historyIndex].currentResponse.msg.replace(
- `[[${num}]]`,
- `<span onclick="window.openDocByIndex(${num}, ${id}, ${historyIndex})" class="poi" style=" cursor: pointer; display: inline-block; width: 20px; height: 20px; font-size: 12px; line-height: 20px; text-align: center; margin: 0 5px; border-radius: 10px;background: #d0d5dd">${num}</span>`
- );
- num = getNum(historys.value[historyIndex].currentResponse.msg);
- }
- }
- if (res.source_list && res.source_list.length && !historys.value[historyIndex].currentResponse.docs.length) {
- handleDocs(res.source_list);
- }
- } catch (e) {}
- }
- if (rData === '[DONE]') {
- console.log(historys.value[historyIndex].currentResponse.msg);
- }
- };
- const handleDocs = (docs) => {
- if (
- docs.length === 1 &&
- "<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答!</span>" === docs[0]
- ) {
- return;
- }
- historys.value[historyIndex].currentResponse.docs = docs.map((v, i) => {
- if (activeTab.value === 'net') {
- return {
- index: v.num,
- doc: v.name,
- link: v.url,
- title: v.title,
- summary: v.summary,
- content: '',
- showContent: false,
- type: 'url'
- };
- }
- if (v.toLowerCase().indexOf('.pdf') > 0) {
- return {
- index: i++,
- doc: v.substring(v.toLowerCase().indexOf('] [') + 3, v.toLowerCase().indexOf('.pdf]') + 4),
- link: v.substring(v.toLowerCase().indexOf('.pdf]') + 6, v.toLowerCase().indexOf('.pdf)') + 4),
- content: v.substring(v.toLowerCase().indexOf('.pdf)') + 5),
- showContent: false,
- type: 'pdf'
- };
- } else if (v.toLowerCase().indexOf('.txt') > 0) {
- return {
- index: i++,
- doc: v.toLowerCase().substring(v.indexOf('] [') + 3, v.toLowerCase().indexOf('.txt]') + 4),
- link: v.toLowerCase().substring(v.indexOf('.txt]') + 6, v.toLowerCase().indexOf('.txt)') + 4),
- content: v.toLowerCase().substring(v.indexOf('.txt)') + 5),
- showContent: false,
- type: 'txt'
- };
- } else if (v.toLowerCase().indexOf('.docx') > 0) {
- return {
- index: i++,
- doc: v.substring(v.toLowerCase().indexOf('] [') + 3, v.toLowerCase().indexOf('.docx]') + 5),
- link: v.substring(v.toLowerCase().indexOf('.docx]') + 7, v.toLowerCase().indexOf('.docx)') + 5),
- content: v.substring(v.toLowerCase().indexOf('.docx)') + 6),
- showContent: false,
- type: 'docx'
- };
- }
- });
- };
- const openDoc = (doc, i) => {
- var link = doc.link;
- var type = doc.type;
- pdfSrc.value = window.formatDocUrl(link, modelType.value)
- showDoc.value = true;
- fileType.value = type;
- pdfContent.value = doc.content;
- pdfNum.value = i;
- };
- const closeDoc = () => {
- showDoc.value = false;
- };
- watch(
- () => showDoc.value,
- (newVal) => {
- // 发布打开关闭,在关闭文档的时候打开相关案例
- PubsubService.publish('switch-relevant-cases-box', newVal);
- }
- );
- const openUrl = (url) => {
- window.open(url, '_blank');
- };
- const openDocByIndex = (ind, id, hIndex = null) => {
- if (hIndex === null) {
- hIndex = historyIndex
- }
- showDoc.value = false;
- let link = null;
- if (id !== historys.value[hIndex].currentResponse.id) {
- if (questHistories.value[id].docs[ind - 1].type === 'url') {
- openUrl(questHistories.value[id].docs[ind - 1].link);
- return;
- }
- link = questHistories.value[id].docs[ind - 1].link;
- fileType.value = questHistories.value[id].docs[ind - 1].type;
- } else {
- if (historys.value[hIndex].currentResponse.docs[ind - 1].type === 'url') {
- openUrl(historys.value[hIndex].currentResponse.docs[ind - 1].link);
- return;
- }
- link = historys.value[hIndex].currentResponse.docs[ind - 1].link;
- fileType.value = historys.value[hIndex].currentResponse.docs[ind - 1].type;
- pdfContent.value = historys.value[hIndex].currentResponse.docs[ind - 1].content;
- pdfNum.value = historys.value[hIndex].currentResponse.docs[ind - 1].num;
- }
- pdfNum.value = ind;
- pdfSrc.value = window.formatDocUrl(link, modelType.value)
- showDoc.value = true;
- };
- window.openDocByIndex = openDocByIndex;
- const getNum = (str) => {
- const matches = str.match(/\[\[(\d+)\]\]/);
- if (matches) {
- return matches[1]; // 输出: 2
- } else {
- return null;
- }
- };
- const sessionId = ref('');
- const sessionCreate = () => {
- const sendData = {
- tittle: historys.value[historyIndex].currentResponse.question,
- creator: userStore.user.syUser.Id,
- userName: userStore.user.syUser.Name,
- deptName: userStore.user.syUser.OrganizationLine
- }
- ManagerAPI.create(sendData).then((res) => {
- if (res.data) {
- sessionId.value = res.data
- }
- });
- }
- const sessionDelete = (id) => {
- const sendData = {
- id
- }
- ManagerAPI.delete(sendData).then((res) => {
- if (res.data) {
- initSessionLists();
- startNewSessionHandle()
- }
- });
- }
- const getSessionList = async (times) => {
- const res = await ManagerAPI.list(userStore.user.syUser.Id, times);
- return res.data
- }
- const sessionLists = reactive([
- {
- title: '今天',
- times: [
- dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
- dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
- ],
- datas: []
- },
- {
- title: '昨天',
- times: [
- dayjs().subtract(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
- dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss')
- ],
- datas: []
- },
- {
- title: '30天内',
- times: [
- dayjs().subtract(30, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
- dayjs().subtract(2, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss')
- ],
- datas: []
- }
- ])
- const initSessionLists = () => {
- sessionLists.forEach(async (session) => {
- session['datas'] = await getSessionList(session['times'])
- })
- }
- initSessionLists();
- const getQuestionList = async (chatId) => {
- const res = await ManagerAPI.getQuestionList({
- chatId
- });
- return res.data
- }
- const switchSession = async (item) => {
- cSessionId.value = sessionId.value = item['id']
- isSessionNew.value = false;
- const results = await getQuestionList(item['id'])
- historys.value = []
- results.forEach((item,index) => {
- let nums = getNumAll(item['answer']);
- let msg = item['answer'];
- const id = questHistories.value.length;
- nums.forEach(num => {
- msg = msg.replace(
- `[[${num}]]`,
- ''
- )
- })
- const docs = JSON.parse(item['answerSources']) || []
- historys.value.push({
- question: item['question'],
- dsChecked: true,
- sourceVisible: false,
- currentResponse: {
- id,
- loading: false,
- hintTxt: `已深度思考(用时 ${item['thinkTime']} 秒)`,
- msg: msg,
- sourceVisible: false,
- showChain: true,
- docs: []
- }
- })
- activeIndex.value = 5
- historyIndex = index;
- // handleDocs(docs)
- })
- }
- const onSessionDeleteHandle = (id) => {
- Modal.confirm({
- content: () => '确定删除该条记录,删除将无法恢复!',
- onOk () {
- sessionDelete(id);
- },
- cancelText: '取消',
- okText: '确定',
- });
- }
- // 埋点采集数据
- const collectQuestion = () => {
- const { question, originAnswer, oDocs, time, keywords = [] } = historys.value[historyIndex].currentResponse;
- const param = {
- question,
- answer: originAnswer,
- answerSources: oDocs,
- thinkTime: time,
- questionType: askType.value === 'zcfg' ? '政策法规' : '土地市场',
- keywords: Array.isArray(keywords) ? keywords.join(',') : keywords,
- creator: userStore.user.syUser.Id,
- chatId: sessionId.value
- };
- if (userStore.isLogin) {
- const { id = '-1', displayName = '游客' } = userStore?.user?.user || {};
- param.user = displayName;
- param.userId = id;
- }
- ManagerAPI.collect(param).then((res) => {
- if (res.data) {
- // 记录日志,用来反馈
- sessionLists.forEach(async (session) => {
- session['datas'] = await getSessionList(session['times'])
- })
- }
- });
- };
- const stopAI = () => {
- if (ctr) {
- ctr.abort();
- }
- };
- </script>
- <style lang="scss" scoped>
- @import './index.scss';
- </style>
|