QAndA.vue 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221
  1. <template>
  2. <div class="app-container">
  3. <!-- 顶部导航 -->
  4. <div class="app-header">
  5. <div class="header-content">
  6. <div class="header-main">
  7. <h1 class="app-title">智能知识库助手</h1>
  8. <p style="color: rgba(255, 255, 255, 0.8);
  9. font-size: 1rem;
  10. margin: 8px 0 0 0;
  11. font-weight: 500;">基于RAG技术的智能问答系统</p>
  12. </div>
  13. <div class="header-stats">
  14. <div class="stat-item">
  15. <span class="stat-number">{{ knowledgeBaseList.length }}</span>
  16. <span class="stat-label">知识库</span>
  17. </div>
  18. <div class="stat-item">
  19. <span class="stat-number">{{ selectedDatasetIds.length }}</span>
  20. <span class="stat-label">已选择</span>
  21. </div>
  22. </div>
  23. <!-- 在 header-content 内的 header-stats 后面添加 -->
  24. <div class="header-actions">
  25. <el-button
  26. class="history-btn"
  27. @click="$router.push('/qanda/history')"
  28. >
  29. <span class="btn-icon">📚</span>
  30. 查看历史问题
  31. </el-button>
  32. </div>
  33. </div>
  34. </div>
  35. <div class="main-layout">
  36. <!-- 左侧知识库侧边栏 -->
  37. <div class="sidebar">
  38. <div class="sidebar-header">
  39. <div class="sidebar-title">
  40. <h3>📚 知识库选择</h3>
  41. <p class="sidebar-subtitle">选择要查询的知识库</p>
  42. </div>
  43. <!-- <el-button-->
  44. <!-- class="refresh-btn"-->
  45. <!-- size="small"-->
  46. <!-- @click="getKnowledgeBaseList"-->
  47. <!-- :loading="refreshing"-->
  48. <!-- >-->
  49. <!-- <span v-if="!refreshing">🔄</span>-->
  50. <!-- <span v-else>...</span>-->
  51. <!-- </el-button>-->
  52. </div>
  53. <div class="knowledge-list">
  54. <div
  55. v-for="item in knowledgeBaseList"
  56. :key="item.dataset_id"
  57. :class="['knowledge-card', { 'active': selectedDatasetIds.includes(item.dataset_id) }]"
  58. @click="toggleDatasetSelection(item.dataset_id)"
  59. >
  60. <div class="card-content">
  61. <div class="knowledge-icon">
  62. <span v-if="selectedDatasetIds.includes(item.dataset_id)">✅</span>
  63. <span v-else>📖</span>
  64. </div>
  65. <div class="knowledge-info">
  66. <span class="knowledge-name">{{ item.name }}</span>
  67. <span class="knowledge-desc" v-if="item.description">{{ item.description }}</span>
  68. <span class="knowledge-status" :class="{ 'active': selectedDatasetIds.includes(item.dataset_id) }">
  69. {{ selectedDatasetIds.includes(item.dataset_id) ? '已选择' : '点击选择' }}
  70. </span>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. <div class="sidebar-footer">
  76. <div class="selected-count">
  77. <span class="count-number">{{ selectedDatasetIds.length }}</span>
  78. <span class="count-label">个知识库已选择</span>
  79. </div>
  80. <el-button
  81. v-if="selectedDatasetIds.length > 0"
  82. class="clear-btn"
  83. size="small"
  84. @click="clearSelection"
  85. >
  86. 清空选择
  87. </el-button>
  88. </div>
  89. </div>
  90. <!-- 右侧主内容区 -->
  91. <div class="content-area">
  92. <!-- 搜索区域 -->
  93. <div class="search-section">
  94. <div class="search-card">
  95. <div class="search-header">
  96. <div class="search-title">
  97. <h2>💬 开始提问</h2>
  98. <p>输入您的问题,AI将基于选定的知识库为您解答</p>
  99. </div>
  100. <div class="search-tips">
  101. <span class="tip-item">🔍 智能检索</span>
  102. <span class="tip-item">🤖 AI分析</span>
  103. <span class="tip-item">📚 多源参考</span>
  104. </div>
  105. </div>
  106. <div class="search-input-group">
  107. <div class="input-container">
  108. <el-input
  109. v-model="query"
  110. placeholder="例如:请介绍一下机器学习的基本概念..."
  111. size="large"
  112. class="search-input"
  113. @keyup.enter="chat"
  114. :disabled="loading"
  115. >
  116. <template #prefix>
  117. <span class="input-icon">💭</span>
  118. </template>
  119. </el-input>
  120. <div class="input-tips" v-if="selectedDatasetIds.length > 0">
  121. 将在 {{ selectedDatasetIds.length }} 个知识库中搜索答案
  122. </div>
  123. </div>
  124. <el-button
  125. @click="chat"
  126. type="primary"
  127. :disabled="loading || !query.trim() || selectedDatasetIds.length === 0"
  128. size="large"
  129. class="search-button"
  130. >
  131. <span v-if="loading" class="button-loading">
  132. <span class="loading-spinner"></span>
  133. 思考中...
  134. </span>
  135. <span v-else class="button-content">
  136. <span class="button-icon">🚀</span>
  137. 开始提问
  138. </span>
  139. </el-button>
  140. </div>
  141. <!-- 快速提问示例 -->
  142. <!-- <div class="quick-questions" v-if="!loading">-->
  143. <!-- <p class="quick-title">💡 试试这些问题:</p>-->
  144. <!-- <div class="question-chips">-->
  145. <!-- <el-tag-->
  146. <!-- v-for="question in quickQuestions"-->
  147. <!-- :key="question"-->
  148. <!-- class="question-chip"-->
  149. <!-- @click="query = question; chat()"-->
  150. <!-- :disabled="loading"-->
  151. <!-- >-->
  152. <!-- {{ question }}-->
  153. <!-- </el-tag>-->
  154. <!-- </div>-->
  155. <!-- </div>-->
  156. </div>
  157. </div>
  158. <!-- 加载状态 -->
  159. <div v-if="loading" class="loading-section">
  160. <div class="loading-content">
  161. <div class="loading-animation">
  162. <div class="thinking-robot">🤖</div>
  163. <div class="thinking-dots">
  164. <span></span>
  165. <span></span>
  166. <span></span>
  167. </div>
  168. </div>
  169. <p class="loading-text">AI正在分析您的问题,请稍候...</p>
  170. <p class="loading-subtext">正在从 {{ selectedDatasetIds.length }} 个知识库中检索相关信息</p>
  171. </div>
  172. </div>
  173. <!-- 最终回答 - 始终展开 -->
  174. <div v-if="chatSummary && !loading" class="thinking-card answer-card">
  175. <div class="card-header">
  176. <div class="header-icon">💡</div>
  177. <div class="header-content">
  178. <h3>最终回答</h3>
  179. <p>&nbsp;&nbsp;&nbsp;基于知识库内容生成的完整答案</p>
  180. </div>
  181. <!-- <div class="header-actions">-->
  182. <!-- <el-tooltip content="复制回答" placement="top">-->
  183. <!-- <el-button text class="action-btn" @click="copyToClipboard(chatSummary)">-->
  184. <!-- 📋-->
  185. <!-- </el-button>-->
  186. <!-- </el-tooltip>-->
  187. <!-- </div>-->
  188. </div>
  189. <div class="card-body">
  190. <div v-html="parsedChatSummary" class="thinking-content"></div>
  191. </div>
  192. </div>
  193. <!-- 思考过程展示 - 可折叠 -->
  194. <div v-if="showThinkingProcess && !loading" class="thinking-section">
  195. <!-- RAG 思考过程 - 可折叠 -->
  196. <div v-if="ragSummary" class="collapsible-section">
  197. <div class="collapsible-header" @click="toggleSection('rag')">
  198. <div class="header-main">
  199. <div class="header-icon">🔍</div>
  200. <div class="header-content">
  201. <h3>检索增强分析</h3>
  202. <p>&nbsp;&nbsp;&nbsp;从知识库中检索相关信息并进行分析</p>
  203. </div>
  204. </div>
  205. <div class="header-actions">
  206. <div class="collapse-icon" :class="{ rotated: expandedSections.rag }">
  207. <span>▼</span>
  208. </div>
  209. </div>
  210. </div>
  211. <div v-show="expandedSections.rag" class="collapsible-content">
  212. <div class="card-body">
  213. <div v-html="parsedRagSummary" class="thinking-content"></div>
  214. </div>
  215. </div>
  216. </div>
  217. <!-- LLM 思考过程 - 可折叠 -->
  218. <div v-if="llmSummary" class="collapsible-section">
  219. <div class="collapsible-header" @click="toggleSection('llm')">
  220. <div class="header-main">
  221. <div class="header-icon">🤔</div>
  222. <div class="header-content">
  223. <h3>AI搜索分析</h3>
  224. <p>&nbsp;&nbsp;&nbsp;基于AI搜索内容进行深度分析</p>
  225. </div>
  226. </div>
  227. <div class="header-actions">
  228. <div class="collapse-icon" :class="{ rotated: expandedSections.llm }">
  229. <span>▼</span>
  230. </div>
  231. </div>
  232. </div>
  233. <div v-show="expandedSections.llm" class="collapsible-content">
  234. <div class="card-body">
  235. <div v-html="parsedLlmSummary" class="thinking-content"></div>
  236. </div>
  237. </div>
  238. </div>
  239. </div>
  240. <!-- 搜索结果 - 可折叠 -->
  241. <div v-if="searchResults.length > 0 && !loading" class="collapsible-section results-section">
  242. <div class="collapsible-header" @click="toggleSection('results')">
  243. <div class="header-main">
  244. <div class="header-icon">📋</div>
  245. <div class="header-content">
  246. <h3>参考内容</h3>
  247. <p>&nbsp;&nbsp;&nbsp;AI回答时参考的知识库内容 ({{ searchResults.length }} 条)</p>
  248. </div>
  249. </div>
  250. <div class="header-actions">
  251. <div class="collapse-icon" :class="{ rotated: expandedSections.results }">
  252. <span>▼</span>
  253. </div>
  254. </div>
  255. </div>
  256. <div v-show="expandedSections.results" class="collapsible-content">
  257. <div class="results-container">
  258. <div class="results-stats">
  259. <span class="stat">共找到 {{ searchResults.length }} 条相关内容</span>
  260. <span class="stat">平均相关度: {{ averageScore }}%</span>
  261. </div>
  262. <div class="results-grid">
  263. <div
  264. v-for="(result, index) in searchResults"
  265. :key="result.contentSummary + index"
  266. class="result-card"
  267. @click="handleDetails(result)"
  268. >
  269. <div class="result-badge">#{{ index + 1 }}</div>
  270. <div class="result-header">
  271. <h4 class="result-title">{{ result.contentSummary }}</h4>
  272. <div class="result-meta">
  273. <span class="score-badge" :class="getScoreClass(result.score)">
  274. {{ (result.score * 100).toFixed(1) }}%
  275. </span>
  276. <span class="source-tag">{{ result.datasetName }}</span>
  277. <span v-if="result.textType === 3" class="file-type pdf-type">PDF</span>
  278. <span v-else class="file-type text-type">文本</span>
  279. </div>
  280. </div>
  281. <p class="result-preview">{{ result.content.substring(0, 150) }}...</p>
  282. <div class="result-footer">
  283. <span class="view-details">
  284. <span class="view-icon">🔍</span>
  285. 查看详情
  286. </span>
  287. </div>
  288. </div>
  289. </div>
  290. </div>
  291. </div>
  292. </div>
  293. <!-- 空状态 -->
  294. <div v-if="!loading && !showThinkingProcess && searchResults.length === 0 && hasSearched" class="empty-state">
  295. <div class="empty-content">
  296. <div class="empty-icon">🔍</div>
  297. <h3>未找到相关内容</h3>
  298. <p>请尝试调整问题关键词或选择其他知识库</p>
  299. <el-button @click="query = ''" class="retry-btn">
  300. 重新提问
  301. </el-button>
  302. </div>
  303. </div>
  304. <!-- 初始状态 -->
  305. <div v-if="!loading && !hasSearched" class="welcome-state">
  306. <div class="welcome-content">
  307. <div class="welcome-icon">🤖</div>
  308. <h3>欢迎使用智能知识库助手</h3>
  309. <p>选择左侧的知识库,输入您的问题,AI将为您提供精准的答案</p>
  310. <div class="feature-list">
  311. <div class="feature-item">
  312. <span class="feature-icon">🔍</span>
  313. <span>智能检索多个知识库</span>
  314. </div>
  315. <div class="feature-item">
  316. <span class="feature-icon">🤔</span>
  317. <span>展示AI思考过程</span>
  318. </div>
  319. <div class="feature-item">
  320. <span class="feature-icon">📚</span>
  321. <span>提供参考来源</span>
  322. </div>
  323. </div>
  324. </div>
  325. </div>
  326. </div>
  327. </div>
  328. <!-- 美化后的弹窗 -->
  329. <el-dialog
  330. v-model="dialogVisible"
  331. width="85%"
  332. class="content-dialog"
  333. :close-on-click-modal="false"
  334. >
  335. <template #header>
  336. <div class="dialog-header">
  337. <div class="dialog-title">
  338. <div class="title-icon" :class="selectedResult.textType === 3 ? 'pdf-icon' : 'text-icon'">
  339. {{ selectedResult.textType === 3 ? '📄' : '📝' }}
  340. </div>
  341. <div class="title-content">
  342. <h3>{{ selectedResult.contentSummary }}</h3>
  343. <div class="title-meta">
  344. <span class="meta-badge knowledge-badge">
  345. <span class="badge-icon">📚</span>
  346. {{ selectedResult.datasetName }}
  347. </span>
  348. <span class="meta-badge score-badge" :class="getScoreClass(selectedResult.score)">
  349. <span class="badge-icon">🎯</span>
  350. 相关度: {{ (selectedResult.score * 100).toFixed(1) }}%
  351. </span>
  352. <span v-if="selectedResult.textType === 3" class="meta-badge file-type-badge pdf-badge">
  353. <span class="badge-icon">📄</span>
  354. PDF文档
  355. </span>
  356. <span v-else class="meta-badge file-type-badge text-badge">
  357. <span class="badge-icon">📝</span>
  358. 文本文档
  359. </span>
  360. </div>
  361. </div>
  362. </div>
  363. </div>
  364. </template>
  365. <div class="dialog-content">
  366. <div class="content-tabs">
  367. <div
  368. class="tab-item"
  369. :class="{ active: activeTab === 'summary' }"
  370. @click="activeTab = 'summary'"
  371. >
  372. <span class="tab-icon">📝</span>
  373. 摘要内容
  374. </div>
  375. <div
  376. v-if="selectedResult.textType !== 3"
  377. class="tab-item"
  378. :class="{ active: activeTab === 'original' }"
  379. @click="activeTab = 'original'"
  380. >
  381. <span class="tab-icon">📖</span>
  382. 完整原文
  383. </div>
  384. <div
  385. v-if="selectedResult.textType === 3"
  386. class="tab-item"
  387. :class="{ active: activeTab === 'pdf' }"
  388. @click="activeTab = 'pdf'"
  389. >
  390. <span class="tab-icon">📄</span>
  391. PDF文档
  392. </div>
  393. </div>
  394. <div class="tab-content">
  395. <div v-show="activeTab === 'summary'" class="content-section summary-section">
  396. <div class="section-header">
  397. <h4>内容摘要</h4>
  398. </div>
  399. <div class="summary-content">
  400. {{ selectedResult.content }}
  401. </div>
  402. </div>
  403. <div v-show="activeTab === 'original'" class="content-section original-section">
  404. <div class="section-header">
  405. <h4>完整原文</h4>
  406. </div>
  407. <div class="original-content">
  408. <pre>{{ originalContent }}</pre>
  409. </div>
  410. </div>
  411. <!-- PDF 内容展示 -->
  412. <div v-show="activeTab === 'pdf'" class="content-section pdf-section">
  413. <div class="section-header">
  414. <h4>PDF文档</h4>
  415. <el-button
  416. v-if="selectedResult.url"
  417. type="primary"
  418. class="view-pdf-btn"
  419. @click="openPdfInNewTab"
  420. >
  421. <span class="button-icon">👀</span>
  422. 在新窗口查看
  423. </el-button>
  424. </div>
  425. <div class="pdf-content" v-if="selectedResult.url">
  426. <div class="pdf-viewer-container">
  427. <iframe
  428. :src="selectedResult.url"
  429. class="pdf-viewer"
  430. frameborder="0"
  431. @load="onPdfLoad"
  432. @error="onPdfError"
  433. ></iframe>
  434. <div v-if="pdfLoading" class="pdf-loading">
  435. <div class="loading-spinner"></div>
  436. <p>正在加载PDF文档...</p>
  437. </div>
  438. </div>
  439. </div>
  440. <div v-else class="pdf-empty">
  441. <div class="empty-icon">📄</div>
  442. <h3>PDF文档不可用</h3>
  443. <p>无法加载PDF文档,请检查文件地址是否正确</p>
  444. </div>
  445. </div>
  446. </div>
  447. </div>
  448. <template #footer>
  449. <div class="dialog-footer">
  450. <div class="footer-actions">
  451. <el-button
  452. class="action-btn secondary"
  453. @click="dialogVisible = false"
  454. >
  455. 取消
  456. </el-button>
  457. <el-button
  458. type="primary"
  459. class="action-btn primary"
  460. @click="dialogVisible = false"
  461. >
  462. 关闭
  463. </el-button>
  464. </div>
  465. </div>
  466. </template>
  467. </el-dialog>
  468. </div>
  469. </template>
  470. <script setup>
  471. import {ref, onMounted, computed} from 'vue';
  472. import {ElMessage, ElMessageBox} from 'element-plus';
  473. import {marked} from 'marked';
  474. import {API_BASE_URL} from "@/config";
  475. // 响应式数据
  476. const knowledgeBaseList = ref([]);
  477. const selectedDatasetIds = ref([11, 12]);
  478. const query = ref('');
  479. const searchResults = ref([]);
  480. const chatSummary = ref('');
  481. const ragSummary = ref('');
  482. const llmSummary = ref('');
  483. const dialogVisible = ref(false);
  484. const selectedResult = ref({});
  485. const originalContent = ref('');
  486. const loading = ref(false);
  487. const refreshing = ref(false);
  488. const hasSearched = ref(false);
  489. const activeTab = ref('summary');
  490. const pdfLoading = ref(false);
  491. // 可折叠区域的展开状态
  492. const expandedSections = ref({
  493. rag: false,
  494. llm: false,
  495. results: false
  496. });
  497. // 快速提问示例
  498. const quickQuestions = ref([
  499. "请总结一下主要概念",
  500. "有哪些重要的注意事项?",
  501. "请提供相关的示例说明",
  502. "这个主题的核心要点是什么?"
  503. ]);
  504. // 计算属性
  505. const showThinkingProcess = computed(() => {
  506. return chatSummary.value || ragSummary.value || llmSummary.value;
  507. });
  508. const averageScore = computed(() => {
  509. if (searchResults.value.length === 0) return 0;
  510. const total = searchResults.value.reduce((sum, result) => sum + result.score, 0);
  511. return (total / searchResults.value.length * 100).toFixed(1);
  512. });
  513. // 解析 Markdown 内容
  514. const parseMarkdown = (content) => {
  515. if (!content) return '';
  516. const markdownSymbols = /[#*+\-`>!]/;
  517. const isMarkdown = markdownSymbols.test(content);
  518. return isMarkdown ? marked(content) : content;
  519. };
  520. const parsedChatSummary = computed(() => parseMarkdown(chatSummary.value));
  521. const parsedRagSummary = computed(() => parseMarkdown(ragSummary.value));
  522. const parsedLlmSummary = computed(() => parseMarkdown(llmSummary.value));
  523. // 根据分数获取样式类
  524. const getScoreClass = (score) => {
  525. if (score >= 0.8) return 'high';
  526. if (score >= 0.6) return 'medium';
  527. return 'low';
  528. };
  529. // 复制到剪贴板
  530. const copyToClipboard = async (text) => {
  531. try {
  532. await navigator.clipboard.writeText(text);
  533. ElMessage.success('已复制到剪贴板');
  534. } catch (err) {
  535. ElMessage.error('复制失败');
  536. }
  537. };
  538. // PDF相关方法
  539. const onPdfLoad = () => {
  540. pdfLoading.value = false;
  541. };
  542. const onPdfError = () => {
  543. pdfLoading.value = false;
  544. ElMessage.error('PDF文档加载失败,请检查网络连接或文件地址');
  545. };
  546. const openPdfInNewTab = () => {
  547. if (selectedResult.value.url) {
  548. window.open(selectedResult.value.url, '_blank');
  549. }
  550. };
  551. // 切换可折叠区域
  552. const toggleSection = (section) => {
  553. expandedSections.value[section] = !expandedSections.value[section];
  554. };
  555. // 清空选择
  556. const clearSelection = async () => {
  557. try {
  558. await ElMessageBox.confirm('确定要清空所有选中的知识库吗?', '提示', {
  559. confirmButtonText: '确定',
  560. cancelButtonText: '取消',
  561. type: 'warning'
  562. });
  563. selectedDatasetIds.value = [];
  564. ElMessage.success('已清空选择');
  565. } catch {
  566. // 用户取消操作
  567. }
  568. };
  569. // 方法
  570. const getKnowledgeBaseList = async () => {
  571. refreshing.value = true;
  572. try {
  573. const response = await fetch(`${API_BASE_URL}/dataset/list`);
  574. const data = await response.json();
  575. knowledgeBaseList.value = data.data;
  576. // ElMessage.success('知识库列表已更新');
  577. } catch (error) {
  578. ElMessage.error('获取知识库列表失败');
  579. } finally {
  580. refreshing.value = false;
  581. }
  582. };
  583. const toggleDatasetSelection = (datasetId) => {
  584. const index = selectedDatasetIds.value.indexOf(datasetId);
  585. if (index === -1) {
  586. selectedDatasetIds.value.push(datasetId);
  587. } else {
  588. selectedDatasetIds.value.splice(index, 1);
  589. }
  590. };
  591. const chat = async () => {
  592. if (loading.value || !query.value.trim()) return;
  593. if (selectedDatasetIds.value.length === 0) {
  594. ElMessage.warning('请先选择知识库');
  595. return;
  596. }
  597. // 重置状态
  598. chatSummary.value = '';
  599. ragSummary.value = '';
  600. llmSummary.value = '';
  601. searchResults.value = [];
  602. hasSearched.value = true;
  603. // 重置展开状态
  604. expandedSections.value = {
  605. rag: false,
  606. llm: false,
  607. results: false
  608. };
  609. loading.value = true;
  610. try {
  611. const datasetIds = selectedDatasetIds.value.join(',');
  612. const response = await fetch(`${API_BASE_URL}/chat?query=${encodeURIComponent(query.value)}&datasetIds=${datasetIds}`);
  613. const data = await response.json();
  614. if (data.data) {
  615. searchResults.value = data.data.results || [];
  616. chatSummary.value = data.data.chat_res || '';
  617. ragSummary.value = data.data.rag_summary || '';
  618. llmSummary.value = data.data.llm_summary || '';
  619. } else {
  620. ElMessage.error('未获取到有效数据');
  621. }
  622. } catch (error) {
  623. ElMessage.error('请求失败,请稍后重试');
  624. } finally {
  625. loading.value = false;
  626. }
  627. };
  628. const handleDetails = async (result) => {
  629. selectedResult.value = result;
  630. dialogVisible.value = true;
  631. // 根据文档类型设置默认标签页
  632. if (result.textType === 3) {
  633. activeTab.value = 'summary';
  634. if (result.url) {
  635. pdfLoading.value = true;
  636. }
  637. } else {
  638. activeTab.value = 'summary';
  639. }
  640. try {
  641. const response = await fetch(`${API_BASE_URL}/content/get?docId=${result.docId}`);
  642. const data = await response.json();
  643. if (data.status_code === 200) {
  644. originalContent.value = data.data.text;
  645. // 更新选中结果的数据,确保包含完整的文档信息
  646. selectedResult.value = {
  647. ...selectedResult.value,
  648. textType: data.data.textType,
  649. url: data.data.url
  650. };
  651. } else {
  652. ElMessage.error('获取原文内容失败');
  653. }
  654. } catch (error) {
  655. ElMessage.error('请求原文内容失败');
  656. }
  657. };
  658. // 生命周期
  659. onMounted(() => {
  660. getKnowledgeBaseList();
  661. });
  662. </script>
  663. <style scoped>
  664. /* 基础样式 */
  665. .app-container {
  666. min-height: 100vh;
  667. background: linear-gradient(135deg, rgba(102, 126, 234, 0.6) 0%, rgba(118, 75, 162, 0.4) 100%);
  668. font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  669. }
  670. /* 顶部导航样式 */
  671. .app-header {
  672. background: rgba(255, 255, 255, 0.1);
  673. backdrop-filter: blur(20px);
  674. padding: 16px 0;
  675. border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  676. }
  677. .header-content {
  678. max-width: 1400px;
  679. margin: 0 auto;
  680. padding: 0 24px;
  681. display: flex;
  682. justify-content: space-between;
  683. align-items: center;
  684. }
  685. .header-main {
  686. flex: 1;
  687. }
  688. .app-title {
  689. color: white;
  690. font-size: 2.25rem;
  691. font-weight: 800;
  692. margin: 0;
  693. text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
  694. background: linear-gradient(135deg, #fff 0%, #e2e8f0 100%);
  695. -webkit-background-clip: text;
  696. -webkit-text-fill-color: transparent;
  697. background-clip: text;
  698. }
  699. .app-subtitle {
  700. color: rgba(255, 255, 255, 0.8);
  701. font-size: 1rem;
  702. margin: 8px 0 0 0;
  703. font-weight: 500;
  704. }
  705. .header-stats {
  706. display: flex;
  707. gap: 24px;
  708. }
  709. .stat-item {
  710. display: flex;
  711. flex-direction: column;
  712. align-items: center;
  713. }
  714. .stat-number {
  715. color: white;
  716. font-size: 1.5rem;
  717. font-weight: 700;
  718. }
  719. .stat-label {
  720. color: rgba(255, 255, 255, 0.7);
  721. font-size: 0.8rem;
  722. margin-top: 4px;
  723. }
  724. /* 主布局 */
  725. .main-layout {
  726. display: flex;
  727. max-width: 1400px;
  728. margin: 0 auto;
  729. padding: 24px;
  730. gap: 24px;
  731. min-height: calc(100vh - 120px);
  732. align-items: flex-start;
  733. }
  734. /* 侧边栏样式 - 固定位置 */
  735. .sidebar {
  736. width: 320px;
  737. flex-shrink: 0;
  738. display: flex;
  739. flex-direction: column;
  740. position: sticky;
  741. top: 24px;
  742. max-height: calc(100vh - 120px);
  743. overflow-y: auto;
  744. }
  745. .sidebar-header {
  746. background: white;
  747. padding: 20px;
  748. border-radius: 20px;
  749. margin-bottom: 16px;
  750. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  751. display: flex;
  752. justify-content: space-between;
  753. align-items: flex-start;
  754. flex-shrink: 0;
  755. }
  756. .sidebar-title h3 {
  757. margin: 0 0 6px 0;
  758. color: #2d3748;
  759. font-size: 1.25rem;
  760. font-weight: 700;
  761. }
  762. .sidebar-subtitle {
  763. margin: 0;
  764. color: #718096;
  765. font-size: 0.85rem;
  766. }
  767. .refresh-btn {
  768. padding: 8px;
  769. border-radius: 10px;
  770. border: 1px solid #e2e8f0;
  771. background: white;
  772. transition: all 0.3s ease;
  773. }
  774. .refresh-btn:hover {
  775. background: #f7fafc;
  776. transform: rotate(90deg);
  777. }
  778. .knowledge-list {
  779. flex: 1;
  780. display: flex;
  781. flex-direction: column;
  782. gap: 12px;
  783. overflow-y: auto;
  784. min-height: 0;
  785. }
  786. .knowledge-card {
  787. background: white;
  788. padding: 16px;
  789. border-radius: 16px;
  790. cursor: pointer;
  791. transition: all 0.3s ease;
  792. border: 2px solid transparent;
  793. display: flex;
  794. align-items: center;
  795. gap: 12px;
  796. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  797. overflow: hidden;
  798. }
  799. .knowledge-card:hover {
  800. transform: translateY(-4px);
  801. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  802. }
  803. .knowledge-card.active {
  804. border-color: #4299e1;
  805. background: linear-gradient(135deg, #ebf8ff 0%, #ffffff 100%);
  806. }
  807. .card-content {
  808. padding: 16px;
  809. display: flex;
  810. align-items: flex-start;
  811. gap: 12px;
  812. }
  813. .knowledge-icon {
  814. font-size: 1.25rem;
  815. width: 40px;
  816. height: 40px;
  817. display: flex;
  818. align-items: center;
  819. justify-content: center;
  820. background: #f7fafc;
  821. border-radius: 10px;
  822. flex-shrink: 0;
  823. }
  824. .knowledge-info {
  825. flex: 1;
  826. min-width: 0;
  827. }
  828. .knowledge-name {
  829. display: block;
  830. font-weight: 600;
  831. color: #2d3748;
  832. margin-bottom: 4px;
  833. font-size: 0.95rem;
  834. line-height: 1.4;
  835. }
  836. .knowledge-desc {
  837. display: block;
  838. color: #718096;
  839. font-size: 0.8rem;
  840. line-height: 1.4;
  841. margin-bottom: 6px;
  842. }
  843. .knowledge-status {
  844. font-size: 0.75rem;
  845. color: #a0aec0;
  846. font-weight: 500;
  847. }
  848. .knowledge-status.active {
  849. color: #4299e1;
  850. font-weight: 600;
  851. }
  852. .sidebar-footer {
  853. background: white;
  854. padding: 16px 20px;
  855. border-radius: 16px;
  856. margin-top: 16px;
  857. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  858. display: flex;
  859. justify-content: space-between;
  860. align-items: center;
  861. flex-shrink: 0;
  862. position: sticky;
  863. bottom: 0;
  864. z-index: 10;
  865. }
  866. .selected-count {
  867. display: flex;
  868. align-items: center;
  869. gap: 8px;
  870. }
  871. .count-number {
  872. background: #4299e1;
  873. color: white;
  874. padding: 4px 10px;
  875. border-radius: 12px;
  876. font-size: 0.85rem;
  877. font-weight: 700;
  878. }
  879. .count-label {
  880. color: #718096;
  881. font-size: 0.85rem;
  882. }
  883. .clear-btn {
  884. font-size: 0.8rem;
  885. padding: 6px 12px;
  886. border-radius: 8px;
  887. }
  888. /* 主内容区样式 */
  889. .content-area {
  890. flex: 1;
  891. display: flex;
  892. flex-direction: column;
  893. gap: 24px;
  894. min-height: 0;
  895. }
  896. .search-section {
  897. width: 100%;
  898. }
  899. .search-card {
  900. background: white;
  901. padding: 32px;
  902. border-radius: 24px;
  903. box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  904. }
  905. .search-header {
  906. display: flex;
  907. justify-content: space-between;
  908. align-items: flex-start;
  909. margin-bottom: 28px;
  910. }
  911. .search-title h2 {
  912. margin: 0 0 8px 0;
  913. color: #2d3748;
  914. font-size: 1.75rem;
  915. font-weight: 700;
  916. }
  917. .search-title p {
  918. margin: 0;
  919. color: #718096;
  920. font-size: 1rem;
  921. }
  922. .search-tips {
  923. display: flex;
  924. gap: 12px;
  925. }
  926. .tip-item {
  927. background: #f7fafc;
  928. padding: 6px 12px;
  929. border-radius: 12px;
  930. font-size: 0.8rem;
  931. color: #718096;
  932. border: 1px solid #e2e8f0;
  933. }
  934. /* 搜索框和按钮高度统一 */
  935. .search-input-group {
  936. display: flex;
  937. gap: 16px;
  938. margin-bottom: 24px;
  939. align-items: stretch;
  940. }
  941. .input-container {
  942. flex: 1;
  943. display: flex;
  944. flex-direction: column;
  945. }
  946. .search-input {
  947. border-radius: 16px;
  948. height: 100%;
  949. }
  950. .search-input :deep(.el-input__wrapper) {
  951. border-radius: 16px;
  952. padding: 16px 20px;
  953. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  954. border: 1px solid #e2e8f0;
  955. transition: all 0.3s ease;
  956. height: 56px;
  957. display: flex;
  958. align-items: center;
  959. }
  960. .search-input :deep(.el-input__wrapper:hover) {
  961. border-color: #4299e1;
  962. box-shadow: 0 4px 20px rgba(66, 153, 225, 0.15);
  963. }
  964. .search-input :deep(.el-input__wrapper.is-focus) {
  965. border-color: #4299e1;
  966. box-shadow: 0 4px 20px rgba(66, 153, 225, 0.2);
  967. }
  968. .input-icon {
  969. font-size: 1.25rem;
  970. margin-right: 8px;
  971. }
  972. .input-tips {
  973. font-size: 0.8rem;
  974. color: #718096;
  975. margin-top: 8px;
  976. text-align: center;
  977. }
  978. .search-button {
  979. border-radius: 16px;
  980. padding: 16px 32px;
  981. font-weight: 600;
  982. transition: all 0.3s ease;
  983. min-width: 140px;
  984. height: 56px;
  985. display: flex;
  986. align-items: center;
  987. justify-content: center;
  988. }
  989. .search-button:not(:disabled):hover {
  990. transform: translateY(-2px);
  991. box-shadow: 0 8px 25px rgba(66, 153, 225, 0.4);
  992. }
  993. .button-loading {
  994. display: flex;
  995. align-items: center;
  996. gap: 8px;
  997. }
  998. .loading-spinner {
  999. width: 16px;
  1000. height: 16px;
  1001. border: 2px solid transparent;
  1002. border-top: 2px solid white;
  1003. border-radius: 50%;
  1004. animation: spin 1s linear infinite;
  1005. }
  1006. @keyframes spin {
  1007. 0% {
  1008. transform: rotate(0deg);
  1009. }
  1010. 100% {
  1011. transform: rotate(360deg);
  1012. }
  1013. }
  1014. .button-content {
  1015. display: flex;
  1016. align-items: center;
  1017. gap: 8px;
  1018. }
  1019. .button-icon {
  1020. font-size: 1.1rem;
  1021. }
  1022. .quick-questions {
  1023. border-top: 1px solid #e2e8f0;
  1024. padding-top: 20px;
  1025. }
  1026. .quick-title {
  1027. margin: 0 0 12px 0;
  1028. color: #718096;
  1029. font-size: 0.9rem;
  1030. font-weight: 600;
  1031. }
  1032. .question-chips {
  1033. display: flex;
  1034. flex-wrap: wrap;
  1035. gap: 8px;
  1036. }
  1037. .question-chip {
  1038. cursor: pointer;
  1039. border-radius: 20px;
  1040. padding: 8px 16px;
  1041. transition: all 0.3s ease;
  1042. border: 1px solid #e2e8f0;
  1043. background: white;
  1044. font-size: 0.85rem;
  1045. }
  1046. .question-chip:hover {
  1047. background: #4299e1;
  1048. color: white;
  1049. transform: translateY(-1px);
  1050. box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
  1051. }
  1052. /* 加载状态 */
  1053. .loading-section {
  1054. background: white;
  1055. border-radius: 24px;
  1056. padding: 60px 32px;
  1057. text-align: center;
  1058. box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  1059. }
  1060. .loading-content {
  1061. max-width: 400px;
  1062. margin: 0 auto;
  1063. }
  1064. .loading-animation {
  1065. margin-bottom: 24px;
  1066. }
  1067. .thinking-robot {
  1068. font-size: 4rem;
  1069. margin-bottom: 16px;
  1070. animation: bounce 2s infinite;
  1071. }
  1072. @keyframes bounce {
  1073. 0%, 20%, 50%, 80%, 100% {
  1074. transform: translateY(0);
  1075. }
  1076. 40% {
  1077. transform: translateY(-10px);
  1078. }
  1079. 60% {
  1080. transform: translateY(-5px);
  1081. }
  1082. }
  1083. .thinking-dots {
  1084. display: flex;
  1085. justify-content: center;
  1086. gap: 8px;
  1087. margin-bottom: 20px;
  1088. }
  1089. .thinking-dots span {
  1090. width: 12px;
  1091. height: 12px;
  1092. border-radius: 50%;
  1093. background: #4299e1;
  1094. animation: bounce 1.4s infinite ease-in-out;
  1095. }
  1096. .thinking-dots span:nth-child(1) {
  1097. animation-delay: -0.32s;
  1098. }
  1099. .thinking-dots span:nth-child(2) {
  1100. animation-delay: -0.16s;
  1101. }
  1102. .loading-text {
  1103. color: #2d3748;
  1104. font-size: 1.2rem;
  1105. font-weight: 600;
  1106. margin: 0 0 8px 0;
  1107. }
  1108. .loading-subtext {
  1109. color: #718096;
  1110. font-size: 0.95rem;
  1111. margin: 0;
  1112. }
  1113. /* 最终回答卡片 */
  1114. .thinking-card.answer-card {
  1115. background: white;
  1116. border-radius: 20px;
  1117. overflow: hidden;
  1118. box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  1119. border-left: 6px solid #48bb78;
  1120. }
  1121. .answer-card .card-header {
  1122. display: flex;
  1123. align-items: center;
  1124. justify-content: space-between;
  1125. padding: 24px 28px;
  1126. background: linear-gradient(135deg, #f0fff4 0%, #ffffff 100%);
  1127. border-bottom: 1px solid #e2e8f0;
  1128. }
  1129. .answer-card .card-body {
  1130. padding: 0 28px 28px;
  1131. }
  1132. /* 可折叠区域样式 */
  1133. .collapsible-section {
  1134. background: white;
  1135. border-radius: 20px;
  1136. overflow: hidden;
  1137. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  1138. margin-bottom: 16px;
  1139. transition: all 0.3s ease;
  1140. }
  1141. .collapsible-section:hover {
  1142. box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
  1143. }
  1144. .collapsible-header {
  1145. display: flex;
  1146. align-items: center;
  1147. justify-content: space-between;
  1148. padding: 20px 28px;
  1149. cursor: pointer;
  1150. transition: background-color 0.3s ease;
  1151. border-bottom: 1px solid transparent;
  1152. }
  1153. .collapsible-header:hover {
  1154. background: rgba(0, 0, 0, 0.02);
  1155. }
  1156. .collapsible-section:has(.collapsible-content:not([style*="display: none"])) .collapsible-header {
  1157. border-bottom-color: #e2e8f0;
  1158. }
  1159. .header-main {
  1160. display: flex;
  1161. align-items: center;
  1162. gap: 16px;
  1163. flex: 1;
  1164. }
  1165. .header-actions {
  1166. display: flex;
  1167. align-items: center;
  1168. gap: 12px;
  1169. }
  1170. .action-btn {
  1171. padding: 8px;
  1172. border-radius: 10px;
  1173. transition: all 0.3s ease;
  1174. }
  1175. .action-btn:hover {
  1176. background: #f7fafc;
  1177. }
  1178. .collapse-icon {
  1179. transition: transform 0.3s ease;
  1180. color: #718096;
  1181. font-size: 14px;
  1182. }
  1183. .collapse-icon.rotated {
  1184. transform: rotate(180deg);
  1185. }
  1186. .collapsible-content {
  1187. padding: 0;
  1188. }
  1189. /* 为不同部分设置不同的左侧边框颜色 */
  1190. .collapsible-section:nth-child(1) .collapsible-header {
  1191. border-left: 6px solid #4299e1;
  1192. }
  1193. .collapsible-section:nth-child(2) .collapsible-header {
  1194. border-left: 6px solid #9f7aea;
  1195. }
  1196. .collapsible-section:nth-child(3) .collapsible-header {
  1197. border-left: 6px solid #ed8936;
  1198. }
  1199. .header-icon {
  1200. font-size: 1.75rem;
  1201. width: 52px;
  1202. height: 52px;
  1203. display: flex;
  1204. align-items: center;
  1205. justify-content: center;
  1206. background: white;
  1207. border-radius: 12px;
  1208. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  1209. }
  1210. .header-content h3 {
  1211. margin: 0 0 6px 0;
  1212. color: #2d3748;
  1213. font-size: 1.3rem;
  1214. font-weight: 700;
  1215. }
  1216. .header-content p {
  1217. margin: 0;
  1218. color: #718096;
  1219. font-size: 0.9rem;
  1220. }
  1221. .card-body {
  1222. padding: 0 28px 28px;
  1223. }
  1224. .thinking-content {
  1225. line-height: 1.7;
  1226. color: #4a5568;
  1227. font-size: 1rem;
  1228. }
  1229. .thinking-content :deep(h1),
  1230. .thinking-content :deep(h2),
  1231. .thinking-content :deep(h3) {
  1232. color: #2d3748;
  1233. margin-top: 1.5em;
  1234. margin-bottom: 0.5em;
  1235. }
  1236. .thinking-content :deep(p) {
  1237. margin-bottom: 1em;
  1238. }
  1239. .thinking-content :deep(ul),
  1240. .thinking-content :deep(ol) {
  1241. margin: 1em 0;
  1242. padding-left: 1.5em;
  1243. }
  1244. .thinking-content :deep(li) {
  1245. margin-bottom: 0.5em;
  1246. }
  1247. .thinking-content :deep(code) {
  1248. background: #f7fafc;
  1249. padding: 2px 6px;
  1250. border-radius: 4px;
  1251. font-family: 'Monaco', 'Consolas', monospace;
  1252. color: #e53e3e;
  1253. font-size: 0.9em;
  1254. }
  1255. .thinking-content :deep(pre) {
  1256. background: #2d3748;
  1257. color: #e2e8f0;
  1258. padding: 20px;
  1259. border-radius: 12px;
  1260. overflow-x: auto;
  1261. margin: 1.5em 0;
  1262. font-size: 0.9em;
  1263. }
  1264. .thinking-content :deep(blockquote) {
  1265. border-left: 4px solid #4299e1;
  1266. margin: 1.5em 0;
  1267. padding-left: 20px;
  1268. color: #718096;
  1269. font-style: italic;
  1270. }
  1271. /* 搜索结果样式 */
  1272. .results-container {
  1273. padding: 0;
  1274. }
  1275. .results-stats {
  1276. padding: 20px 28px;
  1277. background: #f7fafc;
  1278. border-bottom: 1px solid #e2e8f0;
  1279. display: flex;
  1280. gap: 24px;
  1281. }
  1282. .results-stats .stat {
  1283. color: #718096;
  1284. font-size: 0.9rem;
  1285. font-weight: 500;
  1286. }
  1287. .results-grid {
  1288. padding: 24px 28px;
  1289. display: grid;
  1290. grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
  1291. gap: 20px;
  1292. }
  1293. .result-card {
  1294. background: white;
  1295. border-radius: 16px;
  1296. padding: 20px;
  1297. cursor: pointer;
  1298. transition: all 0.3s ease;
  1299. border: 1px solid #e2e8f0;
  1300. box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
  1301. position: relative;
  1302. overflow: hidden;
  1303. }
  1304. .result-card:hover {
  1305. transform: translateY(-4px);
  1306. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  1307. border-color: #4299e1;
  1308. }
  1309. .result-badge {
  1310. position: absolute;
  1311. top: 12px;
  1312. right: 12px;
  1313. background: #4299e1;
  1314. color: white;
  1315. padding: 4px 10px;
  1316. border-radius: 12px;
  1317. font-size: 0.8rem;
  1318. font-weight: 700;
  1319. }
  1320. .result-header {
  1321. margin-bottom: 12px;
  1322. }
  1323. .result-title {
  1324. margin: 0 0 12px 0;
  1325. color: #2d3748;
  1326. font-size: 1.1rem;
  1327. font-weight: 600;
  1328. line-height: 1.4;
  1329. }
  1330. .result-meta {
  1331. display: flex;
  1332. gap: 8px;
  1333. align-items: center;
  1334. flex-wrap: wrap;
  1335. }
  1336. .score-badge {
  1337. padding: 4px 10px;
  1338. border-radius: 12px;
  1339. font-size: 0.8rem;
  1340. font-weight: 700;
  1341. color: white;
  1342. }
  1343. .score-badge.high {
  1344. background: #48bb78;
  1345. }
  1346. .score-badge.medium {
  1347. background: #ed8936;
  1348. }
  1349. .score-badge.low {
  1350. background: #e53e3e;
  1351. }
  1352. .source-tag {
  1353. background: #f7fafc;
  1354. padding: 4px 10px;
  1355. border-radius: 12px;
  1356. font-size: 0.8rem;
  1357. color: #718096;
  1358. border: 1px solid #e2e8f0;
  1359. }
  1360. .file-type {
  1361. padding: 4px 10px;
  1362. border-radius: 12px;
  1363. font-size: 0.8rem;
  1364. font-weight: 600;
  1365. color: white;
  1366. }
  1367. .file-type.pdf-type {
  1368. background: #e53e3e;
  1369. }
  1370. .file-type.text-type {
  1371. background: #48bb78;
  1372. }
  1373. .result-preview {
  1374. margin: 0 0 16px 0;
  1375. color: #718096;
  1376. line-height: 1.5;
  1377. font-size: 0.9rem;
  1378. display: -webkit-box;
  1379. -webkit-line-clamp: 3;
  1380. -webkit-box-orient: vertical;
  1381. overflow: hidden;
  1382. }
  1383. .result-footer {
  1384. display: flex;
  1385. justify-content: flex-end;
  1386. }
  1387. .view-details {
  1388. color: #4299e1;
  1389. font-weight: 600;
  1390. font-size: 0.9rem;
  1391. transition: color 0.3s ease;
  1392. display: flex;
  1393. align-items: center;
  1394. gap: 6px;
  1395. }
  1396. .view-icon {
  1397. font-size: 0.8rem;
  1398. }
  1399. .result-card:hover .view-details {
  1400. color: #2b6cb0;
  1401. }
  1402. /* 空状态 */
  1403. .empty-state {
  1404. background: white;
  1405. border-radius: 24px;
  1406. padding: 80px 32px;
  1407. text-align: center;
  1408. box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  1409. }
  1410. .empty-content {
  1411. max-width: 400px;
  1412. margin: 0 auto;
  1413. }
  1414. .empty-icon {
  1415. font-size: 4rem;
  1416. margin-bottom: 20px;
  1417. }
  1418. .empty-state h3 {
  1419. margin: 0 0 12px 0;
  1420. color: #2d3748;
  1421. font-size: 1.5rem;
  1422. font-weight: 700;
  1423. }
  1424. .empty-state p {
  1425. margin: 0 0 24px 0;
  1426. color: #718096;
  1427. font-size: 1rem;
  1428. line-height: 1.5;
  1429. }
  1430. .retry-btn {
  1431. border-radius: 12px;
  1432. padding: 10px 24px;
  1433. }
  1434. /* 欢迎状态 */
  1435. .welcome-state {
  1436. background: white;
  1437. border-radius: 24px;
  1438. padding: 80px 32px;
  1439. text-align: center;
  1440. box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
  1441. }
  1442. .welcome-content {
  1443. max-width: 500px;
  1444. margin: 0 auto;
  1445. }
  1446. .welcome-icon {
  1447. font-size: 5rem;
  1448. margin-bottom: 24px;
  1449. animation: float 3s ease-in-out infinite;
  1450. }
  1451. @keyframes float {
  1452. 0%, 100% {
  1453. transform: translateY(0px);
  1454. }
  1455. 50% {
  1456. transform: translateY(-10px);
  1457. }
  1458. }
  1459. .welcome-state h3 {
  1460. margin: 0 0 16px 0;
  1461. color: #2d3748;
  1462. font-size: 1.75rem;
  1463. font-weight: 700;
  1464. }
  1465. .welcome-state p {
  1466. margin: 0 0 32px 0;
  1467. color: #718096;
  1468. font-size: 1.1rem;
  1469. line-height: 1.6;
  1470. }
  1471. .feature-list {
  1472. display: flex;
  1473. flex-direction: column;
  1474. gap: 12px;
  1475. max-width: 300px;
  1476. margin: 0 auto;
  1477. }
  1478. .feature-item {
  1479. display: flex;
  1480. align-items: center;
  1481. gap: 12px;
  1482. padding: 12px 16px;
  1483. background: #f7fafc;
  1484. border-radius: 12px;
  1485. color: #4a5568;
  1486. font-size: 0.95rem;
  1487. }
  1488. .feature-icon {
  1489. font-size: 1.2rem;
  1490. }
  1491. /* 弹窗样式 */
  1492. .content-dialog :deep(.el-dialog) {
  1493. border-radius: 24px;
  1494. overflow: hidden;
  1495. box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2);
  1496. }
  1497. .content-dialog :deep(.el-dialog__header) {
  1498. padding: 0;
  1499. margin: 0;
  1500. }
  1501. .content-dialog :deep(.el-dialog__headerbtn) {
  1502. top: 24px;
  1503. right: 24px;
  1504. width: 32px;
  1505. height: 32px;
  1506. border-radius: 8px;
  1507. background: #f7fafc;
  1508. }
  1509. .content-dialog :deep(.el-dialog__headerbtn:hover) {
  1510. background: #e2e8f0;
  1511. }
  1512. .dialog-header {
  1513. padding: 0;
  1514. }
  1515. .dialog-title {
  1516. display: flex;
  1517. align-items: flex-start;
  1518. gap: 16px;
  1519. padding: 32px 32px 0;
  1520. }
  1521. .title-icon {
  1522. font-size: 2.5rem;
  1523. width: 60px;
  1524. height: 60px;
  1525. display: flex;
  1526. align-items: center;
  1527. justify-content: center;
  1528. border-radius: 16px;
  1529. color: white;
  1530. flex-shrink: 0;
  1531. }
  1532. .title-icon.pdf-icon {
  1533. background: linear-gradient(135deg, #e53e3e 0%, #c53030 100%);
  1534. }
  1535. .title-icon.text-icon {
  1536. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  1537. }
  1538. .title-content {
  1539. flex: 1;
  1540. min-width: 0;
  1541. }
  1542. .title-content h3 {
  1543. margin: 0 0 12px 0;
  1544. color: #2d3748;
  1545. font-size: 1.5rem;
  1546. font-weight: 700;
  1547. line-height: 1.4;
  1548. }
  1549. .title-meta {
  1550. display: flex;
  1551. gap: 12px;
  1552. flex-wrap: wrap;
  1553. }
  1554. .meta-badge {
  1555. display: flex;
  1556. align-items: center;
  1557. gap: 6px;
  1558. padding: 6px 12px;
  1559. border-radius: 12px;
  1560. font-size: 0.85rem;
  1561. font-weight: 500;
  1562. }
  1563. .knowledge-badge {
  1564. background: #ebf8ff;
  1565. color: #2b6cb0;
  1566. border: 1px solid #bee3f8;
  1567. }
  1568. .score-badge.high {
  1569. background: #f0fff4;
  1570. color: #2f855a;
  1571. border: 1px solid #c6f6d5;
  1572. }
  1573. .score-badge.medium {
  1574. background: #fffaf0;
  1575. color: #c05621;
  1576. border: 1px solid #feebc8;
  1577. }
  1578. .score-badge.low {
  1579. background: #fff5f5;
  1580. color: #c53030;
  1581. border: 1px solid #fed7d7;
  1582. }
  1583. .file-type-badge.pdf-badge {
  1584. background: #fff5f5;
  1585. color: #c53030;
  1586. border: 1px solid #fed7d7;
  1587. }
  1588. .file-type-badge.text-badge {
  1589. background: #f0fff4;
  1590. color: #2f855a;
  1591. border: 1px solid #c6f6d5;
  1592. }
  1593. .badge-icon {
  1594. font-size: 0.8rem;
  1595. }
  1596. .dialog-content {
  1597. padding: 0;
  1598. }
  1599. .content-tabs {
  1600. display: flex;
  1601. padding: 0 32px;
  1602. border-bottom: 1px solid #e2e8f0;
  1603. margin-top: 24px;
  1604. }
  1605. .tab-item {
  1606. display: flex;
  1607. align-items: center;
  1608. gap: 8px;
  1609. padding: 12px 20px;
  1610. cursor: pointer;
  1611. border-bottom: 2px solid transparent;
  1612. color: #718096;
  1613. font-weight: 500;
  1614. transition: all 0.3s ease;
  1615. }
  1616. .tab-item:hover {
  1617. color: #4299e1;
  1618. }
  1619. .tab-item.active {
  1620. color: #4299e1;
  1621. border-bottom-color: #4299e1;
  1622. }
  1623. .tab-icon {
  1624. font-size: 1.1rem;
  1625. }
  1626. .tab-content {
  1627. padding: 0 32px 32px;
  1628. }
  1629. .content-section {
  1630. margin-top: 24px;
  1631. }
  1632. .section-header {
  1633. display: flex;
  1634. justify-content: space-between;
  1635. align-items: center;
  1636. margin-bottom: 16px;
  1637. }
  1638. .section-header h4 {
  1639. margin: 0;
  1640. color: #2d3748;
  1641. font-size: 1.2rem;
  1642. font-weight: 600;
  1643. }
  1644. .copy-btn, .view-pdf-btn {
  1645. font-size: 0.9rem;
  1646. padding: 6px 12px;
  1647. border-radius: 8px;
  1648. }
  1649. .summary-content {
  1650. background: #f7fafc;
  1651. padding: 20px;
  1652. border-radius: 12px;
  1653. color: #4a5568;
  1654. line-height: 1.6;
  1655. font-size: 0.95rem;
  1656. border: 1px solid #e2e8f0;
  1657. }
  1658. .original-content {
  1659. background: #f7fafc;
  1660. border-radius: 12px;
  1661. border: 1px solid #e2e8f0;
  1662. max-height: 500px;
  1663. overflow-y: auto;
  1664. }
  1665. .original-content pre {
  1666. margin: 0;
  1667. padding: 20px;
  1668. color: #4a5568;
  1669. line-height: 1.6;
  1670. font-size: 0.9rem;
  1671. white-space: pre-wrap;
  1672. word-wrap: break-word;
  1673. }
  1674. /* PDF内容展示样式 */
  1675. .pdf-content {
  1676. flex: 1;
  1677. display: flex;
  1678. flex-direction: column;
  1679. }
  1680. .pdf-viewer-container {
  1681. flex: 1;
  1682. position: relative;
  1683. border: 1px solid #e2e8f0;
  1684. border-radius: 12px;
  1685. overflow: hidden;
  1686. background: #f7fafc;
  1687. min-height: 500px;
  1688. }
  1689. .pdf-viewer {
  1690. width: 100%;
  1691. height: 100%;
  1692. min-height: 500px;
  1693. background: white;
  1694. }
  1695. .pdf-loading {
  1696. position: absolute;
  1697. top: 0;
  1698. left: 0;
  1699. right: 0;
  1700. bottom: 0;
  1701. display: flex;
  1702. flex-direction: column;
  1703. align-items: center;
  1704. justify-content: center;
  1705. background: rgba(255, 255, 255, 0.9);
  1706. }
  1707. .pdf-loading .loading-spinner {
  1708. width: 40px;
  1709. height: 40px;
  1710. border: 4px solid #e2e8f0;
  1711. border-top: 4px solid #4299e1;
  1712. border-radius: 50%;
  1713. animation: spin 1s linear infinite;
  1714. margin-bottom: 16px;
  1715. }
  1716. .pdf-loading p {
  1717. margin: 0;
  1718. color: #718096;
  1719. font-size: 1rem;
  1720. }
  1721. .pdf-empty {
  1722. text-align: center;
  1723. padding: 60px 20px;
  1724. background: #f7fafc;
  1725. border-radius: 12px;
  1726. border: 1px solid #e2e8f0;
  1727. }
  1728. .pdf-empty .empty-icon {
  1729. font-size: 3rem;
  1730. margin-bottom: 16px;
  1731. opacity: 0.6;
  1732. }
  1733. .pdf-empty h3 {
  1734. margin: 0 0 8px 0;
  1735. color: #2d3748;
  1736. font-size: 1.25rem;
  1737. font-weight: 600;
  1738. }
  1739. .pdf-empty p {
  1740. margin: 0;
  1741. color: #718096;
  1742. font-size: 0.95rem;
  1743. }
  1744. .dialog-footer {
  1745. padding: 0 32px 24px;
  1746. }
  1747. .footer-actions {
  1748. display: flex;
  1749. justify-content: flex-end;
  1750. gap: 12px;
  1751. }
  1752. .action-btn {
  1753. border-radius: 12px;
  1754. padding: 10px 24px;
  1755. font-weight: 500;
  1756. transition: all 0.3s ease;
  1757. }
  1758. .action-btn.secondary {
  1759. border: 1px solid #e2e8f0;
  1760. background: white;
  1761. color: #718096;
  1762. }
  1763. .action-btn.secondary:hover {
  1764. background: #f7fafc;
  1765. border-color: #cbd5e0;
  1766. }
  1767. .action-btn.primary {
  1768. background: #4299e1;
  1769. border: 1px solid #4299e1;
  1770. color: white;
  1771. }
  1772. .action-btn.primary:hover {
  1773. background: #3182ce;
  1774. border-color: #3182ce;
  1775. transform: translateY(-1px);
  1776. box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
  1777. }
  1778. /* 在样式部分添加 */
  1779. .header-actions {
  1780. display: flex;
  1781. align-items: center;
  1782. }
  1783. .history-btn {
  1784. background: rgba(255, 255, 255, 0.2);
  1785. border: 1px solid rgba(255, 255, 255, 0.3);
  1786. color: white;
  1787. border-radius: 12px;
  1788. padding: 10px 20px;
  1789. font-weight: 600;
  1790. transition: all 0.3s ease;
  1791. backdrop-filter: blur(10px);
  1792. margin-left: 50px;
  1793. }
  1794. .history-btn:hover {
  1795. background: rgba(255, 255, 255, 0.3);
  1796. transform: translateY(-2px);
  1797. box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2);
  1798. }
  1799. .btn-icon {
  1800. margin-right: 6px;
  1801. }
  1802. /* 响应式设计 */
  1803. @media (max-width: 1200px) {
  1804. .main-layout {
  1805. flex-direction: column;
  1806. }
  1807. .sidebar {
  1808. width: 100%;
  1809. position: static;
  1810. max-height: none;
  1811. margin-bottom: 20px;
  1812. }
  1813. .sidebar-footer {
  1814. position: static;
  1815. }
  1816. .results-grid {
  1817. grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
  1818. }
  1819. }
  1820. @media (max-width: 768px) {
  1821. .app-title {
  1822. font-size: 1.75rem;
  1823. }
  1824. .header-content {
  1825. flex-direction: column;
  1826. gap: 16px;
  1827. text-align: center;
  1828. }
  1829. .header-stats {
  1830. gap: 32px;
  1831. }
  1832. .main-layout {
  1833. padding: 16px;
  1834. }
  1835. .search-card {
  1836. padding: 24px;
  1837. }
  1838. .search-header {
  1839. flex-direction: column;
  1840. gap: 16px;
  1841. text-align: center;
  1842. }
  1843. .search-tips {
  1844. justify-content: center;
  1845. }
  1846. .search-input-group {
  1847. flex-direction: column;
  1848. }
  1849. .search-button {
  1850. width: 100%;
  1851. }
  1852. .collapsible-header {
  1853. padding: 16px 20px;
  1854. }
  1855. .header-main {
  1856. gap: 12px;
  1857. }
  1858. .header-icon {
  1859. width: 44px;
  1860. height: 44px;
  1861. font-size: 1.5rem;
  1862. }
  1863. .results-grid {
  1864. grid-template-columns: 1fr;
  1865. }
  1866. .dialog-title {
  1867. flex-direction: column;
  1868. text-align: center;
  1869. gap: 12px;
  1870. }
  1871. .title-content h3 {
  1872. font-size: 1.3rem;
  1873. }
  1874. .content-tabs {
  1875. padding: 0 20px;
  1876. flex-wrap: wrap;
  1877. }
  1878. .tab-content {
  1879. padding: 0 20px 20px;
  1880. }
  1881. .pdf-viewer {
  1882. min-height: 400px;
  1883. }
  1884. }
  1885. @media (max-width: 480px) {
  1886. .app-title {
  1887. font-size: 1.5rem;
  1888. }
  1889. .search-card {
  1890. padding: 20px;
  1891. }
  1892. .thinking-card .card-header,
  1893. .collapsible-header {
  1894. padding: 16px;
  1895. }
  1896. .card-body {
  1897. padding: 0 16px 16px;
  1898. }
  1899. .results-grid {
  1900. grid-template-columns: 1fr;
  1901. }
  1902. .content-dialog :deep(.el-dialog) {
  1903. width: 95% !important;
  1904. margin: 20px auto;
  1905. }
  1906. .title-meta {
  1907. flex-direction: column;
  1908. align-items: flex-start;
  1909. gap: 8px;
  1910. }
  1911. .result-meta {
  1912. flex-direction: column;
  1913. align-items: flex-start;
  1914. gap: 8px;
  1915. }
  1916. }
  1917. /* 侧边栏滚动条样式 */
  1918. .sidebar::-webkit-scrollbar {
  1919. width: 6px;
  1920. }
  1921. .sidebar::-webkit-scrollbar-track {
  1922. background: #f1f1f1;
  1923. border-radius: 3px;
  1924. }
  1925. .sidebar::-webkit-scrollbar-thumb {
  1926. background: #c1c1c1;
  1927. border-radius: 3px;
  1928. }
  1929. .sidebar::-webkit-scrollbar-thumb:hover {
  1930. background: #a8a8a8;
  1931. }
  1932. .knowledge-list::-webkit-scrollbar {
  1933. width: 4px;
  1934. }
  1935. .knowledge-list::-webkit-scrollbar-track {
  1936. background: transparent;
  1937. }
  1938. .knowledge-list::-webkit-scrollbar-thumb {
  1939. background: #d1d1d1;
  1940. border-radius: 2px;
  1941. }
  1942. </style>