diff --git a/.gitignore b/.gitignore index 7b7a344..c75cae6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,3 @@ Thumbs.db *.bak *.cache -# User settings – contains API keys and tokens, must NOT be committed -model_resolver_settings.json diff --git a/__init__.py b/__init__.py index d22c5f1..f899eaa 100644 --- a/__init__.py +++ b/__init__.py @@ -1603,67 +1603,6 @@ class ModelResolverExtension: ) return web.json_response({"error": str(e)}, status=500) - # ==================== SETTINGS (server-side persistence) ==================== - - import os as _os - import json as _json - - _SETTINGS_FILE = _os.path.join( - _os.path.dirname(_os.path.abspath(__file__)), - "model_resolver_settings.json", - ) - - # Keys that contain sensitive credentials – stored in the file but - # never logged. - _CREDENTIAL_KEYS = { - "civitai_key", - "civitai_session_token", - "hf_token", - "brave_search_api_key", - } - - def _load_settings() -> dict: - try: - if _os.path.isfile(_SETTINGS_FILE): - with open(_SETTINGS_FILE, "r", encoding="utf-8") as f: - data = _json.load(f) - if isinstance(data, dict): - return data - except Exception as exc: - self.logger.warning(f"Model Resolver: could not read settings file: {exc}") - return {} - - def _save_settings(payload: dict) -> None: - # Merge with existing settings so we never lose keys not sent - # by the current request. - current = _load_settings() - current.update({k: v for k, v in payload.items() if k}) - with open(_SETTINGS_FILE, "w", encoding="utf-8") as f: - _json.dump(current, f, indent=2, ensure_ascii=False) - - @routes.get("/model_resolver/settings") - async def get_settings_route(request): - """Return persisted settings (API keys, preferences).""" - try: - data = await asyncio.to_thread(_load_settings) - return web.json_response(data) - except Exception as e: - self.logger.error(f"Model Resolver settings GET error: {e}", exc_info=True) - return web.json_response({"error": str(e)}, status=500) - - @routes.post("/model_resolver/settings") - async def save_settings_route(request): - """Persist settings (API keys, preferences) to disk.""" - try: - payload = await request.json() - if not isinstance(payload, dict): - return web.json_response({"error": "Expected JSON object"}, status=400) - await asyncio.to_thread(_save_settings, payload) - return web.json_response({"success": True}) - except Exception as e: - self.logger.error(f"Model Resolver settings POST error: {e}", exc_info=True) - return web.json_response({"error": str(e)}, status=500) - self.routes_setup = True self.logger.info("Model Resolver: API routes registered successfully") return True diff --git a/core/__init__.py b/core/__init__.py index 9c66869..c1b1b4d 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -13,7 +13,7 @@ Modules: from .log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception from .log_system.logger import logger -from .resolver import analyze_and_find_matches, apply_resolution +from .linker import analyze_and_find_matches, apply_resolution from .scanner import get_model_files from .matcher import find_matches diff --git a/web/resolver.js b/web/resolver.js index 17d2da0..1161ce1 100644 --- a/web/resolver.js +++ b/web/resolver.js @@ -5,14 +5,14 @@ */ import { app } from "../../../scripts/app.js"; -import { ModelResolver as ModelResolverClass } from "./resolver/model_resolver.js"; +import { ModelResolver } from "./resolver/model_resolver.js"; import { registerGlobalHelpers } from "./resolver/globals.js"; registerGlobalHelpers(); -const modelResolver = new ModelResolverClass(); +const ModelResolver = new ModelResolver(); app.registerExtension({ name: "Model Resolver", - setup: modelResolver.setup + setup: ModelResolver.setup }); diff --git a/web/resolver/search/download_target_methods.js b/web/resolver/search/download_target_methods.js index fee46ba..c252478 100644 --- a/web/resolver/search/download_target_methods.js +++ b/web/resolver/search/download_target_methods.js @@ -484,55 +484,6 @@ export const downloadTargetMethods = { }; }, - /** - * Fetch settings saved on the server and sync them into localStorage. - * Call this once when the dialog initialises so every browser gets the - * same tokens without the user having to re-enter them. - */ - async loadSettingsFromServer() { - try { - const resp = await api.fetchApi('/model_resolver/settings'); - if (!resp.ok) return; - const data = await resp.json(); - if (!data || typeof data !== 'object') return; - - // Helper: write to localStorage only when the value from server is - // non-empty, so we don't overwrite a key the user already has locally. - const sync = (localKey, serverValue) => { - if (serverValue !== undefined && serverValue !== null && serverValue !== '') { - localStorage.setItem(localKey, serverValue); - } - }; - - sync('ModelResolver.civitaiApiKey', data.civitai_key); - sync('ModelResolver.civitaiSessionToken', data.civitai_session_token); - sync('ModelResolver.huggingFaceToken', data.hf_token); - sync('ModelResolver.braveSearchApiKey', data.brave_search_api_key); - - if (data.civitai_use_trpc_search !== undefined) - localStorage.setItem('ModelResolver.civitaiUseTrpcSearch', data.civitai_use_trpc_search ? 'true' : 'false'); - if (data.civitai_use_html_fallback !== undefined) - localStorage.setItem('ModelResolver.civitaiUseHtmlFallback', data.civitai_use_html_fallback ? 'true' : 'false'); - if (data.hf_use_api_search !== undefined) - localStorage.setItem('ModelResolver.hfUseApiSearch', data.hf_use_api_search ? 'true' : 'false'); - if (data.hf_use_comfy_org_fallback !== undefined) - localStorage.setItem('ModelResolver.hfUseComfyOrgFallback', data.hf_use_comfy_org_fallback ? 'true' : 'false'); - if (data.hf_use_brave_fallback !== undefined) - localStorage.setItem('ModelResolver.hfUseBraveFallback', data.hf_use_brave_fallback ? 'true' : 'false'); - if (data.civitai_candidate_limit !== undefined) - localStorage.setItem('ModelResolver.civitaiCandidateLimit', `${data.civitai_candidate_limit}`); - - // Source-enabled flags stored as a nested object - if (data.search_source_enabled && typeof data.search_source_enabled === 'object') { - Object.entries(data.search_source_enabled).forEach(([key, val]) => { - if (key) localStorage.setItem(key, val ? 'true' : 'false'); - }); - } - } catch (err) { - console.warn('Model Resolver: could not load settings from server, using localStorage only.', err); - } - }, - async clearSearchCaches() { for (const state of this.searchResultCache.values()) { state.activeSearchRunId = null; diff --git a/web/resolver/shell/lifecycle_graph_methods.js b/web/resolver/shell/lifecycle_graph_methods.js index 5c43ad6..bf70677 100644 --- a/web/resolver/shell/lifecycle_graph_methods.js +++ b/web/resolver/shell/lifecycle_graph_methods.js @@ -23,13 +23,6 @@ export const lifecycleGraphMethods = { // Update button state in case there are active downloads this.updateDownloadAllButtonState(); - // On first open: load settings from server so tokens are available on - // every browser without re-entering them. - if (!this._serverSettingsLoaded) { - this._serverSettingsLoaded = true; - await this.loadSettingsFromServer(); - } - // Ensure all models are loaded for dropdown await this.ensureCapabilitiesLoaded(); await this.ensureAllModelsLoaded(); diff --git a/web/resolver/views/options_methods.js b/web/resolver/views/options_methods.js index f6e488a..ce45f1e 100644 --- a/web/resolver/views/options_methods.js +++ b/web/resolver/views/options_methods.js @@ -717,68 +717,34 @@ export const optionsMethods = { const civitaiCandidateLimit = Number.isFinite(civitaiCandidateLimitRaw) ? Math.min(20, Math.max(1, civitaiCandidateLimitRaw)) : 5; - - // Collect source-enabled flags - const sourceEnabled = {}; + localStorage.setItem('ModelResolver.civitaiApiKey', civitaiInput?.value || ''); + localStorage.setItem('ModelResolver.civitaiSessionToken', civitaiSessionInput?.value || ''); + localStorage.setItem('ModelResolver.civitaiUseTrpcSearch', civitaiUseTrpcSearchInput?.checked ? 'true' : 'false'); + localStorage.setItem('ModelResolver.civitaiUseHtmlFallback', civitaiUseHtmlFallbackInput?.checked ? 'true' : 'false'); + localStorage.setItem('ModelResolver.huggingFaceToken', hfInput?.value || ''); + localStorage.setItem('ModelResolver.braveSearchApiKey', braveInput?.value || ''); + localStorage.setItem('ModelResolver.hfUseApiSearch', hfUseApiSearchInput?.checked ? 'true' : 'false'); + localStorage.setItem('ModelResolver.hfUseComfyOrgFallback', hfUseComfyOrgFallbackInput?.checked ? 'true' : 'false'); + localStorage.setItem('ModelResolver.hfUseBraveFallback', hfUseBraveFallbackInput?.checked ? 'true' : 'false'); + localStorage.setItem('ModelResolver.civitaiCandidateLimit', `${civitaiCandidateLimit}`); if (sourceEnabledInputs.length && !sourceEnabledInputs.some(input => input.checked)) { const localInput = sourceEnabledInputs.find(input => input.dataset.source === 'local'); - if (localInput) localInput.checked = true; + if (localInput) { + localInput.checked = true; + } } sourceEnabledInputs.forEach((input) => { const key = input.dataset.storageKey; if (key) { - sourceEnabled[key] = input.checked; + localStorage.setItem(key, input.checked ? 'true' : 'false'); } }); - - const newSettings = { - civitai_key: civitaiInput?.value || '', - civitai_session_token: civitaiSessionInput?.value || '', - civitai_use_trpc_search: Boolean(civitaiUseTrpcSearchInput?.checked), - civitai_use_html_fallback: Boolean(civitaiUseHtmlFallbackInput?.checked), - civitai_candidate_limit: civitaiCandidateLimit, - hf_token: hfInput?.value || '', - hf_use_api_search: Boolean(hfUseApiSearchInput?.checked), - hf_use_comfy_org_fallback: Boolean(hfUseComfyOrgFallbackInput?.checked), - hf_use_brave_fallback: Boolean(hfUseBraveFallbackInput?.checked), - brave_search_api_key: braveInput?.value || '', - search_source_enabled: sourceEnabled, - }; - - // 1. Write to localStorage (fast local cache) - localStorage.setItem('ModelResolver.civitaiApiKey', newSettings.civitai_key); - localStorage.setItem('ModelResolver.civitaiSessionToken', newSettings.civitai_session_token); - localStorage.setItem('ModelResolver.civitaiUseTrpcSearch', newSettings.civitai_use_trpc_search ? 'true' : 'false'); - localStorage.setItem('ModelResolver.civitaiUseHtmlFallback', newSettings.civitai_use_html_fallback ? 'true' : 'false'); - localStorage.setItem('ModelResolver.huggingFaceToken', newSettings.hf_token); - localStorage.setItem('ModelResolver.braveSearchApiKey', newSettings.brave_search_api_key); - localStorage.setItem('ModelResolver.hfUseApiSearch', newSettings.hf_use_api_search ? 'true' : 'false'); - localStorage.setItem('ModelResolver.hfUseComfyOrgFallback', newSettings.hf_use_comfy_org_fallback ? 'true' : 'false'); - localStorage.setItem('ModelResolver.hfUseBraveFallback', newSettings.hf_use_brave_fallback ? 'true' : 'false'); - localStorage.setItem('ModelResolver.civitaiCandidateLimit', `${civitaiCandidateLimit}`); - Object.entries(sourceEnabled).forEach(([key, val]) => { - localStorage.setItem(key, val ? 'true' : 'false'); - }); - if (civitaiLimitInput) civitaiLimitInput.value = `${civitaiCandidateLimit}`; - - // 2. Persist to server (survives new browsers / profiles) - try { - setStatus('Saving…', ''); - const resp = await api.fetchApi('/model_resolver/settings', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newSettings), - }); - if (!resp.ok) throw new Error(`HTTP ${resp.status}`); - setStatus('Saved on this machine (all browsers).', 'is-saved'); - this.showNotification('Options saved', 'success'); - } catch (err) { - console.warn('Model Resolver: could not save settings to server, kept locally only.', err); - setStatus('Saved locally only (server write failed).', 'is-saved'); - this.showNotification('Options saved locally only', 'warning'); + if (civitaiLimitInput) { + civitaiLimitInput.value = `${civitaiCandidateLimit}`; } - await this.clearSearchCaches(); + setStatus('Options saved locally.', 'is-saved'); + this.showNotification('Options saved and search cache cleared', 'success'); }); }