feat: cache repo list to disk, make search bar a local filter only
- list_all_repos() now saves results to .repo_cache.json on first fetch; subsequent startups load from cache instantly with no HF API calls - GET /modeldownloader/repos?force=1 bypasses cache (used by Refresh button) - Search bar is now a pure client-side filter (repo name/id matching); no API calls on typing — use Search Files or Search HuggingFace buttons for API-backed searches Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e01919e59f
commit
229f136dc4
@@ -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)
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -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
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -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 = [
|
||||
|
||||
+14
-31
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user