ai-chat-ui/server/api/conversation_routes.py

200 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.

"""
会话管理路由 - 对话 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}