style: 统一文件夹图标样式并修复缩进问题
更新前端文件夹相关图标为更一致的填充样式 修复后端Python代码中的缩进不一致问题
This commit is contained in:
parent
192da56fca
commit
295d019d16
@ -398,12 +398,12 @@ async def delete_document(db_id: str, doc_id: str, current_user: User = Depends(
|
|||||||
logger.debug(f"DELETE document {doc_id} info in {db_id}")
|
logger.debug(f"DELETE document {doc_id} info in {db_id}")
|
||||||
try:
|
try:
|
||||||
file_meta_info = await knowledge_base.get_file_basic_info(db_id, doc_id)
|
file_meta_info = await knowledge_base.get_file_basic_info(db_id, doc_id)
|
||||||
|
|
||||||
# Check if it is a folder
|
# Check if it is a folder
|
||||||
is_folder = file_meta_info.get("meta", {}).get("is_folder", False)
|
is_folder = file_meta_info.get("meta", {}).get("is_folder", False)
|
||||||
if is_folder:
|
if is_folder:
|
||||||
await knowledge_base.delete_folder(db_id, doc_id)
|
await knowledge_base.delete_folder(db_id, doc_id)
|
||||||
return {"message": "文件夹删除成功"}
|
return {"message": "文件夹删除成功"}
|
||||||
|
|
||||||
file_name = file_meta_info.get("meta", {}).get("filename")
|
file_name = file_meta_info.get("meta", {}).get("filename")
|
||||||
|
|
||||||
@ -1159,11 +1159,11 @@ async def get_sample_questions(db_id: str, current_user: User = Depends(get_admi
|
|||||||
|
|
||||||
@knowledge.post("/databases/{db_id}/folders")
|
@knowledge.post("/databases/{db_id}/folders")
|
||||||
async def create_folder(
|
async def create_folder(
|
||||||
db_id: str,
|
db_id: str,
|
||||||
folder_name: str = Body(..., embed=True),
|
folder_name: str = Body(..., embed=True),
|
||||||
parent_id: str | None = Body(None, embed=True),
|
parent_id: str | None = Body(None, embed=True),
|
||||||
current_user: User = Depends(get_admin_user)
|
current_user: User = Depends(get_admin_user),
|
||||||
):
|
):
|
||||||
"""创建文件夹"""
|
"""创建文件夹"""
|
||||||
try:
|
try:
|
||||||
return await knowledge_base.create_folder(db_id, folder_name, parent_id)
|
return await knowledge_base.create_folder(db_id, folder_name, parent_id)
|
||||||
@ -1171,12 +1171,13 @@ async def create_folder(
|
|||||||
logger.error(f"创建文件夹失败 {e}, {traceback.format_exc()}")
|
logger.error(f"创建文件夹失败 {e}, {traceback.format_exc()}")
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
@knowledge.put("/databases/{db_id}/documents/{doc_id}/move")
|
@knowledge.put("/databases/{db_id}/documents/{doc_id}/move")
|
||||||
async def move_document(
|
async def move_document(
|
||||||
db_id: str,
|
db_id: str,
|
||||||
doc_id: str,
|
doc_id: str,
|
||||||
new_parent_id: str | None = Body(..., embed=True),
|
new_parent_id: str | None = Body(..., embed=True),
|
||||||
current_user: User = Depends(get_admin_user)
|
current_user: User = Depends(get_admin_user),
|
||||||
):
|
):
|
||||||
"""移动文件或文件夹"""
|
"""移动文件或文件夹"""
|
||||||
logger.debug(f"Move document {doc_id} to {new_parent_id} in {db_id}")
|
logger.debug(f"Move document {doc_id} to {new_parent_id} in {db_id}")
|
||||||
|
|||||||
@ -213,8 +213,9 @@ class KnowledgeBase(ABC):
|
|||||||
def create_folder(self, db_id: str, folder_name: str, parent_id: str | None = None) -> dict:
|
def create_folder(self, db_id: str, folder_name: str, parent_id: str | None = None) -> dict:
|
||||||
"""Create a folder in the database."""
|
"""Create a folder in the database."""
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
folder_id = f"folder-{uuid.uuid4()}"
|
folder_id = f"folder-{uuid.uuid4()}"
|
||||||
|
|
||||||
self.files_meta[folder_id] = {
|
self.files_meta[folder_id] = {
|
||||||
"file_id": folder_id,
|
"file_id": folder_id,
|
||||||
"filename": folder_name,
|
"filename": folder_name,
|
||||||
@ -224,7 +225,7 @@ class KnowledgeBase(ABC):
|
|||||||
"created_at": utc_isoformat(),
|
"created_at": utc_isoformat(),
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"path": folder_name,
|
"path": folder_name,
|
||||||
"file_type": "folder"
|
"file_type": "folder",
|
||||||
}
|
}
|
||||||
self._save_metadata()
|
self._save_metadata()
|
||||||
return self.files_meta[folder_id]
|
return self.files_meta[folder_id]
|
||||||
@ -319,16 +320,16 @@ class KnowledgeBase(ABC):
|
|||||||
if file_info.get("database_id") == db_id:
|
if file_info.get("database_id") == db_id:
|
||||||
created_at = self._normalize_timestamp(file_info.get("created_at"))
|
created_at = self._normalize_timestamp(file_info.get("created_at"))
|
||||||
db_files[file_id] = {
|
db_files[file_id] = {
|
||||||
"file_id": file_id,
|
"file_id": file_id,
|
||||||
"filename": file_info.get("filename", ""),
|
"filename": file_info.get("filename", ""),
|
||||||
"path": file_info.get("path", ""),
|
"path": file_info.get("path", ""),
|
||||||
"type": file_info.get("file_type", ""),
|
"type": file_info.get("file_type", ""),
|
||||||
"status": file_info.get("status", "done"),
|
"status": file_info.get("status", "done"),
|
||||||
"created_at": created_at,
|
"created_at": created_at,
|
||||||
"processing_params": file_info.get("processing_params", None),
|
"processing_params": file_info.get("processing_params", None),
|
||||||
"is_folder": file_info.get("is_folder", False),
|
"is_folder": file_info.get("is_folder", False),
|
||||||
"parent_id": file_info.get("parent_id", None),
|
"parent_id": file_info.get("parent_id", None),
|
||||||
}
|
}
|
||||||
|
|
||||||
# 按创建时间倒序排序文件列表
|
# 按创建时间倒序排序文件列表
|
||||||
sorted_files = dict(
|
sorted_files = dict(
|
||||||
@ -465,24 +466,25 @@ class KnowledgeBase(ABC):
|
|||||||
async def delete_folder(self, db_id: str, folder_id: str) -> None:
|
async def delete_folder(self, db_id: str, folder_id: str) -> None:
|
||||||
"""
|
"""
|
||||||
Recursively delete a folder and its content.
|
Recursively delete a folder and its content.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
db_id: Database ID
|
db_id: Database ID
|
||||||
folder_id: Folder ID to delete
|
folder_id: Folder ID to delete
|
||||||
"""
|
"""
|
||||||
# Find all children
|
# Find all children
|
||||||
children = [
|
children = [
|
||||||
fid for fid, meta in self.files_meta.items()
|
fid
|
||||||
|
for fid, meta in self.files_meta.items()
|
||||||
if meta.get("database_id") == db_id and meta.get("parent_id") == folder_id
|
if meta.get("database_id") == db_id and meta.get("parent_id") == folder_id
|
||||||
]
|
]
|
||||||
|
|
||||||
for child_id in children:
|
for child_id in children:
|
||||||
child_meta = self.files_meta.get(child_id)
|
child_meta = self.files_meta.get(child_id)
|
||||||
if child_meta and child_meta.get("is_folder"):
|
if child_meta and child_meta.get("is_folder"):
|
||||||
await self.delete_folder(db_id, child_id)
|
await self.delete_folder(db_id, child_id)
|
||||||
else:
|
else:
|
||||||
await self.delete_file(db_id, child_id)
|
await self.delete_file(db_id, child_id)
|
||||||
|
|
||||||
# Delete the folder itself
|
# Delete the folder itself
|
||||||
# We call delete_file which should handle the actual removal.
|
# We call delete_file which should handle the actual removal.
|
||||||
# Implementations should ensure they handle folder deletion gracefully (e.g. skip vector deletion)
|
# Implementations should ensure they handle folder deletion gracefully (e.g. skip vector deletion)
|
||||||
@ -512,13 +514,13 @@ class KnowledgeBase(ABC):
|
|||||||
# Check if new_parent_id is a child of file_id (or is file_id itself)
|
# Check if new_parent_id is a child of file_id (or is file_id itself)
|
||||||
if new_parent_id == file_id:
|
if new_parent_id == file_id:
|
||||||
raise ValueError("Cannot move a folder into itself")
|
raise ValueError("Cannot move a folder into itself")
|
||||||
|
|
||||||
# Walk up the tree from new_parent_id
|
# Walk up the tree from new_parent_id
|
||||||
current = new_parent_id
|
current = new_parent_id
|
||||||
while current:
|
while current:
|
||||||
parent_meta = self.files_meta.get(current)
|
parent_meta = self.files_meta.get(current)
|
||||||
if not parent_meta:
|
if not parent_meta:
|
||||||
break # Should not happen if integrity is maintained
|
break # Should not happen if integrity is maintained
|
||||||
if current == file_id:
|
if current == file_id:
|
||||||
raise ValueError("Cannot move a folder into its own subfolder")
|
raise ValueError("Cannot move a folder into its own subfolder")
|
||||||
current = parent_meta.get("parent_id")
|
current = parent_meta.get("parent_id")
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
type="secondary"
|
type="secondary"
|
||||||
@click="showCreateFolderModal"
|
@click="showCreateFolderModal"
|
||||||
:loading="refreshing"
|
:loading="refreshing"
|
||||||
:icon="h(FolderOutlined)"
|
:icon="h(FolderAddOutlined)"
|
||||||
style="margin-left: 8px;"
|
style="margin-left: 8px;"
|
||||||
>新建文件夹</a-button>
|
>新建文件夹</a-button>
|
||||||
</div>
|
</div>
|
||||||
@ -141,8 +141,8 @@
|
|||||||
<template #bodyCell="{ column, text, record }">
|
<template #bodyCell="{ column, text, record }">
|
||||||
<div v-if="column.key === 'filename'">
|
<div v-if="column.key === 'filename'">
|
||||||
<template v-if="record.is_folder">
|
<template v-if="record.is_folder">
|
||||||
<span class="folder-row" style="padding-left: 0; color: var(--gray-900); font-weight: 500; cursor: pointer;" @click="toggleExpand(record)">
|
<span class="folder-row" @click="toggleExpand(record)">
|
||||||
<component :is="expandedRowKeys.includes(record.file_id) ? h(FolderOpenOutlined) : h(FolderOutlined)" style="margin-right: 6px; color: #ffb800;" />
|
<component :is="expandedRowKeys.includes(record.file_id) ? h(FolderOpenFilled) : h(FolderFilled)" style="margin-right: 12px; color: #ffb800;" />
|
||||||
{{ record.filename }}
|
{{ record.filename }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@ -173,7 +173,7 @@
|
|||||||
<div class="file-action-list">
|
<div class="file-action-list">
|
||||||
<template v-if="record.is_folder">
|
<template v-if="record.is_folder">
|
||||||
<a-button type="text" block @click="showCreateFolderModal(record.file_id)">
|
<a-button type="text" block @click="showCreateFolderModal(record.file_id)">
|
||||||
<template #icon><component :is="h(PlusOutlined)" style="width: 14px; height: 14px;" /></template>
|
<template #icon><component :is="h(FolderPlus)" style="width: 14px; height: 14px;" /></template>
|
||||||
新建子文件夹
|
新建子文件夹
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="text" block danger @click="handleDeleteFolder(record)">
|
<a-button type="text" block danger @click="handleDeleteFolder(record)">
|
||||||
@ -219,8 +219,9 @@ import {
|
|||||||
ClockCircleFilled,
|
ClockCircleFilled,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
FolderOpenOutlined,
|
FolderFilled,
|
||||||
FolderOutlined,
|
FolderAddOutlined,
|
||||||
|
FolderOpenFilled,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import {
|
import {
|
||||||
Trash2,
|
Trash2,
|
||||||
@ -228,6 +229,7 @@ import {
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
ChevronLast,
|
ChevronLast,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
|
FolderPlus,
|
||||||
} from 'lucide-vue-next';
|
} from 'lucide-vue-next';
|
||||||
|
|
||||||
const store = useDatabaseStore();
|
const store = useDatabaseStore();
|
||||||
@ -277,7 +279,7 @@ const showCreateFolderModal = (parentId = null) => {
|
|||||||
|
|
||||||
const toggleExpand = (record) => {
|
const toggleExpand = (record) => {
|
||||||
if (!record.is_folder) return;
|
if (!record.is_folder) return;
|
||||||
|
|
||||||
const index = expandedRowKeys.value.indexOf(record.file_id);
|
const index = expandedRowKeys.value.indexOf(record.file_id);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
expandedRowKeys.value.splice(index, 1);
|
expandedRowKeys.value.splice(index, 1);
|
||||||
@ -291,7 +293,7 @@ const handleCreateFolder = async () => {
|
|||||||
message.warning('请输入文件夹名称');
|
message.warning('请输入文件夹名称');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createFolderLoading.value = true;
|
createFolderLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await documentApi.createFolder(store.databaseId, newFolderName.value, currentParentId.value);
|
await documentApi.createFolder(store.databaseId, newFolderName.value, currentParentId.value);
|
||||||
@ -317,7 +319,7 @@ const customRow = (record) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.dataTransfer.setData('application/json', JSON.stringify({
|
event.dataTransfer.setData('application/json', JSON.stringify({
|
||||||
file_id: record.file_id,
|
file_id: record.file_id,
|
||||||
filename: record.filename
|
filename: record.filename
|
||||||
@ -343,14 +345,14 @@ const customRow = (record) => {
|
|||||||
onDrop: async (event) => {
|
onDrop: async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.currentTarget.classList.remove('drop-over-folder');
|
event.currentTarget.classList.remove('drop-over-folder');
|
||||||
|
|
||||||
const data = event.dataTransfer.getData('application/json');
|
const data = event.dataTransfer.getData('application/json');
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { file_id, filename } = JSON.parse(data);
|
const { file_id, filename } = JSON.parse(data);
|
||||||
if (file_id === record.file_id) return;
|
if (file_id === record.file_id) return;
|
||||||
|
|
||||||
// 确认移动
|
// 确认移动
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '移动文件',
|
title: '移动文件',
|
||||||
@ -437,7 +439,7 @@ const buildFileTree = (fileList) => {
|
|||||||
const nodeMap = new Map();
|
const nodeMap = new Map();
|
||||||
const roots = [];
|
const roots = [];
|
||||||
const processedIds = new Set();
|
const processedIds = new Set();
|
||||||
|
|
||||||
// 1. 初始化节点映射,确保 explicit folder 有 children
|
// 1. 初始化节点映射,确保 explicit folder 有 children
|
||||||
fileList.forEach(file => {
|
fileList.forEach(file => {
|
||||||
const item = { ...file, displayName: file.filename };
|
const item = { ...file, displayName: file.filename };
|
||||||
@ -495,10 +497,10 @@ const buildFileTree = (fileList) => {
|
|||||||
for (let i = 0; i < parts.length - 1; i++) {
|
for (let i = 0; i < parts.length - 1; i++) {
|
||||||
const part = parts[i];
|
const part = parts[i];
|
||||||
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
||||||
|
|
||||||
// Find existing node in currentLevel
|
// Find existing node in currentLevel
|
||||||
let node = currentLevel.find(n => n.filename === part && n.is_folder);
|
let node = currentLevel.find(n => n.filename === part && n.is_folder);
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
node = {
|
node = {
|
||||||
file_id: `folder-${currentPath}`,
|
file_id: `folder-${currentPath}`,
|
||||||
@ -513,7 +515,7 @@ const buildFileTree = (fileList) => {
|
|||||||
}
|
}
|
||||||
currentLevel = node.children;
|
currentLevel = node.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = parts[parts.length - 1];
|
const fileName = parts[parts.length - 1];
|
||||||
item.displayName = fileName;
|
item.displayName = fileName;
|
||||||
currentLevel.push(item);
|
currentLevel.push(item);
|
||||||
@ -616,7 +618,7 @@ const onSelectChange = (keys, selectedRows) => {
|
|||||||
const fileKeys = selectedRows
|
const fileKeys = selectedRows
|
||||||
.filter(row => !row.is_folder)
|
.filter(row => !row.is_folder)
|
||||||
.map(row => row.file_id);
|
.map(row => row.file_id);
|
||||||
|
|
||||||
selectedRowKeys.value = fileKeys;
|
selectedRowKeys.value = fileKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1070,7 +1072,8 @@ import ChunkParamsConfig from '@/components/ChunkParamsConfig.vue';
|
|||||||
.folder-row {
|
.folder-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--main-color);
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
@ -1081,7 +1084,7 @@ import ChunkParamsConfig from '@/components/ChunkParamsConfig.vue';
|
|||||||
outline: 2px dashed var(--main-color);
|
outline: 2px dashed var(--main-color);
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
td {
|
td {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -211,11 +211,11 @@ const folderTree = computed(() => {
|
|||||||
// 为了快速实现,我们这里简单实现一个仅用于选择的树构建
|
// 为了快速实现,我们这里简单实现一个仅用于选择的树构建
|
||||||
const files = store.database.files || {};
|
const files = store.database.files || {};
|
||||||
const fileList = Object.values(files);
|
const fileList = Object.values(files);
|
||||||
|
|
||||||
// 构建树的简化版逻辑 (只关心文件夹)
|
// 构建树的简化版逻辑 (只关心文件夹)
|
||||||
const nodeMap = new Map();
|
const nodeMap = new Map();
|
||||||
const roots = [];
|
const roots = [];
|
||||||
|
|
||||||
// 1. 初始化节点
|
// 1. 初始化节点
|
||||||
fileList.forEach(file => {
|
fileList.forEach(file => {
|
||||||
if (file.is_folder) {
|
if (file.is_folder) {
|
||||||
@ -223,7 +223,7 @@ const folderTree = computed(() => {
|
|||||||
nodeMap.set(file.file_id, item);
|
nodeMap.set(file.file_id, item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. 构建层级
|
// 2. 构建层级
|
||||||
fileList.forEach(file => {
|
fileList.forEach(file => {
|
||||||
if (file.is_folder && file.parent_id && nodeMap.has(file.parent_id)) {
|
if (file.is_folder && file.parent_id && nodeMap.has(file.parent_id)) {
|
||||||
@ -242,7 +242,7 @@ const folderTree = computed(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return roots;
|
return roots;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user