homepage: Protoss rename + fused Model Information component
The model cards become the StarCraft trio: frank-large -> frank-dragoon, frank-primary -> frank-archon, frank-secondary -> frank-zealot (family names only; endpoint/API ids unchanged). The three cards fuse into one Model Information component: per-unit Protoss accent colours and hex- clipped unit art, 15px type, clickable StarCraft unit-voice quotations (original .ogg files from the fandom wiki, served from /sfx/), supervision notes (dragoon/archon safe for autonomous use, zealot tight supervision), compaction-model recommendation for frank-zealot, and uncensored-model legal notes rendered as hover tooltips instead of wall text. docs.html carries the same renames plus api-id mapping. App-card updates: Seerr, Calibre-Web, RomM, Jellyfin set to WIP: DOWN with Open links removed (new wip "down" badge state); Copyparty notes it serves all other media apps content including WIP apps; Gitea and Plex noted as needing logins separate from SSO; SearXNG noted as obfuscated, not anonymous (upstream API keys tie to Frank). Plumbing: Caddy @data allowlist grew /sfx/*; deploy_homepage copies sfx/; homepage_config.bats pins the new probe names, the component fields, art/voice-file presence, and the wip-down schema.
This commit is contained in:
1 parent
74da746c51
commit
203b0be145
19 files changed
+312
-88
No files matched your search
+36
-25
@@ -3,7 +3,7 @@
|
||||
# and the pages render. It must be a JSON list; every entry carries name,
|
||||
# category, section, and a host:port probe. category is user or admin;
|
||||
# section must be one the page's SECTION_ORDER renders. method, when
|
||||
# present, is tcp or http. wip, when set, is "open" or "closed".
|
||||
# present, is tcp or http. wip, when set, is "open", "closed" or "down".
|
||||
|
||||
load helpers
|
||||
|
||||
@@ -24,7 +24,7 @@ for svc in data:
|
||||
if "method" in svc:
|
||||
assert svc["method"] in ("tcp", "http"), svc["method"]
|
||||
wip = svc.get("wip")
|
||||
assert wip is False or wip in ("open", "closed"), f"{svc['name']} wip must be false, 'open' or 'closed'"
|
||||
assert wip is False or wip in ("open", "closed", "down"), f"{svc['name']} wip must be false, 'open', 'closed' or 'down'"
|
||||
host, _, port = svc["probe"].rpartition(":")
|
||||
assert host and port.isdigit(), f"bad probe {svc['probe']}"
|
||||
PY
|
||||
@@ -61,36 +61,47 @@ PY
|
||||
|
||||
@test "the LLM status cards track the fleet, not retired service names" {
|
||||
# This drifted twice: the card probing Dankserver's :7770 was still named
|
||||
# frank-small long after frank-medium took that endpoint, and the endpoint
|
||||
# itself has since been frank-medium (radiance), frank-primary (lucebox)
|
||||
# and frank-primary (llama.cpp). The probe target is what matters; the
|
||||
# name has to follow it.
|
||||
# frank-small long after frank-medium took that endpoint; the endpoints
|
||||
# were renamed to the Protoss set on 2026-10-08. The probe target is what
|
||||
# matters; the name has to follow it.
|
||||
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
||||
import json, sys
|
||||
services = json.load(open(sys.argv[1]))
|
||||
by_probe = {s["probe"]: s["name"] for s in services}
|
||||
primary = by_probe.get("192.168.0.235:7770")
|
||||
assert primary == "frank-primary", f"Dankserver :7770 card is named {primary!r}"
|
||||
# Dankclaw serves two endpoints on two ports: frank-large (unsloth llama.cpp
|
||||
# on the iGPU, 7770) and frank-secondary (llama.cpp on the RTX 3090, 7771).
|
||||
assert by_probe.get("127.0.0.1:7770") == "frank-large", by_probe.get("127.0.0.1:7770")
|
||||
assert by_probe.get("127.0.0.1:7771") == "frank-secondary", by_probe.get("127.0.0.1:7771")
|
||||
assert by_probe.get("192.168.0.235:7770") == "frank-archon", by_probe.get("192.168.0.235:7770")
|
||||
assert by_probe.get("127.0.0.1:7770") == "frank-dragoon", by_probe.get("127.0.0.1:7770")
|
||||
assert by_probe.get("127.0.0.1:7771") == "frank-zealot", by_probe.get("127.0.0.1:7771")
|
||||
PY
|
||||
}
|
||||
|
||||
@test "every fleet LLM card carries a user-facing equivalence note" {
|
||||
# Frank's capability framing (2026-10-08): each card must state, in plain
|
||||
# terms, what the model is equivalent to — the cards' whole purpose for
|
||||
# family users is "how smart is this and what is it for".
|
||||
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
||||
import json, sys
|
||||
@test "the Model Information component is complete: text, art, voices, warnings" {
|
||||
# The fused model panel is the family's only description of the fleet.
|
||||
# Every model card must carry: equivalence note, supervision level, the
|
||||
# unit art, at least one playable voice line, and (for uncensored models)
|
||||
# the legal warning. The compaction recommendation must name the zealot.
|
||||
python3 - "$REPO_ROOT/shared/homepage/services.json" "$REPO_ROOT/shared/homepage" <<'PY'
|
||||
import json, os, sys
|
||||
services = json.load(open(sys.argv[1]))
|
||||
fleet = {s["name"]: s for s in services if s.get("name") in ("frank-large", "frank-primary", "frank-secondary")}
|
||||
assert len(fleet) == 3, sorted(fleet)
|
||||
for name, s in fleet.items():
|
||||
eq = s.get("details", {}).get("equiv", "")
|
||||
assert eq.strip(), f"{name} card has no equivalence note"
|
||||
root = sys.argv[2]
|
||||
fleet = [s for s in services if "model" in s]
|
||||
assert {s["name"] for s in fleet} == {"frank-dragoon", "frank-archon", "frank-zealot"}, [s["name"] for s in fleet]
|
||||
for s in fleet:
|
||||
m = s["model"]
|
||||
assert m["equiv"].strip(), f"{s['name']} has no equivalence note"
|
||||
assert m["supervision"].strip(), f"{s['name']} has no supervision note"
|
||||
assert m["sfx"], f"{s['name']} has no voice line"
|
||||
assert os.path.isfile(os.path.join(root, "img", "unit-" + m["unit"] + ".jpg")), f"{s['name']} art missing"
|
||||
for f in m["sfx"] + m.get("voice", {}).get("sfx", []):
|
||||
assert os.path.isfile(os.path.join(root, "sfx", f)), f"{s['name']} voice {f} missing"
|
||||
zealot = next(s for s in fleet if s["name"] == "frank-zealot")
|
||||
assert "compaction" in zealot["model"]["use"].lower(), "zealot must be recommended as compaction model"
|
||||
assert "supervision" in zealot["model"]["supervision"].lower()
|
||||
for s in fleet:
|
||||
m = s["model"]
|
||||
uncensored = "ncensored" in m["spec"] or "bliterated" in m["spec"]
|
||||
if uncensored:
|
||||
assert "legal" in m.get("warn", "").lower(), f"{s['name']} is uncensored but has no legal warning"
|
||||
PY
|
||||
grep -q "desc-equiv" "$REPO_ROOT/shared/homepage/app.js"
|
||||
grep -q "desc-equiv" "$REPO_ROOT/shared/homepage/style.css"
|
||||
grep -q "renderModelInfo" "$REPO_ROOT/shared/homepage/app.js"
|
||||
grep -q ".mi-card" "$REPO_ROOT/shared/homepage/style.css"
|
||||
}
|
||||
Reference in new issue
Block a user