diff --git a/downloader_routes.py b/downloader_routes.py index 8607c27..d69d8ec 100644 --- a/downloader_routes.py +++ b/downloader_routes.py @@ -29,9 +29,12 @@ from .model_manager import ( async def handle_list_repos(request: web.Request) -> web.Response: - """GET /modeldownloader/repos β€” sorted by lastModified desc.""" + """GET /modeldownloader/repos β€” sorted by lastModified desc. + Pass ?force=1 to bypass the disk cache and re-fetch from HuggingFace. + """ + force = request.rel_url.query.get("force", "0") == "1" loop = asyncio.get_event_loop() - repos = await loop.run_in_executor(None, list_all_repos) + repos = await loop.run_in_executor(None, list_all_repos, force) return web.json_response(repos) diff --git a/model_manager.py b/model_manager.py index f555542..4234c27 100644 --- a/model_manager.py +++ b/model_manager.py @@ -2,6 +2,7 @@ Model Manager: HuggingFace + GitHub repo scanning, local model scanning, file classification, downloading. """ +import json import os import re import threading @@ -11,6 +12,8 @@ import requests from pathlib import Path from typing import Optional +_REPO_CACHE_FILE = Path(__file__).parent / ".repo_cache.json" + try: from huggingface_hub import HfApi HF_AVAILABLE = True @@ -539,7 +542,14 @@ def get_specific_repo_info(repo_id: str) -> dict: } -def list_all_repos() -> list[dict]: +def list_all_repos(force: bool = False) -> list[dict]: + # Return cached result unless a forced refresh is requested + if not force and _REPO_CACHE_FILE.exists(): + try: + return json.loads(_REPO_CACHE_FILE.read_text(encoding="utf-8")) + except Exception: + pass + all_repos: list[dict] = [] seen: set[str] = set() for author in TRACKED_AUTHORS: @@ -553,6 +563,13 @@ def list_all_repos() -> list[dict]: seen.add(repo_id) # Sort by lastModified descending (blank strings sort last) all_repos.sort(key=lambda r: r.get("lastModified") or "", reverse=True) + + # Persist to disk cache + try: + _REPO_CACHE_FILE.write_text(json.dumps(all_repos, ensure_ascii=False), encoding="utf-8") + except Exception: + pass + return all_repos diff --git a/pyproject.toml b/pyproject.toml index ae2d1c6..4eda005 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "comfyui-advanced-model-manager" -version = "1.0.6" +version = "1.0.7" description = "Advanced Model Manager for ComfyUI β€” browse, search and download models from HuggingFace and GitHub workflows directly from the ComfyUI interface." license = { file = "LICENSE" } dependencies = [ diff --git a/web/js/modeldownloader.js b/web/js/modeldownloader.js index 78e2418..e0ec5b7 100644 --- a/web/js/modeldownloader.js +++ b/web/js/modeldownloader.js @@ -206,7 +206,7 @@ class ModelDownloaderDialog { // Search box this.searchInput = el("input",{ - type:"text",placeholder:"πŸ” Search repos or files…", + type:"text",placeholder:"πŸ” Filter repos…", style:{flex:"1",minWidth:"160px",padding:"6px 12px", background:"#0d1117",border:"1px solid #30363d", borderRadius:"8px",color:"#e0e0e0",fontSize:"13px"}, @@ -327,7 +327,7 @@ class ModelDownloaderDialog { this.leftList.innerHTML=""; try { const [rr,lr] = await Promise.all([ - fetch("/modeldownloader/repos"), + fetch(`/modeldownloader/repos${force ? "?force=1" : ""}`), fetch("/modeldownloader/local_models"), ]); S.repos = await rr.json(); @@ -437,37 +437,20 @@ class ModelDownloaderDialog { // ── Search ───────────────────────────────────────────────────────────────── _onSearch() { + // Search bar is always a local filter only. + // API searches are triggered exclusively via the dedicated buttons. S.hfSearchResults = null; - const q = S.searchQuery.trim(); + S.fileSearchResults = null; + this._renderLeft(); - // LOCAL mode: just filter in place - if (S.browseMode === MODE_LOCAL) { - S.fileSearchResults = null; - this._renderLeft(); - if (S.localCategory) this._renderLocalFiles(S.localCategory); - return; - } - - // WORKFLOW mode: just filter in place - if (S.browseMode === MODE_WORKFLOW) { - S.fileSearchResults = null; - this._renderLeft(); - if (S.githubWorkflowFiles) this._renderGithubWorkflows(S.githubWorkflowFiles); - return; - } - - // Empty query β†’ clear search, go back to normal view - if (!q) { - S.fileSearchResults = null; - this._renderLeft(); - if (S.selectedRepo && S.repoFiles[S.selectedRepo]) - this._renderFileList(S.repoFiles[S.selectedRepo]); - else this._showPlaceholder(true); - return; - } - - // Otherwise auto-search across all repos - this._triggerFileSearch(); + if (S.browseMode === MODE_LOCAL && S.localCategory) + this._renderLocalFiles(S.localCategory); + else if (S.browseMode === MODE_WORKFLOW && S.githubWorkflowFiles) + this._renderGithubWorkflows(S.githubWorkflowFiles); + else if (S.selectedRepo && S.repoFiles[S.selectedRepo]) + this._renderFileList(S.repoFiles[S.selectedRepo]); + else if (!S.searchQuery.trim()) + this._showPlaceholder(true); } async _triggerFileSearch() {