feat: 优化数据库信息视图和文件删除功能

- 将删除数据库按钮移至编辑对话框的底部,增强用户交互体验。
- 改进文件上传模式选择的样式,提升可用性。
- 添加批量删除文件的处理逻辑,支持用户选择多个文件进行删除。
This commit is contained in:
Wenjie Zhang 2025-05-26 22:50:47 +08:00
parent 6dd293f740
commit 582742d016

View File

@ -18,15 +18,19 @@
<a-button type="primary" @click="showEditModal">
<EditOutlined />
</a-button>
<a-button type="primary" danger @click="deleteDatabse">
<DeleteOutlined />
</a-button>
</template>
</HeaderComponent>
<a-alert v-if="configStore.config.embed_model &&database.embed_model != configStore.config.embed_model" message="向量模型不匹配,请重新选择" type="warning" style="margin: 10px 20px;" />
<!-- 添加编辑对话框 -->
<a-modal v-model:open="editModalVisible" title="编辑知识库信息" @ok="handleEditSubmit">
<a-modal v-model:open="editModalVisible" title="编辑知识库信息">
<template #footer>
<a-button danger @click="deleteDatabse" style="margin-right: auto; margin-left: 0;">
<DeleteOutlined /> 删除数据库
</a-button>
<a-button key="back" @click="editModalVisible = false">取消</a-button>
<a-button key="submit" type="primary" :loading="loading" @click="handleEditSubmit">确定</a-button>
</template>
<a-form :model="editForm" :rules="rules" ref="editFormRef" layout="vertical">
<a-form-item label="知识库名称" name="name" required>
<a-input v-model:value="editForm.name" placeholder="请输入知识库名称" />
@ -98,14 +102,12 @@
</div>
<div class="upload-main">
<div class="source-selector">
<a-radio-group v-model:value="uploadMode" button-style="solid" style="margin-bottom: 16px;">
<a-radio-button value="file">
<FileOutlined /> 上传文件
</a-radio-button>
<a-radio-button value="url">
<LinkOutlined /> 输入网址
</a-radio-button>
</a-radio-group>
<div class="upload-mode-selector" @click="uploadMode = 'file'" :class="{ active: uploadMode === 'file' }">
<FileOutlined /> 上传文件
</div>
<div class="upload-mode-selector" @click="uploadMode = 'url'" :class="{ active: uploadMode === 'url' }">
<LinkOutlined /> 输入网址
</div>
</div>
<!-- 文件上传区域 -->
@ -172,49 +174,37 @@
getCheckboxProps: getCheckboxProps
}">
<template #bodyCell="{ column, text, record }">
<template v-if="column.key === 'filename'">
<a-tooltip :title="record.file_id" placement="right">
<a-button class="main-btn" type="link" @click="openFileDetail(record)">{{ text }}</a-button>
</a-tooltip>
</template>
<template v-else-if="column.key === 'type'">
<span :class="['span-type', text]">{{ text?.toUpperCase() }}</span>
</template>
<template v-else-if="column.key === 'status' && text === 'done'">
<CheckCircleFilled style="color: #41A317;"/>
</template>
<template v-else-if="column.key === 'status' && text === 'failed'">
<CloseCircleFilled style="color: #FF4D4F ;"/>
</template>
<template v-else-if="column.key === 'status' && text === 'processing'">
<HourglassFilled style="color: #1677FF;"/>
</template>
<template v-else-if="column.key === 'status' && text === 'waiting'">
<ClockCircleFilled style="color: #FFCD43;"/>
</template>
<template v-else-if="column.key === 'status' && text === 'pending_indexing'">
<HddOutlined style="color: #FFCD43;"/>
</template>
<template v-else-if="column.key === 'action'">
<div style="display: flex; gap: 10px;">
<a-button
v-if="record.status === 'pending_indexing'"
type="link"
@click="handleIndexFile(record.file_id)"
:loading="state.indexingFile === record.file_id"
:disabled="state.lock || state.indexingFile === record.file_id"
<a-tooltip v-if="column.key === 'filename'" :title="record.file_id" placement="left">
<a-button class="main-btn" type="link" @click="openFileDetail(record)">{{ text }}</a-button>
</a-tooltip>
<span v-else-if="column.key === 'type'" :class="['span-type', text]">{{ text?.toUpperCase() }}</span>
<CheckCircleFilled v-else-if="column.key === 'status' && text === 'done'" style="color: #41A317;"/>
<CloseCircleFilled v-else-if="column.key === 'status' && text === 'failed'" style="color: #FF4D4F ;"/>
<HourglassFilled v-else-if="column.key === 'status' && text === 'processing'" style="color: #1677FF;"/>
<ClockCircleFilled v-else-if="column.key === 'status' && text === 'waiting'" style="color: #FFCD43;"/>
<HddOutlined v-else-if="column.key === 'status' && text === 'pending_indexing'" style="color: #FFCD43;"/>
<a-tooltip v-else-if="column.key === 'created_at'" :title="record.status" placement="left">
<span>{{ formatRelativeTime(Math.round(text*1000)) }}</span>
</a-tooltip>
<div v-else-if="column.key === 'action'" style="display: flex; gap: 10px;">
<a-button
v-if="record.status === 'pending_indexing'"
type="link"
@click="handleIndexFile(record.file_id)"
:loading="state.indexingFile === record.file_id"
:disabled="state.lock || state.indexingFile === record.file_id"
>
索引
</a-button>
<a-button class="del-btn" type="link"
@click="handleDeleteFile(record.file_id)"
:disabled="state.lock || record.status === 'processing' || record.status === 'waiting' || state.indexingFile === record.file_id"
>
索引
</a-button>
<a-button class="del-btn" type="link"
@click="deleteFile(text)"
:disabled="state.lock || record.status === 'processing' || record.status === 'waiting' || state.indexingFile === record.file_id"
>
删除
</a-button>
</div>
</template>
<span v-else-if="column.key === 'created_at'">{{ formatRelativeTime(Math.round(text*1000)) }}</span>
删除
</a-button>
</div>
<span v-else>{{ text }}</span>
</template>
</a-table>
@ -625,7 +615,7 @@ const openFileDetail = (record) => {
});
} catch (error) {
console.error(error);
message.error('获取文件详情失败');
message.error('获取文件详情失败!!!!');
state.fileDetailLoading = false;
state.lock = false;
state.fileDetailModalVisible = false;
@ -682,6 +672,25 @@ const getDatabaseInfo = () => {
}
const deleteFile = (fileId) => {
state.lock = true
console.debug("deleteFile", databaseId.value, fileId)
knowledgeBaseApi.deleteFile(databaseId.value, fileId)
.then(data => {
console.log(data)
message.success(data.message || '删除成功')
getDatabaseInfo()
})
.catch(error => {
console.error(error)
message.error(error.message || '删除失败')
})
.finally(() => {
state.lock = false
})
}
const handleDeleteFile = (fileId) => {
console.log(fileId)
//
Modal.confirm({
@ -689,28 +698,65 @@ const deleteFile = (fileId) => {
content: '确定要删除该文件吗?',
okText: '确认',
cancelText: '取消',
onOk: () => {
state.lock = true
knowledgeBaseApi.deleteFile(databaseId.value, fileId)
.then(data => {
console.log(data)
message.success(data.message || '删除成功')
getDatabaseInfo()
})
.catch(error => {
console.error(error)
message.error(error.message || '删除失败')
})
.finally(() => {
state.lock = false
})
},
onOk: () => deleteFile(fileId),
onCancel: () => {
console.log('Cancel');
},
});
}
//
const handleBatchDelete = () => {
if (!canBatchDelete.value) {
message.info('没有可删除的文件');
return;
}
const files = database.value.files || {};
const fileCount = selectedRowKeys.value.length;
Modal.confirm({
title: '批量删除文件',
content: `确定要删除选中的 ${fileCount} 个文件吗?`,
okText: '确认',
cancelText: '取消',
onOk: async () => {
state.batchDeleting = true;
try {
const promises = selectedRowKeys.value
.filter(fileId => {
const file = files[fileId];
return !(file.status === 'processing' || file.status === 'waiting');
})
.map(fileId =>
deleteFile(fileId)
);
const results = await Promise.allSettled(promises);
const succeeded = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
if (succeeded > 0) {
message.success(`成功删除 ${succeeded} 个文件`);
}
if (failed > 0) {
message.error(`${failed} 个文件删除失败`);
}
selectedRowKeys.value = []; //
getDatabaseInfo(); //
} catch (error) {
console.error('批量删除出错:', error);
message.error('批量删除过程中发生错误');
} finally {
state.batchDeleting = false;
}
},
});
};
const chunkParams = ref({
chunk_size: 1000,
chunk_overlap: 200,
@ -803,55 +849,6 @@ const chunkUrls = () => {
});
};
// - ""
const addToDatabase = () => {
// if (chunkResults.value.length === 0) {
// message.error('')
// return
// }
// state.adding = true
// state.lock = true
// // API
// const fileChunks = {};
// chunkResults.value.forEach(file => {
// fileChunks[file.file_id] = file;
// });
// // add-by-chunks
// knowledgeBaseApi.addByChunks({
// db_id: databaseId.value,
// file_chunks: fileChunks
// })
// .then(data => {
// console.log(data)
// if (data.status === 'failed') {
// message.error(data.message)
// } else {
// message.success(data.message)
// fileList.value = []
// chunkResults.value = []
// activeFileKeys.value = []
// getDatabaseInfo() //
// }
// })
// .catch(error => {
// console.error(error)
// message.error(error.message)
// })
// .finally(() => {
// state.adding = false
// state.lock = false
// })
message.info("此功能已通过新的索引流程处理。文件分块后将自动出现在列表中,可单独进行索引。");
}
const addDocumentByFile = () => {
// chunkFilesaddToDatabase
console.log('此功能已被拆分为两个步骤')
}
const columns = [
// { title: 'ID', dataIndex: 'file_id', key: 'file_id' },
{ title: '文件名', dataIndex: 'filename', key: 'filename', ellipsis: true },
@ -1070,59 +1067,6 @@ const handleBatchIndex = async () => {
}
};
//
const handleBatchDelete = () => {
if (!canBatchDelete.value) {
message.info('没有可删除的文件');
return;
}
const files = database.value.files || {};
const fileCount = selectedRowKeys.value.length;
Modal.confirm({
title: '批量删除文件',
content: `确定要删除选中的 ${fileCount} 个文件吗?`,
okText: '确认',
cancelText: '取消',
onOk: async () => {
state.batchDeleting = true;
state.lock = true;
try {
const promises = selectedRowKeys.value
.filter(fileId => {
const file = files[fileId];
return !(state.lock || file.status === 'processing' || file.status === 'waiting' || state.indexingFile === file.file_id);
})
.map(fileId =>
knowledgeBaseApi.deleteFile(databaseId.value, fileId)
);
const results = await Promise.allSettled(promises);
const succeeded = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
if (succeeded > 0) {
message.success(`成功删除 ${succeeded} 个文件`);
}
if (failed > 0) {
message.error(`${failed} 个文件删除失败`);
}
selectedRowKeys.value = []; //
getDatabaseInfo(); //
} catch (error) {
console.error('批量删除出错:', error);
message.error('批量删除过程中发生错误');
} finally {
state.batchDeleting = false;
state.lock = false;
}
},
});
};
</script>
@ -1467,6 +1411,7 @@ const handleBatchDelete = () => {
background-color: var(--main-light-6);
border-radius: 8px;
border: 1px solid var(--main-light-3);
flex: 0 0 280px;
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
.chunking-params {
@ -1538,6 +1483,26 @@ const handleBatchDelete = () => {
.upload-main {
flex: 1;
.source-selector {
display: flex;
gap: 10px;
margin-bottom: 16px;
.upload-mode-selector {
cursor: pointer;
padding: 8px 16px;
border-radius: 8px;
background-color: var(--main-light-4);
border: 1px solid var(--main-light-3);
transition: all 0.2s ease;
&.active {
color: var(--main-color);
background-color: var(--main-10);
border-color: var(--main-color);
}
}
}
}
}
@ -1620,6 +1585,12 @@ const handleBatchDelete = () => {
align-items: center;
min-height: 200px;
}
.ant-modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
<style lang="less">