"""数据文件接口:上传限制、上传解析、列出、查看、删除,见 docs/screen-agent/data-files.md 第 5 节""" from typing import Annotated, get_args from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile from app.common.response import ApiResponse, success_response from app.core.auth import get_current_user from app.datafiles.service import MAX_FILES_PER_CONVERSATION, DataFileService from app.schemas.base import NonBlankString from app.schemas.data_file import ( DataFileDetail, DataFileInfo, DataFileLimits, DataFileList, DataFileRecord, DataFormat, ) from app.schemas.user import CurrentUser # 上传限制不需要登录,单独一个路由;要先于 /files/{file_id} 注册 limits_router = APIRouter(prefix="/files", tags=["DataFile"]) router = APIRouter(prefix="/files", tags=["DataFile"]) # 文件选择框的过滤条件;后端按文件内容识别格式,不看扩展名 ACCEPT = ".csv,.tsv,.txt,.xlsx,.xls,.xlsb,.json,.jsonl,.ndjson,.parquet" def _service(request: Request) -> DataFileService: return request.app.state.data_file_service def _detail(record: DataFileRecord) -> DataFileDetail: # 按接口模型重新校验,去掉 owner_id、sha256、列概况等只在后端用的字段 return DataFileDetail.model_validate(record.model_dump()) @limits_router.get("/limits") async def get_limits(request: Request) -> ApiResponse[DataFileLimits]: return success_response( DataFileLimits( max_size_mb=_service(request).max_mb, max_files_per_conversation=MAX_FILES_PER_CONVERSATION, formats=list(get_args(DataFormat)), accept=ACCEPT, ) ) @router.post("") async def upload_file( request: Request, conversation_id: Annotated[NonBlankString, Form()], file: Annotated[UploadFile, File()], user: Annotated[CurrentUser, Depends(get_current_user)], ) -> ApiResponse[DataFileDetail]: record = await _service(request).upload( user.id, conversation_id, file.filename, file.file ) return success_response(_detail(record)) @router.get("") async def list_files( request: Request, conversation_id: Annotated[NonBlankString, Query()], user: Annotated[CurrentUser, Depends(get_current_user)], ) -> ApiResponse[DataFileList]: records = await _service(request).list_files(user.id, conversation_id) return success_response( DataFileList( files=[ DataFileInfo.model_validate(record.model_dump()) for record in records ] ) ) @router.get("/{file_id}") async def get_file( file_id: str, request: Request, user: Annotated[CurrentUser, Depends(get_current_user)], ) -> ApiResponse[DataFileDetail]: return success_response(_detail(await _service(request).get_file(user.id, file_id))) @router.delete("/{file_id}") async def delete_file( file_id: str, request: Request, user: Annotated[CurrentUser, Depends(get_current_user)], ) -> ApiResponse[dict]: await _service(request).delete_file(user.id, file_id) return success_response()