feat: 优化数据库信息视图和文件删除功能
- 将删除数据库按钮移至编辑对话框的底部,增强用户交互体验。 - 改进文件上传模式选择的样式,提升可用性。 - 添加批量删除文件的处理逻辑,支持用户选择多个文件进行删除。
This commit is contained in:
parent
6dd293f740
commit
582742d016
@ -18,15 +18,19 @@
|
|||||||
<a-button type="primary" @click="showEditModal">
|
<a-button type="primary" @click="showEditModal">
|
||||||
<EditOutlined />
|
<EditOutlined />
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" danger @click="deleteDatabse">
|
|
||||||
<DeleteOutlined />
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</HeaderComponent>
|
</HeaderComponent>
|
||||||
<a-alert v-if="configStore.config.embed_model &&database.embed_model != configStore.config.embed_model" message="向量模型不匹配,请重新选择" type="warning" style="margin: 10px 20px;" />
|
<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 :model="editForm" :rules="rules" ref="editFormRef" layout="vertical">
|
||||||
<a-form-item label="知识库名称" name="name" required>
|
<a-form-item label="知识库名称" name="name" required>
|
||||||
<a-input v-model:value="editForm.name" placeholder="请输入知识库名称" />
|
<a-input v-model:value="editForm.name" placeholder="请输入知识库名称" />
|
||||||
@ -98,14 +102,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="upload-main">
|
<div class="upload-main">
|
||||||
<div class="source-selector">
|
<div class="source-selector">
|
||||||
<a-radio-group v-model:value="uploadMode" button-style="solid" style="margin-bottom: 16px;">
|
<div class="upload-mode-selector" @click="uploadMode = 'file'" :class="{ active: uploadMode === 'file' }">
|
||||||
<a-radio-button value="file">
|
|
||||||
<FileOutlined /> 上传文件
|
<FileOutlined /> 上传文件
|
||||||
</a-radio-button>
|
</div>
|
||||||
<a-radio-button value="url">
|
<div class="upload-mode-selector" @click="uploadMode = 'url'" :class="{ active: uploadMode === 'url' }">
|
||||||
<LinkOutlined /> 输入网址
|
<LinkOutlined /> 输入网址
|
||||||
</a-radio-button>
|
</div>
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 文件上传区域 -->
|
<!-- 文件上传区域 -->
|
||||||
@ -172,31 +174,21 @@
|
|||||||
getCheckboxProps: getCheckboxProps
|
getCheckboxProps: getCheckboxProps
|
||||||
}">
|
}">
|
||||||
<template #bodyCell="{ column, text, record }">
|
<template #bodyCell="{ column, text, record }">
|
||||||
<template v-if="column.key === 'filename'">
|
<a-tooltip v-if="column.key === 'filename'" :title="record.file_id" placement="left">
|
||||||
<a-tooltip :title="record.file_id" placement="right">
|
|
||||||
<a-button class="main-btn" type="link" @click="openFileDetail(record)">{{ text }}</a-button>
|
<a-button class="main-btn" type="link" @click="openFileDetail(record)">{{ text }}</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
<span v-else-if="column.key === 'type'" :class="['span-type', text]">{{ text?.toUpperCase() }}</span>
|
||||||
<template v-else-if="column.key === 'type'">
|
<CheckCircleFilled v-else-if="column.key === 'status' && text === 'done'" style="color: #41A317;"/>
|
||||||
<span :class="['span-type', text]">{{ text?.toUpperCase() }}</span>
|
<CloseCircleFilled v-else-if="column.key === 'status' && text === 'failed'" style="color: #FF4D4F ;"/>
|
||||||
</template>
|
<HourglassFilled v-else-if="column.key === 'status' && text === 'processing'" style="color: #1677FF;"/>
|
||||||
<template v-else-if="column.key === 'status' && text === 'done'">
|
<ClockCircleFilled v-else-if="column.key === 'status' && text === 'waiting'" style="color: #FFCD43;"/>
|
||||||
<CheckCircleFilled style="color: #41A317;"/>
|
<HddOutlined v-else-if="column.key === 'status' && text === 'pending_indexing'" style="color: #FFCD43;"/>
|
||||||
</template>
|
|
||||||
<template v-else-if="column.key === 'status' && text === 'failed'">
|
<a-tooltip v-else-if="column.key === 'created_at'" :title="record.status" placement="left">
|
||||||
<CloseCircleFilled style="color: #FF4D4F ;"/>
|
<span>{{ formatRelativeTime(Math.round(text*1000)) }}</span>
|
||||||
</template>
|
</a-tooltip>
|
||||||
<template v-else-if="column.key === 'status' && text === 'processing'">
|
|
||||||
<HourglassFilled style="color: #1677FF;"/>
|
<div v-else-if="column.key === 'action'" style="display: flex; gap: 10px;">
|
||||||
</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
|
<a-button
|
||||||
v-if="record.status === 'pending_indexing'"
|
v-if="record.status === 'pending_indexing'"
|
||||||
type="link"
|
type="link"
|
||||||
@ -207,14 +199,12 @@
|
|||||||
索引
|
索引
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button class="del-btn" type="link"
|
<a-button class="del-btn" type="link"
|
||||||
@click="deleteFile(text)"
|
@click="handleDeleteFile(record.file_id)"
|
||||||
:disabled="state.lock || record.status === 'processing' || record.status === 'waiting' || state.indexingFile === record.file_id"
|
:disabled="state.lock || record.status === 'processing' || record.status === 'waiting' || state.indexingFile === record.file_id"
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
<span v-else-if="column.key === 'created_at'">{{ formatRelativeTime(Math.round(text*1000)) }}</span>
|
|
||||||
<span v-else>{{ text }}</span>
|
<span v-else>{{ text }}</span>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
@ -625,7 +615,7 @@ const openFileDetail = (record) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
message.error('获取文件详情失败');
|
message.error('获取文件详情失败!!!!');
|
||||||
state.fileDetailLoading = false;
|
state.fileDetailLoading = false;
|
||||||
state.lock = false;
|
state.lock = false;
|
||||||
state.fileDetailModalVisible = false;
|
state.fileDetailModalVisible = false;
|
||||||
@ -682,15 +672,8 @@ const getDatabaseInfo = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteFile = (fileId) => {
|
const deleteFile = (fileId) => {
|
||||||
console.log(fileId)
|
|
||||||
//删除提示
|
|
||||||
Modal.confirm({
|
|
||||||
title: '删除文件',
|
|
||||||
content: '确定要删除该文件吗?',
|
|
||||||
okText: '确认',
|
|
||||||
cancelText: '取消',
|
|
||||||
onOk: () => {
|
|
||||||
state.lock = true
|
state.lock = true
|
||||||
|
console.debug("deleteFile", databaseId.value, fileId)
|
||||||
knowledgeBaseApi.deleteFile(databaseId.value, fileId)
|
knowledgeBaseApi.deleteFile(databaseId.value, fileId)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
@ -704,13 +687,76 @@ const deleteFile = (fileId) => {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
state.lock = false
|
state.lock = false
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleDeleteFile = (fileId) => {
|
||||||
|
console.log(fileId)
|
||||||
|
//删除提示
|
||||||
|
Modal.confirm({
|
||||||
|
title: '删除文件',
|
||||||
|
content: '确定要删除该文件吗?',
|
||||||
|
okText: '确认',
|
||||||
|
cancelText: '取消',
|
||||||
|
onOk: () => deleteFile(fileId),
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
console.log('Cancel');
|
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({
|
const chunkParams = ref({
|
||||||
chunk_size: 1000,
|
chunk_size: 1000,
|
||||||
chunk_overlap: 200,
|
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 = () => {
|
|
||||||
// 此函数不再需要,由chunkFiles和addToDatabase替代
|
|
||||||
console.log('此功能已被拆分为两个步骤')
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
// { title: '文件ID', dataIndex: 'file_id', key: 'file_id' },
|
// { title: '文件ID', dataIndex: 'file_id', key: 'file_id' },
|
||||||
{ title: '文件名', dataIndex: 'filename', key: 'filename', ellipsis: true },
|
{ 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>
|
</script>
|
||||||
|
|
||||||
@ -1467,6 +1411,7 @@ const handleBatchDelete = () => {
|
|||||||
background-color: var(--main-light-6);
|
background-color: var(--main-light-6);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--main-light-3);
|
border: 1px solid var(--main-light-3);
|
||||||
|
flex: 0 0 280px;
|
||||||
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
.chunking-params {
|
.chunking-params {
|
||||||
@ -1538,6 +1483,26 @@ const handleBatchDelete = () => {
|
|||||||
|
|
||||||
.upload-main {
|
.upload-main {
|
||||||
flex: 1;
|
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;
|
align-items: center;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user