200 lines
6.0 KiB
Python
200 lines
6.0 KiB
Python
"""
|
||
会话管理路由 - 对话 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} |