""" 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