knowledgeSet.vue 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121
  1. <template>
  2. <div class="project-search">
  3. <div class="file-categories">
  4. <el-button type="primary" @click="createNewFolder">
  5. <i class="el-icon-plus"></i> 新建目录
  6. </el-button>
  7. <p>{{ $route.query.name }}</p>
  8. <el-tree
  9. :data="folders"
  10. :props="defaultProps"
  11. @node-click="handleNodeClick"
  12. ref="folderTree"
  13. :default-expanded-keys="['0']"
  14. node-key="id"
  15. highlight-current
  16. >
  17. <span class="custom-tree-node" slot-scope="{ node, data }">
  18. <span class="tree_title">
  19. <i class="el-icon-folder"></i>
  20. <span class="folder-name" :title="node.label">{{
  21. node.label
  22. }}</span>
  23. </span>
  24. <span v-if="data.id !== '001'" :class="getFileCountClass(data.id)"
  25. >{{ data.document_count }} files</span
  26. >
  27. <span
  28. class="folder-actions"
  29. v-if="data.id !== 0 && data.id !== '001'"
  30. >
  31. <el-tooltip
  32. class="item"
  33. effect="dark"
  34. content="编辑"
  35. placement="top"
  36. >
  37. <i class="el-icon-edit" @click.stop="editFolder(data)"></i>
  38. </el-tooltip>
  39. <el-tooltip
  40. class="item"
  41. effect="dark"
  42. content="删除"
  43. placement="top"
  44. >
  45. <i class="el-icon-delete" @click.stop="deleteFolder(data.id)"></i>
  46. </el-tooltip>
  47. </span>
  48. </span>
  49. </el-tree>
  50. </div>
  51. <div class="right">
  52. <dataSearch
  53. @bindSetQuery="setQuery"
  54. @refreshFolders="typeList"
  55. :currentFolderId="queryForm.doc_type_id"
  56. ></dataSearch>
  57. <!-- <dataList
  58. :queryForm="queryForm"
  59. :allowEdit="checkAuth('/document/update')"
  60. :allowDelete="checkAuth('/document/delete')"
  61. ></dataList> -->
  62. <div
  63. class="data-list"
  64. v-loading="loading"
  65. element-loading-text="加载中..."
  66. >
  67. <div class="batch-actions" style="margin: 10px 0">
  68. <el-dropdown
  69. @command="handleBatchCommand"
  70. :disabled="!selectedRows.length"
  71. >
  72. <el-button type="primary" style="color: #fff">
  73. 批量操作
  74. <i data-feather="chevron-down" class="el-icon--right icons"></i>
  75. </el-button>
  76. <el-dropdown-menu slot="dropdown">
  77. <el-dropdown-item command="analysis">
  78. <i data-feather="play" class="icons"></i> 批量解析
  79. </el-dropdown-item>
  80. <el-dropdown-item command="generateQa">
  81. <i data-feather="message-square" class="icons"></i> 生成问答对
  82. </el-dropdown-item>
  83. <el-dropdown-item command="modifyFolder">
  84. <i data-feather="folder" class="icons"></i> 批量修改目录
  85. </el-dropdown-item>
  86. <el-dropdown-item command="delete">
  87. <i data-feather="trash-2" class="icons"></i> 批量删除
  88. </el-dropdown-item>
  89. </el-dropdown-menu>
  90. </el-dropdown>
  91. <el-button type="danger" @click="endAnalysis()">
  92. <i data-feather="stop-circle" class="icons"></i>
  93. 终止所有任务
  94. </el-button>
  95. <el-button type="primary" @click="exportData()">
  96. <i data-feather="download" class="icons"></i> 导出问答对
  97. </el-button>
  98. </div>
  99. <div class="selection-control" v-if="selectedRows.length > 0">
  100. <el-alert
  101. type="info"
  102. show-icon
  103. :closable="false"
  104. style="margin-bottom: 10px"
  105. >
  106. <template v-if="!isAllDataSelected">
  107. <!-- <span>已选择 {{ selectedRows.length }} 项</span> -->
  108. <span
  109. type="text"
  110. style="cursor: pointer; color: #409eff; margin-left: 10px"
  111. @click="handleSelectAllData"
  112. >
  113. 选择全部{{ recordCount }}条数据
  114. </span>
  115. </template>
  116. <template v-else>
  117. <span>已选择全部{{ recordCount }}条数据</span>
  118. <span
  119. type="text"
  120. style="cursor: pointer; color: #409eff; margin-left: 10px"
  121. @click="handleCancelAllSelection"
  122. >
  123. 取消全选
  124. </span>
  125. </template>
  126. </el-alert>
  127. </div>
  128. <el-table
  129. ref="dataTable"
  130. :data="dataList"
  131. style="width: 100%; font-size: 12px"
  132. header-row-class-name="headerBg"
  133. empty-text="暂无数据"
  134. @selection-change="handleSelectionChange"
  135. @sort-change="handleSortChange"
  136. :row-key="getRowKey"
  137. >
  138. <el-table-column
  139. type="selection"
  140. width="55"
  141. :reserve-selection="true"
  142. ></el-table-column>
  143. <el-table-column prop="id" label="ID" align="center" width="80" />
  144. <el-table-column prop="name" label="文件名称" sortable >
  145. <template #default="scope">
  146. <span>{{ scope.row.name }}</span>
  147. </template>
  148. </el-table-column>
  149. <el-table-column prop="type" label="文件类型" align="center" />
  150. <el-table-column prop="qa_count" label="问答对数" align="center"></el-table-column>
  151. <el-table-column prop="chunk_num" label="分块数" align="center" />
  152. <el-table-column
  153. prop="create_time"
  154. label="上传时间"
  155. align="center"
  156. sortable
  157. width="140"
  158. />
  159. <el-table-column prop="run" label="解析状态" align="center">
  160. <template #default="scope">
  161. <el-tooltip
  162. effect="dark"
  163. placement="top"
  164. :disabled="
  165. ![1, 4, 7].includes(Number(scope.row.run))
  166. "
  167. >
  168. <template #content>
  169. <div style="padding: 5px">
  170. <div style="margin-top: 5px; font-size: 12px">
  171. {{
  172. Number(scope.row.run) === 7
  173. ? (scope.row.progress_msg || "正在生成问答对...")
  174. : (scope.row.progress_msg || "正在解析中...")
  175. }}
  176. </div>
  177. </div>
  178. </template>
  179. <div class="status-wrapper">
  180. <template v-if="Number(scope.row.run) === 1||Number(scope.row.run) === 5">
  181. <div class="progress-wrapper">
  182. <el-progress
  183. type="circle"
  184. :percentage="Math.floor(scope.row.progress) || 0"
  185. :width="30"
  186. :stroke-width="2"
  187. :format="percentFormat"
  188. ></el-progress
  189. ><!-- :status="getProgressStatus(scope.row.progress)" -->
  190. </div>
  191. </template>
  192. <template v-else>
  193. <el-tag :type="getStatusType(scope.row.run)" size="small">
  194. {{ getStatusText(scope.row.run) }}
  195. </el-tag>
  196. </template>
  197. </div>
  198. </el-tooltip>
  199. </template>
  200. </el-table-column>
  201. <el-table-column
  202. label="操作"
  203. align="center"
  204. fixed="right"
  205. width="180"
  206. >
  207. <template #default="scope">
  208. <div class="operations-container">
  209. <el-tooltip
  210. class="item"
  211. effect="dark"
  212. content="预览"
  213. placement="top"
  214. >
  215. <el-button
  216. type="text"
  217. size="small"
  218. circle
  219. icon="el-icon-view"
  220. v-if="checkAuth('/document/update')"
  221. style="font-size: 15px"
  222. @click="getName(scope.row)"
  223. ></el-button>
  224. </el-tooltip>
  225. <el-tooltip
  226. class="item"
  227. effect="dark"
  228. content="修改文件名"
  229. placement="top"
  230. >
  231. <el-button
  232. type="text"
  233. size="small"
  234. circle
  235. icon="el-icon-edit"
  236. v-if="checkAuth('/document/update')"
  237. style="font-size: 15px"
  238. @click="btnEdit(scope.row)"
  239. ></el-button>
  240. </el-tooltip>
  241. <el-popover
  242. placement="bottom"
  243. trigger="click"
  244. popper-class="operations-popover"
  245. >
  246. <template #reference>
  247. <el-button
  248. type="text"
  249. size="small"
  250. circle
  251. class="operation-button"
  252. style="font-size: 15px; margin-left: 10px"
  253. icon="el-icon-more"
  254. ></el-button>
  255. </template>
  256. <div class="additional-operations">
  257. <el-tooltip
  258. class="item"
  259. effect="dark"
  260. :content="
  261. scope.row.run == 1 || scope.row.run == 5
  262. ? `解析中`
  263. : '解析'
  264. "
  265. placement="top"
  266. >
  267. <el-button
  268. v-if="scope.row.run != 1 && scope.row.run != 5"
  269. type="text"
  270. icon="el-icon-caret-right"
  271. circle
  272. @click="analysis(scope.row)"
  273. style="font-size: 15px"
  274. >
  275. </el-button>
  276. <el-button
  277. v-else
  278. type="text"
  279. icon="el-icon-loading"
  280. circle
  281. :disabled="true"
  282. style="font-size: 15px"
  283. >
  284. </el-button>
  285. </el-tooltip>
  286. <el-tooltip
  287. class="item"
  288. effect="dark"
  289. content="查看问答对"
  290. placement="top"
  291. >
  292. <el-button
  293. type="text"
  294. size="small"
  295. circle
  296. icon="el-icon-receiving"
  297. @click="getQa(scope.row.id)"
  298. style="font-size: 15px"
  299. ></el-button>
  300. </el-tooltip>
  301. <el-tooltip
  302. class="item"
  303. effect="dark"
  304. content="解析方法"
  305. placement="top"
  306. >
  307. <el-button
  308. type="text"
  309. size="small"
  310. circle
  311. icon="el-icon-tickets"
  312. v-if="checkAuth('/document/update')"
  313. style="font-size: 15px"
  314. @click="Analytical(scope.row)"
  315. ></el-button>
  316. </el-tooltip>
  317. <el-tooltip
  318. class="item"
  319. effect="dark"
  320. content="下载"
  321. placement="top"
  322. >
  323. <el-button
  324. type="text"
  325. size="small"
  326. circle
  327. icon="el-icon-download"
  328. @click="btnDown(scope.row)"
  329. :loading="scope.row.downloading"
  330. style="font-size: 15px"
  331. ></el-button>
  332. </el-tooltip>
  333. <el-tooltip
  334. class="item"
  335. effect="dark"
  336. content="删除"
  337. placement="top"
  338. >
  339. <el-button
  340. type="text"
  341. size="small"
  342. circle
  343. icon="el-icon-delete"
  344. v-if="checkAuth('/document/delete')"
  345. @click="btnDelete(scope.row.id)"
  346. style="font-size: 15px"
  347. ></el-button>
  348. </el-tooltip>
  349. </div>
  350. </el-popover>
  351. </div>
  352. </template>
  353. </el-table-column>
  354. </el-table>
  355. <div class="page-info">
  356. <el-pagination
  357. :currentPage="queryForm.page"
  358. :page-size="queryForm.pageSize"
  359. :total="recordCount"
  360. :page-count="pageTotal"
  361. :page-sizes="[10, 20, 50, 100]"
  362. background
  363. layout="total,sizes, prev, pager, next"
  364. @current-change="ChangePage"
  365. @size-change="handleSizeChange"
  366. >
  367. </el-pagination>
  368. </div>
  369. <el-dialog
  370. title="修改名称"
  371. :visible.sync="dialogVisible"
  372. width="30%"
  373. :before-close="handleClose"
  374. >
  375. <el-form :model="wordForm" ref="wordRef" :rules="wordRules">
  376. <el-form-item label="文件名称:" prop="new_name">
  377. <el-input
  378. v-model="wordForm.new_name"
  379. placeholder="请输入文件名称"
  380. ></el-input>
  381. </el-form-item>
  382. </el-form>
  383. <span slot="footer" class="dialog-footer">
  384. <el-button @click="handleClose">取 消</el-button>
  385. <el-button type="primary" @click="submitBuck">确 定</el-button>
  386. </span>
  387. </el-dialog>
  388. <!-- 解析方法 -->
  389. <el-dialog
  390. title="解析方法"
  391. :visible.sync="anaDialogVisible"
  392. width="30%"
  393. :before-close="handleAnalClose"
  394. >
  395. <el-form
  396. :model="analForm"
  397. ref="analRef"
  398. label-position="top"
  399. :rules="analRules"
  400. >
  401. <el-form-item label="语言选择:" prop="language">
  402. <el-radio-group v-model="analForm.language">
  403. <el-radio label="zh">中文</el-radio>
  404. <el-radio label="en">英文</el-radio>
  405. </el-radio-group>
  406. </el-form-item>
  407. <el-form-item label="页码范围:" prop="new_name">
  408. <el-input-number
  409. v-model="analForm.start_page"
  410. controls-position="right"
  411. :min="1"
  412. ></el-input-number>
  413. <el-input-number
  414. v-model="analForm.end_page"
  415. controls-position="right"
  416. :min="1"
  417. style="margin-left: 10px"
  418. ></el-input-number>
  419. </el-form-item>
  420. <el-form-item label="块token数">
  421. <div class="block">
  422. <el-slider v-model="analForm.token_num" :max="2048" show-input>
  423. </el-slider>
  424. </div>
  425. </el-form-item>
  426. </el-form>
  427. <span slot="footer" class="dialog-footer">
  428. <el-button type="primary" @click="handleAnalClose">取 消</el-button>
  429. <el-button type="primary" @click="submitAnal">确 定</el-button>
  430. </span>
  431. </el-dialog>
  432. <el-dialog
  433. title="新建目录"
  434. :visible.sync="newFolderDialogVisible"
  435. width="30%"
  436. >
  437. <el-form
  438. :model="newFolderForm"
  439. :rules="newFolderRules"
  440. ref="newFolderFormRef"
  441. >
  442. <el-form-item label="文件夹名称" prop="name">
  443. <el-input v-model="newFolderForm.name"></el-input>
  444. </el-form-item>
  445. </el-form>
  446. <span slot="footer" class="dialog-footer">
  447. <el-button type="primary" @click="newFolderDialogVisible = false"
  448. >取 消</el-button
  449. >
  450. <el-button type="primary" @click="submitNewFolder">确 定</el-button>
  451. </span>
  452. </el-dialog>
  453. <!-- 批量修改目录 -->
  454. <el-dialog
  455. title="批量修改目录"
  456. :visible.sync="batchModifyFolderDialogVisible"
  457. width="30%"
  458. >
  459. <el-form
  460. :model="batchModifyFolderForm"
  461. :rules="batchModifyFolderRules"
  462. ref="batchModifyFolderFormRef"
  463. >
  464. <el-form-item label="选择目录" prop="folderId">
  465. <el-select
  466. v-model="batchModifyFolderForm.folderId"
  467. placeholder="请选择目录"
  468. >
  469. <el-option
  470. v-for="folder in folders.filter((f) => f.id !== 0)"
  471. :key="folder.id"
  472. :label="folder.name"
  473. :value="folder.id"
  474. >
  475. </el-option>
  476. </el-select>
  477. </el-form-item>
  478. </el-form>
  479. <span slot="footer" class="dialog-footer">
  480. <el-button
  481. type="primary"
  482. @click="batchModifyFolderDialogVisible = false"
  483. >取 消</el-button
  484. >
  485. <el-button type="primary" @click="submitBatchModifyFolder"
  486. >确 定</el-button
  487. >
  488. </span>
  489. </el-dialog>
  490. <!-- 问答对弹窗 -->
  491. <el-dialog
  492. title="问答对"
  493. :visible.sync="qaDialogVisible"
  494. width="80%"
  495. :before-close="handleQaDialogClose"
  496. >
  497. <div class="qa-dialog-content">
  498. <div class="qa-dialog-header">
  499. <el-tag>文档ID: {{ currentDocumentId }}</el-tag>
  500. <el-tag type="success" v-if="qaList.length">
  501. 共 {{ qaList.length }} 个问答对
  502. </el-tag>
  503. </div>
  504. <div class="qa-content" v-loading="qaLoading">
  505. <div v-if="qaList.length === 0 && !qaLoading" class="empty-state">
  506. <i class="el-icon-document"></i>
  507. <p>暂无问答对数据</p>
  508. </div>
  509. <div v-else class="qa-list">
  510. <div
  511. v-for="(item, index) in qaList"
  512. :key="index"
  513. class="qa-item"
  514. >
  515. <div class="qa-question">
  516. <div class="qa-label">问:</div>
  517. <div class="qa-text">{{ item.question }}</div>
  518. </div>
  519. <div class="qa-answer">
  520. <div class="qa-label">答:</div>
  521. <div class="qa-text">{{ item.answer }}</div>
  522. </div>
  523. <div class="qa-meta" v-if="item.confidence || item.source">
  524. <el-tag size="mini" v-if="item.confidence">
  525. 置信度: {{ item.confidence }}
  526. </el-tag>
  527. <el-tag size="mini" type="info" v-if="item.source">
  528. 来源: {{ item.source }}
  529. </el-tag>
  530. </div>
  531. </div>
  532. </div>
  533. </div>
  534. </div>
  535. <span slot="footer" class="dialog-footer">
  536. <el-button
  537. type="primary"
  538. icon="el-icon-refresh"
  539. @click="fetchQAData"
  540. :loading="qaLoading"
  541. >
  542. 刷新数据
  543. </el-button>
  544. <el-button @click="handleQaDialogClose">关闭</el-button>
  545. </span>
  546. </el-dialog>
  547. </div>
  548. </div>
  549. </div>
  550. </template>
  551. <script>
  552. import { mapGetters } from "vuex";
  553. import { dataSearch, dataList } from "./components";
  554. import {
  555. getBucketContents,
  556. deleteFile,
  557. nameGetUrl,
  558. renameFile,
  559. analysis,
  560. batchAnalysis,
  561. getRunStatus,
  562. insertType,
  563. updateType,
  564. selectTypeList,
  565. deleteType,
  566. Info,
  567. batchMove,
  568. selectType0,
  569. delDocumentList,
  570. getdocpro,
  571. analysisPro,
  572. resultTask,
  573. endClear,
  574. generateQa,
  575. } from "@/api/knowledge";
  576. import { exportQaToExcel,queryDocumentContent } from "@/api/document";
  577. import { validateExportParams, logJSON, checkJSONSize } from "@/utils/data-validator";
  578. export default {
  579. components: {
  580. dataSearch,
  581. dataList,
  582. },
  583. computed: {
  584. ...mapGetters(["roleInfo", "authList"]),
  585. },
  586. data() {
  587. return {
  588. queryForm: {
  589. page: 1,
  590. pageSize: 10,
  591. bucket_id: "",
  592. doc_type_id: 0,
  593. },
  594. typeForm: {
  595. page: 1,
  596. pageSize: 9999,
  597. kb_id: "",
  598. },
  599. folders: [{ id: "001", name: "全部", document_count: 0 }],
  600. defaultProps: {
  601. children: "children",
  602. label: "name",
  603. },
  604. // dataList 组件的数据
  605. loading: false,
  606. dialogVisible: false,
  607. currentDataId: 0,
  608. recordCount: 0,
  609. pageTotal: 1,
  610. dataList: [],
  611. currentData: {},
  612. wordForm: {
  613. document_id: "",
  614. new_name: "",
  615. },
  616. wordRules: {
  617. new_name: [
  618. { required: true, message: "请输文件名称", trigger: "blur" },
  619. ],
  620. },
  621. anaDialogVisible: false,
  622. analForm: {
  623. start_page: 1,
  624. end_page: 10000,
  625. token_num: 512,
  626. language: "en",
  627. },
  628. analRules: {},
  629. selectedRows: [],
  630. sortColumn: "",
  631. sortOrder: "",
  632. analysisStatusCheckers: {},
  633. newFolderDialogVisible: false,
  634. newFolderForm: {
  635. name: "",
  636. kb_id: "",
  637. status: 5,
  638. },
  639. newFolderRules: {
  640. name: [
  641. { required: true, message: "请输入文件夹名称", trigger: "blur" },
  642. ],
  643. },
  644. /* 批量修改目录 */
  645. batchModifyFolderDialogVisible: false,
  646. batchModifyFolderForm: {
  647. folderId: null,
  648. },
  649. batchModifyFolderRules: {
  650. folderId: [
  651. { required: true, message: "请选择目标目录", trigger: "change" },
  652. ],
  653. },
  654. selectedRows: [], // 已选择的行
  655. allSelectedRows: new Map(), // 存储所有选中的行
  656. isAllDataSelected: false, // 是否选中所有数据
  657. /* 问答对弹窗 */
  658. qaDialogVisible: false,
  659. currentDocumentId: null,
  660. qaList: [],
  661. qaLoading: false,
  662. };
  663. },
  664. mounted() {
  665. this.queryForm.bucket_id = this.$route.query.id;
  666. this.typeForm.kb_id = this.$route.query.id;
  667. /* this.$nextTick(() => {
  668. this.initializeTree();
  669. }); */
  670. this.typeList();
  671. /* this.search(); */
  672. },
  673. beforeDestroy() {
  674. Object.values(this.analysisStatusCheckers).forEach(clearTimeout);
  675. },
  676. methods: {
  677. /* 导出 */
  678. async exportData() {
  679. try {
  680. // 显示加载状态
  681. const loading = this.$loading({
  682. lock: true,
  683. text: '正在导出,请稍候...',
  684. spinner: 'el-icon-loading',
  685. background: 'rgba(0, 0, 0, 0.7)'
  686. });
  687. // 准备导出参数
  688. const exportParams = {
  689. kb_id: this.queryForm.bucket_id, // 知识库ID
  690. doc_type_id: this.queryForm.doc_type_id === 0 ? null : this.queryForm.doc_type_id, // 目录ID,0表示全部
  691. export_to_local: true,
  692. };
  693. // 如果有选中的文档,只导出选中的
  694. if (this.selectedRows.length > 0) {
  695. exportParams.document_ids = this.selectedRows.map(row => row.id);
  696. }
  697. // 验证和清理导出参数
  698. const validation = validateExportParams(exportParams);
  699. if (!validation.isValid) {
  700. loading.close();
  701. this.$message.error(`参数验证失败: ${validation.errors.join(', ')}`);
  702. return;
  703. }
  704. if (validation.warnings.length > 0) {
  705. console.warn('参数警告:', validation.warnings);
  706. }
  707. // 使用清理后的参数
  708. const finalParams = validation.cleanedParams;
  709. // 检查JSON数据大小
  710. const sizeInfo = checkJSONSize(finalParams);
  711. if (sizeInfo.isLarge) {
  712. console.warn(`导出数据较大: ${sizeInfo.kb}KB`);
  713. }
  714. // 日志输出
  715. logJSON(finalParams, '最终导出参数');
  716. // 调用导出接口
  717. const response = await exportQaToExcel(finalParams);
  718. console.log(response);
  719. // 检查响应格式
  720. if (response.status === 200) {
  721. const { data } = response;
  722. const { message, data: exportData } = data;
  723. // 如果有下载链接,直接使用浏览器下载
  724. if (exportData && exportData.download_url) {
  725. // 直接打开新窗口下载
  726. const url=process.env.VUE_APP_BASE_API+exportData.download_url
  727. console.log(url);
  728. window.open(url, '_blank');
  729. loading.close();
  730. this.$message.success(message || '导出成功!');
  731. } else {
  732. loading.close();
  733. this.$message.error('获取下载链接失败');
  734. }
  735. } else {
  736. loading.close();
  737. this.$message.error('导出请求失败');
  738. }
  739. } catch (error) {
  740. console.error('导出失败:', error);
  741. this.$message.error('导出失败,请稍后重试');
  742. if (loading) loading.close();
  743. }
  744. },
  745. // 下载文件的通用方法
  746. downloadFile(downloadUrl, fileName) {
  747. try {
  748. const link = document.createElement('a');
  749. link.href = downloadUrl;
  750. link.download = fileName || `问答对导出_${new Date().toISOString().slice(0, 10)}.xlsx`;
  751. link.target = '_blank'; // 在新窗口打开,避免跨域问题
  752. // 触发下载
  753. document.body.appendChild(link);
  754. link.click();
  755. document.body.removeChild(link);
  756. this.$message.success('文件下载已开始!');
  757. } catch (error) {
  758. console.error('下载失败:', error);
  759. this.$message.error('文件下载失败,请稍后重试');
  760. }
  761. },
  762. /* 查看问答对 */
  763. getQa(id) {
  764. this.currentDocumentId = id;
  765. this.qaDialogVisible = true;
  766. this.fetchQAData();
  767. },
  768. /* 获取问答对数据 */
  769. async fetchQAData() {
  770. if (!this.currentDocumentId) return;
  771. this.qaLoading = true;
  772. try {
  773. const response = await queryDocumentContent({
  774. document_id: this.currentDocumentId
  775. });
  776. if (response.status === 200 ) {
  777. // 根据接口返回的数据结构调整
  778. this.qaList = response.data[0].qa_pairs || response.result || [];
  779. // 如果接口返回的不是数组格式,进行处理
  780. if (!Array.isArray(this.qaList)) {
  781. if (this.qaList.items) {
  782. this.qaList = this.qaList.items;
  783. } else if (this.qaList.qa_pairs) {
  784. this.qaList = this.qaList.qa_pairs;
  785. console.log(this.qaList);
  786. } else {
  787. this.qaList = [];
  788. }
  789. }
  790. if (this.qaList.length === 0) {
  791. this.$message.info('该文档暂无问答对数据');
  792. }
  793. } else {
  794. this.$message.error(response.message || '获取问答对数据失败');
  795. this.qaList = [];
  796. }
  797. } catch (error) {
  798. console.error('获取问答对数据失败:', error);
  799. this.$message.error('获取问答对数据失败,请稍后重试');
  800. this.qaList = [];
  801. } finally {
  802. this.qaLoading = false;
  803. }
  804. },
  805. /* 关闭问答对弹窗 */
  806. handleQaDialogClose() {
  807. this.qaDialogVisible = false;
  808. this.currentDocumentId = null;
  809. this.qaList = [];
  810. },
  811. // 获取行的唯一标识
  812. getRowKey(row) {
  813. return row.id;
  814. },
  815. /* 功按钮 */
  816. handleBatchCommand(command) {
  817. switch (command) {
  818. case "analysis":
  819. this.batchAnalysis();
  820. break;
  821. case "generateQa":
  822. this.batchGenerateQa();
  823. break;
  824. case "modifyFolder":
  825. this.batchModifyFolder();
  826. break;
  827. case "delete":
  828. this.batchDelect();
  829. break;
  830. }
  831. },
  832. // 批量生成问答对
  833. batchGenerateQa() {
  834. if (this.selectedRows.length === 0) {
  835. this.$message.warning("请选择要生成问答对的文件");
  836. return;
  837. }
  838. const loading = this.$loading({
  839. lock: true,
  840. text: '正在生成问答对,请稍候...',
  841. spinner: 'el-icon-loading',
  842. background: 'rgba(0, 0, 0, 0.7)'
  843. });
  844. const selectedIds = this.selectedRows.map(row => row.id);
  845. generateQa({
  846. document_ids: JSON.stringify(selectedIds)
  847. }).then((res) => {
  848. console.log('生成问答对响应:', res);
  849. if (res.status === 200) {
  850. this.$message.success('问答对生成任务已提交');
  851. // 更新选中文档的状态
  852. this.selectedRows.forEach(row => {
  853. this.$set(row, 'run', 6); // 设置状态为"待生成问答对"
  854. this.startAnalysisStatusChecker(row); // 开始检查状态
  855. });
  856. this.clearSelection(); // 清除选择
  857. } else {
  858. this.$message.error(res.data?.message || '生成问答对失败');
  859. }
  860. }).catch(error => {
  861. console.error('生成问答对时出错:', error);
  862. this.$message.error('生成问答对失败,请稍后重试');
  863. }).finally(() => {
  864. loading.close();
  865. });
  866. },
  867. getProgressStatus(progress) {
  868. // el-progress 只接受 'success'/'exception'/'warning' 这几个值
  869. if (progress >= 100) {
  870. return "success";
  871. }
  872. // 如果进度小于100,返回undefined,使用默认样式
  873. return undefined;
  874. },
  875. /* 操作栏 */
  876. handleCommand(command, row) {
  877. switch (command) {
  878. case "analysis":
  879. this.analysis(row);
  880. break;
  881. case "analyticalMethod":
  882. this.Analytical(row);
  883. break;
  884. case "download":
  885. this.btnDown(row);
  886. break;
  887. case "delete":
  888. this.btnDelete(row.id);
  889. break;
  890. }
  891. },
  892. /* 状态展示 */
  893. getStatusType(status) {
  894. const statusMap = {
  895. 0: "info", // 未解析
  896. 1: "warning", // 解析中
  897. 3: "success", // 解析成功,问答对生成成功
  898. 4: "danger", // 解析失败
  899. 5: "info", // 待解析
  900. 6: "warning", // 待生成问答对
  901. 7: "danger" // 解析成功,问答对生成失败
  902. };
  903. return statusMap[status] || "info";
  904. },
  905. getStatusText(status) {
  906. const statusMap = {
  907. 0: "未解析",
  908. 1: "解析中",
  909. 3: "处理成功",
  910. 4: "解析失败",
  911. 5: "待解析",
  912. 6: "待生成问答对",
  913. 7: "处理失败"
  914. };
  915. return statusMap[status] || "未知状态";
  916. },
  917. /* 判断类名 */
  918. getFileCountClass(id) {
  919. const isSpecialFolder = id === 0 || id === "001";
  920. console.log(
  921. `ID: ${id}, Class: ${isSpecialFolder ? "file-counts" : "file-count"}`
  922. );
  923. return isSpecialFolder ? "file-counts" : "file-count";
  924. },
  925. /* 批量修改目录 */
  926. batchModifyFolder() {
  927. if (this.selectedRows.length === 0) {
  928. this.$message.warning("请选择要修改目录的文件");
  929. return;
  930. }
  931. this.batchModifyFolderDialogVisible = true;
  932. },
  933. submitBatchModifyFolder() {
  934. this.$refs.batchModifyFolderFormRef.validate((valid) => {
  935. if (valid) {
  936. const selectedIds = this.selectedRows.map((row) => row.id);
  937. console.log(selectedIds);
  938. batchMove({
  939. ids: JSON.stringify(selectedIds),
  940. doc_type_id: this.batchModifyFolderForm.folderId,
  941. })
  942. .then((res) => {
  943. if (res.status === 200) {
  944. this.$message.success("批量修改目录成功");
  945. this.batchModifyFolderDialogVisible = false;
  946. this.search(); // Refresh the document list
  947. this.typeList(); // Refresh the folder list
  948. this.clearSelection();
  949. } else {
  950. this.$message.error("批量修改目录失败");
  951. }
  952. })
  953. .catch((error) => {
  954. console.error("批量修改目录时出错:", error);
  955. this.$message.error("批量修改目录时出错");
  956. });
  957. }
  958. });
  959. },
  960. editFolder(folder) {
  961. this.$prompt("请输入新的文件夹名称", "编辑文件夹", {
  962. confirmButtonText: "确定",
  963. cancelButtonText: "取消",
  964. inputValue: folder.name,
  965. inputValidator: (value) => {
  966. if (!value) {
  967. return "文件夹名称不能为空";
  968. }
  969. return true;
  970. },
  971. })
  972. .then(({ value }) => {
  973. if (value !== folder.name) {
  974. const updatedFolder = { ...folder, name: value };
  975. updateType(updatedFolder)
  976. .then((res) => {
  977. if (res.status === 200) {
  978. this.$message.success("更新文件夹成功");
  979. this.typeList(); // 刷新文件夹列表
  980. } else {
  981. this.$message.error("更新文件夹失败");
  982. }
  983. })
  984. .catch((error) => {
  985. console.error("更新文件夹时出错:", error);
  986. this.$message.error("更新文件夹时出错");
  987. });
  988. }
  989. })
  990. .catch(() => {
  991. // 用户取消操作,不做任何处理
  992. });
  993. },
  994. deleteFolder(folderId) {
  995. this.$confirm("确定要删除该文件夹吗?删除后无法恢复。", "警告", {
  996. confirmButtonText: "确定",
  997. cancelButtonText: "取消",
  998. type: "warning",
  999. })
  1000. .then(() => {
  1001. deleteType({ id: folderId })
  1002. .then((res) => {
  1003. if (res.status === 200) {
  1004. this.$message.success("删除文件夹成功");
  1005. this.typeList(); // 刷新文件夹列表
  1006. } else {
  1007. this.$message.error("删除文件夹失败");
  1008. }
  1009. })
  1010. .catch((error) => {
  1011. console.error("删除文件夹时出错:", error);
  1012. this.$message.error("删除文件夹时出错");
  1013. });
  1014. })
  1015. .catch(() => {
  1016. // 用户取消删除操作,不做任何处理
  1017. });
  1018. },
  1019. /* 目录初始化 */
  1020. typeList() {
  1021. this.loading = true;
  1022. Promise.all([
  1023. selectTypeList(this.typeForm),
  1024. selectType0({ kb_id: this.typeForm.kb_id, type_id: 0 }),
  1025. ])
  1026. .then(([typeListRes, type0Res]) => {
  1027. if (typeListRes.status === 200 && type0Res.status === 200) {
  1028. const folderList = typeListRes.data.dataList.map((folder) => ({
  1029. ...folder,
  1030. id: folder.id === 0 ? "001" : folder.id.toString(),
  1031. document_count: folder.document_count || 0,
  1032. }));
  1033. // Calculate total documents
  1034. const totalDocuments = folderList.reduce(
  1035. (sum, folder) => sum + folder.document_count,
  1036. 0
  1037. );
  1038. // Get the count of "其他" documents from selectType0 response
  1039. const otherFolderCount = type0Res.data || 0;
  1040. this.folders = [
  1041. {
  1042. id: "001",
  1043. name: "全部",
  1044. document_count: totalDocuments + otherFolderCount,
  1045. },
  1046. ...folderList.filter(
  1047. (folder) => folder.id !== "001" && folder.id !== 0
  1048. ),
  1049. {
  1050. id: 0,
  1051. name: "其他",
  1052. document_count: otherFolderCount,
  1053. },
  1054. ];
  1055. // 直接在这里执行一次性的初始化
  1056. this.$nextTick(() => {
  1057. if (this.$refs.folderTree) {
  1058. this.$refs.folderTree.setCurrentKey("001");
  1059. // 只在这里调用一次 search
  1060. this.queryForm.doc_type_id = "";
  1061. this.search();
  1062. }
  1063. });
  1064. } else {
  1065. this.$message.error("获取文件夹列表败");
  1066. }
  1067. })
  1068. .catch((error) => {
  1069. console.error("获取文件夹列表时出错:", error);
  1070. this.$message.error("获取文件夹列表时出错");
  1071. })
  1072. .finally(() => {
  1073. this.loading = false;
  1074. });
  1075. },
  1076. /* initializeTree() {
  1077. this.$nextTick(() => {
  1078. if (this.$refs.folderTree) {
  1079. this.$refs.folderTree.setCurrentKey(0);
  1080. this.handleNodeClick(this.folders[0]);
  1081. }
  1082. });
  1083. }, */
  1084. createNewFolder() {
  1085. this.newFolderDialogVisible = true;
  1086. },
  1087. /* 新增 */
  1088. submitNewFolder() {
  1089. this.$refs.newFolderFormRef.validate((valid) => {
  1090. if (valid) {
  1091. this.newFolderForm.kb_id = this.$route.query.id;
  1092. insertType(this.newFolderForm).then((res) => {
  1093. if (res.status === 200) {
  1094. this.$message.success("创建文件夹成功");
  1095. this.newFolderDialogVisible = false;
  1096. this.$refs.newFolderFormRef.resetFields();
  1097. this.typeList(); // Refresh folder list
  1098. } else {
  1099. this.$message.error("创建文件夹失败");
  1100. }
  1101. });
  1102. }
  1103. });
  1104. },
  1105. // 移除或简化 initializeTree 方法,因为相关逻辑已经到 typeList 中
  1106. initializeTree() {
  1107. // 可以保留这个方法但不执行 search
  1108. this.$nextTick(() => {
  1109. if (this.$refs.folderTree) {
  1110. this.$refs.folderTree.setCurrentKey("001");
  1111. }
  1112. });
  1113. },
  1114. /* initializeTree() {
  1115. this.$nextTick(() => {
  1116. if (this.$refs.folderTree) {
  1117. this.$refs.folderTree.setCurrentKey("001");
  1118. this.handleNodeClick(this.folders[0]);
  1119. }
  1120. });
  1121. }, */
  1122. checkAuth(path) {
  1123. if (this.roleInfo.is_admin === 1) {
  1124. return true;
  1125. }
  1126. return this.authList.some(
  1127. (auth) => auth.type === 999 && auth.path === path
  1128. );
  1129. },
  1130. setQuery(e) {
  1131. this.queryForm = { ...this.queryForm, ...e };
  1132. this.search();
  1133. },
  1134. handleNodeClick(data) {
  1135. this.queryForm.doc_type_id = data.id === "001" ? "" : data.id;
  1136. this.queryForm.page = 1; // Reset to first page when changing folder
  1137. this.search();
  1138. },
  1139. /*列表事件 */
  1140. /* 排序 */
  1141. handleSortChange({ prop, order }) {
  1142. this.sortColumn = prop;
  1143. this.sortOrder = order;
  1144. this.sortData();
  1145. },
  1146. sortData() {
  1147. const { sortColumn, sortOrder } = this;
  1148. if (!sortColumn || !sortOrder) {
  1149. this.search(); // Reset to original order
  1150. return;
  1151. }
  1152. this.dataList.sort((a, b) => {
  1153. let comparison = 0;
  1154. if (sortColumn === "name") {
  1155. comparison = a.name.localeCompare(b.name, "zh-CN");
  1156. } else if (sortColumn === "create_time") {
  1157. comparison = new Date(a.create_time) - new Date(b.create_time);
  1158. }
  1159. return sortOrder === "ascending" ? comparison : -comparison;
  1160. });
  1161. },
  1162. /* 处理选择变化*/
  1163. handleSelectionChange(selection) {
  1164. if (!this.isAllDataSelected) {
  1165. // 更新当前页选中状态
  1166. this.dataList.forEach((row) => {
  1167. if (selection.find((item) => item.id === row.id)) {
  1168. this.allSelectedRows.set(row.id, row);
  1169. } else {
  1170. this.allSelectedRows.delete(row.id);
  1171. }
  1172. });
  1173. this.selectedRows = Array.from(this.allSelectedRows.values());
  1174. }
  1175. },
  1176. // 选择全部数据
  1177. async handleSelectAllData() {
  1178. try {
  1179. // 构建查询参数,获取所有数据
  1180. const params = {
  1181. ...this.queryForm,
  1182. page: 1,
  1183. page_size: this.recordCount,
  1184. };
  1185. const response = await getBucketContents(params);
  1186. if (response.status === 200) {
  1187. const allData = response.data.documents;
  1188. // 清空之前的选择
  1189. this.allSelectedRows.clear();
  1190. // 将所有数据添加到选中集合
  1191. allData.forEach((item) => {
  1192. this.allSelectedRows.set(item.id, item);
  1193. });
  1194. this.isAllDataSelected = true;
  1195. this.selectedRows = allData;
  1196. // 选中当前页显示的所有行
  1197. this.$nextTick(() => {
  1198. if (this.$refs.dataTable) {
  1199. this.dataList.forEach((row) => {
  1200. this.$refs.dataTable.toggleRowSelection(row, true);
  1201. });
  1202. }
  1203. });
  1204. this.$message.success(`已选中全部${this.recordCount}条数据`);
  1205. }
  1206. } catch (error) {
  1207. console.error("获取全部数据失败:", error);
  1208. this.$message.error("选择全部数据失败");
  1209. }
  1210. },
  1211. // 取消全选
  1212. handleCancelAllSelection() {
  1213. this.clearSelection();
  1214. this.isAllDataSelected = false;
  1215. this.$message.info("已取消全部选择");
  1216. },
  1217. // 修改清除选择方法
  1218. clearSelection() {
  1219. if (this.$refs.dataTable) {
  1220. this.$refs.dataTable.clearSelection();
  1221. }
  1222. this.selectedRows = [];
  1223. this.allSelectedRows.clear();
  1224. this.isAllDataSelected = false;
  1225. },
  1226. // 修改分页方法,保持选中状态
  1227. ChangePage(e) {
  1228. this.queryForm.page = e;
  1229. this.search().then(() => {
  1230. this.$nextTick(() => {
  1231. if (this.$refs.dataTable) {
  1232. this.dataList.forEach((row) => {
  1233. if (this.isAllDataSelected || this.allSelectedRows.has(row.id)) {
  1234. this.$refs.dataTable.toggleRowSelection(row, true);
  1235. }
  1236. });
  1237. }
  1238. });
  1239. });
  1240. },
  1241. batchAnalysis() {
  1242. const selectedIds = this.selectedRows.map((row) => row.id);
  1243. if (selectedIds.length === 0) {
  1244. this.$message.warning("请选择要解析的文件");
  1245. return;
  1246. }
  1247. this.$confirm("确定要批量解析选中的文件吗?", "提示", {
  1248. confirmButtonText: "确定",
  1249. cancelButtonText: "取消",
  1250. type: "warning",
  1251. })
  1252. .then(() => {
  1253. this.loading = true;
  1254. analysisPro({
  1255. document_ids: JSON.stringify(selectedIds),
  1256. ocr_params: {
  1257. language: this.analForm.language,
  1258. max_retries: 3,
  1259. timeout: [10, 180],
  1260. },
  1261. }) /* { ids: JSON.stringify(selectedIds) } */
  1262. .then((res) => {
  1263. if (res.status === 200) {
  1264. this.$message.success("批量解析任务已提交");
  1265. setTimeout(() => {
  1266. this.updateAnalysisStatus(selectedIds);
  1267. }, 5000);
  1268. this.clearSelection();
  1269. } else {
  1270. this.$message.error("批量解析请求失败");
  1271. }
  1272. })
  1273. .catch((error) => {
  1274. console.error("批量解析错误:", error);
  1275. this.$message.error("批量解析过程中出错误");
  1276. })
  1277. .finally(() => {
  1278. this.loading = false;
  1279. });
  1280. })
  1281. .catch(() => {
  1282. // 取消操作
  1283. });
  1284. },
  1285. /* 批量删除 */
  1286. batchDelect() {
  1287. const selectedIds = this.selectedRows.map((row) => row.id);
  1288. if (selectedIds.length === 0) {
  1289. this.$message.warning("请选择要删除的文件");
  1290. return;
  1291. }
  1292. this.$confirm("确定要批量删除选中的文件吗?", "提示", {
  1293. confirmButtonText: "确定",
  1294. cancelButtonText: "取消",
  1295. type: "warning",
  1296. })
  1297. .then(() => {
  1298. this.loading = true;
  1299. delDocumentList({ document_ids: JSON.stringify(selectedIds) })
  1300. .then((res) => {
  1301. if (res.status === 200) {
  1302. this.$message.success("批量删除已完成");
  1303. this.search();
  1304. } else {
  1305. this.$message.error("批量删除请求失败");
  1306. }
  1307. })
  1308. .catch((error) => {
  1309. /* console.error("批量删除错误:", error);
  1310. this.$message.error("批量删除过程中出现错误"); */
  1311. })
  1312. .finally(() => {
  1313. this.loading = false;
  1314. });
  1315. })
  1316. .catch(() => {
  1317. // 取消操作
  1318. });
  1319. },
  1320. updateAnalysisStatus(ids) {
  1321. this.dataList.forEach((row) => {
  1322. if (ids.includes(row.id)) {
  1323. row.run = 1; // 设置状态为"解析中"
  1324. this.startAnalysisStatusChecker(row);
  1325. }
  1326. });
  1327. },
  1328. clearSelection() {
  1329. this.$refs.dataTable.clearSelection();
  1330. this.selectedRows = [];
  1331. },
  1332. /* 跳转页面 */
  1333. getName(e) {
  1334. /* this.$router.push({
  1335. path: "/knowledge/category/infoList",
  1336. query: { id: e.id, type: e.type },
  1337. }); */
  1338. let _this = this;
  1339. let a = document.createElement("a");
  1340. a.href = `#/infoList?id=${e.id}&type=${e.type.toLowerCase()}`; // 使用 hash
  1341. a.target = "_blank";
  1342. a.click();
  1343. },
  1344. /*终止 */
  1345. endAnalysis() {
  1346. endClear().then((res) => {
  1347. if (res.status !== 200) return;
  1348. this.search();
  1349. });
  1350. },
  1351. /* 解析 */
  1352. analysis(row) {
  1353. if (row.run == 1 || row.run == 5 || row.run == 6) return;
  1354. // Set the status to "解析中" (1) immediately
  1355. this.$set(row, "run", 1);
  1356. const analysisConfig = {
  1357. document_ids: JSON.stringify([row.id]),
  1358. start_page: row.start_page ?? 0, // 使用空值合并运算符
  1359. end_page: row.end_page ?? 1000, // 使用空值合并运算符
  1360. max_tokens: row.token_num,
  1361. ocr_params: {
  1362. language: this.analForm.language, //en,zh
  1363. max_retries: 3,
  1364. timeout: [10, 180],
  1365. },
  1366. };
  1367. analysisPro(analysisConfig)
  1368. .then((res) => {
  1369. if (res.status === 200) {
  1370. this.$set(row, "progress", 0);
  1371. // Start checking the status immediately after successful API call
  1372. setTimeout(() => {
  1373. this.startAnalysisStatusChecker(row);
  1374. }, 5000);
  1375. } else {
  1376. this.$message.error("解析请求失败");
  1377. this.$set(row, "run", 0);
  1378. }
  1379. })
  1380. .catch((error) => {
  1381. console.error("解析错误:", error);
  1382. this.$message.error("解析过程中出现错误");
  1383. this.$set(row, "run", 0);
  1384. });
  1385. },
  1386. /* 任务监控 */
  1387. startAnalysisStatusChecker(row) {
  1388. // 清除该行已存在的检查器
  1389. if (this.analysisStatusCheckers[row.id]) {
  1390. clearTimeout(this.analysisStatusCheckers[row.id]);
  1391. }
  1392. const checkStatus = () => {
  1393. resultTask(row.id)
  1394. .then((res) => {
  1395. if (res.status === 200) {
  1396. const newStatus = Number(res.data.document_status);
  1397. const newProgress = Number(res.data.progress) === 100 ? 0 : Number(res.data.progress);
  1398. const newProgressMsg = res.data.progress_msg;
  1399. // 更新状态和进度
  1400. this.$set(row, "run", newStatus);
  1401. this.$set(row, "progress", newProgress);
  1402. this.$set(row, "progress_msg", newProgressMsg);
  1403. // 根据不同状态处理
  1404. switch (newStatus) {
  1405. case 3: // 解析成功
  1406. this.$message.success("解析成功");
  1407. delete this.analysisStatusCheckers[row.id];
  1408. this.refreshCurrentList();
  1409. break;
  1410. case 4: // 解析失败
  1411. this.$message.error(newProgressMsg || "解析失败");
  1412. delete this.analysisStatusCheckers[row.id];
  1413. break;
  1414. case 1: // 解析中
  1415. case 5: // 待解析
  1416. case 6: // 待生成问答对
  1417. case 7: // 解析成功,问答对生成失败
  1418. // 设置检查间隔
  1419. const checkInterval = newStatus === 6 ? 5000 : 15000;
  1420. // 更新进度信息
  1421. if (newStatus === 6) {
  1422. this.$set(row, "progress_msg", "正在生成问答对...");
  1423. } else if (newStatus === 7) {
  1424. const progressMsg = res.data.Result?.message || "问答对生成失败";
  1425. this.$set(row, "progress_msg", progressMsg);
  1426. }
  1427. // 继续检查状态
  1428. this.analysisStatusCheckers[row.id] = setTimeout(checkStatus, checkInterval);
  1429. break;
  1430. }
  1431. } else {
  1432. this.$message.error("获取解析状态失败");
  1433. delete this.analysisStatusCheckers[row.id];
  1434. }
  1435. })
  1436. .catch((error) => {
  1437. console.error("获取解析状态错误:", error);
  1438. this.$message.error("获取解析状态时出现错误");
  1439. delete this.analysisStatusCheckers[row.id];
  1440. });
  1441. };
  1442. // 立即开始检查
  1443. checkStatus();
  1444. },
  1445. // 修改 refreshCurrentList 方法
  1446. refreshCurrentList() {
  1447. // 保存当前页码和每页显示数量
  1448. const currentPage = this.queryForm.page;
  1449. const currentPageSize = this.queryForm.pageSize;
  1450. // 重新获取数据
  1451. return this.search()
  1452. .then(() => {
  1453. // 恢复之前的页码和每页显示数量
  1454. this.queryForm.page = currentPage;
  1455. this.queryForm.pageSize = currentPageSize;
  1456. })
  1457. .catch((error) => {
  1458. console.error("刷新列表时出错:", error);
  1459. this.$message.error("刷新列表失败,请稍后重试");
  1460. });
  1461. },
  1462. handleAnalClose() {
  1463. this.anaDialogVisible = false;
  1464. },
  1465. /* 确认 */
  1466. submitAnal() {
  1467. this.dataList.map((el) => {
  1468. if (this.analForm.id == el.id) {
  1469. el.language = this.analForm.language;
  1470. el.start_page = this.analForm.start_page;
  1471. el.end_page = this.analForm.end_page;
  1472. el.token_num = this.analForm.token_num;
  1473. }
  1474. });
  1475. this.anaDialogVisible = false;
  1476. },
  1477. /* 解析方法 */
  1478. Analytical(e) {
  1479. this.analForm.id = e.id;
  1480. this.analForm.token_num = e.token_num;
  1481. this.anaDialogVisible = true;
  1482. },
  1483. editWidth() {
  1484. if (this.allowDelete && this.allowEdit) {
  1485. return 200;
  1486. }
  1487. if (this.allowDelete || this.allowEdit) {
  1488. return 120;
  1489. }
  1490. return 100;
  1491. },
  1492. onFocusVal(e) {
  1493. let _this = this;
  1494. _this.currentDataId = e.target.dataset.id;
  1495. },
  1496. onChangeVal(e) {
  1497. let _this = this;
  1498. let par = {
  1499. id: _this.currentDataId,
  1500. val: e,
  1501. };
  1502. },
  1503. btnDelete(e) {
  1504. let _this = this;
  1505. let par = {
  1506. document_id: e,
  1507. };
  1508. _this
  1509. .$confirm("您是否确认删除该记录?", "提示", {
  1510. confirmButtonText: "确认",
  1511. cancelButtonText: "取消",
  1512. type: "warning",
  1513. })
  1514. .then((res) => {
  1515. deleteFile(par).then((res) => {
  1516. if (res.status !== 200) return;
  1517. _this.$message.success(res.data);
  1518. _this.search();
  1519. });
  1520. })
  1521. .catch(() => {});
  1522. },
  1523. searchData() {
  1524. let _this = this;
  1525. _this.dialogVisible = false;
  1526. _this.search();
  1527. },
  1528. /* 关闭 */
  1529. handleClose(done) {
  1530. done();
  1531. },
  1532. /* 修改文件名 */
  1533. btnEdit(e) {
  1534. this.wordForm.new_name = e.name;
  1535. this.wordForm.document_id = e.id;
  1536. this.dialogVisible = true;
  1537. },
  1538. /* 提交 */
  1539. submitBuck() {
  1540. this.$refs.wordRef.validate((valid) => {
  1541. if (valid) {
  1542. renameFile(this.wordForm).then((res) => {
  1543. if (res.status !== 200) return;
  1544. this.search();
  1545. this.dialogVisible = false;
  1546. this.wordForm = {
  1547. new_name: "",
  1548. document_id: "",
  1549. };
  1550. });
  1551. } else {
  1552. this.$message.error("请填写文档名称");
  1553. }
  1554. });
  1555. },
  1556. //下载
  1557. btnDown(e) {
  1558. // Set downloading flag for this specific row
  1559. this.$set(e, "downloading", true);
  1560. nameGetUrl({
  1561. document_id: e.id,
  1562. })
  1563. .then((res) => {
  1564. if (res.status !== 200) {
  1565. this.$message.error("获取下载链接失败");
  1566. return;
  1567. }
  1568. return fetch(res.data.url);
  1569. })
  1570. .then((response) => response.blob())
  1571. .then((blob) => {
  1572. const url = window.URL.createObjectURL(blob);
  1573. const link = document.createElement("a");
  1574. link.href = url;
  1575. link.download = e.name;
  1576. document.body.appendChild(link);
  1577. link.click();
  1578. setTimeout(() => {
  1579. document.body.removeChild(link);
  1580. window.URL.revokeObjectURL(url);
  1581. }, 100);
  1582. })
  1583. .catch((error) => {
  1584. console.error("下载出错:", error);
  1585. this.$message.error("下载失败,请稍后重试");
  1586. })
  1587. .finally(() => {
  1588. // Reset downloading flag when done
  1589. this.$set(e, "downloading", false);
  1590. });
  1591. },
  1592. handleClose() {
  1593. let _this = this;
  1594. _this.currentData = {};
  1595. _this.dialogVisible = false;
  1596. },
  1597. //搜索
  1598. search() {
  1599. let _this = this;
  1600. _this.loading = true;
  1601. return new Promise((resolve, reject) => {
  1602. getBucketContents(_this.queryForm)
  1603. .then((res) => {
  1604. if (res.status !== 200) {
  1605. reject(new Error("Invalid response status"));
  1606. return;
  1607. }
  1608. _this.dataList = res.data.documents.map((el) => ({
  1609. ...el,
  1610. token_num: 512,
  1611. start_page: _this.analForm.start_page,
  1612. end_page: _this.analForm.end_page,
  1613. }));
  1614. // 获取失败状态的文档的失败原因
  1615. const failedDocs = _this.dataList.filter(
  1616. (doc) => Number(doc.run) === 4
  1617. );
  1618. if (failedDocs.length > 0) {
  1619. Promise.all(
  1620. failedDocs.map((doc) =>
  1621. resultTask(doc.id)
  1622. .then((res) => {
  1623. if (res.status === 200) {
  1624. // 找到对应的文档并更新其 progress_msg
  1625. const docIndex = _this.dataList.findIndex(
  1626. (item) => item.id === doc.id
  1627. );
  1628. if (docIndex !== -1) {
  1629. _this.$set(
  1630. _this.dataList[docIndex],
  1631. "progress_msg",
  1632. res.data.progress_msg
  1633. );
  1634. }
  1635. }
  1636. })
  1637. .catch((error) => {
  1638. console.error(
  1639. `获取文档 ${doc.id} 的失败原因时出错:`,
  1640. error
  1641. );
  1642. })
  1643. )
  1644. );
  1645. }
  1646. _this.pageTotal = res.data.pagination.total_count;
  1647. _this.recordCount = res.data.pagination.total_count;
  1648. _this.queryForm.pageSize = res.data.pagination.total_size;
  1649. _this.loading = false;
  1650. // Initialize the analysis status for each row
  1651. _this.dataList.forEach((row) => {
  1652. if (row.run == 1 || row.run == 5 || row.run == 6) {
  1653. _this.startAnalysisStatusChecker(row);
  1654. }
  1655. });
  1656. resolve();
  1657. })
  1658. .catch((error) => {
  1659. _this.loading = false;
  1660. reject(error);
  1661. });
  1662. });
  1663. },
  1664. // 处理每页显示数量变化
  1665. handleSizeChange(val) {
  1666. this.queryForm.pageSize = val;
  1667. this.queryForm.page = 1;
  1668. this.search().then(() => {
  1669. // 恢复选中状态
  1670. this.$nextTick(() => {
  1671. this.dataList.forEach((row) => {
  1672. if (this.pageSelected.has(row.id)) {
  1673. this.$refs.dataTable.toggleRowSelection(row, true);
  1674. }
  1675. });
  1676. });
  1677. });
  1678. },
  1679. // 添加格式化方法
  1680. percentFormat(percentage) {
  1681. return `${percentage}%`;
  1682. },
  1683. },
  1684. };
  1685. </script>
  1686. <style lang="scss" scoped>
  1687. .project-search {
  1688. display: flex;
  1689. height: calc(100vh - 100px); // 假设顶部导栏高度为60px
  1690. background-color: #f9fafb;
  1691. .custom-tree-node {
  1692. width: 100%;
  1693. display: flex;
  1694. align-items: center;
  1695. justify-content: space-between;
  1696. padding: 8px 0;
  1697. position: relative;
  1698. .el-icon-folder {
  1699. color: #409eff;
  1700. margin-right: 8px;
  1701. }
  1702. .file-count {
  1703. color: #909399;
  1704. font-size: 12px;
  1705. padding: 2px 6px;
  1706. border-radius: 10px;
  1707. transition: opacity 0.3s;
  1708. }
  1709. .file-counts {
  1710. color: #909399;
  1711. font-size: 12px;
  1712. padding: 2px 6px;
  1713. border-radius: 10px;
  1714. transition: opacity 0.3s;
  1715. }
  1716. .folder-actions {
  1717. position: absolute;
  1718. right: 0;
  1719. top: 50%;
  1720. transform: translateY(-50%);
  1721. display: none;
  1722. padding-left: 10px;
  1723. i {
  1724. font-size: 16px;
  1725. margin-left: 8px;
  1726. color: #606266;
  1727. cursor: pointer;
  1728. &:hover {
  1729. color: #409eff;
  1730. }
  1731. }
  1732. }
  1733. &:hover {
  1734. .file-count {
  1735. opacity: 0;
  1736. }
  1737. .folder-actions {
  1738. display: inline-block;
  1739. }
  1740. }
  1741. }
  1742. .el-tree-node__content:hover {
  1743. background-color: #f5f7fa;
  1744. }
  1745. .el-tree-node__content:hover {
  1746. background-color: #f5f7fa;
  1747. }
  1748. .file-categories {
  1749. flex: 0 0 280px;
  1750. padding: 20px;
  1751. background-color: #ffffff;
  1752. box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
  1753. overflow-y: auto;
  1754. .el-button {
  1755. width: 100%;
  1756. margin-bottom: 20px;
  1757. }
  1758. .el-tree {
  1759. background-color: transparent;
  1760. }
  1761. }
  1762. .right {
  1763. flex: 1;
  1764. padding: 20px;
  1765. overflow-y: auto;
  1766. min-width: 0;
  1767. }
  1768. .custom-tree-node {
  1769. font-size: 14px;
  1770. width: 100%;
  1771. display: flex;
  1772. align-items: center;
  1773. justify-content: space-between;
  1774. padding: 8px 0;
  1775. .el-icon-folder {
  1776. color: #409eff;
  1777. margin-right: 8px;
  1778. }
  1779. .file-count {
  1780. color: #909399;
  1781. font-size: 12px;
  1782. background-color: #f0f2f5;
  1783. padding: 2px 6px;
  1784. border-radius: 10px;
  1785. }
  1786. .tree_title {
  1787. display: flex;
  1788. align-items: center;
  1789. width: 140px;
  1790. .folder-name {
  1791. white-space: nowrap;
  1792. overflow: hidden;
  1793. text-overflow: ellipsis;
  1794. max-width: calc(100% - 24px); // 减去图标的宽度
  1795. }
  1796. }
  1797. }
  1798. }
  1799. .el-tree {
  1800. margin-left: 10px;
  1801. }
  1802. ::v-deep .el-tree-node__expand-icon {
  1803. display: none;
  1804. }
  1805. ::v-deep .el-tag {
  1806. display: inline-flex;
  1807. align-items: center;
  1808. justify-content: center;
  1809. height: auto;
  1810. padding: 5px 10px;
  1811. }
  1812. .batch-actions {
  1813. display: flex;
  1814. gap: 10px;
  1815. .el-dropdown {
  1816. /* margin-right: auto; */
  1817. }
  1818. .icons {
  1819. width: 15px;
  1820. height: 15px;
  1821. vertical-align: middle;
  1822. margin-right: 4px;
  1823. }
  1824. }
  1825. ::v-deep .el-dropdown-menu__item {
  1826. display: flex;
  1827. align-items: center;
  1828. padding: 8px 16px;
  1829. .icons {
  1830. width: 14px;
  1831. height: 14px;
  1832. margin-right: 8px;
  1833. }
  1834. }
  1835. @import "./components/dataList.scss";
  1836. .status-wrapper {
  1837. display: inline-flex;
  1838. align-items: center;
  1839. justify-content: center;
  1840. transition: none !important;
  1841. }
  1842. .progress-wrapper {
  1843. display: inline-flex;
  1844. align-items: center;
  1845. justify-content: center;
  1846. padding: 2px;
  1847. transition: none !important;
  1848. ::v-deep .el-progress {
  1849. line-height: 1;
  1850. transition: none !important;
  1851. .el-progress__text {
  1852. font-size: 12px !important;
  1853. color: inherit;
  1854. transition: none !important;
  1855. }
  1856. .el-progress-circle {
  1857. margin: 0;
  1858. transition: none !important;
  1859. }
  1860. // 禁用 SVG 动画
  1861. svg {
  1862. transition: none !important;
  1863. }
  1864. // 禁用进度条路径动画
  1865. .el-progress-circle__track,
  1866. .el-progress-circle__path {
  1867. transition: none !important;
  1868. }
  1869. }
  1870. }
  1871. // 添加选择提示区域样式
  1872. .selection-control {
  1873. margin-bottom: 10px;
  1874. .el-alert {
  1875. padding: 8px 16px;
  1876. }
  1877. }
  1878. // 添加以下样式来处理表格响应式布局
  1879. ::v-deep .el-table {
  1880. width: 100% !important;
  1881. // 设置表格在小屏幕时的行为
  1882. &__body-wrapper {
  1883. overflow-x: auto !important;
  1884. }
  1885. // 确保表格单元格内容在空间不足时正确换行
  1886. .cell {
  1887. white-space: nowrap;
  1888. overflow: hidden;
  1889. text-overflow: ellipsis;
  1890. }
  1891. // 文件名称列允许换行显示
  1892. .el-table__row td:nth-child(3) .cell {
  1893. white-space: normal;
  1894. word-break: break-word;
  1895. }
  1896. }
  1897. // 确保分页区域不会造成横向滚动
  1898. .page-info {
  1899. margin-top: 20px;
  1900. display: flex;
  1901. justify-content: flex-end;
  1902. flex-wrap: wrap;
  1903. }
  1904. /* 问答对弹窗样式 */
  1905. .qa-dialog-content {
  1906. padding: 0;
  1907. }
  1908. .qa-dialog-header {
  1909. margin-bottom: 20px;
  1910. padding-bottom: 12px;
  1911. border-bottom: 1px solid #e8e8e8;
  1912. display: flex;
  1913. gap: 8px;
  1914. align-items: center;
  1915. }
  1916. .qa-content {
  1917. min-height: 300px;
  1918. max-height: 500px;
  1919. overflow-y: auto;
  1920. }
  1921. .empty-state {
  1922. text-align: center;
  1923. padding: 60px 0;
  1924. color: #999;
  1925. }
  1926. .empty-state i {
  1927. font-size: 48px;
  1928. color: #ddd;
  1929. margin-bottom: 16px;
  1930. display: block;
  1931. }
  1932. .empty-state p {
  1933. margin: 0;
  1934. font-size: 16px;
  1935. }
  1936. .qa-list {
  1937. space-y: 20px;
  1938. }
  1939. .qa-item {
  1940. background: #f8f9fa;
  1941. border-radius: 8px;
  1942. padding: 20px;
  1943. margin-bottom: 16px;
  1944. border-left: 4px solid #409eff;
  1945. transition: all 0.3s ease;
  1946. }
  1947. .qa-item:hover {
  1948. box-shadow: 0 2px 12px rgba(64, 158, 255, 0.1);
  1949. }
  1950. .qa-question, .qa-answer {
  1951. display: flex;
  1952. margin-bottom: 12px;
  1953. align-items: flex-start;
  1954. }
  1955. .qa-answer {
  1956. margin-bottom: 16px;
  1957. }
  1958. .qa-label {
  1959. flex-shrink: 0;
  1960. width: 40px;
  1961. font-weight: bold;
  1962. color: #409eff;
  1963. margin-right: 8px;
  1964. }
  1965. .qa-question .qa-label {
  1966. color: #e6a23c;
  1967. }
  1968. .qa-text {
  1969. flex: 1;
  1970. line-height: 1.6;
  1971. color: #333;
  1972. word-break: break-word;
  1973. }
  1974. .qa-meta {
  1975. display: flex;
  1976. gap: 8px;
  1977. padding-top: 8px;
  1978. border-top: 1px solid #eee;
  1979. }
  1980. </style>