123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784 |
- <template>
- <div class="knowledge-base-management">
- <div class="top-bar">
- <div class="search">
- <el-input v-model="querySearch" size="large" placeholder="搜索" @keydown.enter="getKnowledgeBase"
- :prefix-icon="Search" />
- </div>
- <div class="menu-change">
- <el-radio-group v-model="arrange" size="large" fill="#5780FC">
- <el-radio-button label="New York" value="list">
- <el-icon size="24">
- <List />
- </el-icon>
- </el-radio-button>
- <el-radio-button label="Washington" value="grid">
- <el-icon size="24">
- <Grid />
- </el-icon>
- </el-radio-button>
- </el-radio-group>
- </div>
- </div>
- <main>
- <div class="grid-arrange" v-if="arrange === 'grid'">
- <div class="box">
- <div>
- <span class="icon" @click="handleCreateKB"><el-icon>
- <Plus />
- </el-icon></span>
- <span class="text">创建知识库</span>
- </div>
- </div>
- <div class="box box1" v-for="(item, index) in KBData" :key="item.id">
- <div class="top">
- <span class="icon">
- <i class="folder-icon" style="height: 32px;width:32px;"></i>
- </span>
- <span class="text">
- <div class="title" @click="toKMById(item.id)">{{ item.name }}</div>
- <div class="remark">
- <el-tag type="info" effect="plain" hit>{{ item.tags }}</el-tag>
- </div>
- </span>
- </div>
- <div class="middle">
- <el-text line-clamp="3">
- {{ item.description }}
- </el-text>
- </div>
- <div class="bottom">
- <span class="add-label">
- <i class="label-icon"></i>
- <i class="label-text">添加标签</i>
- <i class="circle-plus" @click="handleAddKBTags(index)"> <el-icon>
- <CirclePlus />
- </el-icon></i>
- </span>
- <el-popover class="box-item" popper-class="grid-arrange-operation" title="Title"
- content="Bottom Left prompts info" placement="bottom" trigger="click">
- <template #reference>
- <span class="operation">
- <el-icon>
- <MoreFilled />
- </el-icon>
- </span>
- </template>
- <template #default>
- <div><el-button link @click="handleEditKB(item)">编辑</el-button></div>
- <div><el-button link type="danger" @click="handleDeleteKB(item.id)">删除</el-button></div>
- </template>
- </el-popover>
- </div>
- </div>
- </div>
- <div class="list-arrange" v-if="arrange === 'list'">
- <div class="list-arrange-top"><button class="create-kb" @click="handleCreateKB">创建知识库</button></div>
- <el-table class="elTable" :data="KBData" style="width: 100%; min-width: 0px;">
- <el-table-column prop="name" label="知识库名称">
- <template #default="{ row }">
- <div class="list-name">
- <div class="icon-area">
- <i class="document-icon"></i>
- </div>
- <div class="text-area">
- <div class="name" @click="toKMById(row.id)">{{ row.name }}</div>
- <div class="remark">{{ row.description }}</div>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column prop="file_count" label="文件上数量" min-width="120" />
- <el-table-column prop="tags" label="知识库标签">
- <template #default="{ row }">
- <i style="white-space: nowrap;text-overflow: ellipsis;">{{ row.tags }}</i>
- </template>
- </el-table-column>
- <el-table-column prop="created_at" label="知识库创建时间" min-width="150" />
- <el-table-column prop="creator" label="创建人" />
- <el-table-column fixed="right" label="操作" width="200">
- <template #default="{ row }">
- <el-button link type="primary" @click="toKMById(row.id)">
- 查看
- </el-button>
- <el-button link type="primary" @click="handleEditKB(row)">
- 编辑
- </el-button>
- <el-button link type="danger" @click="handleDeleteKB(row.id)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </main>
- <footer>
- <div class="pagination" v-if="totalPage > 1 || paginationData.defaultPageSize !== paginationData.currentPageSize">
- <el-pagination :current-page="paginationData.currentPage" :page-sizes="paginationData.pageSizes"
- :disabled="paginationData.disabled" :default-page-size="paginationData.defaultPageSize"
- layout="total, sizes, prev, pager, next, jumper" :total="paginationData.total" @size-change="handleSizeChange"
- @current-change="handleCurrentChange" />
- </div>
- </footer>
- <!-- 创建知识库的对话框 -->
- <el-dialog v-model="dialogFormVisible" class="kb-dialog" :show-close="true" title="创建知识库" width="800">
- <!-- <template #header>
- <div class="kb-dialog-header">
- <i @click="dialogFormVisible = false"><el-icon>
- <ArrowLeft />
- </el-icon></i>
- <i>创建知识库</i>
- </div>
- </template> -->
- <div class="form-title">定义知识库</div>
- <el-form :model="formData" label-width="auto" :rules="rules" ref="formRef" style="max-width: 700px">
- <el-form-item label="知识库名称:" label-width="150" prop="name" required label-position="left">
- <el-input maxlength="50" v-model.trim="formData.name" show-word-limit />
- <div class="name-tip">仅支持中文、英文、数字、下划线(_)、中划线(-)、英文点(.)</div>
- </el-form-item>
- <el-form-item label="知识库备注:" prop="description" label-width="150" label-position="left">
- <el-input maxlength="400" type="textarea" show-word-limit v-model="formData.description" resize="none"
- :autosize="{ minRows: 6, maxRows: 6 }" placeholder="请输入知识库内容备注说明,便于查找和管理知识库。备注不影响Agent对知识库的调用效果" />
- </el-form-item>
- <div style="margin-bottom: 20px;"></div>
- <el-form-item label="知识库标签:" prop="tags" label-width="150" label-position="left">
- <el-input-tag type="text" placeholder="按Enter回车键添加输入内容为标签" v-model="formData.tags" />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">
- 确认
- </el-button>
- <el-button @click="dialogFormVisible = false">取消</el-button>
- </div>
- </template>
- </el-dialog>
- <EditKBDialog v-model="editKBDialogData.show" :knowledgeBase="editKBDialogData.kb" @updateKB="handleUpdateKB" />
- </div>
- </template>
- <script setup>
- import { ref, getCurrentInstance, onMounted, watch, toRaw, computed } from "vue"
- import { useRouter } from "vue-router"
- import { Search } from '@element-plus/icons-vue'
- import { confirmDelete } from "@/utils/confirmation"
- import EditKBDialog from "@/components/EditKBDialog.vue"
- import { api } from "@/utils/config.js"
- import { ElMessage, ElMessageBox } from "element-plus"
- const router = useRouter()
- const { proxy } = getCurrentInstance()
- let editKBDialogData = ref({ show: false, kb: {} })
- let KBData = ref([])
- let arrange = ref("grid")
- let querySearch = ref("")
- let dialogFormVisible = ref(false)
- defineOptions({
- name: 'KBMComponent'
- });
- const formData = ref({
- name: "",
- description: "",
- tags: ""
- })
- const rules = {
- name: [
- { required: true, message: '', trigger: 'blur' },
- {
- pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_\-\. ]+$/u,
- message: '',
- trigger: 'blur'
- }
- ],
- }
- const handleSubmit = () => {
- const formRef = proxy.$refs['formRef']
- console.log('formRef', formRef)
- formRef.validate((valid) => {
- if (valid) {
- // console.log("通过")
- postKnowledgeBase(formData.value)
- dialogFormVisible.value = false
- proxy.$refs['formRef'].resetFields()
- } else {
- console.log("未通过")
- }
- })
- }
- const handleCreateKB = () => {
- dialogFormVisible.value = true
- }
- const handleDeleteKB = (id) => {
- confirmDelete({
- title: '要删除知识库吗?',
- content: '删除知识库是不可逆的。用户将无法再访问您的知识库,所有的提示配置和日志将被永久删除。'
- },
- async () => {
- try {
- const data = await proxy.$http.delete(api.knowledgeBase + `/${id}`)
- ElMessage({
- message: "知识库删除成功",
- type: 'success'
- })
- getKnowledgeBase()
- } catch (e) {
- console.log(e)
- }
- // console.log("删除")
- // let { data, code } = result
- // if (code === 200) {
- // getKnowledgeBase()
- // }
- },
- () => {
- console.log("不删除")
- }
- )
- }
- function handleAddKBTags(index) {
- const item = KBData.value[index]
- ElMessageBox.prompt('请输入标签', '添加知识库标签', {
- confirmButtonText: '提交',
- cancelButtonText: '取消',
- inputValue: item.tags,
- inputPattern:
- /^\S.*$/u,
- inputErrorMessage: '无效输入',
- })
- .then(async ({ value }) => {
- const data = await proxy.$http.put(api.knowledgeBase + `/${item.id}`, {
- "name": item.name,
- "description": item.description,
- "tags": value
- })
- ElMessage({
- type: 'success',
- message: `添加知识库标签成功!`,
- })
- KBData.value.splice(index, 1, data)
- // getKnowledgeBase()
- })
- .catch((e) => {
- console.log(e)
- })
- }
- const handleEditKB = (data) => {
- editKBDialogData.value.show = true
- // console.log(toRaw(data))
- editKBDialogData.value.kb = toRaw(data)
- // console.log(toRaw(data), editKBDialogData.value)
- }
- const handleUpdateKB = () => {
- getKnowledgeBase()
- }
- const paginationData = ref({
- currentPage: 1,
- pageSizes: [11, 50, 100, 200],
- disabled: false,
- total: 0,
- defaultPageSize: 11,
- currentPageSize: 11
- })
- const totalPage = computed(() => {
- return Math.ceil(paginationData.value.total / paginationData.value.currentPageSize)
- })
- const handleSizeChange = (pageSize) => {
- paginationData.value.currentPageSize = pageSize
- // console.log('handleSizeChange', pageSize)
- }
- const handleCurrentChange = (page) => {
- paginationData.value.currentPage = page
- // console.log('handleCurrentChange', page)
- }
- const getKnowledgeBase = async () => {
- try {
- const data = await proxy.$http.get(api.knowledgeBase,
- {
- params: {
- name: querySearch.value,
- pageNo: paginationData.value.currentPage,
- pageSize: paginationData.value.currentPageSize
- }
- })
- // const { data, msg, code } = result
- KBData.value = data.list
- paginationData.value.total = data.total
- // if (code === 200) {
- // KBData.value = data.list
- // paginationData.value.total = data.total
- // }
- } catch (e) {
- console.log(e)
- }
- }
- const postKnowledgeBase = async (formData) => {
- try {
- const data = await proxy.$http.post(api.knowledgeBase, {
- name: formData.name,
- description: formData.description,
- tags: formData.tags
- })
- // const { data, msg, code } = result
- getKnowledgeBase()
- // if (code === 200) {
- // // KBData.value = data
- // getKnowledgeBase()
- // }
- } catch (e) {
- console.log(e)
- }
- }
- watch(() => paginationData.value.currentPage, () => {
- getKnowledgeBase()
- }, { immediate: true })
- watch(() => paginationData.value.currentPageSize, () => {
- getKnowledgeBase()
- })
- const toKMById = (id) => {
- router.push({ name: 'km', params: { kbId: id } })
- }
- onMounted(() => {
- })
- </script>
- <style lang="less" scoped>
- @import "@/assets/css/common.less";
- // @bgColor: #F2F4F7;
- .knowledge-base-management {
- background-color: @bgColor;
- // width: 100%;
- min-height: 100%;
- box-sizing: border-box;
- .top-bar {
- display: flex;
- justify-content: space-between;
- background: white;
- // padding: 5px 10px;
- }
- .search {
- padding: 10px 20px;
- margin: 5px;
- background: white;
- :deep(.el-input__wrapper) {
- background-color: @bgColor;
- border-radius: 30px;
- }
- .el-input {
- width: 300px;
- }
- }
- .menu-change {
- margin: 5px;
- padding: 5px 20px;
- background-color: #fff;
- :deep(.el-radio-button__inner) {
- padding: 5px 30px;
- }
- :deep(.el-radio-button) {
- &:first-child {
- .el-radio-button__inner {
- border-radius: 10px 0px 0px 10px;
- }
- }
- &:last-child {
- .el-radio-button__inner {
- border-radius: 0px 10px 10px 0px;
- }
- }
- }
- }
- main {
- padding: 10px 10px;
- // width: 100%;
- .grid-arrange {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
- gap: 20px;
- padding: 0px 20px;
- .text {
- font-weight: bold;
- margin-left: 10px;
- }
- .box {
- // width: ;
- height: 200px;
- border: 1px solid #E5E8EC;
- border-radius: 10px;
- box-shadow: 0px 0px 1px gray;
- box-sizing: border-box;
- padding: 20px 10px 20px 15px;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- .top {
- display: flex;
- flex: 0 0 auto;
- .icon {
- flex: 0 0 auto;
- }
- // font-size: 13px;
- .text {
- display: flex;
- flex-direction: column;
- justify-content: space-evenly;
- flex: 1 1 auto;
- min-width: 0px;
- .title {
- &:hover {
- text-decoration: underline;
- cursor: pointer;
- }
- white-space: nowrap;
- display: inline-block;
- text-overflow: ellipsis;
- // max-width: 100%;
- // width: 100px;
- overflow: hidden;
- }
- .remark {
- color: #C7CAD2;
- font-weight: 100;
- font-size: 13px;
- display: inline;
- white-space: nowrap;
- text-overflow: ellipsis;
- max-width: 100%;
- overflow: hidden;
- }
- }
- }
- .middle {
- flex: 1 1 auto;
- overflow: hidden;
- color: #A7B8CF;
- .el-text {
- line-height: 1.5;
- }
- }
- .bottom {
- flex: 0 0 auto;
- display: flex;
- justify-content: space-between;
- span {
- display: inline-block;
- }
- .add-label {
- display: flex;
- align-items: center;
- gap: 0px 5px;
- .circle-plus {
- &:hover {
- color: #409EFF;
- }
- }
- .label-icon {
- width: 20px;
- height: 20px;
- // margin-right: 10px;
- }
- .label-text {
- vertical-align: bottom;
- }
- }
- .operation {
- padding: 10px;
- background: #EFEFF0;
- }
- }
- }
- .box1 {
- background-color: #FCFCFD;
- .icon {
- background-color: #F5F8FF;
- }
- }
- }
- .list-arrange {
- // width: 100%;
- .list-arrange-top {
- clear: both;
- min-height: 50px;
- background-color: #fff;
- padding: 10px 20px;
- box-sizing: border-box;
- .create-kb {
- margin-left: auto;
- display: inline-block;
- float: right;
- border: none;
- padding: 8px 15px;
- border-radius: 5px;
- font-weight: bold;
- color: #fff;
- font-size: 18px;
- background-color: #2468F2;
- }
- }
- /* el-table 宽度自适应 */
- .elTable {
- width: 100%;
- }
- .elTable :deep(.el-table__header-wrapper) table,
- .elTable :deep(.el-table__body-wrapper) table {
- width: 100% !important;
- }
- .elTable :deep(.el-table__body),
- .elTable :deep(.el-table__footer),
- .elTable :deep(.el-table__header) {
- table-layout: auto;
- }
- .elTable :deep(.el-table__empty-block),
- .elTable :deep(.el-table__body) {
- width: 100% !important;
- }
- :deep(.el-table__inner-wrapper) {
- .el-table__header-wrapper {
- .el-table__cell {
- background-color: #F2F5F9;
- border: 1px solid #FFFFFF;
- border-collapse: collapse;
- }
- }
- }
- :deep(.el-table__body) {
- .list-name {
- display: flex;
- align-items: center;
- .icon-area {
- flex: 0 0 auto;
- padding: 4px;
- background-color: #7733FF;
- border-radius: 50%;
- .document-icon {
- width: 32px;
- height: 32px;
- }
- }
- .text-area {
- margin-left: 10px;
- flex: 1 1 auto;
- min-width: 0px;
- display: flex;
- flex-direction: column;
- align-items: center;
- .name {
- width: 100%;
- font-weight: bold;
- font-size: 16px;
- white-space: nowrap;
- text-overflow: ellipsis;
- cursor: pointer;
- overflow: hidden;
- display: inline-block;
- vertical-align: middle;
- }
- .remark {
- width: 100%;
- white-space: nowrap;
- text-overflow: ellipsis;
- cursor: pointer;
- overflow: hidden;
- display: inline-block;
- color: #C8C9CB;
- vertical-align: middle;
- }
- }
- }
- }
- }
- .icon {
- display: inline-block;
- background-color: #F9FAFB;
- padding: 10px;
- }
- }
- .pagination {
- // text-align: right;
- // background-color: @bgColor;
- display: flex;
- padding: 0px 20px;
- :deep(.el-pagination) {
- background-color: #fff;
- // justify-content: end;
- margin-left: auto;
- padding: 10px 20px 20px;
- // margin: 0px 20px;
- .page-box {
- border: 1px solid #E7E8EE;
- border-radius: 3px;
- box-sizing: border-box;
- }
- .el-pager {
- .is-active {
- color: #fff;
- background-color: #5780FC;
- }
- .number {
- .page-box();
- margin-left: 10px;
- &:last-child {
- margin-right: 10px;
- }
- }
- }
- .btn-next,
- .btn-quicknext,
- .btn-quickprev,
- .btn-prev {
- .page-box()
- }
- .btn-quicknext,
- .btn-quickprev {
- margin-left: 10px;
- }
- }
- }
- .kb-dialog {
- .form-title {
- font-weight: bold;
- font-size: 16px;
- color: #000;
- padding: 20px 0px;
- }
- .el-dialog__header {
- .kb-dialog-header {
- font-weight: bold;
- font-size: 20px;
- display: flex;
- align-items: center;
- color: #2B2C38;
- .el-icon {
- margin-right: 30px;
- cursor: pointer;
- }
- }
- .el-dialog__headerbtn {
- display: none;
- }
- }
- .name-tip {
- color: #C7CAD2;
- text-align: right;
- // margin-left: auto;
- }
- .dialog-footer {
- text-align: center;
- padding: 50px 0px 100px;
- .el-button,
- .el-button.is-round {
- padding: 10px 40px;
- }
- }
- }
- }
- </style>
- <style lang="less">
- .grid-arrange-operation {
- .el-popover__title {
- display: none;
- }
- padding: 0px;
- .el-button {
- font-size: 16px;
- min-width: 0px !important;
- width: 50px !important;
- }
- }
- </style>
|