|
@@ -1,2669 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="ai-search-detail" id="pageContainer">
|
|
|
- <a-affix>
|
|
|
- <div class="header">
|
|
|
- <ai-search-header />
|
|
|
- </div>
|
|
|
- <div class="sub-header">
|
|
|
- <ai-search-sub-header @search="ask" />
|
|
|
- </div>
|
|
|
- </a-affix>
|
|
|
- <div class="search-panel">
|
|
|
- <a-affix
|
|
|
- ref="markdownTocAffixRef"
|
|
|
- :style="`width: 20%; margin-left: 13%;max-height: ${tocHeight}px;`"
|
|
|
- :offset-top="210"
|
|
|
- v-show="!showDoc"
|
|
|
- >
|
|
|
- <markdown-toc :toc="tocs" :class="`markdown-toc-${askType}`" />
|
|
|
- </a-affix>
|
|
|
- <div :class="`search-detail search-detail-${askType}`" ref="searchRef">
|
|
|
- <div
|
|
|
- class="search-result"
|
|
|
- id="searchResult"
|
|
|
- style="overflow-x: hidden; height: auto"
|
|
|
- ref="messageContainer"
|
|
|
- >
|
|
|
- <div class="top">
|
|
|
- <div class="title">
|
|
|
- <div class="icon"></div>
|
|
|
- <div class="question" style="cursor: pointer" @click="openAskModal">
|
|
|
- {{ question }}
|
|
|
- </div>
|
|
|
- <div v-show="askType === 'tdsc'" class="graph-switch">
|
|
|
- <a-switch v-model:checked="graph" @change="questLandMark" />
|
|
|
- 生成图表
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div :class="`result-panel result-panel-${askType}`">
|
|
|
- <div class="search-steps" v-show="activeIndex !== -1 && activeIndex < 5">
|
|
|
- <div
|
|
|
- v-show="st.key <= activeIndex"
|
|
|
- :class="`step ${st.key < activeIndex ? 'step-done' : ''}`"
|
|
|
- v-for="(st, i) in steps"
|
|
|
- :key="`step-${i}`"
|
|
|
- >
|
|
|
- <div class="info">
|
|
|
- <div class="icon"></div>
|
|
|
- <div class="title">{{ st.name }}</div>
|
|
|
- <div class="tags">
|
|
|
- <div
|
|
|
- v-if="st.key === 2 && askType === 'tdsc'"
|
|
|
- style="width: 20%; font-size: 14px; line-height: 22px"
|
|
|
- class="tag-last"
|
|
|
- >
|
|
|
- {{ st.agent }}
|
|
|
- </div>
|
|
|
- <div
|
|
|
- :class="`tag${st.key === 0 ? ' tag-first' : ''}`"
|
|
|
- v-if="st.key < 3 && askType !== 'tdsc'"
|
|
|
- :title="t"
|
|
|
- v-for="(t, i) in st.tags"
|
|
|
- :key="`tag-${i}`"
|
|
|
- >
|
|
|
- {{ t }}
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="d"
|
|
|
- style="
|
|
|
- width: 80%;
|
|
|
- overflow: hidden;
|
|
|
- animation: scroll-text-992e3d76 25s linear infinite;
|
|
|
- "
|
|
|
- v-if="st.key < 3 && askType === 'tdsc'"
|
|
|
- >
|
|
|
- <div
|
|
|
- :title="t"
|
|
|
- :class="`tag${askType === 'tdsc' ? ' tag-rol' : ''}`"
|
|
|
- v-for="(t, i) in st.tags"
|
|
|
- :key="`tag-${i}`"
|
|
|
- >
|
|
|
- <div>{{ t }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="progress">
|
|
|
- <a-progress size="small" :show-info="false" :percent="st.prog" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="result">
|
|
|
- <template v-if="askType === 'zcfg'">
|
|
|
- <div class="tabs" style="position: relative">
|
|
|
- <div
|
|
|
- :class="`tab${activeTab === t.key ? ' tab-active' : ''}`"
|
|
|
- v-for="t in tabs"
|
|
|
- :key="t.key"
|
|
|
- @click="changeTab(t.key)"
|
|
|
- >
|
|
|
- <div class="title">{{ t.name }}</div>
|
|
|
- <div class="bottom"></div>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="scope-type"
|
|
|
- style="
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- position: absolute;
|
|
|
- right: 5px;
|
|
|
- top: 10px;
|
|
|
- "
|
|
|
- >
|
|
|
- <div class="label">搜索模式:</div>
|
|
|
- <a-radio-group
|
|
|
- v-model:value="answerType"
|
|
|
- @change="changeAnswerType"
|
|
|
- name="radioGroup"
|
|
|
- >
|
|
|
- <a-radio value="0">简洁</a-radio>
|
|
|
- <a-radio value="1">深入</a-radio>
|
|
|
- <a-radio value="2">研究</a-radio>
|
|
|
- </a-radio-group>
|
|
|
- <div class="ds-box">
|
|
|
- <i class="iconfont icon-deepsee"></i>
|
|
|
- <div class="switch">
|
|
|
- <a-switch v-model:checked="dsChecked" @change="onChange" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="result-view">
|
|
|
- <div class="q-r">
|
|
|
- <div class="ds-content-box" v-if="dsChecked">
|
|
|
- <div class="icon">
|
|
|
- <img src="/images/icon-ds.png" />
|
|
|
- </div>
|
|
|
- <div class="ds-panel">
|
|
|
- <div class="ds-loading" v-if="dsHintTxt" @click="dsUp = !dsUp">
|
|
|
- {{ dsHintTxt }}
|
|
|
- <DownOutlined class="icon-arrow" :class="{ rotate: dsUp }" />
|
|
|
- </div>
|
|
|
- <div class="ds-con" v-if="!dsUp">
|
|
|
- <vue-markdown-it
|
|
|
- id="dsMarkdown"
|
|
|
- :source="
|
|
|
- currentResponse.streamMock
|
|
|
- ? currentResponse.streamMsg.indexOf('###') > -1
|
|
|
- ? currentResponse.streamMsg.substring(
|
|
|
- 0,
|
|
|
- currentResponse.streamMsg.indexOf('###')
|
|
|
- )
|
|
|
- : currentResponse.streamMsg
|
|
|
- : currentResponse.msg.indexOf('###') > -1
|
|
|
- ? currentResponse.msg.substring(
|
|
|
- 0,
|
|
|
- currentResponse.msg.indexOf('###')
|
|
|
- )
|
|
|
- : currentResponse.msg
|
|
|
- "
|
|
|
- :options="{
|
|
|
- html: true,
|
|
|
- linkify: true
|
|
|
- }"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <a-spin :indicator="indicator" v-if="dsLoading" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <vue-markdown-it
|
|
|
- id="resMarkdown"
|
|
|
- :source="
|
|
|
- currentResponse.streamMock
|
|
|
- ? currentResponse.streamMsg.indexOf('###') > -1
|
|
|
- ? currentResponse.streamMsg.substring(
|
|
|
- currentResponse.streamMsg.indexOf('###')
|
|
|
- )
|
|
|
- : ''
|
|
|
- : dsChecked
|
|
|
- ? currentResponse.msg.indexOf('###') > -1
|
|
|
- ? currentResponse.msg.substring(currentResponse.msg.indexOf('###'))
|
|
|
- : ''
|
|
|
- : currentResponse.msg
|
|
|
- "
|
|
|
- :options="{
|
|
|
- html: true,
|
|
|
- linkify: true
|
|
|
- }"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <a-skeleton
|
|
|
- active
|
|
|
- v-if="!dsLoading && (activeIndex < 2 || !currentResponse.msg)"
|
|
|
- />
|
|
|
- <div v-if="activeIndex >= 5" class="follow">
|
|
|
- <div class="follow-btn" style="cursor: pointer">
|
|
|
- <!-- <a-button v-show="activeTab === 'knowledge'" style="margin-right: 10px;float: left" type="link" color="green" size="small" @click="reAnswer">-->
|
|
|
- <a-button
|
|
|
- style="
|
|
|
- margin-right: 35px;
|
|
|
- float: left;
|
|
|
- font-family: Microsoft YaHei;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 16px;
|
|
|
- color: #465d7c;
|
|
|
- "
|
|
|
- type="link"
|
|
|
- color="green"
|
|
|
- size="small"
|
|
|
- @click="changeTab(activeTab)"
|
|
|
- >
|
|
|
- 重新生成
|
|
|
- </a-button>
|
|
|
- <div class="eval-card" style="float: left">
|
|
|
- <div
|
|
|
- :class="`eval-icon eval-icon-like${evaluate === 'like' ? '-s' : ''}`"
|
|
|
- @click="evalResponse('like')"
|
|
|
- ></div>
|
|
|
- <a-divider type="vertical" />
|
|
|
- <div
|
|
|
- :class="`eval-icon eval-icon-dislike${
|
|
|
- evaluate === 'dislike' ? '-s' : ''
|
|
|
- }`"
|
|
|
- @click="evalResponse('dislike')"
|
|
|
- ></div>
|
|
|
- <a-divider type="vertical" />
|
|
|
- <a-dropdown :trigger="['click']">
|
|
|
- <a class="ant-dropdown-link" @click.prevent>
|
|
|
- <div :class="`eval-icon eval-icon-copy`"></div>
|
|
|
- </a>
|
|
|
- <template #overlay>
|
|
|
- <a-menu>
|
|
|
- <a-menu-item key="0" @click="copy()"> 复制到剪贴板</a-menu-item>
|
|
|
- <a-menu-divider />
|
|
|
- <a-menu-item key="1" @click="exportAnswer('doc')">
|
|
|
- 导出 Word
|
|
|
- </a-menu-item>
|
|
|
- <a-menu-divider />
|
|
|
- <a-menu-item key="2" @click="exportAnswer('pdf')">
|
|
|
- 导出 PDF
|
|
|
- </a-menu-item>
|
|
|
- </a-menu>
|
|
|
- </template>
|
|
|
- </a-dropdown>
|
|
|
- </div>
|
|
|
-
|
|
|
- <a-button
|
|
|
- style="
|
|
|
- margin-right: 8px;
|
|
|
- background: rgba(33, 133, 242, 0.5);
|
|
|
- border-radius: 5px;
|
|
|
- "
|
|
|
- type="primary"
|
|
|
- size="small"
|
|
|
- v-show="activeTab === 'knowledge'"
|
|
|
- @click="followVisible = !followVisible"
|
|
|
- >
|
|
|
- 追问
|
|
|
- </a-button>
|
|
|
- <!-- <a-dropdown :trigger="['click']">-->
|
|
|
- <!-- <a class="ant-dropdown-link" @click.prevent>-->
|
|
|
- <!-- <MoreOutlined />-->
|
|
|
- <!-- </a>-->
|
|
|
- <!-- <template #overlay>-->
|
|
|
- <!-- <a-menu>-->
|
|
|
- <!-- <a-menu-item key="0">-->
|
|
|
- <!-- 导出 Word-->
|
|
|
- <!-- </a-menu-item>-->
|
|
|
- <!-- <a-menu-divider />-->
|
|
|
- <!-- <a-menu-item key="1">-->
|
|
|
- <!-- 导出 PDF-->
|
|
|
- <!-- </a-menu-item>-->
|
|
|
- <!-- </a-menu>-->
|
|
|
- <!-- </template>-->
|
|
|
- <!-- </a-dropdown>-->
|
|
|
- </div>
|
|
|
- <div class="follow-input" v-show="followVisible">
|
|
|
- <a-textarea
|
|
|
- v-model:value="followQuestion"
|
|
|
- class="input-text"
|
|
|
- v-model="followQuestion"
|
|
|
- placeholder="继续提问"
|
|
|
- />
|
|
|
- <div class="button" @click="followAsk">发送</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- <div class="more-questions" v-if="activeTab === 'knowledge' && activeIndex >= 4">-->
|
|
|
- <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-show="activeIndex === 5">
|
|
|
- <div class="title">
|
|
|
- <div class="icon"></div>
|
|
|
- <div class="text">来源</div>
|
|
|
- <div class="num">({{ currentResponse.docs.length }})</div>
|
|
|
- </div>
|
|
|
- <div class="items">
|
|
|
- <div
|
|
|
- class="item"
|
|
|
- v-if="activeTab !== 'net'"
|
|
|
- v-for="(doc, i) in 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 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="!currentResponse.msg" />
|
|
|
- <vue-markdown-it
|
|
|
- :source="
|
|
|
- currentResponse.streamMock
|
|
|
- ? currentResponse.streamMsg
|
|
|
- : currentResponse.msg
|
|
|
- "
|
|
|
- :options="{
|
|
|
- html: true,
|
|
|
- linkify: true
|
|
|
- }"
|
|
|
- />
|
|
|
- <!-- {{currentResponse.msg}}-->
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="chart-card" v-if="currentResponse.hasChart" id="tdscChartCard">
|
|
|
- <div class="chart-title">生成图表</div>
|
|
|
- <a-skeleton
|
|
|
- active
|
|
|
- style="height: 100%"
|
|
|
- v-if="!currentResponse.chartOption"
|
|
|
- />
|
|
|
- <div v-else class="chart" id="summaryChart"></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- <div class="map-panel" id="tdscMapPanel">-->
|
|
|
- <!-- <div class="map-title">空间位置</div>-->
|
|
|
- <!-- <div class="map">-->
|
|
|
- <!-- <a-i-map />-->
|
|
|
- <!-- </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>
|
|
|
-
|
|
|
- <a-modal force-render class="input-modal" v-model:open="open" :closable="false" width="700px">
|
|
|
- <template #footer>
|
|
|
- <div class="footer" style="height: 26px; margin: 0 auto">
|
|
|
- <div class="left" style="float: left; cursor: pointer" @click="clearModalTextArea">
|
|
|
- 清空
|
|
|
- </div>
|
|
|
- <a-button style="float: right" type="primary" size="small" @click="sendModalTextArea">
|
|
|
- 发送
|
|
|
- </a-button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div class="modal-que">
|
|
|
- <ai-textarea :q="question" ref="aiTextAreaRef" @enter="ask" />
|
|
|
- </div>
|
|
|
- </a-modal>
|
|
|
- <affix-card />
|
|
|
- <div class="footer" style="position: sticky; z-index: 9999">
|
|
|
- <basic-footer />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-<script setup>
|
|
|
-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 { ref, defineProps, watch } from 'vue';
|
|
|
-import CommonAPI from '@/api/common';
|
|
|
-import ManagerAPI from '@/api/manager';
|
|
|
-import AIMap from '@/components/Map/AIMap.vue';
|
|
|
-import * as echarts from 'echarts';
|
|
|
-import PubsubService from '@/utils/PubsubService';
|
|
|
-import MarkdownToc from '@/components/markdown-toc/MarkdownToc.vue';
|
|
|
-import AiSearchHeader from '@/views/ai-search/components/AiSearchHeader.vue';
|
|
|
-import AiSearchSubHeader from '@/views/ai-search/components/AiSearchSubHeader.vue';
|
|
|
-import AffixCard from '@/views/components/AffixCard.vue';
|
|
|
-import BasicFooter from '@/layout/footer/BasicFooter.vue';
|
|
|
-import { useUserStore } from '@/stores';
|
|
|
-const { user: u = {}, updateUser } = useUserStore();
|
|
|
-const { user = {}, token } = u || {};
|
|
|
-import UserAPI from '@/api/user';
|
|
|
-const aiLoading = ref(false);
|
|
|
-const router = useRouter();
|
|
|
-const markdownTocAffixRef = ref(null);
|
|
|
-const tocHeight = ref(530);
|
|
|
-import { LoadingOutlined, DownOutlined } from '@ant-design/icons-vue';
|
|
|
-import { h } from 'vue';
|
|
|
-const indicator = h(LoadingOutlined, {
|
|
|
- style: {
|
|
|
- fontSize: '24px'
|
|
|
- },
|
|
|
- spin: true
|
|
|
-});
|
|
|
-const props = defineProps({
|
|
|
- searchType: '',
|
|
|
- askType: {
|
|
|
- type: String,
|
|
|
- default: 'zcfg'
|
|
|
- }
|
|
|
-});
|
|
|
-
|
|
|
-const answerType = ref('0');
|
|
|
-const dsChecked = ref(false);
|
|
|
-const scope = ref('net');
|
|
|
-watch(
|
|
|
- () => router.currentRoute.value,
|
|
|
- (value) => {
|
|
|
- const { query } = value;
|
|
|
- if (query.q) {
|
|
|
- ask(decodeURIComponent(query.q));
|
|
|
- console.log(decodeURIComponent(query.q));
|
|
|
- answerType.value = query.type || '0';
|
|
|
- scope.value = query.scope || 'net';
|
|
|
- var ds = query.ds;
|
|
|
- dsHintTxt.value = '';
|
|
|
- if (ds && ds == '1') {
|
|
|
- ds = true;
|
|
|
- } else {
|
|
|
- ds = false;
|
|
|
- }
|
|
|
- dsChecked.value = ds;
|
|
|
- }
|
|
|
- }
|
|
|
-);
|
|
|
-
|
|
|
-watch(props.searchType, (val) => {
|
|
|
- activeTab.value = val;
|
|
|
-});
|
|
|
-
|
|
|
-const question = ref('国有土地的使用方式有哪些?');
|
|
|
-const followQuestion = ref('');
|
|
|
-const statusText = ref('检索中');
|
|
|
-const activeIndex = ref(0);
|
|
|
-const activeTab = ref(props.searchType);
|
|
|
-let ctr = null;
|
|
|
-const aiTextAreaRef = ref(null);
|
|
|
-const searchRef = ref(null);
|
|
|
-
|
|
|
-const evaluate = ref(null);
|
|
|
-
|
|
|
-const scrollToBottom = () => {
|
|
|
- const messageContainer = document.getElementById('messageContainer');
|
|
|
- if (messageContainer && searchRef.value) {
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const clearModalTextArea = () => {
|
|
|
- aiTextAreaRef.value.clear();
|
|
|
-};
|
|
|
-
|
|
|
-const sendModalTextArea = () => {
|
|
|
- aiTextAreaRef.value.send();
|
|
|
-};
|
|
|
-
|
|
|
-const tocs = ref([]);
|
|
|
-const open = ref(false);
|
|
|
-const showDoc = ref(false);
|
|
|
-const pdfSrc = ref('');
|
|
|
-const pdfContent = ref('');
|
|
|
-const pdfNum = ref(1);
|
|
|
-const fileType = ref('pdf');
|
|
|
-const dsLoading = ref(true);
|
|
|
-const startTime = ref(0);
|
|
|
-const endTime = ref(0);
|
|
|
-const dsHintTxt = ref('');
|
|
|
-const dsUp = ref(false);
|
|
|
-const times = ref(0);
|
|
|
-const timers = ref([]);
|
|
|
-const currentResponse = ref({
|
|
|
- streamMsg: '',
|
|
|
- streamMock: false,
|
|
|
- msg: '',
|
|
|
- originAnswer: '',
|
|
|
- docs: []
|
|
|
-});
|
|
|
-let streamMockInterval = null;
|
|
|
-const streamToAnswer = () => {
|
|
|
- console.log('streamToAnswer');
|
|
|
- currentResponse.value.index = 0;
|
|
|
- streamMockInterval = setInterval(() => {
|
|
|
- const { originAnswer = '', msg, streamMsg = '', id, index = 0 } = currentResponse.value;
|
|
|
-
|
|
|
- if (currentResponse.value.mockStart || index <= originAnswer.length) {
|
|
|
- currentResponse.value.streamMsg = originAnswer.substr(0, index + 2).replaceAll('\n', ' \n');
|
|
|
- if (originAnswer) {
|
|
|
- currentResponse.value.index += 2;
|
|
|
- }
|
|
|
- let num = getNum(currentResponse.value.streamMsg);
|
|
|
-
|
|
|
- while (num) {
|
|
|
- const docsNum = currentResponse.value.docs.length;
|
|
|
- if (docsNum && num > docsNum + 1) {
|
|
|
- }
|
|
|
-
|
|
|
- currentResponse.value.streamMsg = currentResponse.value.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(currentResponse.value.streamMsg);
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (streamMockInterval) {
|
|
|
- clearInterval(streamMockInterval);
|
|
|
- streamMockInterval = null;
|
|
|
- }
|
|
|
-
|
|
|
- activeIndex.value = 5;
|
|
|
-
|
|
|
- searchRef.value.scrollTop = 0;
|
|
|
- generateToc();
|
|
|
- console.log('mock 结束');
|
|
|
- }
|
|
|
- }, 50);
|
|
|
-};
|
|
|
-
|
|
|
-const tabs = [
|
|
|
- { key: 'knowledge', name: '知识库' },
|
|
|
- { key: 'paper', name: '学术' },
|
|
|
- { key: 'net', name: '全网' }
|
|
|
-];
|
|
|
-
|
|
|
-const steps = ref([
|
|
|
- {
|
|
|
- key: 0,
|
|
|
- name: '问题分析',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 1,
|
|
|
- name: '知识搜索',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 2,
|
|
|
- name: '整理答案',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 3,
|
|
|
- name: '回答完成',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- }
|
|
|
-]);
|
|
|
-
|
|
|
-const doStep = () => {
|
|
|
- setInterval(() => {
|
|
|
- steps.value.forEach((v) => {
|
|
|
- if (v.key < activeIndex.value) {
|
|
|
- v.prog = 100;
|
|
|
- } else if (v.key === activeIndex.value) {
|
|
|
- if (v.prog + 1 === 100) {
|
|
|
- } else {
|
|
|
- v.prog += 1;
|
|
|
- }
|
|
|
- } else {
|
|
|
- v.prog = 0;
|
|
|
- }
|
|
|
- });
|
|
|
- }, 100);
|
|
|
-};
|
|
|
-
|
|
|
-// 相关案例关键词
|
|
|
-const caseKeyWords = ref([]);
|
|
|
-
|
|
|
-const setActiveIndexTags = (index, tags) => {
|
|
|
- steps.value[index].tags = [];
|
|
|
- // 把问题分析部分的关键词带出去
|
|
|
- if (index == 0 && tags.length > 0) {
|
|
|
- caseKeyWords.value = tags;
|
|
|
- }
|
|
|
- for (let i = 0; i < tags.length; i++) {
|
|
|
- setTimeout(function () {
|
|
|
- steps.value[index].tags = tags.slice(0, i + 1);
|
|
|
- }, 500 * (i + 1));
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const evalResponse = (evalType) => {
|
|
|
- // ManagerAPI.feedback(currentResponse.value.logId, evalType === 'like' ? 1 : 2).then((res) => {
|
|
|
- // evaluate.value = evalType;
|
|
|
- // message.info('感谢你的反馈!');
|
|
|
- // });
|
|
|
-};
|
|
|
-
|
|
|
-const changeStatusText = () => {
|
|
|
- let i = 0;
|
|
|
- setInterval(() => {
|
|
|
- statusText.value = '检索中' + '.'.repeat(i);
|
|
|
-
|
|
|
- if (i === 3) {
|
|
|
- i = 0;
|
|
|
- }
|
|
|
- i++;
|
|
|
- }, 500);
|
|
|
-};
|
|
|
-changeStatusText();
|
|
|
-const askType = ref('zcfg');
|
|
|
-const search = (q, type = 'zcfg') => {
|
|
|
- question.value = q;
|
|
|
- askType.value = type;
|
|
|
- if (ctr) {
|
|
|
- ctr.abort();
|
|
|
- ctr = null;
|
|
|
- if (streamMockInterval) {
|
|
|
- clearInterval(streamMockInterval);
|
|
|
- streamMockInterval = null;
|
|
|
- }
|
|
|
- }
|
|
|
- if (type === 'zcfg') {
|
|
|
- quest();
|
|
|
- } else {
|
|
|
- questLandMark();
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const graph = ref(false);
|
|
|
-
|
|
|
-const questLandMark = async () => {
|
|
|
- activeIndex.value = 0;
|
|
|
- tocs.value = [];
|
|
|
- steps.value = [
|
|
|
- {
|
|
|
- key: 0,
|
|
|
- name: '问题分析',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 1,
|
|
|
- name: '任务拆解',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 2,
|
|
|
- name: 'Agent调用',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 3,
|
|
|
- name: '回答完成',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- }
|
|
|
- ];
|
|
|
-
|
|
|
- currentResponse.value = {
|
|
|
- question: question.value,
|
|
|
- id: 1,
|
|
|
- msg: '',
|
|
|
- originAnswer: '',
|
|
|
- docs: [],
|
|
|
- msgFinished: false,
|
|
|
- hasChart: false
|
|
|
- };
|
|
|
- await getQuestionKeyWords();
|
|
|
- ctr = new AbortController();
|
|
|
- await fetchEventSource(window.AppGlobalConfig.landMarketUrl, {
|
|
|
- method: 'POST',
|
|
|
- openWhenHidden: true,
|
|
|
- // redirect: 'follow',
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json'
|
|
|
- },
|
|
|
- body: JSON.stringify({
|
|
|
- data: question.value + (graph.value ? ',需要生成图表' : ',不需要生成图表')
|
|
|
- }),
|
|
|
- // mode: 'no-cors',
|
|
|
- signal: ctr.signal,
|
|
|
- async onmessage(msg) {
|
|
|
- const { data } = msg;
|
|
|
-
|
|
|
- if (!['', '[FINISH]', '[DONE]'].includes(data)) {
|
|
|
- try {
|
|
|
- currentResponse.value.originAnswer = data;
|
|
|
- const response = JSON.parse(data);
|
|
|
-
|
|
|
- if (response) {
|
|
|
- const { agent_responses = [] } = response;
|
|
|
-
|
|
|
- for (let agent of agent_responses) {
|
|
|
- // 问题分析
|
|
|
- if (agent.agent_name === 'plan_dispatcher' && activeIndex.value === 0) {
|
|
|
- if (
|
|
|
- agent.choices.length > 0 &&
|
|
|
- agent.choices.filter((v) => v.role === 'assistant').length > 0
|
|
|
- ) {
|
|
|
- const chio = agent.choices.filter((v) => v.role === 'assistant')[0];
|
|
|
- if (chio.finished) {
|
|
|
- activeIndex.value = 1;
|
|
|
- setActiveIndexTags(0, [chio.content]);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (agent.agent_name === 'land_supply_planner' && activeIndex.value === 1) {
|
|
|
- if (
|
|
|
- agent.choices.length > 0 &&
|
|
|
- agent.choices.filter((v) => v.role === 'assistant').length > 0
|
|
|
- ) {
|
|
|
- const chio = agent.choices.filter((v) => v.role === 'assistant')[0];
|
|
|
-
|
|
|
- if (chio.finished) {
|
|
|
- activeIndex.value = 2;
|
|
|
- setActiveIndexTags(1, [chio.content]);
|
|
|
- if (chio.content.includes('generate_chart')) {
|
|
|
- currentResponse.value.hasChart = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (
|
|
|
- ['generate_chart', 'LandSupplySqlAgent', 'summary'].includes(agent.agent_name) &&
|
|
|
- activeIndex.value === 2
|
|
|
- ) {
|
|
|
- if (
|
|
|
- agent.choices.length > 0 &&
|
|
|
- agent.choices.filter((v) => v.role === 'assistant').length > 0
|
|
|
- ) {
|
|
|
- const chio = agent.choices.filter((v) => v.role === 'assistant')[0];
|
|
|
-
|
|
|
- if (chio.finished && steps.value[2].agent !== agent.agent_name) {
|
|
|
- steps.value[2].agent = agent.agent_name;
|
|
|
- setActiveIndexTags(2, [chio.content]);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (agent.agent_name === 'summary') {
|
|
|
- steps.value[2].agent = 'summary';
|
|
|
- if (activeIndex.value === 2) {
|
|
|
- activeIndex.value = 3;
|
|
|
- }
|
|
|
- if (agent.choices.length === 3) {
|
|
|
- if (!currentResponse.value.msgFinished) {
|
|
|
- currentResponse.value.msg = agent.choices[2].content;
|
|
|
- currentResponse.value.msgFinished = agent.choices[2].finished;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (agent.agent_name === 'generate_chart') {
|
|
|
- currentResponse.value.hasChart = true;
|
|
|
-
|
|
|
- if (agent.executed) {
|
|
|
- const option = JSON.parse(
|
|
|
- agent.exec_res.replace('```json', '').replace('```', '').trim()
|
|
|
- );
|
|
|
-
|
|
|
- currentResponse.value.chartOption = option;
|
|
|
- setTimeout(() => {
|
|
|
- initChart(option);
|
|
|
- }, 1000);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error(e, data);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (data === '[FINISH]') {
|
|
|
- activeIndex.value = 5;
|
|
|
- }
|
|
|
- },
|
|
|
- onclose() {
|
|
|
- activeIndex.value = 5;
|
|
|
- generateToc();
|
|
|
- collectQuestion();
|
|
|
- },
|
|
|
- onerror(err) {
|
|
|
- throw err;
|
|
|
- },
|
|
|
- onopen() {}
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-const openAskModal = () => {
|
|
|
- open.value = true;
|
|
|
- nextTick(() => {
|
|
|
- if (aiTextAreaRef.value) {
|
|
|
- aiTextAreaRef.value.setText(question.value);
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-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 (dsChecked.value) {
|
|
|
- dsHintTxt.value = '';
|
|
|
- dsLoading.value = true;
|
|
|
- }
|
|
|
- question.value = q;
|
|
|
- open.value = false;
|
|
|
- showDoc.value = false;
|
|
|
- askType.value = 'zcfg';
|
|
|
- // activeTab.value = 'knowledge';
|
|
|
- quest(isFllow);
|
|
|
-};
|
|
|
-
|
|
|
-const followAsk = () => {
|
|
|
- ask(followQuestion.value, true);
|
|
|
- followVisible.value = false;
|
|
|
- followQuestion.value = '';
|
|
|
- showDoc.value = false;
|
|
|
-};
|
|
|
-
|
|
|
-let questionUrl = '/chat/kb_chat';
|
|
|
-
|
|
|
-const changeTab = (tab) => {
|
|
|
- if (!dsChecked.value) {
|
|
|
- dsLoading.value = false;
|
|
|
- dsHintTxt.value = '';
|
|
|
- } else {
|
|
|
- dsLoading.value = true;
|
|
|
- dsHintTxt.value = '';
|
|
|
- }
|
|
|
- times.value = 0;
|
|
|
- if (timers.value) {
|
|
|
- timers.value.forEach((t) => {
|
|
|
- clearTimeout(t);
|
|
|
- t = null;
|
|
|
- });
|
|
|
- }
|
|
|
- timers.value = [];
|
|
|
- if (tab === 'net') {
|
|
|
- questionUrl = '/chat/bing_chat';
|
|
|
- } else {
|
|
|
- questionUrl = '/chat/kb_chat';
|
|
|
- }
|
|
|
-
|
|
|
- activeTab.value = tab;
|
|
|
- if (ctr) {
|
|
|
- if (streamMockInterval) {
|
|
|
- clearInterval(streamMockInterval);
|
|
|
- streamMockInterval = null;
|
|
|
- }
|
|
|
- ctr.abort();
|
|
|
- }
|
|
|
- quest();
|
|
|
-};
|
|
|
-const onChange = () => {
|
|
|
- dsChange();
|
|
|
- changeAnswerType();
|
|
|
-};
|
|
|
-//ds绑定用户改变
|
|
|
-const dsChange = () => {
|
|
|
- if (useUserStore().isLogin) {
|
|
|
- var ds = '0';
|
|
|
- if (dsChecked.value) {
|
|
|
- ds = '1';
|
|
|
- }
|
|
|
- UserAPI.changeDeepseek(ds).then((res) => {
|
|
|
- if (res?.success) {
|
|
|
- user.enableDeepseek = parseInt(ds);
|
|
|
- updateUser({ user, token });
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- if (timers.value) {
|
|
|
- timers.value.forEach((t) => {
|
|
|
- clearTimeout(t);
|
|
|
- t = null;
|
|
|
- });
|
|
|
- timers.value = [];
|
|
|
- }
|
|
|
- times.value = 0;
|
|
|
- dsHintTxt.value = '';
|
|
|
- //打字机效果 切换会打印
|
|
|
- aiLoading.value = true;
|
|
|
-};
|
|
|
-const changeAnswerType = () => {
|
|
|
- if (!dsChecked.value) {
|
|
|
- dsLoading.value = false;
|
|
|
- dsHintTxt.value = '';
|
|
|
- } else {
|
|
|
- dsLoading.value = true;
|
|
|
- dsHintTxt.value = '';
|
|
|
- }
|
|
|
- if (ctr) {
|
|
|
- if (streamMockInterval) {
|
|
|
- clearInterval(streamMockInterval);
|
|
|
- streamMockInterval = null;
|
|
|
- }
|
|
|
- ctr.abort();
|
|
|
- }
|
|
|
- quest();
|
|
|
-};
|
|
|
-
|
|
|
-const questHistories = ref([]);
|
|
|
-
|
|
|
-const followVisible = ref(false);
|
|
|
-let scb = null;
|
|
|
-const quest = async (isFllow) => {
|
|
|
- startTime.value = Date.now();
|
|
|
- window.scroll({ top: 0, behavior: 'smooth' });
|
|
|
- tocs.value = [];
|
|
|
- question.value = (isFllow ? '追问: ' : '') + question.value;
|
|
|
- evaluate.value = null;
|
|
|
- activeIndex.value = 0;
|
|
|
- steps.value = [
|
|
|
- {
|
|
|
- key: 0,
|
|
|
- name: '问题分析',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 1,
|
|
|
- name: '知识搜索',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 2,
|
|
|
- name: '整理答案',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- },
|
|
|
- {
|
|
|
- key: 3,
|
|
|
- name: '正在生成答案...',
|
|
|
- tags: [],
|
|
|
- prog: 0
|
|
|
- }
|
|
|
- ];
|
|
|
-
|
|
|
- if (!isFllow) {
|
|
|
- showDoc.value = false;
|
|
|
- }
|
|
|
- searchRef.value.scrollTop = 0;
|
|
|
- if (isFllow) {
|
|
|
- const { id, question, msg, docs, originAnswer, keywords = [] } = currentResponse.value;
|
|
|
- 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(() => {
|
|
|
- scrollToBottom();
|
|
|
- }, 500);
|
|
|
- }
|
|
|
- ctr = new AbortController();
|
|
|
-
|
|
|
- const id = questHistories.value.length;
|
|
|
- currentResponse.value = {
|
|
|
- question: question.value,
|
|
|
- id,
|
|
|
- msg: '',
|
|
|
- originAnswer: '',
|
|
|
- streamMock: false,
|
|
|
- streamMsg: '',
|
|
|
- docs: []
|
|
|
- };
|
|
|
- activeIndex.value = 0;
|
|
|
- await getQuestionKeyWords();
|
|
|
-
|
|
|
- if (activeTab.value === 'net') {
|
|
|
- questionUrl = '/chat/bing_chat';
|
|
|
- } else {
|
|
|
- questionUrl = '/chat/kb_chat';
|
|
|
- }
|
|
|
- getRecommendedQuestion();
|
|
|
-
|
|
|
- const topKs = window?.AppGlobalConfig?.topKs || {
|
|
|
- 0: 5,
|
|
|
- 1: 10,
|
|
|
- 2: 15
|
|
|
- };
|
|
|
-
|
|
|
- const body =
|
|
|
- activeTab.value === 'net'
|
|
|
- ? {
|
|
|
- query: question.value,
|
|
|
- stream: true,
|
|
|
- model: dsChecked.value ? 'deepseek-r1' : '',
|
|
|
- search_type: answerType.value
|
|
|
- }
|
|
|
- : {
|
|
|
- query: question.value,
|
|
|
- mode: 'local_kb',
|
|
|
- kb_name: activeTab.value === 'paper' ? 'compose_paper_material_total' : 'policy',
|
|
|
- top_k: topKs[answerType.value],
|
|
|
- search_type: answerType.value,
|
|
|
- score_threshold: 0.5,
|
|
|
- model: dsChecked.value ? 'deepseek-r1' : '',
|
|
|
- history: isFllow ? getFlowHistory() : [],
|
|
|
- stream: true,
|
|
|
- prompt_name: 'rag_context_qa.md',
|
|
|
- return_direct: false
|
|
|
- };
|
|
|
-
|
|
|
- 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') {
|
|
|
- currentResponse.value.streamMock = true;
|
|
|
- currentResponse.value.mockStart = true;
|
|
|
- streamToAnswer();
|
|
|
- }
|
|
|
-
|
|
|
- await fetchEventSource(window.AppGlobalConfig.knowledgeServer + questionUrl, {
|
|
|
- method: 'POST',
|
|
|
- openWhenHidden: true,
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json'
|
|
|
- },
|
|
|
-
|
|
|
- body: JSON.stringify(body),
|
|
|
- signal: ctr.signal,
|
|
|
- async onmessage(msg) {
|
|
|
- if (activeIndex.value !== 3) {
|
|
|
- activeIndex.value = 3;
|
|
|
- setActiveIndexTags(2, ['知识整理', '要点归纳', '总结摘要']);
|
|
|
- }
|
|
|
-
|
|
|
- activeTab.value === 'net' ? handleNetResponse(msg, id) : handleKnowledgeResponse(msg, id);
|
|
|
- },
|
|
|
- onclose() {
|
|
|
- if (scb !== null) {
|
|
|
- clearInterval(scb);
|
|
|
- scb = null;
|
|
|
-
|
|
|
- collectQuestion();
|
|
|
- }
|
|
|
-
|
|
|
- if (currentResponse.value.streamMock) {
|
|
|
- currentResponse.value.mockStart = false;
|
|
|
- } else {
|
|
|
- activeIndex.value = 5;
|
|
|
-
|
|
|
- searchRef.value.scrollTop = 0;
|
|
|
- generateToc();
|
|
|
- }
|
|
|
- },
|
|
|
- onerror(err) {
|
|
|
- throw err;
|
|
|
- },
|
|
|
- onopen() {
|
|
|
- setActiveIndexTags(1, ['国家法律法规', '地方性法规', '政府规章', '政策解读', '经典案例']);
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-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 = [];
|
|
|
- }
|
|
|
- if (dsChecked.value) {
|
|
|
- endTime.value = Date.now();
|
|
|
- var time = ((endTime.value - startTime.value) / 1000).toFixed(0);
|
|
|
- dsHintTxt.value = `已深度思考(用时 ${time} 秒)`;
|
|
|
- dsLoading.value = false;
|
|
|
- aiLoading.value=true;
|
|
|
- }
|
|
|
- currentResponse.value.originAnswer = rData.choices[0]?.delta?.content.replaceAll(
|
|
|
- '\n',
|
|
|
- ` \n`
|
|
|
- );
|
|
|
- currentResponse.value.msg = rData.choices[0]?.delta?.content.replaceAll('\n', ` \n`);
|
|
|
- let num = getNum(currentResponse.value.msg);
|
|
|
- while (num) {
|
|
|
- const docsNum = currentResponse.value.docs.length;
|
|
|
- if (docsNum && num > docsNum + 1) {
|
|
|
- }
|
|
|
-
|
|
|
- currentResponse.value.msg = currentResponse.value.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(currentResponse.value.msg);
|
|
|
- }
|
|
|
- } else {
|
|
|
- aiLoading.value = false;
|
|
|
- if (dsChecked.value) {
|
|
|
- dsHintTxt.value = '思考中...';
|
|
|
- //ds模式打字机效果输出
|
|
|
- const timer = setTimeout(() => {
|
|
|
- if (!aiLoading.value) {
|
|
|
- currentResponse.value.originAnswer += rData.choices[0]?.delta?.content.replaceAll(
|
|
|
- '\n',
|
|
|
- ` \n`
|
|
|
- );
|
|
|
- currentResponse.value.msg += rData.choices[0]?.delta?.content.replaceAll('\n', ` \n`);
|
|
|
- let num = getNum(currentResponse.value.msg);
|
|
|
- while (num) {
|
|
|
- const docsNum = currentResponse.value.docs.length;
|
|
|
- if (docsNum && num > docsNum + 1) {
|
|
|
- }
|
|
|
-
|
|
|
- currentResponse.value.msg = currentResponse.value.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(currentResponse.value.msg);
|
|
|
- }
|
|
|
- }
|
|
|
- clearTimeout(timer);
|
|
|
- timers.value.push(timer);
|
|
|
- }, times.value * 15);
|
|
|
- } else {
|
|
|
- currentResponse.value.originAnswer += rData.choices[0]?.delta?.content.replaceAll(
|
|
|
- '\n',
|
|
|
- ` \n`
|
|
|
- );
|
|
|
- currentResponse.value.msg += rData.choices[0]?.delta?.content.replaceAll('\n', ` \n`);
|
|
|
- let num = getNum(currentResponse.value.msg);
|
|
|
- while (num) {
|
|
|
- const docsNum = currentResponse.value.docs.length;
|
|
|
- if (docsNum && num > docsNum + 1) {
|
|
|
- }
|
|
|
-
|
|
|
- currentResponse.value.msg = currentResponse.value.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(currentResponse.value.msg);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // currentResponse.value.msg += rData.choices[0]?.delta?.content.replaceAll('\n', ` \n`);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!currentResponse.value.docs.length) {
|
|
|
- if (rData.docs && rData.docs.length) {
|
|
|
- handleDocs(rData.docs);
|
|
|
- }
|
|
|
- }
|
|
|
- times.value++;
|
|
|
-};
|
|
|
-
|
|
|
-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);
|
|
|
- currentResponse.value.msg = res.error;
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
- if (res.rag_finish) {
|
|
|
- if (activeIndex.value < 4) {
|
|
|
- activeIndex.value = 4;
|
|
|
- }
|
|
|
- if (dsChecked.value) {
|
|
|
- endTime.value = Date.now();
|
|
|
- var time = ((endTime.value - startTime.value) / 1000).toFixed(0);
|
|
|
- dsHintTxt.value = `已深度思考(用时 ${time} 秒)`;
|
|
|
- dsLoading.value = false;
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (dsChecked.value && dsHintTxt.value != '思考中...') {
|
|
|
- dsHintTxt.value = '思考中...';
|
|
|
- }
|
|
|
- }
|
|
|
- if (res.result) {
|
|
|
- currentResponse.value.originAnswer = res.result;
|
|
|
- currentResponse.value.msg = res.result.replaceAll('\n', ` \n`);
|
|
|
-
|
|
|
- let num = getNum(currentResponse.value.msg);
|
|
|
-
|
|
|
- while (num) {
|
|
|
- const docsNum = currentResponse.value.docs.length;
|
|
|
- if (docsNum && num > docsNum + 1) {
|
|
|
- currentResponse.value.msg = currentResponse.value.msg.replace(`[[${num}]]`, ``);
|
|
|
- }
|
|
|
- currentResponse.value.msg = currentResponse.value.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(currentResponse.value.msg);
|
|
|
- }
|
|
|
- }
|
|
|
- if (res.source_list && res.source_list.length && !currentResponse.value.docs.length) {
|
|
|
- handleDocs(res.source_list);
|
|
|
- }
|
|
|
- } catch (e) {}
|
|
|
- }
|
|
|
-
|
|
|
- if (rData === '[DONE]') {
|
|
|
- console.log(currentResponse.value.msg);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const handleDocs = (docs) => {
|
|
|
- if (
|
|
|
- docs.length === 1 &&
|
|
|
- "<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答!</span>" === docs[0]
|
|
|
- ) {
|
|
|
- // message.warn("知识库中未收录相关内容,将为您自动切换全网模式,请稍候......");
|
|
|
- // changeTab('net');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- currentResponse.value.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.indexOf('.pdf') > 0) {
|
|
|
- return {
|
|
|
- index: i++,
|
|
|
- doc: v.substring(v.indexOf('] [') + 3, v.indexOf('.pdf]') + 4),
|
|
|
- link: v.substring(v.indexOf('.pdf]') + 6, v.indexOf('.pdf)') + 4),
|
|
|
- content: v.substring(v.indexOf('.pdf)') + 5),
|
|
|
- showContent: false,
|
|
|
- type: 'pdf'
|
|
|
- };
|
|
|
- } else if (v.indexOf('.txt') > 0) {
|
|
|
- return {
|
|
|
- index: i++,
|
|
|
- doc: v.substring(v.indexOf('] [') + 3, v.indexOf('.txt]') + 4),
|
|
|
- link: v.substring(v.indexOf('.txt]') + 6, v.indexOf('.txt)') + 4),
|
|
|
- content: v.substring(v.indexOf('.txt)') + 5),
|
|
|
- showContent: false,
|
|
|
- type: 'txt'
|
|
|
- };
|
|
|
- } else if (v.indexOf('.docx') > 0) {
|
|
|
- return {
|
|
|
- index: i++,
|
|
|
- doc: v.substring(v.indexOf('] [') + 3, v.indexOf('.docx]') + 5),
|
|
|
- link: v.substring(v.indexOf('.docx]') + 7, v.indexOf('.docx)') + 5),
|
|
|
- content: v.substring(v.indexOf('.docx)') + 6),
|
|
|
- showContent: false,
|
|
|
- type: 'docx'
|
|
|
- };
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
-const openDoc = (doc, i) => {
|
|
|
- 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 +
|
|
|
- (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 !== currentResponse.value.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 (currentResponse.value.docs[ind - 1].type === 'url') {
|
|
|
- openUrl(currentResponse.value.docs[ind - 1].link);
|
|
|
- return;
|
|
|
- }
|
|
|
- link = currentResponse.value.docs[ind - 1].link;
|
|
|
- fileType.value = currentResponse.value.docs[ind - 1].type;
|
|
|
- pdfContent.value = currentResponse.value.docs[ind - 1].content;
|
|
|
- pdfNum.value = currentResponse.value.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 +
|
|
|
- (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 = [] } = currentResponse.value;
|
|
|
- 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) {
|
|
|
- // // 记录日志,用来反馈
|
|
|
- // currentResponse.value.logId = res.data;
|
|
|
- // }
|
|
|
- // });
|
|
|
-};
|
|
|
-
|
|
|
-const questions = ref([]);
|
|
|
-let recommendedQuestions = [];
|
|
|
-let recommendedQuestionsIndex = 0;
|
|
|
-const getRecommendedQuestion = async (b = false) => {
|
|
|
- const { question } = currentResponse.value;
|
|
|
- const myHeaders = new Headers();
|
|
|
- myHeaders.append('Content-Type', 'application/json');
|
|
|
- questions.value = [];
|
|
|
- if (!b) {
|
|
|
- recommendedQuestions = [];
|
|
|
- }
|
|
|
- recommendedQuestionsIndex = 0;
|
|
|
- const raw = JSON.stringify({
|
|
|
- messages: [
|
|
|
- {
|
|
|
- content: `你会收到一个用户提问,请根据问题延伸出10个子问题。
|
|
|
-
|
|
|
-在你回答问题的时候,还需要注意推荐给用户的问题必须以列表的形式返回。
|
|
|
-for example:
|
|
|
-[
|
|
|
- "1、南京市在推进产业用地高质量利用方面采取了哪些具体措施?这些措施的效果如何?",
|
|
|
- "2、杭州市创新型产业用地管理的具体实施细则是什么?这些细则如何促进传统产业转型升级?",
|
|
|
- "3、南京市和杭州市在土地供应方式上有哪些不同?这些不同如何影响各自的产业发展?",
|
|
|
- "4、南京市如何通过政策支持和激励措施吸引重大投资项目?这些措施的实际效果如何?",
|
|
|
- "5、杭州市“工业上楼”项目的实施情况如何?这一政策对提升土地利用效率有何影响?",
|
|
|
-]
|
|
|
-
|
|
|
-以下是用户提问:${question}`,
|
|
|
- role: 'user'
|
|
|
- }
|
|
|
- ],
|
|
|
- // model: 'qwen1.5-chat',
|
|
|
- model: 'qwen2.5-instruct',
|
|
|
- stream: false
|
|
|
- });
|
|
|
-
|
|
|
- const requestOptions = {
|
|
|
- method: 'POST',
|
|
|
- headers: myHeaders,
|
|
|
- body: raw,
|
|
|
- redirect: 'follow'
|
|
|
- };
|
|
|
-
|
|
|
- fetch(window.AppGlobalConfig.knowledgeServer + '/chat/chat/completions', requestOptions)
|
|
|
- .then((response) => response.json())
|
|
|
- .then((msg) => {
|
|
|
- const str = msg.choices[0]?.message?.content;
|
|
|
- if (str) {
|
|
|
- recommendedQuestions = JSON.parse(str);
|
|
|
- questions.value = recommendedQuestions.slice(0, 5);
|
|
|
- }
|
|
|
- })
|
|
|
- .catch((error) => console.error(error));
|
|
|
-};
|
|
|
-
|
|
|
-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 } = currentResponse.value;
|
|
|
- const myHeaders = new Headers();
|
|
|
- myHeaders.append('Content-Type', 'application/json');
|
|
|
- questions.value = [];
|
|
|
- const raw = JSON.stringify({
|
|
|
- messages: [
|
|
|
- {
|
|
|
- // 精确关键词提取叙述
|
|
|
- content: `
|
|
|
- 请从以下文本中提取核心关键词,确保关键词简洁明了且准确反映文本的主要内容。
|
|
|
- 请按照以下格式输出:
|
|
|
- 关键词:关键词1,关键词2。
|
|
|
- 文本如下:“${question}”`,
|
|
|
- role: 'user'
|
|
|
- }
|
|
|
- ],
|
|
|
- // model: 'qwen1.5-chat',
|
|
|
- model: 'qwen2.5-instruct',
|
|
|
- stream: false
|
|
|
- });
|
|
|
-
|
|
|
- const requestOptions = {
|
|
|
- method: 'POST',
|
|
|
- headers: myHeaders,
|
|
|
- body: raw,
|
|
|
- redirect: 'follow'
|
|
|
- };
|
|
|
-
|
|
|
- return fetch(window.AppGlobalConfig.knowledgeServer + '/chat/chat/completions', requestOptions)
|
|
|
- .then((response) => response.json())
|
|
|
- .then((msg) => {
|
|
|
- activeIndex.value = 1;
|
|
|
- const str = msg.choices[0]?.message?.content;
|
|
|
- if (str) {
|
|
|
- // steps.value[0].tags = str.split('\n\n').replaceAll('核心词为:', '').replaceAll("。", "").map(q => {
|
|
|
- //
|
|
|
- // return q.substring(q.indexOf('.') + 1, q.length)
|
|
|
- // }).splice(0,3);
|
|
|
- const keywords = splitWords(str).slice(0, 3);
|
|
|
- setActiveIndexTags(0, keywords);
|
|
|
- currentResponse.value.keywords = keywords;
|
|
|
- }
|
|
|
- })
|
|
|
- .catch((error) => console.error(error));
|
|
|
-};
|
|
|
-
|
|
|
-const splitWords = (word) => {
|
|
|
- if (word) {
|
|
|
- word = word
|
|
|
- .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 exportAnswer = (type) => {
|
|
|
- const { question, originAnswer } = currentResponse.value;
|
|
|
- CommonAPI.answerExport(type, { question, answer: originAnswer });
|
|
|
-};
|
|
|
-
|
|
|
-const copy = async () => {
|
|
|
- try {
|
|
|
- const input = document.createElement('input');
|
|
|
- input.value = currentResponse.value.msg;
|
|
|
- document.body.appendChild(input);
|
|
|
- input.select();
|
|
|
- document.execCommand('copy');
|
|
|
- document.body.removeChild(input);
|
|
|
-
|
|
|
- message.info('内容已复制到剪贴板');
|
|
|
- } catch (err) {
|
|
|
- console.error(err);
|
|
|
- // console.error('复制到剪贴板失败', err);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-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(
|
|
|
- `/#/ai-search?q=${encodeURI(q.trim())}&scope=${activeTab.value}&type=${answerType.value}`,
|
|
|
- '_blank'
|
|
|
- );
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-function initChart(option = {}) {
|
|
|
- let chart = echarts.init(document.getElementById('summaryChart'));
|
|
|
- if (option.option) {
|
|
|
- chart.setOption(option.option);
|
|
|
- } else {
|
|
|
- chart.setOption(option);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const generateToc = () => {
|
|
|
- if (askType.value === 'tdsc') {
|
|
|
- const summaryTitle = document.getElementById('tdscSummaryTitle');
|
|
|
- const chartCard = document.getElementById('tdscChartCard');
|
|
|
- const mapPanel = document.getElementById('tdscMapPanel');
|
|
|
-
|
|
|
- tocs.value = [];
|
|
|
- const offset = 550 + 210;
|
|
|
- tocs.value.push({
|
|
|
- id: 'tdscSummaryTitle',
|
|
|
- children: [],
|
|
|
- text: '总结',
|
|
|
- top: summaryTitle.offsetTop + offset
|
|
|
- });
|
|
|
-
|
|
|
- if (chartCard.style.display !== 'none') {
|
|
|
- tocs.value.push({
|
|
|
- id: 'tdscChartCard',
|
|
|
- children: [],
|
|
|
- text: '生成图表',
|
|
|
- top: chartCard.offsetTop + offset
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // tocs.value.push({
|
|
|
- // id: 'tdscMapPanel',
|
|
|
- // children: [],
|
|
|
- // text: '空间位置',
|
|
|
- // top: mapPanel.offsetTop + offset
|
|
|
- // });
|
|
|
- return;
|
|
|
- }
|
|
|
- const markdowns = document.getElementById('resMarkdown').children;
|
|
|
- let index = 1;
|
|
|
- let children = [];
|
|
|
- let toc = [];
|
|
|
- for (const markdown of markdowns) {
|
|
|
- if (markdown.tagName.length === 2 && markdown.tagName.toLocaleUpperCase().startsWith('H')) {
|
|
|
- const id = 'title' + index++ + new Date().getTime();
|
|
|
- markdown.setAttribute('id', id);
|
|
|
- const tocItem = {
|
|
|
- id,
|
|
|
- text: markdown.innerText.trim(),
|
|
|
- children: [],
|
|
|
- top: markdown.offsetTop
|
|
|
- };
|
|
|
- children = tocItem.children;
|
|
|
- toc.push(tocItem);
|
|
|
- }
|
|
|
- if (markdown.tagName.toLocaleUpperCase().trim() === 'P') {
|
|
|
- const pChildren = markdown.children;
|
|
|
- for (const pChild of pChildren) {
|
|
|
- if (pChild.tagName.toLocaleUpperCase().trim() === 'STRONG') {
|
|
|
- const id = 'strong' + index++ + new Date().getTime();
|
|
|
- pChild.setAttribute('id', id);
|
|
|
- const strongItem = {
|
|
|
- id,
|
|
|
- text: pChild?.innerText?.trim(),
|
|
|
- children: [],
|
|
|
- top: pChild.offsetTop
|
|
|
- };
|
|
|
- children.push(strongItem);
|
|
|
- // children = strongItem.children
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- tocs.value = toc;
|
|
|
-
|
|
|
- console.log('toc', toc);
|
|
|
-};
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- tocHeight.value = window.document.body.offsetHeight - 210 - 51;
|
|
|
- doStep();
|
|
|
- const { query } = router.currentRoute.value;
|
|
|
- if (query.q) {
|
|
|
- ask(decodeURIComponent(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;
|
|
|
- }
|
|
|
- dsHintTxt.value = '';
|
|
|
- if (ds && ds == '1') {
|
|
|
- ds = true;
|
|
|
- dsLoading.value = true;
|
|
|
- } else {
|
|
|
- ds = false;
|
|
|
- dsLoading.value = false;
|
|
|
- }
|
|
|
- dsChecked.value = ds;
|
|
|
- activeTab.value = scope.value;
|
|
|
- }
|
|
|
-
|
|
|
- window.addEventListener(
|
|
|
- 'scroll',
|
|
|
- () => {
|
|
|
- if (markdownTocAffixRef.value && markdownTocAffixRef.value.updatePosition) {
|
|
|
- markdownTocAffixRef.value.updatePosition();
|
|
|
- }
|
|
|
- },
|
|
|
- true
|
|
|
- );
|
|
|
-});
|
|
|
-
|
|
|
-defineExpose({ search, changeActiveTab, stopAI });
|
|
|
-</script>
|
|
|
-<style scoped lang="scss">
|
|
|
-@import 'src/assets/scss/variables';
|
|
|
-
|
|
|
-.ai-search-detail {
|
|
|
- width: 100%;
|
|
|
- background: $background_color;
|
|
|
-
|
|
|
- .header {
|
|
|
- width: 100%;
|
|
|
- height: 60px;
|
|
|
- }
|
|
|
-
|
|
|
- .sub-header {
|
|
|
- width: 100%;
|
|
|
- height: 130px;
|
|
|
- }
|
|
|
-
|
|
|
- .search-panel {
|
|
|
- width: 100%;
|
|
|
- display: flex;
|
|
|
- padding: 51px 0;
|
|
|
-
|
|
|
- .markdown-toc-tdsc {
|
|
|
- :deep(.toc-list) {
|
|
|
- margin: 30px 0;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .search-detail {
|
|
|
- width: calc(52%);
|
|
|
- background: #ffffff;
|
|
|
- margin-left: 52px;
|
|
|
-
|
|
|
- &-tdsc {
|
|
|
- width: calc(52%);
|
|
|
- }
|
|
|
-
|
|
|
- .search-result {
|
|
|
- height: calc(100% - 40px);
|
|
|
-
|
|
|
- .top {
|
|
|
- width: 100%;
|
|
|
- //height: 105px;
|
|
|
- background: #fff;
|
|
|
- //margin-bottom: 5px;
|
|
|
- border-radius: 4px 4px 4px 4px;
|
|
|
-
|
|
|
- .title {
|
|
|
- width: calc(100% - 7px - 2px);
|
|
|
- display: flex;
|
|
|
- //margin-bottom: 24PX;
|
|
|
- align-items: center;
|
|
|
- min-height: 33px;
|
|
|
-
|
|
|
- .icon {
|
|
|
- width: 33px;
|
|
|
- height: 29px;
|
|
|
- background: url('@/assets/images/ai-search/question-prefixicon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- margin-right: 16px;
|
|
|
- }
|
|
|
-
|
|
|
- .question {
|
|
|
- width: calc(100% - 32px - 150px);
|
|
|
- //line-height: 20px;
|
|
|
- font-family: PingFang SC, PingFang SC;
|
|
|
- font-weight: 600;
|
|
|
- font-size: 24px;
|
|
|
- color: #212121;
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
- }
|
|
|
-
|
|
|
- .graph-switch {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-left: 10px;
|
|
|
- width: 110px;
|
|
|
-
|
|
|
- button {
|
|
|
- margin-right: 2px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .status {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-top: 15px;
|
|
|
- justify-content: center;
|
|
|
-
|
|
|
- @keyframes shake {
|
|
|
- 0% {
|
|
|
- transform: translateX(0);
|
|
|
- }
|
|
|
- 25% {
|
|
|
- transform: translateX(0px);
|
|
|
- }
|
|
|
- 50% {
|
|
|
- transform: translateX(2px);
|
|
|
- }
|
|
|
- 75% {
|
|
|
- transform: translateX(0px);
|
|
|
- }
|
|
|
- 100% {
|
|
|
- transform: translateX(2px);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .text {
|
|
|
- width: 55px;
|
|
|
- font-family: Alibaba PuHuiTi 3, Alibaba PuHuiTi 30;
|
|
|
- font-weight: normal;
|
|
|
- font-size: 14px;
|
|
|
- color: #9da6b5;
|
|
|
- line-height: 20px;
|
|
|
- text-stroke: 1px rgba(0, 0, 0, 0);
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
- -webkit-text-stroke: 1px rgba(0, 0, 0, 0);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .result-panel {
|
|
|
- width: 100%;
|
|
|
- background: #fff;
|
|
|
- padding: 29px 0;
|
|
|
-
|
|
|
- .search-steps {
|
|
|
- width: 100%;
|
|
|
- padding: 4px 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 30px;
|
|
|
- flex-direction: column;
|
|
|
-
|
|
|
- .step {
|
|
|
- width: 100%;
|
|
|
-
|
|
|
- .info {
|
|
|
- width: 100%;
|
|
|
- padding: 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .icon {
|
|
|
- width: 18px;
|
|
|
- height: 18px;
|
|
|
- background: url('@/assets/images/ai-search/step-wait-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- margin-right: 7px;
|
|
|
- }
|
|
|
-
|
|
|
- .title {
|
|
|
- width: 120px;
|
|
|
-
|
|
|
- font-family: PingFang SC, PingFang SC;
|
|
|
- font-weight: 600;
|
|
|
- font-size: 16px;
|
|
|
- color: #212121;
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
- }
|
|
|
-
|
|
|
- .tags {
|
|
|
- width: calc(100% - 10px - 27px - 100px);
|
|
|
- display: flex;
|
|
|
- justify-content: end;
|
|
|
- overflow: hidden;
|
|
|
- height: 30px;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .tag {
|
|
|
- padding: 4px 14px;
|
|
|
-
|
|
|
- margin-left: 8px;
|
|
|
- line-height: 14px;
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
- white-space: nowrap;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- max-width: 200px;
|
|
|
- cursor: pointer;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 14px;
|
|
|
- color: #2185f2;
|
|
|
- height: 24px;
|
|
|
- background: #f5f9fc;
|
|
|
- border: 1px solid #e9eef5;
|
|
|
- font-family: PingFang SC, PingFang SC;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 14px;
|
|
|
- color: #525b74;
|
|
|
- /* line-height: 0px; */
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
-
|
|
|
- &-first {
|
|
|
- background: none;
|
|
|
-
|
|
|
- font-family: Microsoft YaHei;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 16px;
|
|
|
- color: #666666;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .tag-rol {
|
|
|
- display: inline-block;
|
|
|
- white-space: nowrap;
|
|
|
- box-sizing: border-box;
|
|
|
- //animation: scroll-text 25s linear infinite;
|
|
|
- //width: 100%;
|
|
|
- background: none;
|
|
|
- height: 22px;
|
|
|
- max-width: none;
|
|
|
- overflow: unset;
|
|
|
- }
|
|
|
-
|
|
|
- @keyframes scroll-text {
|
|
|
- 0% {
|
|
|
- transform: translateX(100%);
|
|
|
- }
|
|
|
- 100% {
|
|
|
- transform: translateX(-100%);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- &-done {
|
|
|
- .info {
|
|
|
- .icon {
|
|
|
- width: 18px;
|
|
|
- height: 18px;
|
|
|
- background: url('@/assets/images/ai-search/step-done-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- margin-right: 7px;
|
|
|
- }
|
|
|
-
|
|
|
- .title {
|
|
|
- font-family: Microsoft YaHei;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 16px;
|
|
|
- color: #333333;
|
|
|
- line-height: 30px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .result {
|
|
|
- width: 100%;
|
|
|
-
|
|
|
- .tabs {
|
|
|
- display: flex;
|
|
|
- border-bottom: 1px solid #ccc;
|
|
|
- .ds-box {
|
|
|
- display: flex;
|
|
|
- height: 36px;
|
|
|
- align-items: center;
|
|
|
- .iconfont {
|
|
|
- color: #4d6bfe;
|
|
|
- font-size: 22px;
|
|
|
- margin-right: 13px;
|
|
|
- }
|
|
|
- .switch {
|
|
|
- :deep(.ant-switch-checked) {
|
|
|
- background-color: #117ff9;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- .tab {
|
|
|
- font-family: PingFang SC, PingFang SC;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 24px;
|
|
|
- color: #212121;
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
- margin-right: 28px;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- .title {
|
|
|
- margin-bottom: 9px;
|
|
|
- }
|
|
|
-
|
|
|
- .bottom {
|
|
|
- height: 0;
|
|
|
- margin-top: 10px;
|
|
|
- }
|
|
|
-
|
|
|
- &-active,
|
|
|
- &:hover {
|
|
|
- font-family: PingFang SC, PingFang SC;
|
|
|
- font-weight: 400;
|
|
|
- font-size: 24px;
|
|
|
- color: #1586fa;
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
-
|
|
|
- .bottom {
|
|
|
- width: 100%;
|
|
|
- height: 4px;
|
|
|
- background: #446ae7;
|
|
|
- border-radius: 2px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .result-view {
|
|
|
- width: 100%;
|
|
|
- height: 50%;
|
|
|
- overflow-y: auto;
|
|
|
-
|
|
|
- .q-r {
|
|
|
- padding: 20px;
|
|
|
- line-height: 30px;
|
|
|
- background: #f5f8fa;
|
|
|
- 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% - 40px);
|
|
|
- .ds-loading {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- .icon-arrow {
|
|
|
- padding-left: 5px;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
- }
|
|
|
- .rotate {
|
|
|
- transform: rotate(180deg);
|
|
|
- }
|
|
|
- }
|
|
|
- .ds-con {
|
|
|
- border-left: 1px solid #ccc;
|
|
|
- padding-left: 10px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 追问
|
|
|
- .follow {
|
|
|
- padding: 0 20px 20px 0;
|
|
|
- width: 100%;
|
|
|
- text-align: right;
|
|
|
- height: auto;
|
|
|
- min-height: 40px;
|
|
|
-
|
|
|
- .eval-card {
|
|
|
- width: auto;
|
|
|
- display: flex;
|
|
|
- /* float: left; */
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .eval-icon {
|
|
|
- width: 22px;
|
|
|
- height: 22px;
|
|
|
-
|
|
|
- &-like {
|
|
|
- background: url('@/assets/images/ai-search/eval-like-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
-
|
|
|
- &-s,
|
|
|
- &:hover {
|
|
|
- background: url('@/assets/images/ai-search/eval-like-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- &-dislike {
|
|
|
- background: url('@/assets/images/ai-search/eval-dislike-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
-
|
|
|
- &-s,
|
|
|
- &:hover {
|
|
|
- background: url('@/assets/images/ai-search/eval-dislike-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- &-copy {
|
|
|
- background: url('@/assets/images/ai-search/eval-copy-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
-
|
|
|
- &-s,
|
|
|
- &:hover {
|
|
|
- background: url('@/assets/images/ai-search/eval-copy-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .follow-input {
|
|
|
- margin-top: 10px;
|
|
|
- position: relative;
|
|
|
-
|
|
|
- textarea {
|
|
|
- width: 100%;
|
|
|
- height: 120px;
|
|
|
- border-color: white;
|
|
|
- border-radius: 10px;
|
|
|
- resize: none;
|
|
|
- padding: 10px;
|
|
|
- background: transparent;
|
|
|
- font-size: 19px;
|
|
|
- border-color: #4096ff;
|
|
|
- box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
|
|
|
-
|
|
|
- textarea:focus-visible,
|
|
|
- textarea:focus {
|
|
|
- border: none !important;
|
|
|
- outline: none;
|
|
|
- }
|
|
|
-
|
|
|
- textarea:focus {
|
|
|
- border: none !important;
|
|
|
- outline: none;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .button {
|
|
|
- width: 110px;
|
|
|
- height: 40px;
|
|
|
- background: linear-gradient(90deg, #5c62ea, #517de2);
|
|
|
- border-radius: 20px;
|
|
|
- position: absolute;
|
|
|
- right: 10px;
|
|
|
- bottom: 12px;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- font-family: Alibaba PuHuiTi 2;
|
|
|
- font-weight: normal;
|
|
|
- font-size: 16px;
|
|
|
- color: #ffffff;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .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%;
|
|
|
-
|
|
|
- .title {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- font-family: PingFang SC;
|
|
|
- font-weight: bold;
|
|
|
- font-size: 20px;
|
|
|
- color: #333333;
|
|
|
- line-height: 30px;
|
|
|
-
|
|
|
- .icon {
|
|
|
- width: 20px;
|
|
|
- height: 20px;
|
|
|
- margin-right: 8px;
|
|
|
- background: url('@/assets/images/ai-search/source-icon.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
-
|
|
|
- .text {
|
|
|
- font-family: Alibaba PuHuiTi 3, Alibaba PuHuiTi 30;
|
|
|
- font-weight: normal;
|
|
|
- //font-size: 16px;
|
|
|
- color: #404557;
|
|
|
- line-height: 30px;
|
|
|
- text-align: left;
|
|
|
- font-style: normal;
|
|
|
- text-transform: none;
|
|
|
- margin-right: 5px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .items {
|
|
|
- .item {
|
|
|
- width: calc(100% - 10px);
|
|
|
- min-height: 82px;
|
|
|
- background: rgba(255, 255, 255, 0.5);
|
|
|
- border-radius: 10px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- padding: 20px;
|
|
|
- color: #888;
|
|
|
- display: flex;
|
|
|
- line-height: 25px;
|
|
|
- margin-bottom: 20px;
|
|
|
- margin: 12px 0;
|
|
|
- background: #f1f9ff;
|
|
|
- border-radius: 8px 8px 8px 8px;
|
|
|
-
|
|
|
- .doc {
|
|
|
- display: flex;
|
|
|
- width: 100%;
|
|
|
-
|
|
|
- p {
|
|
|
- width: calc(100% - 25px);
|
|
|
-
|
|
|
- .doc-link {
|
|
|
- cursor: pointer;
|
|
|
- color: #446ae7;
|
|
|
-
|
|
|
- &: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;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .input-modal {
|
|
|
- .modal-que {
|
|
|
- background: #0e91fd;
|
|
|
-
|
|
|
- textarea {
|
|
|
- width: 100%;
|
|
|
- height: 257px !important;
|
|
|
- border: none;
|
|
|
- border-radius: 10px;
|
|
|
- resize: none;
|
|
|
- padding: 10px;
|
|
|
- background: transparent;
|
|
|
- font-size: 19px;
|
|
|
-
|
|
|
- &:focus-visible {
|
|
|
- border: none !important;
|
|
|
- outline: none;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|
|
|
-
|