Files
SillyTavern_replica/backend/services/preset_service.py

204 lines
6.2 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.
"""
Preset Service
预设服务层 - 处理预设的 CRUD 操作
"""
import json
import os
from pathlib import Path
from typing import List, Dict, Any, Optional
from datetime import datetime
from core.config import settings
class PresetService:
"""预设服务类"""
@staticmethod
def _extract_preset_name_from_filename(filename: str) -> str:
"""
从文件名提取预设名称,去掉时间戳和文件后缀
Args:
filename: 文件名(不含路径)
Returns:
清理后的预设名称
Examples:
"Default.json" -> "Default"
"MyPreset_1234567890.json" -> "MyPreset"
"Test_1714567890123.json" -> "Test"
"""
# 去掉 .json 后缀
name = filename.replace('.json', '')
# 去掉末尾的时间戳(下划线+数字组合)
# 匹配模式_后面跟着10-13位数字Unix时间戳
import re
name = re.sub(r'_\d{10,13}$', '', name)
return name
@staticmethod
def _get_preset_path(name: str) -> Path:
"""获取预设文件路径"""
return settings.PRESET_PATH / f"{name}.json"
@staticmethod
def _load_preset(name: str) -> Optional[Dict[str, Any]]:
"""加载预设 JSON 文件"""
path = PresetService._get_preset_path(name)
if not path.exists():
return None
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
raise ValueError(f"Failed to load preset '{name}': {str(e)}")
@staticmethod
def _save_preset(name: str, data: Dict[str, Any]):
"""保存预设到 JSON 文件"""
path = PresetService._get_preset_path(name)
try:
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e:
raise ValueError(f"Failed to save preset '{name}': {str(e)}")
@staticmethod
def list_presets() -> List[Dict[str, Any]]:
"""
获取所有预设的列表(仅基本信息)
Returns:
预设列表,每个包含 name, description, component_count, temperature 等
"""
presets = []
for json_file in settings.PRESET_PATH.glob("*.json"):
try:
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# 计算组件数量
entries = data.get("entries", [])
prompts = data.get("prompts", [])
component_count = len(entries) if entries else len(prompts)
# 提取温度参数(支持内部结构和 SillyTavern 结构)
temperature = data.get("temperature", 1.0)
# 从文件名提取预设名称(去掉时间戳和后缀)
preset_name = PresetService._extract_preset_name_from_filename(json_file.name)
preset_info = {
"name": preset_name,
"description": data.get("description", ""),
"component_count": component_count,
"temperature": temperature
}
presets.append(preset_info)
except Exception as e:
print(f"Error loading preset {json_file.name}: {e}")
continue
# 按名称排序
presets.sort(key=lambda x: x.get("name", ""))
return presets
@staticmethod
def get_preset(name: str) -> Dict[str, Any]:
"""
获取指定预设的完整数据
Args:
name: 预设名称
Returns:
预设完整数据
"""
data = PresetService._load_preset(name)
if not data:
raise FileNotFoundError(f"Preset '{name}' not found")
return data
@staticmethod
def create_preset(name: str, preset_data: Dict[str, Any]) -> Dict[str, Any]:
"""
创建新预设
Args:
name: 预设名称
preset_data: 预设数据
Returns:
创建的预设数据
"""
# 检查是否已存在
if PresetService._get_preset_path(name).exists():
raise ValueError(f"Preset '{name}' already exists")
# 确保有必要的字段
if "name" not in preset_data:
preset_data["name"] = name
# 添加时间戳
now = int(datetime.now().timestamp())
if "createdAt" not in preset_data:
preset_data["createdAt"] = now
if "updatedAt" not in preset_data:
preset_data["updatedAt"] = now
PresetService._save_preset(name, preset_data)
return preset_data
@staticmethod
def update_preset(name: str, update_data: Dict[str, Any]) -> Dict[str, Any]:
"""
更新预设
Args:
name: 预设名称
update_data: 要更新的数据
Returns:
更新后的预设数据
"""
data = PresetService._load_preset(name)
if not data:
raise FileNotFoundError(f"Preset '{name}' not found")
# 更新字段
for key, value in update_data.items():
if key not in ["name", "createdAt"]: # 不允许修改名称和创建时间
data[key] = value
# 更新时间戳
data["updatedAt"] = int(datetime.now().timestamp())
PresetService._save_preset(name, data)
return data
@staticmethod
def delete_preset(name: str) -> bool:
"""
删除预设
Args:
name: 预设名称
Returns:
是否删除成功
"""
path = PresetService._get_preset_path(name)
if not path.exists():
raise FileNotFoundError(f"Preset '{name}' not found")
path.unlink()
return True