ForcePilot/backend/server/routers/workspace_router.py
Wenjie Zhang a0f9b0b6fd feat(kb):新增知识库文件管理功能
- 更新了 KnowledgeBaseManager 以支持新的文件管理方法。
- 在 workspace_router 中为知识库文件操作新增了端点,包括文件树列表、文件预览和文件下载。
- 在 workspace_api.js 中添加了相应的 API 调用,用于知识库交互。
- 增强了 AgentFilePreview 组件,支持预览变体之间的切换。
- 更新了导航逻辑,重定向至知识管理标签页,而非数据库视图。
- 修改了 WorkspaceView 以处理知识库条目和预览,包括加载和展示知识文件。
- 通过在文件操作期间提供加载与错误状态的反馈,改善了用户体验。
2026-05-26 17:39:12 +08:00

180 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import io
from urllib.parse import quote
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from server.utils.auth_middleware import get_required_user
from yuxi import knowledge_base
from yuxi.services.workspace_service import (
create_workspace_directory,
delete_workspace_path,
download_workspace_file,
list_workspace_tree,
read_workspace_file_content,
upload_workspace_file,
write_workspace_file_content,
)
from yuxi.storage.postgres.models_business import User
workspace = APIRouter(prefix="/workspace", tags=["workspace"])
class CreateWorkspaceDirectoryRequest(BaseModel):
parent_path: str
name: str
class UpdateWorkspaceFileContentRequest(BaseModel):
path: str
content: str
async def _ensure_knowledge_read_access(current_user: User, db_id: str) -> None:
allowed = await knowledge_base.check_accessible(
{
"role": current_user.role,
"department_id": current_user.department_id,
},
db_id,
)
if not allowed:
raise HTTPException(status_code=403, detail="Access denied")
def _raise_knowledge_read_error(error: ValueError) -> None:
message = str(error) or "知识库文件读取失败"
if message.startswith("Dify 知识库不支持"):
raise HTTPException(status_code=501, detail=message) from error
raise HTTPException(status_code=400, detail=message) from error
@workspace.get("/tree", response_model=dict)
async def get_workspace_tree(
path: str = Query("/", description="工作区目录路径"),
recursive: bool = Query(False, description="是否递归返回子目录文件"),
files_only: bool = Query(False, description="是否仅返回文件"),
current_user: User = Depends(get_required_user),
):
return await list_workspace_tree(
path=path,
recursive=recursive,
files_only=files_only,
current_user=current_user,
)
@workspace.get("/file", response_model=dict)
async def get_workspace_file(
path: str = Query(..., description="工作区文件路径"),
current_user: User = Depends(get_required_user),
):
return await read_workspace_file_content(path=path, current_user=current_user)
@workspace.get("/knowledge/tree", response_model=dict)
async def get_workspace_knowledge_tree(
db_id: str = Query(..., description="知识库 ID"),
parent_id: str | None = Query(None, description="父文件夹 ID"),
recursive: bool = Query(False, description="是否递归返回子目录文件"),
files_only: bool = Query(False, description="是否仅返回文件"),
current_user: User = Depends(get_required_user),
):
await _ensure_knowledge_read_access(current_user, db_id)
try:
return await knowledge_base.list_file_tree(
db_id=db_id,
parent_id=parent_id,
recursive=recursive,
files_only=files_only,
)
except ValueError as error:
_raise_knowledge_read_error(error)
@workspace.get("/knowledge/file", response_model=dict)
async def get_workspace_knowledge_file(
db_id: str = Query(..., description="知识库 ID"),
file_id: str = Query(..., description="知识库文件 ID"),
variant: str = Query("parsed", description="预览模式parsed 或 original"),
current_user: User = Depends(get_required_user),
):
await _ensure_knowledge_read_access(current_user, db_id)
try:
return await knowledge_base.read_file_preview(db_id=db_id, file_id=file_id, variant=variant)
except ValueError as error:
_raise_knowledge_read_error(error)
@workspace.get("/knowledge/download")
async def download_workspace_knowledge_file(
db_id: str = Query(..., description="知识库 ID"),
file_id: str = Query(..., description="知识库文件 ID"),
variant: str = Query("original", description="下载模式original 或 parsed"),
current_user: User = Depends(get_required_user),
):
await _ensure_knowledge_read_access(current_user, db_id)
try:
data = await knowledge_base.get_file_download(db_id=db_id, file_id=file_id, variant=variant)
except ValueError as error:
_raise_knowledge_read_error(error)
filename = data["filename"]
return StreamingResponse(
io.BytesIO(data["content"]),
media_type=data["media_type"],
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"},
)
@workspace.put("/file", response_model=dict)
async def update_workspace_file(
payload: UpdateWorkspaceFileContentRequest,
current_user: User = Depends(get_required_user),
):
return await write_workspace_file_content(
path=payload.path,
content=payload.content,
current_user=current_user,
)
@workspace.delete("/file", response_model=dict)
async def delete_workspace_file_route(
path: str = Query(..., description="工作区文件或目录路径"),
current_user: User = Depends(get_required_user),
):
return await delete_workspace_path(path=path, current_user=current_user)
@workspace.post("/directory", response_model=dict)
async def create_workspace_directory_route(
payload: CreateWorkspaceDirectoryRequest,
current_user: User = Depends(get_required_user),
):
return await create_workspace_directory(
parent_path=payload.parent_path,
name=payload.name,
current_user=current_user,
)
@workspace.post("/upload", response_model=dict)
async def upload_workspace_file_route(
parent_path: str = Form(..., description="父目录路径"),
file: UploadFile = File(..., description="上传文件"),
current_user: User = Depends(get_required_user),
):
return await upload_workspace_file(parent_path=parent_path, file=file, current_user=current_user)
@workspace.get("/download")
async def download_workspace(
path: str = Query(..., description="工作区文件路径"),
current_user: User = Depends(get_required_user),
):
return await download_workspace_file(path=path, current_user=current_user)