"""Settings registry for KikoTools. This module provides a centralized settings management system for all KikoTools. Tools can register their settings, which are then exposed in ComfyUI's settings UI. """ import json import os from typing import Dict, Any, List, Optional, Union from dataclasses import dataclass, field @dataclass class SettingDefinition: """Definition of a single setting.""" id: str name: str type: str # "boolean", "combo", "number", "string", "custom" default: Any description: Optional[str] = None options: Optional[Union[List[Any], Dict[str, Any]]] = None min_value: Optional[float] = None max_value: Optional[float] = None step: Optional[float] = None on_change: Optional[str] = None # JavaScript callback as string @dataclass class ToolSettings: """Settings collection for a single tool.""" tool_name: str display_name: str settings: List[SettingDefinition] = field(default_factory=list) class SettingsRegistry: """Central registry for all KikoTools settings.""" def __init__(self): self.tools: Dict[str, ToolSettings] = {} self.settings_by_id: Dict[str, SettingDefinition] = {} def register_tool_settings( self, tool_name: str, display_name: str, settings: Dict[str, Dict[str, Any]] ) -> None: """Register settings for a tool. Args: tool_name: Internal tool identifier (e.g., "embedding_autocomplete") display_name: Display name for the tool (e.g., "Embedding Autocomplete") settings: Dictionary of setting configurations { "enabled": { "type": "boolean", "default": True, "description": "Enable embedding autocomplete" }, "max_suggestions": { "type": "combo", "default": 20, "options": [10, 20, 50], "description": "Maximum number of suggestions" } } """ tool_settings = ToolSettings(tool_name, display_name) for setting_key, config in settings.items(): # Generate fully qualified setting ID setting_id = f"kikotools.{tool_name}.{setting_key}" # Create display name with branding setting_name = f"🫶 {display_name}: {setting_key.replace('_', ' ').title()}" setting_def = SettingDefinition( id=setting_id, name=setting_name, type=config.get("type", "string"), default=config.get("default"), description=config.get("description"), options=config.get("options"), min_value=config.get("min"), max_value=config.get("max"), step=config.get("step"), on_change=config.get("on_change"), ) tool_settings.settings.append(setting_def) self.settings_by_id[setting_id] = setting_def self.tools[tool_name] = tool_settings def get_setting(self, setting_id: str) -> Optional[SettingDefinition]: """Get a setting definition by ID.""" return self.settings_by_id.get(setting_id) def get_tool_settings(self, tool_name: str) -> Optional[ToolSettings]: """Get all settings for a tool.""" return self.tools.get(tool_name) def generate_frontend_registration(self) -> str: """Generate JavaScript code for frontend settings registration.""" js_lines = [ "// Auto-generated KikoTools settings registration", "// This file is automatically generated by the settings registry", "", "import { app } from '../../scripts/app.js';", "", "app.registerExtension({", " name: 'kikotools.settings',", " async init() {", " // Register all KikoTools settings", ] for tool_name, tool_settings in self.tools.items(): js_lines.append(f" // {tool_settings.display_name} settings") for setting in tool_settings.settings: js_lines.append(" app.ui.settings.addSetting({") js_lines.append(f' id: "{setting.id}",') js_lines.append(f' name: "{setting.name}",') js_lines.append( f" defaultValue: {self._js_value(setting.default)}," ) js_lines.append(f' type: "{setting.type}",') if setting.description: js_lines.append(f' tooltip: "{setting.description}",') if setting.type == "combo" and setting.options: js_lines.append(" options: (value) => {") js_lines.append( f" const options = {json.dumps(setting.options)};" ) js_lines.append(" return options.map(opt => ({") js_lines.append(" value: opt,") js_lines.append(" text: String(opt),") js_lines.append(" selected: opt === value") js_lines.append(" }));") js_lines.append(" }},") if setting.type == "number": if setting.min_value is not None: js_lines.append(f" min: {setting.min_value},") if setting.max_value is not None: js_lines.append(f" max: {setting.max_value},") if setting.step is not None: js_lines.append(f" step: {setting.step},") if setting.on_change: js_lines.append(" onChange(value) {") js_lines.append(f" {setting.on_change}") js_lines.append(" }") js_lines.append(" }});") js_lines.append("") js_lines.extend([" }", "});", ""]) return "\n".join(js_lines) def _js_value(self, value: Any) -> str: """Convert Python value to JavaScript literal.""" if isinstance(value, bool): return "true" if value else "false" elif isinstance(value, str): return f'"{value}"' elif value is None: return "null" else: return str(value) def save_frontend_settings( self, output_path: str = "web/js/kikoSettings.js" ) -> None: """Save the generated frontend settings to a file.""" js_content = self.generate_frontend_registration() os.makedirs(os.path.dirname(output_path), exist_ok=True) with open(output_path, "w") as f: f.write(js_content) def get_all_settings(self) -> Dict[str, Any]: """Get all registered settings as a dictionary.""" result = {} for tool_name, tool_settings in self.tools.items(): result[tool_name] = { "display_name": tool_settings.display_name, "settings": { setting.id.split(".")[-1]: { "type": setting.type, "default": setting.default, "description": setting.description, "options": setting.options, } for setting in tool_settings.settings }, } return result