lora sheet update

This commit is contained in:
2026-06-28 15:22:52 -07:00
parent 2639829e43
commit 76ade4f768

View File

@@ -15,6 +15,11 @@ import struct
import sys import sys
from pathlib import Path from pathlib import Path
import hashlib
import time
from urllib.error import HTTPError, URLError
from urllib.request import urlopen, Request
def read_safetensors_meta(filepath): def read_safetensors_meta(filepath):
try: try:
@@ -127,7 +132,106 @@ def html_id():
return hashlib.md5(str(time.monotonic_ns()).encode()).hexdigest()[:8] return hashlib.md5(str(time.monotonic_ns()).encode()).hexdigest()[:8]
def generate_html(loras): # --- Source URL lookup cache ---
CACHE_DIR = os.path.expanduser("~/.cache")
CACHE_FILE = os.path.join(CACHE_DIR, "lora-sources.json")
def load_source_cache():
if os.path.exists(CACHE_FILE):
try:
with open(CACHE_FILE) as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return {}
return {}
def save_source_cache(cache):
os.makedirs(CACHE_DIR, exist_ok=True)
with open(CACHE_FILE, "w") as f:
json.dump(cache, f, indent=2)
def file_blake2b_hash(filepath):
h = hashlib.blake2b(digest_size=32)
with open(filepath, "rb") as f:
while True:
chunk = f.read(65536)
if not chunk:
break
h.update(chunk)
return h.hexdigest()
def _fetch_json(url, timeout=10):
req = Request(url, headers={"User-Agent": "lora-trigger-sheet/1.0"})
with urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read().decode())
def lookup_source(filepath, filename, cache):
now = int(time.time())
cached = cache.get(filename)
if cached:
url = cached.get("url")
label = cached.get("label")
if url and (now - cached.get("last_checked", 0)) < 86400 * 30:
return url, label
# CivitAI model-version lookup by BLAKE2b file hash
try:
h = file_blake2b_hash(filepath)
data = _fetch_json(
f"https://civitai.com/api/v1/model-versions/by-hash/{h}"
)
if isinstance(data, dict):
model_id = data.get("modelId")
version_id = data.get("id")
if model_id:
url = f"https://civitai.com/models/{model_id}"
if version_id:
url += f"?modelVersionId={version_id}"
cache[filename] = {
"url": url, "label": "CivitAI",
"model_name": data.get("name", ""),
"last_checked": now,
}
return url, "CivitAI"
except (HTTPError, URLError, json.JSONDecodeError, OSError):
pass
# Fallback: HuggingFace model search by filename
try:
q = (
filename.replace(".safetensors", "")
.replace("_", " ")
.replace("-", " ")
)
data = _fetch_json(
"https://huggingface.co/api/models"
f"?search={q}&sort=downloads&direction=-1&limit=3"
)
if isinstance(data, list) and len(data) > 0:
model_id = data[0].get("modelId", "")
if model_id:
url = f"https://huggingface.co/{model_id}"
cache[filename] = {
"url": url, "label": "HuggingFace",
"model_name": model_id, "last_checked": now,
}
return url, "HuggingFace"
except (HTTPError, URLError, json.JSONDecodeError, OSError):
pass
cache[filename] = {"url": None, "label": None, "last_checked": now}
return None, None
def generate_html(loras, sources=None):
if sources is None:
sources = {}
rows = [] rows = []
no_trigger_files = [] no_trigger_files = []
@@ -165,18 +269,24 @@ def generate_html(loras):
no_trigger_files.append((fname_no_ext, tool, base)) no_trigger_files.append((fname_no_ext, tool, base))
triggers = "<span class=\"n\">(none)</span>" triggers = "<span class=\"n\">(none)</span>"
rows.append((fname_no_ext, tool, base, triggers)) src_url, src_label = sources.get(fname, (None, None))
rows.append((fname_no_ext, tool, base, triggers, src_url or "", src_label or ""))
num_files = len(rows) num_files = len(rows)
table_rows = "" table_rows = ""
for fname, tool, base, triggers in rows: for fname, tool, base, triggers, src_url, src_label in rows:
if src_url:
src_cell = f'<a href="{_e(src_url)}" target="_blank" rel="noopener">{_e(src_label)}</a>'
else:
src_cell = '<span class="n">—</span>'
table_rows += ( table_rows += (
f" <tr>\n" f" <tr>\n"
f" <td class=\"fn\">{_e(fname)}</td>\n" f" <td class=\"fn\">{_e(fname)}</td>\n"
f" <td class=\"tl\">{_e(tool)}</td>\n" f" <td class=\"tl\">{_e(tool)}</td>\n"
f" <td class=\"bm\">{_e(base)}</td>\n" f" <td class=\"bm\">{_e(base)}</td>\n"
f" <td class=\"tr\">{triggers}</td>\n" f" <td class=\"tr\">{triggers}</td>\n"
f" <td class=\"sr\">{src_cell}</td>\n"
f" </tr>\n" f" </tr>\n"
) )
@@ -209,8 +319,9 @@ tr:hover td{{background:#161b22}}
.ml:hover{{border-bottom:1px solid #58a6ff}} .ml:hover{{border-bottom:1px solid #58a6ff}}
.ht{{display:none}} .ht{{display:none}}
mark.hl{{background:#264f78;color:#f0f6fc;border-radius:2px;padding:0 2px}} mark.hl{{background:#264f78;color:#f0f6fc;border-radius:2px;padding:0 2px}}
.c0{{width:26%}}.c1{{width:9%}}.c2{{width:13%}}.c3{{width:52%}} .sr a{{color:#58a6ff;text-decoration:none;font-size:0.78rem}}
@media(max-width:768px){{.c1,.c2,td:nth-child(2),td:nth-child(3){{display:none}}.c0{{width:30%}}.c3{{width:70%}}}} .c0{{width:24%}}.c1{{width:8%}}.c2{{width:12%}}.c3{{width:48%}}.c4{{width:8%}}
@media(max-width:768px){{.c1,.c2,.c4,td:nth-child(2),td:nth-child(3),td:nth-child(5){{display:none}}.c0{{width:30%}}.c3{{width:70%}}}}
.foot{{margin-top:20px;color:#8b949e}} .foot{{margin-top:20px;color:#8b949e}}
.foot h2{{font-size:1rem;color:#f0f6fc;margin-bottom:6px}} .foot h2{{font-size:1rem;color:#f0f6fc;margin-bottom:6px}}
.foot ul{{list-style:none;columns:3;font-size:0.82rem}} .foot ul{{list-style:none;columns:3;font-size:0.82rem}}
@@ -225,6 +336,7 @@ mark.hl{{background:#264f78;color:#f0f6fc;border-radius:2px;padding:0 2px}}
<th class="c1" onclick="s(1)">Tool <span class="ar"></span></th> <th class="c1" onclick="s(1)">Tool <span class="ar"></span></th>
<th class="c2" onclick="s(2)">Base <span class="ar"></span></th> <th class="c2" onclick="s(2)">Base <span class="ar"></span></th>
<th class="c3" onclick="s(3)">Trigger(s) <span class="ar"></span></th> <th class="c3" onclick="s(3)">Trigger(s) <span class="ar"></span></th>
<th class="c4" onclick="s(4)">Source <span class="ar"></span></th>
</tr></thead><tbody id="b"> </tr></thead><tbody id="b">
{table_rows}</tbody></table> {table_rows}</tbody></table>
@@ -354,7 +466,20 @@ def main():
meta = read_safetensors_meta(fpath) meta = read_safetensors_meta(fpath)
loras.append((fname, meta)) loras.append((fname, meta))
html = generate_html(loras) # Look up source URLs with disk-backed cache
cache = load_source_cache()
sources = {}
total = len(loras)
for i, (fname, _) in enumerate(loras, 1):
fpath = os.path.join(target, fname)
url, label = lookup_source(fpath, fname, cache)
if url:
sources[fname] = (url, label)
if i % 10 == 0 or i == total:
print(f" source lookup: {i}/{total}", file=sys.stderr)
save_source_cache(cache)
html = generate_html(loras, sources)
if len(args) >= 2: if len(args) >= 2:
out_path = os.path.abspath(os.path.expanduser(args[1])) out_path = os.path.abspath(os.path.expanduser(args[1]))
@@ -364,7 +489,8 @@ def main():
with open(out_path, "w") as f: with open(out_path, "w") as f:
f.write(html) f.write(html)
print(f"Generated {out_path} with {len(loras)} LoRA entries") out_count = sum(1 for v in sources.values() if v[0])
print(f"Generated {out_path} with {len(loras)} LoRA entries ({out_count} with source links)")
if __name__ == "__main__": if __name__ == "__main__":