KnowledgeBaseManagement.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  1. <template>
  2. <div class="knowledge-base-management">
  3. <div class="top-bar">
  4. <div class="search">
  5. <el-input v-model="querySearch" size="large" placeholder="搜索" @keydown.enter="getKnowledgeBase"
  6. :prefix-icon="Search" />
  7. </div>
  8. <div class="menu-change">
  9. <el-radio-group v-model="arrange" size="large" fill="#5780FC">
  10. <el-radio-button label="New York" value="list">
  11. <el-icon size="24">
  12. <List />
  13. </el-icon>
  14. </el-radio-button>
  15. <el-radio-button label="Washington" value="grid">
  16. <el-icon size="24">
  17. <Grid />
  18. </el-icon>
  19. </el-radio-button>
  20. </el-radio-group>
  21. </div>
  22. </div>
  23. <main>
  24. <div class="grid-arrange" v-if="arrange === 'grid'">
  25. <div class="box">
  26. <div>
  27. <span class="icon" @click="handleCreateKB"><el-icon>
  28. <Plus />
  29. </el-icon></span>
  30. <span class="text">创建知识库</span>
  31. </div>
  32. </div>
  33. <div class="box box1" v-for="(item, index) in KBData" :key="item.id">
  34. <div class="top">
  35. <span class="icon">
  36. <i class="folder-icon" style="height: 32px;width:32px;"></i>
  37. </span>
  38. <span class="text">
  39. <div class="title" @click="toKMById(item.id)">{{ item.name }}</div>
  40. <div class="remark">
  41. <el-tag type="info" effect="plain" hit>{{ item.tags }}</el-tag>
  42. </div>
  43. </span>
  44. </div>
  45. <div class="middle">
  46. <el-text line-clamp="3">
  47. {{ item.description }}
  48. </el-text>
  49. </div>
  50. <div class="bottom">
  51. <span class="add-label">
  52. <i class="label-icon"></i>
  53. <i class="label-text">添加标签</i>
  54. <i class="circle-plus" @click="handleAddKBTags(index)"> <el-icon>
  55. <CirclePlus />
  56. </el-icon></i>
  57. </span>
  58. <el-popover class="box-item" popper-class="grid-arrange-operation" title="Title"
  59. content="Bottom Left prompts info" placement="bottom" trigger="click">
  60. <template #reference>
  61. <span class="operation">
  62. <el-icon>
  63. <MoreFilled />
  64. </el-icon>
  65. </span>
  66. </template>
  67. <template #default>
  68. <div><el-button link @click="handleEditKB(item)">编辑</el-button></div>
  69. <div><el-button link type="danger" @click="handleDeleteKB(item.id)">删除</el-button></div>
  70. </template>
  71. </el-popover>
  72. </div>
  73. </div>
  74. </div>
  75. <div class="list-arrange" v-if="arrange === 'list'">
  76. <div class="list-arrange-top"><button class="create-kb" @click="handleCreateKB">创建知识库</button></div>
  77. <el-table class="elTable" :data="KBData" style="width: 100%; min-width: 0px;">
  78. <el-table-column prop="name" label="知识库名称">
  79. <template #default="{ row }">
  80. <div class="list-name">
  81. <div class="icon-area">
  82. <i class="document-icon"></i>
  83. </div>
  84. <div class="text-area">
  85. <div class="name" @click="toKMById(row.id)">{{ row.name }}</div>
  86. <div class="remark">{{ row.description }}</div>
  87. </div>
  88. </div>
  89. </template>
  90. </el-table-column>
  91. <el-table-column prop="file_count" label="文件上数量" min-width="120" />
  92. <el-table-column prop="tags" label="知识库标签">
  93. <template #default="{ row }">
  94. <i style="white-space: nowrap;text-overflow: ellipsis;">{{ row.tags }}</i>
  95. </template>
  96. </el-table-column>
  97. <el-table-column prop="created_at" label="知识库创建时间" min-width="150" />
  98. <el-table-column prop="creator" label="创建人" />
  99. <el-table-column fixed="right" label="操作" width="200">
  100. <template #default="{ row }">
  101. <el-button link type="primary" @click="toKMById(row.id)">
  102. 查看
  103. </el-button>
  104. <el-button link type="primary" @click="handleEditKB(row)">
  105. 编辑
  106. </el-button>
  107. <el-button link type="danger" @click="handleDeleteKB(row.id)">删除</el-button>
  108. </template>
  109. </el-table-column>
  110. </el-table>
  111. </div>
  112. </main>
  113. <footer>
  114. <div class="pagination" v-if="totalPage > 1 || paginationData.defaultPageSize !== paginationData.currentPageSize">
  115. <el-pagination :current-page="paginationData.currentPage" :page-sizes="paginationData.pageSizes"
  116. :disabled="paginationData.disabled" :default-page-size="paginationData.defaultPageSize"
  117. layout="total, sizes, prev, pager, next, jumper" :total="paginationData.total" @size-change="handleSizeChange"
  118. @current-change="handleCurrentChange" />
  119. </div>
  120. </footer>
  121. <!-- 创建知识库的对话框 -->
  122. <el-dialog v-model="dialogFormVisible" class="kb-dialog" :show-close="true" title="创建知识库" width="800">
  123. <!-- <template #header>
  124. <div class="kb-dialog-header">
  125. <i @click="dialogFormVisible = false"><el-icon>
  126. <ArrowLeft />
  127. </el-icon></i>
  128. <i>创建知识库</i>
  129. </div>
  130. </template> -->
  131. <div class="form-title">定义知识库</div>
  132. <el-form :model="formData" label-width="auto" :rules="rules" ref="formRef" style="max-width: 700px">
  133. <el-form-item label="知识库名称:" label-width="150" prop="name" required label-position="left">
  134. <el-input maxlength="50" v-model.trim="formData.name" show-word-limit />
  135. <div class="name-tip">仅支持中文、英文、数字、下划线(_)、中划线(-)、英文点(.)</div>
  136. </el-form-item>
  137. <el-form-item label="知识库备注:" prop="description" label-width="150" label-position="left">
  138. <el-input maxlength="400" type="textarea" show-word-limit v-model="formData.description" resize="none"
  139. :autosize="{ minRows: 6, maxRows: 6 }" placeholder="请输入知识库内容备注说明,便于查找和管理知识库。备注不影响Agent对知识库的调用效果" />
  140. </el-form-item>
  141. <div style="margin-bottom: 20px;"></div>
  142. <el-form-item label="知识库标签:" prop="tags" label-width="150" label-position="left">
  143. <el-input-tag type="text" placeholder="按Enter回车键添加输入内容为标签" v-model="formData.tags" />
  144. </el-form-item>
  145. </el-form>
  146. <template #footer>
  147. <div class="dialog-footer">
  148. <el-button type="primary" @click="handleSubmit">
  149. 确认
  150. </el-button>
  151. <el-button @click="dialogFormVisible = false">取消</el-button>
  152. </div>
  153. </template>
  154. </el-dialog>
  155. <EditKBDialog v-model="editKBDialogData.show" :knowledgeBase="editKBDialogData.kb" @updateKB="handleUpdateKB" />
  156. </div>
  157. </template>
  158. <script setup>
  159. import { ref, getCurrentInstance, onMounted, watch, toRaw, computed } from "vue"
  160. import { useRouter } from "vue-router"
  161. import { Search } from '@element-plus/icons-vue'
  162. import { confirmDelete } from "@/utils/confirmation"
  163. import EditKBDialog from "@/components/EditKBDialog.vue"
  164. import { api } from "@/utils/config.js"
  165. import { ElMessage, ElMessageBox } from "element-plus"
  166. const router = useRouter()
  167. const { proxy } = getCurrentInstance()
  168. let editKBDialogData = ref({ show: false, kb: {} })
  169. let KBData = ref([])
  170. let arrange = ref("grid")
  171. let querySearch = ref("")
  172. let dialogFormVisible = ref(false)
  173. defineOptions({
  174. name: 'KBMComponent'
  175. });
  176. const formData = ref({
  177. name: "",
  178. description: "",
  179. tags: ""
  180. })
  181. const rules = {
  182. name: [
  183. { required: true, message: '', trigger: 'blur' },
  184. {
  185. pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_\-\. ]+$/u,
  186. message: '',
  187. trigger: 'blur'
  188. }
  189. ],
  190. }
  191. const handleSubmit = () => {
  192. const formRef = proxy.$refs['formRef']
  193. console.log('formRef', formRef)
  194. formRef.validate((valid) => {
  195. if (valid) {
  196. // console.log("通过")
  197. postKnowledgeBase(formData.value)
  198. dialogFormVisible.value = false
  199. proxy.$refs['formRef'].resetFields()
  200. } else {
  201. console.log("未通过")
  202. }
  203. })
  204. }
  205. const handleCreateKB = () => {
  206. dialogFormVisible.value = true
  207. }
  208. const handleDeleteKB = (id) => {
  209. confirmDelete({
  210. title: '要删除知识库吗?',
  211. content: '删除知识库是不可逆的。用户将无法再访问您的知识库,所有的提示配置和日志将被永久删除。'
  212. },
  213. async () => {
  214. try {
  215. const data = await proxy.$http.delete(api.knowledgeBase + `/${id}`)
  216. ElMessage({
  217. message: "知识库删除成功",
  218. type: 'success'
  219. })
  220. getKnowledgeBase()
  221. } catch (e) {
  222. console.log(e)
  223. }
  224. // console.log("删除")
  225. // let { data, code } = result
  226. // if (code === 200) {
  227. // getKnowledgeBase()
  228. // }
  229. },
  230. () => {
  231. console.log("不删除")
  232. }
  233. )
  234. }
  235. function handleAddKBTags(index) {
  236. const item = KBData.value[index]
  237. ElMessageBox.prompt('请输入标签', '添加知识库标签', {
  238. confirmButtonText: '提交',
  239. cancelButtonText: '取消',
  240. inputValue: item.tags,
  241. inputPattern:
  242. /^\S.*$/u,
  243. inputErrorMessage: '无效输入',
  244. })
  245. .then(async ({ value }) => {
  246. const data = await proxy.$http.put(api.knowledgeBase + `/${item.id}`, {
  247. "name": item.name,
  248. "description": item.description,
  249. "tags": value
  250. })
  251. ElMessage({
  252. type: 'success',
  253. message: `添加知识库标签成功!`,
  254. })
  255. KBData.value.splice(index, 1, data)
  256. // getKnowledgeBase()
  257. })
  258. .catch((e) => {
  259. console.log(e)
  260. })
  261. }
  262. const handleEditKB = (data) => {
  263. editKBDialogData.value.show = true
  264. // console.log(toRaw(data))
  265. editKBDialogData.value.kb = toRaw(data)
  266. // console.log(toRaw(data), editKBDialogData.value)
  267. }
  268. const handleUpdateKB = () => {
  269. getKnowledgeBase()
  270. }
  271. const paginationData = ref({
  272. currentPage: 1,
  273. pageSizes: [11, 50, 100, 200],
  274. disabled: false,
  275. total: 0,
  276. defaultPageSize: 11,
  277. currentPageSize: 11
  278. })
  279. const totalPage = computed(() => {
  280. return Math.ceil(paginationData.value.total / paginationData.value.currentPageSize)
  281. })
  282. const handleSizeChange = (pageSize) => {
  283. paginationData.value.currentPageSize = pageSize
  284. // console.log('handleSizeChange', pageSize)
  285. }
  286. const handleCurrentChange = (page) => {
  287. paginationData.value.currentPage = page
  288. // console.log('handleCurrentChange', page)
  289. }
  290. const getKnowledgeBase = async () => {
  291. try {
  292. const data = await proxy.$http.get(api.knowledgeBase,
  293. {
  294. params: {
  295. name: querySearch.value,
  296. pageNo: paginationData.value.currentPage,
  297. pageSize: paginationData.value.currentPageSize
  298. }
  299. })
  300. // const { data, msg, code } = result
  301. KBData.value = data.list
  302. paginationData.value.total = data.total
  303. // if (code === 200) {
  304. // KBData.value = data.list
  305. // paginationData.value.total = data.total
  306. // }
  307. } catch (e) {
  308. console.log(e)
  309. }
  310. }
  311. const postKnowledgeBase = async (formData) => {
  312. try {
  313. const data = await proxy.$http.post(api.knowledgeBase, {
  314. name: formData.name,
  315. description: formData.description,
  316. tags: formData.tags
  317. })
  318. // const { data, msg, code } = result
  319. getKnowledgeBase()
  320. // if (code === 200) {
  321. // // KBData.value = data
  322. // getKnowledgeBase()
  323. // }
  324. } catch (e) {
  325. console.log(e)
  326. }
  327. }
  328. watch(() => paginationData.value.currentPage, () => {
  329. getKnowledgeBase()
  330. }, { immediate: true })
  331. watch(() => paginationData.value.currentPageSize, () => {
  332. getKnowledgeBase()
  333. })
  334. const toKMById = (id) => {
  335. router.push({ name: 'km', params: { kbId: id } })
  336. }
  337. onMounted(() => {
  338. })
  339. </script>
  340. <style lang="less" scoped>
  341. @import "@/assets/css/common.less";
  342. // @bgColor: #F2F4F7;
  343. .knowledge-base-management {
  344. background-color: @bgColor;
  345. // width: 100%;
  346. min-height: 100%;
  347. box-sizing: border-box;
  348. .top-bar {
  349. display: flex;
  350. justify-content: space-between;
  351. background: white;
  352. // padding: 5px 10px;
  353. }
  354. .search {
  355. padding: 10px 20px;
  356. margin: 5px;
  357. background: white;
  358. :deep(.el-input__wrapper) {
  359. background-color: @bgColor;
  360. border-radius: 30px;
  361. }
  362. .el-input {
  363. width: 300px;
  364. }
  365. }
  366. .menu-change {
  367. margin: 5px;
  368. padding: 5px 20px;
  369. background-color: #fff;
  370. :deep(.el-radio-button__inner) {
  371. padding: 5px 30px;
  372. }
  373. :deep(.el-radio-button) {
  374. &:first-child {
  375. .el-radio-button__inner {
  376. border-radius: 10px 0px 0px 10px;
  377. }
  378. }
  379. &:last-child {
  380. .el-radio-button__inner {
  381. border-radius: 0px 10px 10px 0px;
  382. }
  383. }
  384. }
  385. }
  386. main {
  387. padding: 10px 10px;
  388. // width: 100%;
  389. .grid-arrange {
  390. display: grid;
  391. grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
  392. gap: 20px;
  393. padding: 0px 20px;
  394. .text {
  395. font-weight: bold;
  396. margin-left: 10px;
  397. }
  398. .box {
  399. // width: ;
  400. height: 200px;
  401. border: 1px solid #E5E8EC;
  402. border-radius: 10px;
  403. box-shadow: 0px 0px 1px gray;
  404. box-sizing: border-box;
  405. padding: 20px 10px 20px 15px;
  406. display: flex;
  407. flex-direction: column;
  408. justify-content: space-between;
  409. .top {
  410. display: flex;
  411. flex: 0 0 auto;
  412. .icon {
  413. flex: 0 0 auto;
  414. }
  415. // font-size: 13px;
  416. .text {
  417. display: flex;
  418. flex-direction: column;
  419. justify-content: space-evenly;
  420. flex: 1 1 auto;
  421. min-width: 0px;
  422. .title {
  423. &:hover {
  424. text-decoration: underline;
  425. cursor: pointer;
  426. }
  427. white-space: nowrap;
  428. display: inline-block;
  429. text-overflow: ellipsis;
  430. // max-width: 100%;
  431. // width: 100px;
  432. overflow: hidden;
  433. }
  434. .remark {
  435. color: #C7CAD2;
  436. font-weight: 100;
  437. font-size: 13px;
  438. display: inline;
  439. white-space: nowrap;
  440. text-overflow: ellipsis;
  441. max-width: 100%;
  442. overflow: hidden;
  443. }
  444. }
  445. }
  446. .middle {
  447. flex: 1 1 auto;
  448. overflow: hidden;
  449. color: #A7B8CF;
  450. .el-text {
  451. line-height: 1.5;
  452. }
  453. }
  454. .bottom {
  455. flex: 0 0 auto;
  456. display: flex;
  457. justify-content: space-between;
  458. span {
  459. display: inline-block;
  460. }
  461. .add-label {
  462. display: flex;
  463. align-items: center;
  464. gap: 0px 5px;
  465. .circle-plus {
  466. &:hover {
  467. color: #409EFF;
  468. }
  469. }
  470. .label-icon {
  471. width: 20px;
  472. height: 20px;
  473. // margin-right: 10px;
  474. }
  475. .label-text {
  476. vertical-align: bottom;
  477. }
  478. }
  479. .operation {
  480. padding: 10px;
  481. background: #EFEFF0;
  482. }
  483. }
  484. }
  485. .box1 {
  486. background-color: #FCFCFD;
  487. .icon {
  488. background-color: #F5F8FF;
  489. }
  490. }
  491. }
  492. .list-arrange {
  493. // width: 100%;
  494. .list-arrange-top {
  495. clear: both;
  496. min-height: 50px;
  497. background-color: #fff;
  498. padding: 10px 20px;
  499. box-sizing: border-box;
  500. .create-kb {
  501. margin-left: auto;
  502. display: inline-block;
  503. float: right;
  504. border: none;
  505. padding: 8px 15px;
  506. border-radius: 5px;
  507. font-weight: bold;
  508. color: #fff;
  509. font-size: 18px;
  510. background-color: #2468F2;
  511. }
  512. }
  513. /* el-table 宽度自适应 */
  514. .elTable {
  515. width: 100%;
  516. }
  517. .elTable :deep(.el-table__header-wrapper) table,
  518. .elTable :deep(.el-table__body-wrapper) table {
  519. width: 100% !important;
  520. }
  521. .elTable :deep(.el-table__body),
  522. .elTable :deep(.el-table__footer),
  523. .elTable :deep(.el-table__header) {
  524. table-layout: auto;
  525. }
  526. .elTable :deep(.el-table__empty-block),
  527. .elTable :deep(.el-table__body) {
  528. width: 100% !important;
  529. }
  530. :deep(.el-table__inner-wrapper) {
  531. .el-table__header-wrapper {
  532. .el-table__cell {
  533. background-color: #F2F5F9;
  534. border: 1px solid #FFFFFF;
  535. border-collapse: collapse;
  536. }
  537. }
  538. }
  539. :deep(.el-table__body) {
  540. .list-name {
  541. display: flex;
  542. align-items: center;
  543. .icon-area {
  544. flex: 0 0 auto;
  545. padding: 4px;
  546. background-color: #7733FF;
  547. border-radius: 50%;
  548. .document-icon {
  549. width: 32px;
  550. height: 32px;
  551. }
  552. }
  553. .text-area {
  554. margin-left: 10px;
  555. flex: 1 1 auto;
  556. min-width: 0px;
  557. display: flex;
  558. flex-direction: column;
  559. align-items: center;
  560. .name {
  561. width: 100%;
  562. font-weight: bold;
  563. font-size: 16px;
  564. white-space: nowrap;
  565. text-overflow: ellipsis;
  566. cursor: pointer;
  567. overflow: hidden;
  568. display: inline-block;
  569. vertical-align: middle;
  570. }
  571. .remark {
  572. width: 100%;
  573. white-space: nowrap;
  574. text-overflow: ellipsis;
  575. cursor: pointer;
  576. overflow: hidden;
  577. display: inline-block;
  578. color: #C8C9CB;
  579. vertical-align: middle;
  580. }
  581. }
  582. }
  583. }
  584. }
  585. .icon {
  586. display: inline-block;
  587. background-color: #F9FAFB;
  588. padding: 10px;
  589. }
  590. }
  591. .pagination {
  592. // text-align: right;
  593. // background-color: @bgColor;
  594. display: flex;
  595. padding: 0px 20px;
  596. :deep(.el-pagination) {
  597. background-color: #fff;
  598. // justify-content: end;
  599. margin-left: auto;
  600. padding: 10px 20px 20px;
  601. // margin: 0px 20px;
  602. .page-box {
  603. border: 1px solid #E7E8EE;
  604. border-radius: 3px;
  605. box-sizing: border-box;
  606. }
  607. .el-pager {
  608. .is-active {
  609. color: #fff;
  610. background-color: #5780FC;
  611. }
  612. .number {
  613. .page-box();
  614. margin-left: 10px;
  615. &:last-child {
  616. margin-right: 10px;
  617. }
  618. }
  619. }
  620. .btn-next,
  621. .btn-quicknext,
  622. .btn-quickprev,
  623. .btn-prev {
  624. .page-box()
  625. }
  626. .btn-quicknext,
  627. .btn-quickprev {
  628. margin-left: 10px;
  629. }
  630. }
  631. }
  632. .kb-dialog {
  633. .form-title {
  634. font-weight: bold;
  635. font-size: 16px;
  636. color: #000;
  637. padding: 20px 0px;
  638. }
  639. .el-dialog__header {
  640. .kb-dialog-header {
  641. font-weight: bold;
  642. font-size: 20px;
  643. display: flex;
  644. align-items: center;
  645. color: #2B2C38;
  646. .el-icon {
  647. margin-right: 30px;
  648. cursor: pointer;
  649. }
  650. }
  651. .el-dialog__headerbtn {
  652. display: none;
  653. }
  654. }
  655. .name-tip {
  656. color: #C7CAD2;
  657. text-align: right;
  658. // margin-left: auto;
  659. }
  660. .dialog-footer {
  661. text-align: center;
  662. padding: 50px 0px 100px;
  663. .el-button,
  664. .el-button.is-round {
  665. padding: 10px 40px;
  666. }
  667. }
  668. }
  669. }
  670. </style>
  671. <style lang="less">
  672. .grid-arrange-operation {
  673. .el-popover__title {
  674. display: none;
  675. }
  676. padding: 0px;
  677. .el-button {
  678. font-size: 16px;
  679. min-width: 0px !important;
  680. width: 50px !important;
  681. }
  682. }
  683. </style>