""" 会话管理路由 - 对话 CRUD 和文件上传 与平台无关的通用功能,不涉及任何 LLM 平台细节。 """ import json import os from datetime import datetime from pathlib import Path from typing import Dict from fastapi import File, HTTPException, UploadFile from fastapi.responses import FileResponse import sys sys.path.append(str(Path(__file__).parent.parent)) from database import get_db from utils.helpers import generate_unique_id from utils.logger import log_error, log_exception, log_info # 配置上传目录 upload_dir = Path(__file__).parent.parent / "uploads" upload_dir.mkdir(exist_ok=True) # ── 会话管理 ───────────────────────────────────────────────────── async def get_conversations_handler(): """获取所有对话处理器""" db = get_db() return db.list_conversations() async def get_conversation_handler(conversation_id: str): """获取特定对话处理器""" db = get_db() conversation = db.get_conversation(conversation_id) if not conversation: raise HTTPException(status_code=404, detail="对话不存在") return conversation async def save_conversation_handler(data: dict): """保存或更新对话处理器""" try: db = get_db() conversation_id = data.get("id") # 检查是否已存在 existing = db.get_conversation(conversation_id) if conversation_id else None if existing: # 更新现有会话 return db.update_conversation(conversation_id, data) else: # 创建新会话 return db.create_conversation(data) except Exception as e: log_error(f"Error saving conversation: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) async def delete_conversation_handler(conversation_id: str): """删除对话处理器""" db = get_db() success = db.delete_conversation(conversation_id) if success: return {"success": True, "message": "删除成功"} else: raise HTTPException(status_code=404, detail="对话不存在") # ── 文件上传 ───────────────────────────────────────────────────── async def upload_file_handler(file: UploadFile = File(...)): """文件上传处理器""" try: # 允许的 MIME 类型(宽松策略) allowed_types = { # 图片 "image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp", "image/svg+xml", # 文本类 "text/plain", "text/csv", "text/markdown", "text/html", "text/xml", "application/json", "application/xml", # PDF "application/pdf", # Office 文档 "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", } # 允许的扩展名(兜底:MIME 类型可能被浏览器误判) allowed_extensions = { ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".txt", ".md", ".csv", ".json", ".xml", ".yaml", ".yml", ".log", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".py", ".js", ".ts", ".html", ".css", } file_extension = Path(file.filename).suffix.lower() if ( file.content_type not in allowed_types and file_extension not in allowed_extensions ): raise HTTPException( status_code=400, detail=f"不支持的文件类型: {file.content_type}({file_extension})", ) # 生成唯一文件名 unique_filename = f"{int(datetime.utcnow().timestamp())}_{generate_unique_id()}{file_extension}" file_path = upload_dir / unique_filename # 保存文件到本地(临时缓存) content = await file.read() with open(file_path, "wb") as f: f.write(content) # 文件关闭后再上传到 OSS from utils.oss_uploader import upload_file as oss_upload oss_result = oss_upload(str(file_path)) file_url = oss_result["url"] # 返回文件信息 result = { "url": file_url, "name": file.filename, "size": len(content), "mimeType": file.content_type, } log_info(f"File uploaded: {result}") return result except Exception as e: log_error(f"Upload error: {str(e)}") raise HTTPException(status_code=500, detail=f"上传失败: {str(e)}") def serve_upload_handler(filename: str): """提供上传文件访问处理器""" file_path = upload_dir / filename if not file_path.exists(): raise HTTPException(status_code=404, detail="文件不存在") return FileResponse(str(file_path)) # ── 停止生成 ───────────────────────────────────────────────────── async def stop_generation_handler(message_id: str = None): """停止生成处理器""" message = ( f"已发出停止指令,消息ID: {message_id}" if message_id else "已发出停止指令" ) return {"success": True, "message": message}