重构后端成功
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user