重构路由架构,修复导致的前端出错

This commit is contained in:
2026-04-06 00:52:04 +08:00
parent 7a62139683
commit e8dedb5ec4
12 changed files with 2055 additions and 1102 deletions

View File

@@ -1,7 +1,4 @@
from fastapi import APIRouter, HTTPException, status
from pathlib import Path
import json
from typing import List, Dict
from backend.core.models.chat_history import ChatHistory, Message
router = APIRouter(prefix="/chats", tags=["chats"])
@@ -9,84 +6,35 @@ router = APIRouter(prefix="/chats", tags=["chats"])
# ========== 聊天历史基础路由 ==========
@router.get("", response_model=Dict[str, List[Dict]])
@router.get("", response_model=dict)
async def list_all_chats():
"""获取所有角色的所有聊天列表"""
data_dir = Path("data")
if not data_dir.exists():
return {"chats": []}
chats = []
for role_dir in data_dir.iterdir():
if role_dir.is_dir():
for chat_file in role_dir.glob("*.jsonl"):
try:
with open(chat_file, 'r', encoding='utf-8') as f:
# 读取第一行获取元数据
first_line = f.readline()
metadata = json.loads(first_line)
chats.append({
"role_name": role_dir.name,
"chat_name": chat_file.stem,
"user_name": metadata.get("user_name", "User"),
"character_name": metadata.get("character_name", "Assistant"),
"last_modified": metadata.get("last_modified", ""),
"message_count": sum(1 for _ in f) # 统计剩余行数(消息数)
})
except Exception as e:
continue # 跳过损坏的聊天文件
return {"chats": chats}
return await ChatHistory.list_all_chats()
@router.get("/{role_name}/{chat_name}")
async def get_chat(role_name: str, chat_name: str):
"""获取指定聊天的完整内容"""
try:
chat_history = ChatHistory.load_from_file(role_name, chat_name)
return {
"metadata": chat_history.chat_metadata.dict(),
"messages": chat_history.to_chatbox_format()
}
except FileNotFoundError:
return await ChatHistory.get_chat(role_name, chat_name)
except FileNotFoundError as e:
raise HTTPException(status_code=404, detail="Chat not found")
@router.post("/{role_name}", status_code=status.HTTP_201_CREATED)
async def create_chat(role_name: str, chat_name: str, metadata: Dict = None):
async def create_chat(role_name: str, chat_name: str, metadata: dict = None):
"""创建新聊天"""
role_dir = Path("data") / role_name
role_dir.mkdir(parents=True, exist_ok=True)
chat_path = role_dir / f"{chat_name}.jsonl"
if chat_path.exists():
try:
return await ChatHistory.create_chat(role_name, chat_name, metadata)
except FileExistsError:
raise HTTPException(status_code=400, detail="Chat already exists")
# 创建聊天历史对象
chat_history = ChatHistory(
chat_metadata=metadata or {},
messages=[]
)
# 保存到文件
chat_history.save_to_file(role_name, chat_name)
return {"message": "Chat created successfully"}
@router.put("/{role_name}/{chat_name}")
async def update_chat(role_name: str, chat_name: str, update_data: Dict):
async def update_chat(role_name: str, chat_name: str, update_data: dict):
"""更新聊天元数据"""
try:
chat_history = ChatHistory.load_from_file(role_name, chat_name)
# 更新元数据
if "metadata" in update_data:
for key, value in update_data["metadata"].items():
if hasattr(chat_history.chat_metadata, key):
setattr(chat_history.chat_metadata, key, value)
# 保存更改
chat_history.save_to_file(role_name, chat_name)
return {"message": "Chat metadata updated successfully"}
return await ChatHistory.update_chat(role_name, chat_name, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
@@ -94,11 +42,10 @@ async def update_chat(role_name: str, chat_name: str, update_data: Dict):
@router.delete("/{role_name}/{chat_name}")
async def delete_chat(role_name: str, chat_name: str):
"""删除指定聊天"""
chat_path = Path("data") / role_name / f"{chat_name}.jsonl"
if not chat_path.exists():
try:
return await ChatHistory.delete_chat(role_name, chat_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
chat_path.unlink()
return {"message": "Chat deleted successfully"}
# ========== 聊天消息路由 ==========
@@ -107,8 +54,7 @@ async def delete_chat(role_name: str, chat_name: str):
async def list_messages(role_name: str, chat_name: str):
"""获取聊天的所有消息"""
try:
chat_history = ChatHistory.load_from_file(role_name, chat_name)
return {"messages": chat_history.to_chatbox_format()}
return await ChatHistory.list_messages(role_name, chat_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
@@ -117,58 +63,27 @@ async def list_messages(role_name: str, chat_name: str):
async def get_message(role_name: str, chat_name: str, floor: int):
"""获取指定楼层的消息"""
try:
chat_history = ChatHistory.load_from_file(role_name, chat_name)
message = next((msg for msg in chat_history.messages if msg.floor == floor), None)
if not message:
raise HTTPException(status_code=404, detail="Message not found")
return message.dict()
return await ChatHistory.get_message(role_name, chat_name, floor)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
@router.post("/{role_name}/{chat_name}/messages", status_code=status.HTTP_201_CREATED)
async def add_message(role_name: str, chat_name: str, message_data: Dict):
async def add_message(role_name: str, chat_name: str, message_data: dict):
"""向聊天添加新消息"""
try:
chat_history = ChatHistory.load_from_file(role_name, chat_name)
# 创建消息对象
message = Message(**message_data)
# 检查楼层是否已存在
if any(msg.floor == message.floor for msg in chat_history.messages):
raise HTTPException(status_code=400, detail="Message floor already exists")
# 添加消息
chat_history.messages.append(message)
# 保存更改
chat_history.save_to_file(role_name, chat_name)
return {"message": "Message added successfully", "floor": message.floor}
return await ChatHistory.add_message(role_name, chat_name, message_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.put("/{role_name}/{chat_name}/messages/{floor}")
async def update_message(role_name: str, chat_name: str, floor: int, update_data: Dict):
async def update_message(role_name: str, chat_name: str, floor: int, update_data: dict):
"""更新指定楼层的消息"""
try:
chat_history = ChatHistory.load_from_file(role_name, chat_name)
message = next((msg for msg in chat_history.messages if msg.floor == floor), None)
if not message:
raise HTTPException(status_code=404, detail="Message not found")
# 更新消息字段
for key, value in update_data.items():
if hasattr(message, key):
setattr(message, key, value)
# 保存更改
chat_history.save_to_file(role_name, chat_name)
return {"message": "Message updated successfully"}
return await ChatHistory.update_message(role_name, chat_name, floor, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
@@ -177,17 +92,6 @@ async def update_message(role_name: str, chat_name: str, floor: int, update_data
async def delete_message(role_name: str, chat_name: str, floor: int):
"""删除指定楼层的消息"""
try:
chat_history = ChatHistory.load_from_file(role_name, chat_name)
# 查找并删除消息
original_length = len(chat_history.messages)
chat_history.messages = [msg for msg in chat_history.messages if msg.floor != floor]
if len(chat_history.messages) == original_length:
raise HTTPException(status_code=404, detail="Message not found")
# 保存更改
chat_history.save_to_file(role_name, chat_name)
return {"message": "Message deleted successfully"}
return await ChatHistory.delete_message(role_name, chat_name, floor)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")

View File

@@ -1,7 +1,4 @@
from fastapi import APIRouter, HTTPException, status
from pathlib import Path
import json
from typing import List, Dict
from backend.core.models.PromptList import AIDesignSpec
from backend.core.models.PromptComponent import PromptComponent
@@ -10,110 +7,46 @@ router = APIRouter(prefix="/presets", tags=["presets"])
# ========== 预设基础路由 ==========
@router.get("", response_model=Dict[str, List[Dict]])
@router.get("", response_model=dict)
async def list_presets():
"""获取所有预设列表及其基本信息"""
preset_dir = Path("data/preset")
if not preset_dir.exists():
return {"presets": []}
presets = []
for preset_file in preset_dir.glob("*.json"):
try:
with open(preset_file, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
presets.append({
"name": preset_file.stem,
"description": preset_data.get("description", ""),
"component_count": len(preset_data.get("prompts", [])),
"temperature": preset_data.get("temperature", 1.0)
})
except Exception as e:
continue # 跳过损坏的预设文件
return {"presets": presets}
return await AIDesignSpec.list_all_presets()
@router.get("/{preset_name}")
async def get_preset(preset_name: str):
"""获取指定预设的完整内容"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
with open(preset_path, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
# 转换为AIDesignSpec对象进行验证
ai_design_spec = AIDesignSpec(**preset_data)
return ai_design_spec.dict()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to load preset: {str(e)}")
return await AIDesignSpec.get_preset(preset_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_preset(preset_name: str, preset_data: Dict):
async def create_preset(preset_name: str, preset_data: dict):
"""创建新预设"""
preset_dir = Path("data/preset")
preset_dir.mkdir(parents=True, exist_ok=True)
preset_path = preset_dir / f"{preset_name}.json"
if preset_path.exists():
raise HTTPException(status_code=400, detail="Preset already exists")
try:
# 验证并转换为AIDesignSpec对象
ai_design_spec = AIDesignSpec(**preset_data)
# 保存到文件
with open(preset_path, 'w', encoding='utf-8') as f:
json.dump(ai_design_spec.dict(), f, ensure_ascii=False, indent=2)
return {"message": "Preset created successfully", "name": preset_name}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to create preset: {str(e)}")
return await AIDesignSpec.create_preset(preset_name, preset_data)
except FileExistsError:
raise HTTPException(status_code=400, detail="Preset already exists")
@router.put("/{preset_name}")
async def update_preset(preset_name: str, update_data: Dict):
async def update_preset(preset_name: str, update_data: dict):
"""更新预设配置"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
# 加载现有预设
with open(preset_path, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
# 更新字段
for key, value in update_data.items():
preset_data[key] = value
# 验证并转换为AIDesignSpec对象
ai_design_spec = AIDesignSpec(**preset_data)
# 保存更新
with open(preset_path, 'w', encoding='utf-8') as f:
json.dump(ai_design_spec.dict(), f, ensure_ascii=False, indent=2)
return {"message": "Preset updated successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to update preset: {str(e)}")
return await AIDesignSpec.update_preset(preset_name, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
@router.delete("/{preset_name}")
async def delete_preset(preset_name: str):
"""删除指定预设"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
preset_path.unlink()
return {"message": "Preset deleted successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to delete preset: {str(e)}")
return await AIDesignSpec.delete_preset(preset_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
# ========== 预设组件路由 ==========
@@ -121,144 +54,45 @@ async def delete_preset(preset_name: str):
@router.get("/{preset_name}/components")
async def list_preset_components(preset_name: str):
"""获取预设中的所有组件"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
with open(preset_path, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
# 获取组件列表
components = preset_data.get("prompts", [])
return {"components": components}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to load components: {str(e)}")
return await AIDesignSpec.list_components(preset_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
@router.get("/{preset_name}/components/{component_id}")
async def get_preset_component(preset_name: str, component_id: str):
"""获取指定组件的详情"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
with open(preset_path, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
# 查找组件
components = preset_data.get("prompts", [])
component = next((c for c in components if c.get("identifier") == component_id), None)
if not component:
raise HTTPException(status_code=404, detail="Component not found")
return component
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to load component: {str(e)}")
return await AIDesignSpec.get_component(preset_name, component_id)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
@router.post("/{preset_name}/components", status_code=status.HTTP_201_CREATED)
async def add_preset_component(preset_name: str, component_data: Dict):
async def add_preset_component(preset_name: str, component_data: dict):
"""向预设添加新组件"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
# 加载预设数据
with open(preset_path, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
# 验证组件数据
component = PromptComponent(**component_data)
# 检查组件ID是否已存在
components = preset_data.get("prompts", [])
if any(c.get("identifier") == component.identifier for c in components):
raise HTTPException(status_code=400, detail="Component identifier already exists")
# 添加组件
components.append(component.dict())
preset_data["prompts"] = components
# 保存更新
with open(preset_path, 'w', encoding='utf-8') as f:
json.dump(preset_data, f, ensure_ascii=False, indent=2)
return {"message": "Component added successfully", "identifier": component.identifier}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to add component: {str(e)}")
return await AIDesignSpec.add_component_to_preset(preset_name, component_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.put("/{preset_name}/components/{component_id}")
async def update_preset_component(preset_name: str, component_id: str, update_data: Dict):
async def update_preset_component(preset_name: str, component_id: str, update_data: dict):
"""更新指定组件"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
# 加载预设数据
with open(preset_path, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
# 查找并更新组件
components = preset_data.get("prompts", [])
component_index = next((i for i, c in enumerate(components) if c.get("identifier") == component_id), None)
if component_index is None:
raise HTTPException(status_code=404, detail="Component not found")
# 更新组件字段
for key, value in update_data.items():
components[component_index][key] = value
# 保存更新
with open(preset_path, 'w', encoding='utf-8') as f:
json.dump(preset_data, f, ensure_ascii=False, indent=2)
return {"message": "Component updated successfully"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to update component: {str(e)}")
return await AIDesignSpec.update_component_in_preset(preset_name, component_id, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
@router.delete("/{preset_name}/components/{component_id}")
async def delete_preset_component(preset_name: str, component_id: str):
"""从预设中删除指定组件"""
preset_path = Path("data/preset") / f"{preset_name}.json"
if not preset_path.exists():
raise HTTPException(status_code=404, detail="Preset not found")
try:
# 加载预设数据
with open(preset_path, 'r', encoding='utf-8') as f:
preset_data = json.load(f)
# 查找并删除组件
components = preset_data.get("prompts", [])
original_length = len(components)
components = [c for c in components if c.get("identifier") != component_id]
if len(components) == original_length:
raise HTTPException(status_code=404, detail="Component not found")
# 更新预设数据
preset_data["prompts"] = components
# 保存更新
with open(preset_path, 'w', encoding='utf-8') as f:
json.dump(preset_data, f, ensure_ascii=False, indent=2)
return {"message": "Component deleted successfully"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to delete component: {str(e)}")
return await AIDesignSpec.delete_component_from_preset(preset_name, component_id)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")