lora sheet update
This commit is contained in:
@@ -15,6 +15,11 @@ import struct
|
||||
import sys
|
||||
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):
|
||||
try:
|
||||
@@ -127,7 +132,106 @@ def html_id():
|
||||
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 = []
|
||||
no_trigger_files = []
|
||||
|
||||
@@ -165,18 +269,24 @@ def generate_html(loras):
|
||||
no_trigger_files.append((fname_no_ext, tool, base))
|
||||
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)
|
||||
|
||||
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 += (
|
||||
f" <tr>\n"
|
||||
f" <td class=\"fn\">{_e(fname)}</td>\n"
|
||||
f" <td class=\"tl\">{_e(tool)}</td>\n"
|
||||
f" <td class=\"bm\">{_e(base)}</td>\n"
|
||||
f" <td class=\"tr\">{triggers}</td>\n"
|
||||
f" <td class=\"sr\">{src_cell}</td>\n"
|
||||
f" </tr>\n"
|
||||
)
|
||||
|
||||
@@ -209,8 +319,9 @@ tr:hover td{{background:#161b22}}
|
||||
.ml:hover{{border-bottom:1px solid #58a6ff}}
|
||||
.ht{{display:none}}
|
||||
mark.hl{{background:#264f78;color:#f0f6fc;border-radius:2px;padding:0 2px}}
|
||||
.c0{{width:26%}}.c1{{width:9%}}.c2{{width:13%}}.c3{{width:52%}}
|
||||
@media(max-width:768px){{.c1,.c2,td:nth-child(2),td:nth-child(3){{display:none}}.c0{{width:30%}}.c3{{width:70%}}}}
|
||||
.sr a{{color:#58a6ff;text-decoration:none;font-size:0.78rem}}
|
||||
.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 h2{{font-size:1rem;color:#f0f6fc;margin-bottom:6px}}
|
||||
.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="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="c4" onclick="s(4)">Source <span class="ar"></span></th>
|
||||
</tr></thead><tbody id="b">
|
||||
{table_rows}</tbody></table>
|
||||
|
||||
@@ -354,7 +466,20 @@ def main():
|
||||
meta = read_safetensors_meta(fpath)
|
||||
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:
|
||||
out_path = os.path.abspath(os.path.expanduser(args[1]))
|
||||
@@ -364,7 +489,8 @@ def main():
|
||||
with open(out_path, "w") as f:
|
||||
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__":
|
||||
|
||||
Reference in New Issue
Block a user