Toadied's picture
2312
8b383ad verified
Raw
History Blame Contribute Delete
5.99 kB
import json
from typing import Dict
from fastapi import APIRouter, Form, HTTPException
from datetime import datetime
import logging
import os
from pydantic import BaseModel
from helloAgents.tools.registry import global_registry
from helloAgents.tools.builtin.rag_tool import RAGTool
# 2. 定义请求体
class FileListRequest(BaseModel):
user_id: str
logger = logging.getLogger(__name__)
ingest_router = APIRouter()
# ------------------------------
# ⚙️ 企业级配置(和你原有一致)
# ------------------------------
MANIFEST_FILE = ".file_manifest.json"
CHUNK_SIZE = 8192
MAX_PARALLEL_FILES = 4
SAVE_BASE_DIR = "./knowledge_base" # 统一基座目录
# ------------------------------
# 🆕 新增:获取用户上传文档接口
# ------------------------------
@ingest_router.post("/list")
async def get_user_uploaded_files(body: FileListRequest):
"""
获取指定命名空间下所有已上传的文件列表
返回:所有真实文件、文件路径、哈希、去重状态、文件信息
"""
user_id = body.user_id
if not user_id.strip():
raise HTTPException(status_code=400, detail="用户ID不能为空")
# 用户文件目录
user_dir = os.path.join(SAVE_BASE_DIR, user_id)
if not os.path.exists(user_dir):
return {
"success": True,
"user_id": user_id,
"total_files": 0,
"files": [],
"msg": "该用户暂无上传文件"
}
# 加载 manifest 清单(你原有去重/版本管理核心)
manifest = load_manifest(user_dir)
if not manifest:
return {
"success": True,
"user_id": user_id,
"total_files": 0,
"files": [],
"msg": "该用户暂无上传文件(清单为空)"
}
# 构建文件列表
file_list = []
for filename, file_hash in manifest.items():
file_path = os.path.join(user_dir, filename)
# 获取文件基本信息
file_stat = os.stat(file_path)
create_time = datetime.fromtimestamp(file_stat.st_ctime).strftime("%Y-%m-%d %H:%M:%S")
update_time = datetime.fromtimestamp(file_stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
file_size = file_stat.st_size # 字节
# 文件格式
_, ext = os.path.splitext(filename)
ext = ext.lower().lstrip(".")
file_list.append({
"filename": filename,
"original_filename": filename.split("_v")[0] + "." + ext if "_v" in filename else filename,
"file_path": file_path,
"file_hash": file_hash,
"file_type": ext,
"file_size_bytes": file_size,
"file_size": f"{round(file_size / 1024 / 1024, 2)} MB" if file_size > 1024*1024 else f"{round(file_size / 1024, 2)} KB",
"create_time": create_time,
"update_time": update_time,
"is_versioned": "_v" in filename # 是否是版本文件
})
# 按上传时间倒序排列(最新在前)
file_list = sorted(file_list, key=lambda x: x["create_time"], reverse=True)
return {
"success": True,
"user_id": user_id,
"total_files": len(file_list),
"files": file_list,
"msg": f"成功获取 {len(file_list)} 个文件"
}
def load_manifest(ns_path: str) -> Dict[str, str]:
manifest_path = os.path.join(ns_path, MANIFEST_FILE)
if os.path.exists(manifest_path):
try:
with open(manifest_path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
logger.warning(f"读取 manifest 失败: {manifest_path}")
return {}
return {}
class DeleteFileListRequest(BaseModel):
user_id: str
doc_id: str
# ------------------------------
# 🗑️ 新增:删除文档接口(通过 file_hash 删除)
# ------------------------------
@ingest_router.post("/delete")
async def delete_file_by_hash(
body: DeleteFileListRequest
):
# 接收前端传来的 user_id 和 doc_id
user_id = body.user_id
doc_id = body.doc_id
if not user_id.strip() or not doc_id.strip():
raise HTTPException(status_code=400, detail="user_id 和 doc_id 不能为空")
user_dir = os.path.join(SAVE_BASE_DIR, user_id)
manifest = load_manifest(user_dir)
# 1. 通过 doc_id 找到对应的文件名
target_filename = None
for filename, f_doc_id in manifest.items():
if f_doc_id == doc_id:
target_filename = filename
break
if not target_filename:
raise HTTPException(status_code=404, detail="文件不存在或已删除")
# 2. 删除真实文件
file_path = os.path.join(user_dir, target_filename)
if os.path.exists(file_path):
os.remove(file_path)
# 3. 从 manifest 中移除
del manifest[target_filename]
save_manifest(user_dir, manifest)
# 删除向量库
rag_tool: RAGTool = global_registry.get_tool("rag")
result = rag_tool.run({
"action": "delete_document",
"user_id": user_id,
"doc_id": doc_id
})
if result:
return {
"success": True,
"msg": "删除成功",
"filename": target_filename,
"doc_id": doc_id
}
else:
return {
"success": False,
"msg": "删除失败,请手动检查",
"filename": target_filename,
"doc_id": doc_id
}
def save_manifest(ns_path: str, manifest: Dict[str, str]):
manifest_path = os.path.join(ns_path, MANIFEST_FILE)
temp_path = manifest_path + ".tmp"
with open(temp_path, "w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
os.replace(temp_path, manifest_path)