|
@@ -0,0 +1,1897 @@
|
|
|
+<template>
|
|
|
+ <div class="home_box">
|
|
|
+ <div class="left_box">
|
|
|
+ <button>
|
|
|
+ <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>
|
|
|
+ <li>
|
|
|
+ <span class="title">今天</span>
|
|
|
+ <ul>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ </ul>
|
|
|
+ </li>
|
|
|
+ <li>
|
|
|
+ <span class="title">昨天</span>
|
|
|
+ <ul>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ </ul>
|
|
|
+ </li>
|
|
|
+ <li>
|
|
|
+ <span class="title">30天内</span>
|
|
|
+ <ul>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ <li>什么是闲置土地</li>
|
|
|
+ </ul>
|
|
|
+ </li>
|
|
|
+ </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 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="dsUp = !dsUp">
|
|
|
+ {{ history.currentResponse.hintTxt }}
|
|
|
+ <DownOutlined class="icon-arrow" :class="{ rotate: dsUp }" />
|
|
|
+ </div>
|
|
|
+ <div class="ds-con" v-if="!dsUp">
|
|
|
+ <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('###') > -1
|
|
|
+ ? history.currentResponse.msg.substring(
|
|
|
+ 0,
|
|
|
+ history.currentResponse.msg.indexOf('###')
|
|
|
+ )
|
|
|
+ : 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('###') > -1
|
|
|
+ ? history.currentResponse.msg.substring(history.currentResponse.msg.indexOf('###'))
|
|
|
+ : ''
|
|
|
+ : history.currentResponse.msg
|
|
|
+ "
|
|
|
+ :options="{
|
|
|
+ html: true,
|
|
|
+ linkify: true
|
|
|
+ }"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="more-questions" v-if="activeIndex >= 4">
|
|
|
+ <div class="title" style="margin-bottom: 14px">
|
|
|
+ 你可以继续问我:
|
|
|
+ <div class="change-title" @click="changeRecommendedQuestions">
|
|
|
+ <div class="change-icon"></div>
|
|
|
+ 换一批
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="questions" style="cursor: pointer">
|
|
|
+ <div
|
|
|
+ class="question"
|
|
|
+ @click="openRecommendedQuestion(q)"
|
|
|
+ v-for="(q, i) in questions"
|
|
|
+ :key="i"
|
|
|
+ style="
|
|
|
+ line-height: 14px;
|
|
|
+ text-align: left;
|
|
|
+ font-style: normal;
|
|
|
+ text-transform: none;
|
|
|
+ width: fit-content;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ background: #ffffff;
|
|
|
+ box-shadow: 0px 1px 8px 0px #e4e4e4;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #e9e9e9;
|
|
|
+ padding: 16px;
|
|
|
+ font-family: PingFang SC;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #2185f2;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {{ q }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="source" v-if="activeTab !== 'original'" v-show="activeIndex === 5">
|
|
|
+ <div class="title">基于{{ history.currentResponse.docs.length }}个参考来源</div>
|
|
|
+ <div class="items">
|
|
|
+ <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
|
|
|
+ v-show="askType === 'zcfg'"
|
|
|
+ class="search-result result-history"
|
|
|
+ style="overflow-y: hidden; height: auto"
|
|
|
+ v-if="questHistories.length > 0"
|
|
|
+ id="messageContainer"
|
|
|
+ >
|
|
|
+ <template v-for="(qh, i) in [...questHistories].reverse()" :key="`qh-${i}`">
|
|
|
+ <div class="top">
|
|
|
+ <div class="title">
|
|
|
+ <div class="icon"></div>
|
|
|
+ <div class="question" style="cursor: pointer">{{ qh.question }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="result-panel">
|
|
|
+ <div class="result">
|
|
|
+ <div class="tabs">
|
|
|
+ <div :class="`tab tab-active`">
|
|
|
+ <div class="title">回答</div>
|
|
|
+ <div class="bottom"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="result-view">
|
|
|
+ <div class="q-r">
|
|
|
+ <vue-markdown-it
|
|
|
+ :source="qh.msg"
|
|
|
+ :toc="true"
|
|
|
+ :options="{
|
|
|
+ html: true,
|
|
|
+ linkify: true
|
|
|
+ }"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <a-affix v-if="showDoc" style="width: 52%; font-size: 16px" :offset-top="210">
|
|
|
+ <div
|
|
|
+ class="doc"
|
|
|
+ style="width: 100%; background-color: white; height: calc(100vh - 190px); overflow: auto"
|
|
|
+ >
|
|
|
+ <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>
|
|
|
+ </a-affix>
|
|
|
+ </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="1">
|
|
|
+ <div :class="{tool_1: true, active: modelType === '1'}">
|
|
|
+ DeepSeek
|
|
|
+ </div>
|
|
|
+ </a-menu-item>
|
|
|
+ <a-menu-item key="0">
|
|
|
+ <div :class="{tool_1: true, active: modelType === '0'}">
|
|
|
+ 通义千问
|
|
|
+ </div>
|
|
|
+ </a-menu-item>
|
|
|
+ </a-menu>
|
|
|
+ </template>
|
|
|
+ <a-button style="width: 120px">
|
|
|
+ {{ modelType === '1' ? '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" @click="onSendHandle">
|
|
|
+ <i class="iconfont icon-a-lujing9250"></i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import {
|
|
|
+ LoadingOutlined,
|
|
|
+ UpOutlined,
|
|
|
+ DownOutlined
|
|
|
+} from "@ant-design/icons-vue";
|
|
|
+import { fetchEventSource } from '@microsoft/fetch-event-source';
|
|
|
+import PDFViewer from '@/components/pdf/PdfCanvas.vue';
|
|
|
+import WordViewer from '@/components/pdf/WordViewer.vue';
|
|
|
+import AiTextarea from '@/components/TextArea/AiTextarea.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, defineProps, watch } from 'vue';
|
|
|
+import CommonAPI from '@/api/common';
|
|
|
+import ManagerAPI from '@/api/manager';
|
|
|
+import PubsubService from '@/utils/PubsubService';
|
|
|
+import { useUserStore } from '@/stores';
|
|
|
+
|
|
|
+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 onKeydownHandle = (e) => {
|
|
|
+ if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
+ e.preventDefault();
|
|
|
+ onSendHandle();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const onSendHandle = () => {
|
|
|
+ if (historys.value.length > 0 && historys.value[historyIndex].currentResponse.loading) {
|
|
|
+ message.error('回答输出中,请稍后操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!cQuestion.value) {
|
|
|
+ message.error('请输入问题');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ historyIndex++;
|
|
|
+ historys.value.push({
|
|
|
+ question: cQuestion.value,
|
|
|
+ dsChecked: true,
|
|
|
+ currentResponse: {
|
|
|
+ loading: true,
|
|
|
+ hintTxt: '',
|
|
|
+ streamMsg: '',
|
|
|
+ streamMock: false,
|
|
|
+ msg: '',
|
|
|
+ originAnswer: '',
|
|
|
+ docs: []
|
|
|
+ }
|
|
|
+ })
|
|
|
+ ask(decodeURIComponent(cQuestion.value));
|
|
|
+ cQuestion.value = '';
|
|
|
+}
|
|
|
+
|
|
|
+const toToolPage = (path) => {
|
|
|
+ window.open(path, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
+const aiLoading = ref(false);
|
|
|
+const router = useRouter();
|
|
|
+const indicator = h(LoadingOutlined, {
|
|
|
+ style: {
|
|
|
+ fontSize: '24px'
|
|
|
+ },
|
|
|
+ spin: true
|
|
|
+});
|
|
|
+const props = defineProps({
|
|
|
+ searchType: '',
|
|
|
+ askType: {
|
|
|
+ type: String,
|
|
|
+ default: 'zcfg'
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const modelType = ref('1')
|
|
|
+const answerType = ref('0');
|
|
|
+const scope = ref('net');
|
|
|
+
|
|
|
+watch(props.searchType, (val) => {
|
|
|
+ activeTab.value = val;
|
|
|
+});
|
|
|
+
|
|
|
+const question = ref('国有土地的使用方式有哪些?');
|
|
|
+const statusText = ref('检索中');
|
|
|
+const activeIndex = ref(0);
|
|
|
+const activeTab = ref(props.searchType);
|
|
|
+let ctr = null;
|
|
|
+
|
|
|
+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 dsUp = ref(false);
|
|
|
+const times = ref(0);
|
|
|
+const timers = ref([]);
|
|
|
+let streamMockInterval = null;
|
|
|
+const streamToAnswer = () => {
|
|
|
+ console.log('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})" 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;
|
|
|
+
|
|
|
+ console.log('mock 结束');
|
|
|
+ }
|
|
|
+ }, 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';
|
|
|
+ // activeTab.value = 'knowledge';
|
|
|
+ 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' });
|
|
|
+ question.value = (isFllow ? '追问: ' : '') + question.value;
|
|
|
+ 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;
|
|
|
+ activeIndex.value = 0;
|
|
|
+ getQuestionKeyWords();
|
|
|
+ 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 === '1' ? '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: isFllow ? getFlowHistory() : [],
|
|
|
+ stream: true,
|
|
|
+ prompt_name: 'rag_context_qa.md',
|
|
|
+ return_direct: false
|
|
|
+ }
|
|
|
+ } else if (activeTab.value === 'original') {
|
|
|
+ body = {
|
|
|
+ query: question.value,
|
|
|
+ stream: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (activeTab.value === 'knowledge' && isFllow) {
|
|
|
+ if (questHistories.value.length > 0) {
|
|
|
+ body.history_keyword = questHistories.value[questHistories.value.length - 1].keywords;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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.knowledgeServer : 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) {
|
|
|
+ throw err;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const getFlowHistory = () => {
|
|
|
+ const lastHistory = [...questHistories.value].splice(-1);
|
|
|
+ const parm = [];
|
|
|
+ lastHistory.forEach((item) => {
|
|
|
+ parm.push({ role: 'user', content: item.question });
|
|
|
+ });
|
|
|
+
|
|
|
+ return parm;
|
|
|
+};
|
|
|
+
|
|
|
+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.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;
|
|
|
+ if (docsNum && num > docsNum + 1) {
|
|
|
+ }
|
|
|
+
|
|
|
+ historys.value[historyIndex].currentResponse.msg = historys.value[historyIndex].currentResponse.msg.replace(
|
|
|
+ `[[${num}]]`,
|
|
|
+ `<span onclick="window.openDocByIndex(${num}, ${id})" 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;
|
|
|
+ if (docsNum && num > docsNum + 1) {
|
|
|
+ }
|
|
|
+
|
|
|
+ historys.value[historyIndex].currentResponse.msg = historys.value[historyIndex].currentResponse.msg.replace(
|
|
|
+ `[[${num}]]`,
|
|
|
+ `<span onclick="window.openDocByIndex(${num}, ${id})" 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) {
|
|
|
+ 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})" 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) => {
|
|
|
+ if (window.AppGlobalConfig.isDisabledSource) return;
|
|
|
+ var link = doc.link;
|
|
|
+ var type = doc.type;
|
|
|
+ pdfSrc.value = link.replace(
|
|
|
+ window.AppGlobalConfig.knowledgeDocUrl.replace(
|
|
|
+ '=policy&',
|
|
|
+ activeTab.value === 'paper' ? '=compose_paper_material_total&' : '=policy&'
|
|
|
+ ),
|
|
|
+ window.AppGlobalConfig.knowledgeDocUrlProxy.replace(
|
|
|
+ '=policy&',
|
|
|
+ activeTab.value === 'paper' ? '=compose_paper_material_total&' : '=policy&'
|
|
|
+ )
|
|
|
+ );
|
|
|
+ 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) => {
|
|
|
+ showDoc.value = false;
|
|
|
+ let link = null;
|
|
|
+ if (id !== historys.value[historyIndex].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[historyIndex].currentResponse.docs[ind - 1].type === 'url') {
|
|
|
+ openUrl(historys.value[historyIndex].currentResponse.docs[ind - 1].link);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ link = historys.value[historyIndex].currentResponse.docs[ind - 1].link;
|
|
|
+ fileType.value = historys.value[historyIndex].currentResponse.docs[ind - 1].type;
|
|
|
+ pdfContent.value = historys.value[historyIndex].currentResponse.docs[ind - 1].content;
|
|
|
+ pdfNum.value = historys.value[historyIndex].currentResponse.docs[ind - 1].num;
|
|
|
+ }
|
|
|
+ pdfNum.value = ind;
|
|
|
+ pdfSrc.value = link.replace(
|
|
|
+ window.AppGlobalConfig.knowledgeDocUrl.replace(
|
|
|
+ '=policy&',
|
|
|
+ activeTab.value === 'paper' ? '=compose_paper_material_total&' : '=policy&'
|
|
|
+ ),
|
|
|
+ window.AppGlobalConfig.knowledgeDocUrlProxy.replace(
|
|
|
+ '=policy&',
|
|
|
+ activeTab.value === 'paper' ? '=compose_paper_material_total&' : '=policy&'
|
|
|
+ )
|
|
|
+ );
|
|
|
+ showDoc.value = true;
|
|
|
+};
|
|
|
+window.openDocByIndex = openDocByIndex;
|
|
|
+const getNum = (str) => {
|
|
|
+ const matches = str.match(/\[\[(\d+)\]\]/);
|
|
|
+
|
|
|
+ if (matches) {
|
|
|
+ return matches[1]; // 输出: 2
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 埋点采集数据
|
|
|
+const collectQuestion = () => {
|
|
|
+ const { question, originAnswer, keywords = [] } = historys.value[historyIndex].currentResponse;
|
|
|
+ const userStore = useUserStore();
|
|
|
+ const param = {
|
|
|
+ question,
|
|
|
+ answer: originAnswer,
|
|
|
+ questionType: askType.value === 'zcfg' ? '政策法规' : '土地市场',
|
|
|
+ keywords: Array.isArray(keywords) ? keywords.join(',') : keywords,
|
|
|
+ user: '游客',
|
|
|
+ userId: '-1'
|
|
|
+ };
|
|
|
+ if (userStore.isLogin) {
|
|
|
+ const { id = '-1', displayName = '游客' } = userStore?.user?.user || {};
|
|
|
+ param.user = displayName;
|
|
|
+ param.userId = id;
|
|
|
+ }
|
|
|
+
|
|
|
+ ManagerAPI.collect(param).then((res) => {
|
|
|
+ if (res.data) {
|
|
|
+ // 记录日志,用来反馈
|
|
|
+ historys.value[historyIndex].currentResponse.logId = res.data;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const questions = ref([]);
|
|
|
+let recommendedQuestions = [];
|
|
|
+let recommendedQuestionsIndex = 0;
|
|
|
+const getRecommendedQuestion = async (b = false) => {
|
|
|
+ const { question } = historys.value[historyIndex].currentResponse;
|
|
|
+ const myHeaders = new Headers();
|
|
|
+ myHeaders.append('Content-Type', 'application/json');
|
|
|
+ questions.value = [];
|
|
|
+ if (!b) {
|
|
|
+ recommendedQuestions = [];
|
|
|
+ }
|
|
|
+ recommendedQuestionsIndex = 0;
|
|
|
+ const raw = JSON.stringify({
|
|
|
+ query: `你会收到一个用户提问,请根据问题延伸出10个子问题。
|
|
|
+
|
|
|
+在你回答问题的时候,还需要注意推荐给用户的问题必须以列表的形式返回。
|
|
|
+for example:
|
|
|
+[
|
|
|
+ "1、南京市在推进产业用地高质量利用方面采取了哪些具体措施?这些措施的效果如何?",
|
|
|
+ "2、杭州市创新型产业用地管理的具体实施细则是什么?这些细则如何促进传统产业转型升级?",
|
|
|
+ "3、南京市和杭州市在土地供应方式上有哪些不同?这些不同如何影响各自的产业发展?",
|
|
|
+ "4、南京市如何通过政策支持和激励措施吸引重大投资项目?这些措施的实际效果如何?",
|
|
|
+ "5、杭州市“工业上楼”项目的实施情况如何?这一政策对提升土地利用效率有何影响?",
|
|
|
+]
|
|
|
+
|
|
|
+以下是用户提问:${question}`,
|
|
|
+ // model: 'qwen1.5-chat',
|
|
|
+ stream: false
|
|
|
+ });
|
|
|
+
|
|
|
+ const requestOptions = {
|
|
|
+ method: 'POST',
|
|
|
+ headers: myHeaders,
|
|
|
+ body: raw,
|
|
|
+ redirect: 'follow'
|
|
|
+ };
|
|
|
+
|
|
|
+ const rootUrl = modelType.value === '1' ? window.AppGlobalConfig.knowledgeServer : window.AppGlobalConfig.aiServer
|
|
|
+ fetch(rootUrl + '/chat/chat', requestOptions)
|
|
|
+ .then((response) => {
|
|
|
+ return response.json()
|
|
|
+ })
|
|
|
+ .then((msgStr) => {
|
|
|
+ const msg = JSON.parse(msgStr)
|
|
|
+ const str = msg.choices[0]?.message?.content;
|
|
|
+ if (str) {
|
|
|
+ const str1 = str.slice(str.indexOf("</think>") + 7)
|
|
|
+ recommendedQuestions = formatRecommendedQuestions(str1);
|
|
|
+ questions.value = recommendedQuestions.slice(0, 5);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((error) => console.error(error));
|
|
|
+};
|
|
|
+const formatRecommendedQuestions = (str) => {
|
|
|
+ return str
|
|
|
+ .replace(/`|$$|$$|"|,/g, '') // 清除引号、反引号、方括号和逗号
|
|
|
+ .split('\n') // 按换行分割
|
|
|
+ .map(line => line.trim()) // 去除首尾空格
|
|
|
+ .filter(line => { // 双条件过滤
|
|
|
+ return (
|
|
|
+ line.length > 0 && // 过滤空行
|
|
|
+ /^\d+[、.]/.test(line) // 匹配"1、"或"1."开头
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .map(line => {
|
|
|
+ // 统一标点为中文顿号并保留末尾空格
|
|
|
+ return line
|
|
|
+ .replace(/^\d+\./, m => m.replace('.', '、')) // 英文标点转中文
|
|
|
+ .replace(/\s*$/, ' ') // 强制保留末尾3空格
|
|
|
+ });
|
|
|
+}
|
|
|
+const changeRecommendedQuestions = () => {
|
|
|
+ recommendedQuestionsIndex++;
|
|
|
+ if (recommendedQuestionsIndex % 2 === 0) {
|
|
|
+ questions.value = recommendedQuestions.slice(0, 5);
|
|
|
+ } else {
|
|
|
+ questions.value = recommendedQuestions.slice(5);
|
|
|
+ getRecommendedQuestion(true)
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const getQuestionKeyWords = async () => {
|
|
|
+ const { question } = historys.value[historyIndex].currentResponse;
|
|
|
+ const myHeaders = new Headers();
|
|
|
+ myHeaders.append('Content-Type', 'application/json');
|
|
|
+ questions.value = [];
|
|
|
+ const raw = JSON.stringify({
|
|
|
+ query: `请从以下文本中提取核心关键词,确保关键词简洁明了且准确反映文本的主要内容。 请按照以下格式输出:关键词:关键词1,关键词2。文本如下:“${question}”`,
|
|
|
+ stream: false
|
|
|
+ });
|
|
|
+
|
|
|
+ const requestOptions = {
|
|
|
+ method: 'POST',
|
|
|
+ headers: myHeaders,
|
|
|
+ body: raw,
|
|
|
+ redirect: 'follow'
|
|
|
+ };
|
|
|
+
|
|
|
+ const rootUrl = modelType.value === '1' ? window.AppGlobalConfig.knowledgeServer : window.AppGlobalConfig.aiServer
|
|
|
+ return fetch(rootUrl + '/chat/chat', requestOptions)
|
|
|
+ .then((response) => response.json())
|
|
|
+ .then((msgStr) => {
|
|
|
+ const msg = JSON.parse(msgStr)
|
|
|
+ activeIndex.value = 1;
|
|
|
+ const str = msg.choices[0]?.message?.content;
|
|
|
+ if (str) {
|
|
|
+ const str1 = str.slice(str.indexOf("</think>")+7)
|
|
|
+ const keywords = splitWords(str1).slice(0, 3);
|
|
|
+ historys.value[historyIndex].currentResponse.keywords = keywords;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((error) => console.error(error));
|
|
|
+};
|
|
|
+
|
|
|
+const splitWords = (word) => {
|
|
|
+ if (word) {
|
|
|
+ word = word
|
|
|
+ .replaceAll('核心词为:', '')
|
|
|
+ .replaceAll('问题核心词:', '')
|
|
|
+ .replaceAll('>', '')
|
|
|
+ .replaceAll('。', '')
|
|
|
+ .trim();
|
|
|
+
|
|
|
+ if (word.indexOf('\n\n') !== -1) {
|
|
|
+ return word.split('\n\n');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (word.indexOf(',') !== -1) {
|
|
|
+ return word.split(',');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (word.indexOf('、') !== -1) {
|
|
|
+ return word.split('、');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (word.indexOf(',') !== -1) {
|
|
|
+ return word.split(',');
|
|
|
+ }
|
|
|
+
|
|
|
+ return [word];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+const changeActiveTab = (tab) => {
|
|
|
+ activeTab.value = tab;
|
|
|
+};
|
|
|
+
|
|
|
+const stopAI = () => {
|
|
|
+ if (ctr) {
|
|
|
+ ctr.abort();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const openRecommendedQuestion = (q) => {
|
|
|
+ if (q) {
|
|
|
+ if (q.charAt(1) === '、') {
|
|
|
+ q = q.substring(2);
|
|
|
+ }
|
|
|
+ window.open(
|
|
|
+ `/aisearch/#/ai-search?q=${encodeURI(q.trim())}&scope=${activeTab.value}&type=${answerType.value}`,
|
|
|
+ '_blank'
|
|
|
+ );
|
|
|
+ }
|
|
|
+};
|
|
|
+onMounted(() => {
|
|
|
+ const { query } = router.currentRoute.value;
|
|
|
+ if (query.q) {
|
|
|
+ answerType.value = query.type || '0';
|
|
|
+ scope.value = query.scope || 'net';
|
|
|
+ var ds = '0';
|
|
|
+ if (!useUserStore().isLogin) {
|
|
|
+ ds = query.ds;
|
|
|
+ } else {
|
|
|
+ ds = useUserStore().user.user.enableDeepseek
|
|
|
+ ? useUserStore().user.user.enableDeepseek + ''
|
|
|
+ : query.ds;
|
|
|
+ }
|
|
|
+ activeTab.value = scope.value;
|
|
|
+ }
|
|
|
+
|
|
|
+});
|
|
|
+
|
|
|
+defineExpose({ changeActiveTab, stopAI });
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.home_box {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ >div {
|
|
|
+ height: 100%;
|
|
|
+ &.left_box {
|
|
|
+ padding: 30px 20px;
|
|
|
+ background: #fff;
|
|
|
+ width: 260px;
|
|
|
+ border-right: 1px solid #D9E3F2;
|
|
|
+ >button {
|
|
|
+ width: 100%;
|
|
|
+ background: #EAF2FF;
|
|
|
+ border-radius: 10px 10px 10px 10px;
|
|
|
+ border: 1px solid #C5DAFD;
|
|
|
+ color: #3A81EF;
|
|
|
+ padding: 10px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+ >div {
|
|
|
+ >.menu_down {
|
|
|
+ display: flex;
|
|
|
+ align-content: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 10px 0px 10px 0px;
|
|
|
+ font-size: 15px;
|
|
|
+ color: #111;
|
|
|
+ >.icon {
|
|
|
+ color: #646370;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.history {
|
|
|
+ >.history_box {
|
|
|
+ max-height: 600px;
|
|
|
+ overflow-y: auto;
|
|
|
+ >ul {
|
|
|
+ >li {
|
|
|
+ margin-top: 5px;
|
|
|
+ >.title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666666;
|
|
|
+ }
|
|
|
+ >ul {
|
|
|
+ padding-left: 15px;
|
|
|
+ >li {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #222222;
|
|
|
+ padding: 8px 0px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.ai_tools {
|
|
|
+ >ul {
|
|
|
+ >li {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 5px 0px;
|
|
|
+ cursor: pointer;
|
|
|
+ >.icon {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 5px;
|
|
|
+ border-radius: 5px;
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+ &:nth-child(1) {
|
|
|
+ >.icon {
|
|
|
+ background: #D4F3F7;
|
|
|
+ >.iconfont {
|
|
|
+ color: #29BECD;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &:nth-child(2) {
|
|
|
+ >.icon {
|
|
|
+ background: #E5EFFC;
|
|
|
+ >.iconfont {
|
|
|
+ color: #4F7FFF;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ >.txt {
|
|
|
+ font-size: 15px;
|
|
|
+ color: #222222;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.right_box {
|
|
|
+ flex: 1;
|
|
|
+ background: linear-gradient( 180deg, #EFF6FF 0%, #FAFCFF 29%, #FFFFFF 100%);
|
|
|
+ .chat-container {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 800px;
|
|
|
+ height: 100%;
|
|
|
+ margin: 0 auto;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ flex: 1;
|
|
|
+ .messages-container {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ flex: 1;
|
|
|
+ padding: 15px 0px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message {
|
|
|
+ border-radius: 12px;
|
|
|
+ position: relative;
|
|
|
+ animation: messageAppear 0.3s ease-out;
|
|
|
+ word-wrap: break-word;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message.user {
|
|
|
+ background: #E3EEFF;
|
|
|
+ color: white;
|
|
|
+ align-self: flex-end;
|
|
|
+ border-radius: 10px;
|
|
|
+ color: #111111;
|
|
|
+ padding: 10px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message.assistant {
|
|
|
+ align-self: flex-start;
|
|
|
+ border-radius: 12px 12px 12px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message.loading {
|
|
|
+ background: #f8f9fa;
|
|
|
+ padding: 16px;
|
|
|
+ width: 80px;
|
|
|
+ height: 40px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-container {
|
|
|
+ padding: 10px;
|
|
|
+ background: #FFFFFF;
|
|
|
+ box-shadow: 0px 4px 10px 1px rgba(0,0,0,0.1);
|
|
|
+ border-radius: 12px 12px 12px 12px;
|
|
|
+ border: 1px solid #E7E8EA;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ >textarea {
|
|
|
+ border: 0px;
|
|
|
+ outline: none;
|
|
|
+ }
|
|
|
+ >.bottom_box {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ >.tools_box {
|
|
|
+ display: flex;
|
|
|
+ >div {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ >.send_btn {
|
|
|
+ background: linear-gradient( 124deg, #505DFF 0%, #418CFF 100%);
|
|
|
+ border-radius: 5px;
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ line-height: 40px;
|
|
|
+ text-align: center;
|
|
|
+ color: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ >i {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .input-box {
|
|
|
+ width: 100%;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border: 1px solid #e9ecef;
|
|
|
+ border-radius: 8px;
|
|
|
+ resize: none;
|
|
|
+ min-height: 44px;
|
|
|
+ max-height: 200px;
|
|
|
+ font-size: 16px;
|
|
|
+ transition: border-color 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-dots {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-dots span {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ margin: 0 3px;
|
|
|
+ background: #adb5bd;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: bounce 1.4s infinite ease-in-out;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-dots span:nth-child(2) {
|
|
|
+ animation-delay: 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-dots span:nth-child(3) {
|
|
|
+ animation-delay: 0.4s;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes messageAppear {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(10px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes bounce {
|
|
|
+ 0%, 80%, 100% {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ 40% {
|
|
|
+ transform: translateY(-8px);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style scoped lang="scss">
|
|
|
+@import 'src/assets/scss/variables';
|
|
|
+.ai-search-detail {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+
|
|
|
+ .search-panel {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ .search-detail {
|
|
|
+ &-tdsc {
|
|
|
+ width: calc(52%);
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-result {
|
|
|
+ height: calc(100% - 40px);
|
|
|
+
|
|
|
+
|
|
|
+ .result-panel {
|
|
|
+ width: 100%;
|
|
|
+ .result {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .result-view {
|
|
|
+ width: 100%;
|
|
|
+ height: 50%;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ .q-r {
|
|
|
+ line-height: 30px;
|
|
|
+ border-radius: 4px 4px 4px 4px;
|
|
|
+ margin: 20px 0;
|
|
|
+ .ds-content-box {
|
|
|
+ display: flex;
|
|
|
+ .icon {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: white;
|
|
|
+ border: 1px solid #117ff9;
|
|
|
+ img {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .ds-panel {
|
|
|
+ margin-top: -5px;
|
|
|
+ margin-left: 10px;
|
|
|
+ width: calc(100% - 30px);
|
|
|
+ .ds-loading {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ .icon-arrow {
|
|
|
+ padding-left: 5px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .rotate {
|
|
|
+ transform: rotate(180deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .ds-con {
|
|
|
+ border-left: 2px solid #eee;
|
|
|
+ padding-left: 15px;
|
|
|
+ color: rgba(0,0,0,0.6) !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .more-questions {
|
|
|
+ .title {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 14px;
|
|
|
+
|
|
|
+ .change-title {
|
|
|
+ margin-left: 20px;
|
|
|
+ display: flex;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #3c3c3c;
|
|
|
+
|
|
|
+ .change-icon {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ margin-right: 4px;
|
|
|
+ background: url('@/assets/images/ai-search/icon-chang-question.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .source {
|
|
|
+ width: 100%;
|
|
|
+ background: #eff4ff;
|
|
|
+ padding: 30px;
|
|
|
+ padding-bottom: 0px;
|
|
|
+ border-radius: 10px;
|
|
|
+ .title {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 17px;
|
|
|
+ color: #0F0F0F;
|
|
|
+ line-height: 13px;
|
|
|
+ text-align: left;
|
|
|
+ font-style: normal;
|
|
|
+ text-transform: none;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .items {
|
|
|
+ .item {
|
|
|
+ width: calc(100% - 10px);
|
|
|
+ min-height: 82px;
|
|
|
+ border-radius: 10px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ color: #888;
|
|
|
+ display: flex;
|
|
|
+ line-height: 25px;
|
|
|
+ border-radius: 8px 8px 8px 8px;
|
|
|
+
|
|
|
+ .doc {
|
|
|
+ display: flex;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ p {
|
|
|
+ width: calc(100% - 25px);
|
|
|
+
|
|
|
+ .doc-link {
|
|
|
+ cursor: pointer;
|
|
|
+ color: #3082F4;
|
|
|
+ font-size: 14px;
|
|
|
+ &:hover {
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .doc-icon {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ margin-left: 5px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &-show {
|
|
|
+ background: url('/images/zcbd/ai-search/icon-doc-show.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &-hide {
|
|
|
+ background: url('/images/zcbd/ai-search/icon-doc-hide.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .content {
|
|
|
+ //white-space: pre-line;
|
|
|
+ width: 100%;
|
|
|
+ font-family: Alibaba PuHuiTi 3, Alibaba PuHuiTi 30;
|
|
|
+ font-weight: normal;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #757575;
|
|
|
+ line-height: 28px;
|
|
|
+ text-align: left;
|
|
|
+ font-style: normal;
|
|
|
+ text-transform: none;
|
|
|
+
|
|
|
+ &-hide {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-url {
|
|
|
+ margin: 12px 0;
|
|
|
+ background: #f1f9ff;
|
|
|
+ border-radius: 8px 8px 8px 8px;
|
|
|
+
|
|
|
+ .bottom {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .title-icon {
|
|
|
+ display: flex;
|
|
|
+ justify-content: left;
|
|
|
+ align-items: center;
|
|
|
+ width: 90%;
|
|
|
+ float: left;
|
|
|
+
|
|
|
+ .icon {
|
|
|
+ width: 23px;
|
|
|
+ height: 23px;
|
|
|
+ background: url('/images/zcbd/ai-search/icon-net-url.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title {
|
|
|
+ color: #a9a3a3cc;
|
|
|
+ font-size: 16px;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .index {
|
|
|
+ float: right;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 20px;
|
|
|
+ text-align: center;
|
|
|
+ margin: 0 5px;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: #d0d5dd;
|
|
|
+ color: #000;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 土地市场
|
|
|
+ .map-answer {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ min-height: 600px;
|
|
|
+
|
|
|
+ .left-panel {
|
|
|
+ width: 100%;
|
|
|
+ background: #ffffff;
|
|
|
+ z-index: 1000;
|
|
|
+ //position: relative;
|
|
|
+ left: 12px;
|
|
|
+ top: 15px;
|
|
|
+ border-radius: 10px;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ width: 100%;
|
|
|
+ height: 30px;
|
|
|
+ text-align: left;
|
|
|
+ line-height: 30px;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content {
|
|
|
+ width: 100%;
|
|
|
+ height: auto;
|
|
|
+
|
|
|
+ .chart-card {
|
|
|
+ width: 100%;
|
|
|
+ height: 330px;
|
|
|
+
|
|
|
+ .chart-title {
|
|
|
+ width: 100%;
|
|
|
+ height: 30px;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bolder;
|
|
|
+ padding: 0 5px;
|
|
|
+ line-height: 30px;
|
|
|
+ margin: 10px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart {
|
|
|
+ width: 100%;
|
|
|
+ height: 300px;
|
|
|
+ }
|
|
|
+
|
|
|
+ //background: #0a84ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-card {
|
|
|
+ width: 100%;
|
|
|
+ height: 50%;
|
|
|
+
|
|
|
+ .summary-title {
|
|
|
+ width: 100%;
|
|
|
+ height: 30px;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bolder;
|
|
|
+ padding: 0 5px;
|
|
|
+ line-height: 30px;
|
|
|
+ margin: 10px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .summary-content {
|
|
|
+ width: calc(100% - 20px);
|
|
|
+ height: calc(100% - 30px);
|
|
|
+ padding: 0;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ overflow-y: auto;
|
|
|
+ border-radius: 10px;
|
|
|
+ margin: 10px;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Noto Sans, Helvetica,
|
|
|
+ Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
|
|
|
+ font-size: 16px;
|
|
|
+ line-height: 1.5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-panel {
|
|
|
+ width: 100%;
|
|
|
+ height: 630px;
|
|
|
+
|
|
|
+ .map-title {
|
|
|
+ width: 100%;
|
|
|
+ height: 30px;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bolder;
|
|
|
+ padding: 0 5px;
|
|
|
+ line-height: 30px;
|
|
|
+ margin: 10px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .map {
|
|
|
+ width: 100%;
|
|
|
+ height: 600px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ::-webkit-scrollbar {
|
|
|
+ width: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::-webkit-scrollbar-thumb {
|
|
|
+ background-color: rgb(0 0 0 / 10%);
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+</style>
|