重构后端成功

This commit is contained in:
2026-04-28 00:45:18 +08:00
parent dd17206e1f
commit 8b10ef5828
75 changed files with 244 additions and 16685 deletions

View File

@@ -1,97 +1,56 @@
from fastapi import APIRouter, HTTPException, status
from backend.core.models.chat_history import ChatHistory, Message
# TODO: 实现 ChatService 来替代旧的 ChatHistory 逻辑
# from services.chat_service import ChatService
router = APIRouter(prefix="/chat", tags=["chat"])
# ========== 聊天历史基础路由 ==========
@router.get("", response_model=dict)
async def list_all_chats():
"""获取所有角色的所有聊天列表"""
return await ChatHistory.list_all_chats()
# return await ChatService.list_all_chats()
return {"chats": []}
@router.get("/{role_name}/{chat_name}")
async def get_chat(role_name: str, chat_name: str):
"""获取指定聊天的完整内容"""
try:
return await ChatHistory.get_chat(role_name, chat_name)
except FileNotFoundError as e:
raise HTTPException(status_code=404, detail="Chat not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.post("/{role_name}", status_code=status.HTTP_201_CREATED)
async def create_chat(role_name: str, chat_name: str, metadata: dict = None):
"""创建新聊天"""
try:
return await ChatHistory.create_chat(role_name, chat_name, metadata)
except FileExistsError:
raise HTTPException(status_code=400, detail="Chat already exists")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.put("/{role_name}/{chat_name}")
async def update_chat(role_name: str, chat_name: str, update_data: dict):
"""更新聊天元数据"""
try:
return await ChatHistory.update_chat(role_name, chat_name, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.delete("/{role_name}/{chat_name}")
async def delete_chat(role_name: str, chat_name: str):
"""删除指定聊天"""
try:
return await ChatHistory.delete_chat(role_name, chat_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
# ========== 聊天消息路由 ==========
raise HTTPException(status_code=501, detail="Not Implemented")
@router.get("/{role_name}/{chat_name}/messages")
async def list_messages(role_name: str, chat_name: str):
"""获取聊天的所有消息"""
try:
return await ChatHistory.list_messages(role_name, chat_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.get("/{role_name}/{chat_name}/messages/{floor}")
async def get_message(role_name: str, chat_name: str, floor: int):
"""获取指定楼层的消息"""
try:
return await ChatHistory.get_message(role_name, chat_name, floor)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@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):
"""向聊天添加新消息"""
try:
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))
raise HTTPException(status_code=501, detail="Not Implemented")
@router.put("/{role_name}/{chat_name}/messages/{floor}")
async def update_message(role_name: str, chat_name: str, floor: int, update_data: dict):
"""更新指定楼层的消息"""
try:
return await ChatHistory.update_message(role_name, chat_name, floor, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.delete("/{role_name}/{chat_name}/messages/{floor}")
async def delete_message(role_name: str, chat_name: str, floor: int):
"""删除指定楼层的消息"""
try:
return await ChatHistory.delete_message(role_name, chat_name, floor)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Chat not found")
raise HTTPException(status_code=501, detail="Not Implemented")

View File

@@ -1,98 +1,60 @@
from fastapi import APIRouter, HTTPException, status
from backend.core.models.PromptList import AIDesignSpec
from backend.core.models.PromptComponent import PromptComponent
# TODO: 实现 PresetService 来替代旧的 AIDesignSpec 逻辑
# from services.preset_service import PresetService
router = APIRouter(prefix="/presets", tags=["presets"])
# ========== 预设基础路由 ==========
@router.get("", response_model=dict)
async def list_presets():
"""获取所有预设列表及其基本信息"""
return await AIDesignSpec.list_all_presets()
# return await PresetService.list_all_presets()
return {"presets": []}
@router.get("/{preset_name}")
async def get_preset(preset_name: str):
"""获取指定预设的完整内容"""
try:
return await AIDesignSpec.get_preset(preset_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
# try:
# return await PresetService.get_preset(preset_name)
# except FileNotFoundError:
# raise HTTPException(status_code=404, detail="Preset not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_preset(preset_name: str, preset_data: dict):
"""创建新预设"""
try:
return await AIDesignSpec.create_preset(preset_name, preset_data)
except FileExistsError:
raise HTTPException(status_code=400, detail="Preset already exists")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.put("/{preset_name}")
async def update_preset(preset_name: str, update_data: dict):
"""更新预设配置"""
try:
return await AIDesignSpec.update_preset(preset_name, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.delete("/{preset_name}")
async def delete_preset(preset_name: str):
"""删除指定预设"""
try:
return await AIDesignSpec.delete_preset(preset_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
# ========== 预设组件路由 ==========
raise HTTPException(status_code=501, detail="Not Implemented")
@router.get("/{preset_name}/components")
async def list_preset_components(preset_name: str):
"""获取预设中的所有组件"""
try:
return await AIDesignSpec.list_components(preset_name)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.get("/{preset_name}/components/{component_id}")
async def get_preset_component(preset_name: str, component_id: str):
"""获取指定组件的详情"""
try:
return await AIDesignSpec.get_component(preset_name, component_id)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.post("/{preset_name}/components", status_code=status.HTTP_201_CREATED)
async def add_preset_component(preset_name: str, component_data: dict):
"""向预设添加新组件"""
try:
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))
raise HTTPException(status_code=501, detail="Not Implemented")
@router.put("/{preset_name}/components/{component_id}")
async def update_preset_component(preset_name: str, component_id: str, update_data: dict):
"""更新指定组件"""
try:
return await AIDesignSpec.update_component_in_preset(preset_name, component_id, update_data)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.delete("/{preset_name}/components/{component_id}")
async def delete_preset_component(preset_name: str, component_id: str):
"""从预设中删除指定组件"""
try:
return await AIDesignSpec.delete_component_from_preset(preset_name, component_id)
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Preset not found")
raise HTTPException(status_code=501, detail="Not Implemented")

View File

@@ -1,4 +1,4 @@
# 标准库导入
3# 标准库导入
import os
import shutil
import logging
@@ -10,18 +10,8 @@ from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from fastapi.responses import JSONResponse, FileResponse
# 本地模块导入
# 本地模块导入
from backend.core.models.WorldBook import WorldBook
from backend.core.models.WorldItem import (
WorldItem,
TriggerConfig,
KeywordTriggerConfig,
RAGTriggerConfig,
ConditionTriggerConfig,
TriggerStrategy
)
from backend.core.config import settings
from models.internal import WorldInfo, WorldInfoEntry
from core.config import settings
# 配置日志
logger = logging.getLogger(__name__)
@@ -40,58 +30,15 @@ async def list_worldbooks():
Returns:
List[Dict[str, Any]]: 世界书列表
"""
try:
worldbooks = []
search_dir = settings.WORLDBOOKS_PATH
# 检查目录是否存在
if not os.path.exists(search_dir):
logger.warning(f"目录不存在: {search_dir}")
return []
for filename in os.listdir(search_dir):
if filename.endswith(".json"):
file_path = os.path.join(search_dir, filename)
try:
# 加载世界书基本信息
# 传入文件名(不带扩展名)
world_book = WorldBook.load(Path(file_path).stem)
worldbooks.append(world_book.to_summary_dict())
except Exception as e:
logger.warning(f"加载世界书 {filename} 失败: {str(e)}")
continue
logger.info(f"获取世界书列表: 共 {len(worldbooks)}")
return worldbooks
except Exception as e:
logger.error(f"获取世界书列表失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"获取世界书列表失败: {str(e)}")
# TODO: 实现 WorldBookService
return []
@router.get("/{name}", response_model=Dict[str, Any])
async def get_worldbook(name: str):
"""
获取指定名称的世界书
Args:
name: 世界书名称
Returns:
Dict[str, Any]: 世界书数据
"""
try:
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
world_book = WorldBook.load(name)
logger.info(f"获取世界书: {name}")
return world_book.to_dict()
except HTTPException:
raise
except Exception as e:
logger.error(f"获取世界书 {name} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"获取世界书失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.post("/", response_model=Dict[str, Any])
async def create_worldbook(
@@ -100,47 +47,8 @@ async def create_worldbook(
):
"""
创建新世界书
Args:
name: 世界书名称
description: 世界书描述
file: 可选的上传文件SillyTavern 格式)
Returns:
Dict[str, Any]: 创建的世界书数据
"""
try:
# 如果上传了文件,从文件导入
if file:
# 保存临时文件
temp_path = os.path.join(settings.WORLDBOOKS_PATH, f"temp_{file.filename}")
with open(temp_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
try:
# 从文件加载世界书
world_book = WorldBook.load(Path(temp_path).stem)
# 更新名称和描述
world_book.name = name
# 保存世界书
world_book.save()
logger.info(f"从文件创建世界书: {name}")
finally:
# 删除临时文件
if os.path.exists(temp_path):
os.remove(temp_path)
else:
# 创建空世界书
world_book = WorldBook.create_empty(name)
logger.info(f"创建空世界书: {name}")
return world_book.to_dict()
except HTTPException:
raise
except Exception as e:
logger.error(f"创建世界书 {name} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"创建世界书失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.put("/{name}", response_model=Dict[str, Any])
async def update_worldbook(
@@ -149,417 +57,61 @@ async def update_worldbook(
):
"""
更新世界书
Args:
name: 世界书名称
description: 世界书描述(可选)
file: 可选的上传文件SillyTavern 格式)
Returns:
Dict[str, Any]: 更新后的世界书数据
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 加载世界书
world_book = WorldBook.load(name)
# 如果上传了文件,从文件导入并合并
if file:
# 保存临时文件
temp_path = os.path.join(settings.WORLDBOOKS_PATH, f"temp_{file.filename}")
with open(temp_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
try:
# 从文件加载世界书
imported_book = WorldBook.load(Path(temp_path).stem)
# 合并条目
world_book.merge_from_book(imported_book)
logger.info(f"从文件更新世界书: {name}")
finally:
# 删除临时文件
if os.path.exists(temp_path):
os.remove(temp_path)
# 保存世界书
world_book.save()
logger.info(f"更新世界书: {name}")
return world_book.to_dict()
except HTTPException:
raise
except Exception as e:
logger.error(f"更新世界书 {name} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"更新世界书失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.delete("/{name}")
async def delete_worldbook(name: str):
"""
删除世界书
Args:
name: 世界书名称
Returns:
Dict[str, Any]: 删除结果
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 获取文件路径
file_path = WorldBook.get_file_path(name)
# 删除文件
os.remove(file_path)
logger.info(f"删除世界书: {name}")
return {"success": True, "message": f"世界书 '{name}' 已删除"}
except HTTPException:
raise
except Exception as e:
logger.error(f"删除世界书 {name} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"删除世界书失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.get("/{name}/entries", response_model=List[Dict[str, Any]])
async def list_worldbook_entries(name: str):
"""
获取世界书的所有条目(包括已禁用的条目)
Args:
name: 世界书名称
Returns:
List[Dict[str, Any]]: 条目列表
获取世界书的所有条目
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 加载世界书
world_book = WorldBook.load(name)
# 获取所有条目的核心信息
entries = world_book.get_all_entries()
logger.info(f"获取世界书 {name} 的所有条目: 共 {len(entries)}")
return entries
except HTTPException:
raise
except Exception as e:
logger.error(f"获取世界书 {name} 的条目失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"获取世界书条目失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.get("/{name}/entries/{uid}", response_model=Dict[str, Any])
async def get_worldbook_entry(name: str, uid: int):
"""
获取世界书的指定条目
Args:
name: 世界书名称
uid: 条目 UID
Returns:
Dict[str, Any]: 条目数据
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 加载世界书
world_book = WorldBook.load(name)
# 获取条目
entry = world_book.get_entry(uid)
if entry is None:
raise HTTPException(status_code=404, detail=f"条目 UID {uid} 不存在")
logger.info(f"获取世界书 {name} 的条目: UID={uid}")
return entry.to_dict()
except HTTPException:
raise
except Exception as e:
logger.error(f"获取世界书 {name} 的条目 {uid} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"获取世界书条目失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.post("/{name}/entries", response_model=Dict[str, Any])
async def create_worldbook_entry(name: str, entry_data: Dict[str, Any]):
"""
在世界书中创建新条目
Args:
name: 世界书名称
entry_data: 条目数据
Returns:
Dict[str, Any]: 创建的条目数据
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 加载世界书
world_book = WorldBook.load(name)
# 处理触发配置数据
trigger_data = entry_data.pop("trigger_config", None)
if trigger_data and "triggers" in trigger_data:
# 创建新的触发配置对象
trigger_config = TriggerConfig()
# 处理每个触发策略
for strategy_str, trigger_info in trigger_data["triggers"].items():
try:
strategy = TriggerStrategy(strategy_str)
enabled = trigger_info[0] if isinstance(trigger_info, list) and len(trigger_info) > 0 else False
config_data = trigger_info[1] if isinstance(trigger_info, list) and len(trigger_info) > 1 else None
# 根据触发策略创建对应的配置对象
if strategy == TriggerStrategy.KEYWORD and config_data:
config = KeywordTriggerConfig(**config_data)
elif strategy == TriggerStrategy.RAG and config_data:
config = RAGTriggerConfig(**config_data)
elif strategy == TriggerStrategy.CONDITION and config_data:
config = ConditionTriggerConfig(**config_data)
else:
config = None
# 设置触发策略
trigger_config.set_trigger(strategy, enabled, config)
except Exception as e:
logger.warning(f"处理触发策略 {strategy_str} 失败: {str(e)}")
continue
# 设置触发配置
entry_data["trigger_config"] = trigger_config
# 创建条目
entry = WorldItem.Entry(**entry_data)
# 添加条目
world_book.add_entry(entry)
# 保存世界书
world_book.save()
logger.info(f"在世界书 {name} 中创建条目: UID={entry.uid}")
return entry.to_dict()
except HTTPException:
raise
except Exception as e:
logger.error(f"在世界书 {name} 中创建条目失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"创建世界书条目失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.put("/{name}/entries/{uid}", response_model=Dict[str, Any])
async def update_worldbook_entry(name: str, uid: int, entry_data: Dict[str, Any]):
"""
更新世界书的指定条目
Args:
name: 世界书名称
uid: 条目 UID
entry_data: 条目数据
Returns:
Dict[str, Any]: 更新后的条目数据
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 加载世界书
world_book = WorldBook.load(name)
# 检查条目是否存在
if world_book.get_entry(uid) is None:
raise HTTPException(status_code=404, detail=f"条目 UID {uid} 不存在")
# 处理触发配置数据
trigger_data = entry_data.pop("trigger_config", None)
if trigger_data and "triggers" in trigger_data:
# 创建新的触发配置对象
trigger_config = TriggerConfig()
# 处理每个触发策略
for strategy_str, trigger_info in trigger_data["triggers"].items():
try:
strategy = TriggerStrategy(strategy_str)
enabled = trigger_info[0] if isinstance(trigger_info, list) and len(trigger_info) > 0 else False
config_data = trigger_info[1] if isinstance(trigger_info, list) and len(trigger_info) > 1 else None
# 根据触发策略创建对应的配置对象
if strategy == TriggerStrategy.KEYWORD and config_data:
config = KeywordTriggerConfig(**config_data)
elif strategy == TriggerStrategy.RAG and config_data:
config = RAGTriggerConfig(**config_data)
elif strategy == TriggerStrategy.CONDITION and config_data:
config = ConditionTriggerConfig(**config_data)
else:
config = None
# 设置触发策略
trigger_config.set_trigger(strategy, enabled, config)
except Exception as e:
logger.warning(f"处理触发策略 {strategy_str} 失败: {str(e)}")
continue
# 设置触发配置
entry_data["trigger_config"] = trigger_config
valid_fields = WorldItem.Entry.model_fields.keys()
filtered_data = {k: v for k, v in entry_data.items() if k in valid_fields}
# 更新条目
success = world_book.update_entry(uid, **filtered_data)
if not success:
raise HTTPException(status_code=500, detail="更新条目失败")
# 保存世界书
world_book.save()
# 获取更新后的条目
entry = world_book.get_entry(uid)
logger.info(f"更新世界书 {name} 的条目: UID={uid}")
return entry.to_dict()
except HTTPException:
raise
except Exception as e:
logger.error(f"更新世界书 {name} 的条目 {uid} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"更新世界书条目失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.delete("/{name}/entries/{uid}")
async def delete_worldbook_entry(name: str, uid: int):
"""
删除世界书的指定条目
Args:
name: 世界书名称
uid: 条目 UID
Returns:
Dict[str, Any]: 删除结果
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 加载世界书
world_book = WorldBook.load(name)
# 删除条目
success = world_book.remove_entry(uid)
if not success:
raise HTTPException(status_code=404, detail=f"条目 UID {uid} 不存在")
# 保存世界书
world_book.save()
logger.info(f"删除世界书 {name} 的条目: UID={uid}")
return {"success": True, "message": f"条目 UID {uid} 已删除"}
except HTTPException:
raise
except Exception as e:
logger.error(f"删除世界书 {name} 的条目 {uid} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"删除世界书条目失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.post("/{name}/import", response_model=Dict[str, Any])
async def import_worldbook(name: str, file: UploadFile = File(...)):
"""
从文件导入世界书
Args:
name: 世界书名称
file: 上传的文件SillyTavern 格式)
Returns:
Dict[str, Any]: 导入的世界书数据
"""
try:
# 保存临时文件
temp_path = os.path.join(settings.WORLDBOOKS_PATH, f"temp_{file.filename}")
with open(temp_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
try:
# 从文件加载世界书
world_book = WorldBook.load(Path(temp_path).stem)
# 如果世界书已存在,合并条目
if WorldBook.exists(name):
existing_book = WorldBook.load(name)
existing_book.merge_from_book(world_book)
# 保存合并后的世界书
existing_book.save()
world_book = existing_book
logger.info(f"导入并合并世界书: {name}")
else:
# 设置名称并保存
world_book.name = name
world_book.save()
logger.info(f"导入新世界书: {name}")
return world_book.to_dict()
finally:
# 删除临时文件
if os.path.exists(temp_path):
os.remove(temp_path)
except Exception as e:
logger.error(f"导入世界书 {name} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"导入世界书失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")
@router.get("/{name}/export")
async def export_worldbook(name: str):
"""
导出世界书为 SillyTavern 格式
Args:
name: 世界书名称
Returns:
FileResponse: 导出的文件
"""
try:
# 检查世界书是否存在
if not WorldBook.exists(name):
raise HTTPException(status_code=404, detail=f"世界书 '{name}' 不存在")
# 加载世界书
world_book = WorldBook.load(name)
# 创建导出文件路径
export_path = os.path.join(settings.WORLDBOOKS_PATH, f"export_{name}.json")
# 导出为 SillyTavern 格式
world_book.to_sillytavern_json(export_path)
logger.info(f"导出世界书: {name}")
# 返回文件
return FileResponse(
path=export_path,
filename=f"{name}.json",
media_type="application/json"
)
except HTTPException:
raise
except Exception as e:
logger.error(f"导出世界书 {name} 失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"导出世界书失败: {str(e)}")
raise HTTPException(status_code=501, detail="Not Implemented")