Files
Dumitru Picalau 8d0fa337b2 chore: fixes from the 2026-10-21 review (#93)
Shorter release notes, SeedList help, small-model docs; _chat_turn split and pyright in CI; ESLint a11y ratchet and Modal focus trap; tags/models/Image Filter tests; AI LAN warning, web allow expiry, one thinking setting per answer on local models, recommended hints and Reset to recommended (16k context minimum, 32k recommended); Assembler Clear template clears the negative; Vue 3.5.44, SECURITY and Dependabot.
2026-10-22 06:15:13 +02:00

117 lines
4.9 KiB
Python

"""How the user builds: a short note on the library's own conventions.
The chat and the pack planner read it so what they make extends the user's
system instead of starting a parallel one: options written the way the
user writes them (tags or sentences, how long), the tags and axis names
they already use, the variables their stacks set, the standing negatives in
their templates, and the wildcards worth reusing. Computed from the rows
the route read; nothing is stored.
"""
from __future__ import annotations
import re
from collections import Counter
from typing import Any
MAX_REUSE = 25
MAX_TAGS = 30
def option_style(texts: list[str]) -> str | None:
""""tags" when most options are comma lists of short pieces,
"sentences" when they read as phrases; None with too few to tell."""
texts = [t for t in texts if isinstance(t, str) and t.strip() and "{" not in t]
if len(texts) < 6:
return None
taggy = 0
for t in texts:
parts = [p.strip() for p in t.split(",") if p.strip()]
words = len(t.split())
if "_" in t or (len(parts) > 1 and words / len(parts) <= 3) or words <= 2:
taggy += 1
share = taggy / len(texts)
if share >= 0.65:
return "tags"
if share <= 0.35:
return "sentences"
return None
def _words(texts: list[str]) -> float:
lens = [len(t.split()) for t in texts if isinstance(t, str) and t.strip()]
return sum(lens) / len(lens) if lens else 0.0
def summarize(modules: list[dict[str, Any]], bundles: list[dict[str, Any]] | None = None,
templates: list[dict[str, Any]] | None = None) -> dict[str, Any]:
"""``{style, words, tags, axes, vars, negatives, reuse}``."""
wilds = [m for m in modules if m.get("type") == "wildcard"
and isinstance(m.get("payload"), dict)]
texts: list[str] = []
tags: Counter[str] = Counter()
axes: Counter[str] = Counter()
reuse: list[dict[str, Any]] = []
for m in wilds:
p = m["payload"]
opts = [o for o in p.get("options") or [] if isinstance(o, dict)]
texts += [v for o in opts if isinstance(v := o.get("value"), str)]
for o in opts:
tags.update(t for t in o.get("sub_categories") or [] if isinstance(t, str))
axes.update(k for k in (p.get("tag_groups") or {}) if isinstance(k, str))
if p.get("var_binding") and len(opts) >= 4:
reuse.append({"name": m.get("name") or "", "var": p["var_binding"],
"count": len(opts), "tags": sorted(
{t for o in opts for t in o.get("sub_categories") or []
if isinstance(t, str)})[:8]})
var_use: Counter[str] = Counter()
for b in bundles or []:
for c in b.get("children") or []:
p = c.get("payload") if isinstance(c, dict) else None
if isinstance(p, dict):
v = p.get("var_binding") or p.get("output_var")
if isinstance(v, str) and v:
var_use[v] += 1
negatives: Counter[str] = Counter()
for t in templates or []:
neg = t.get("negative_template")
if isinstance(neg, str):
for part in re.split(r"[,\n]", neg):
part = part.strip()
if part and not part.startswith("$"):
negatives[part.lower()] += 1
reuse.sort(key=lambda r: -r["count"])
return {
"style": option_style(texts),
"words": round(_words(texts), 1),
"tags": [t for t, _ in tags.most_common(MAX_TAGS)],
"axes": [a for a, _ in axes.most_common(10)],
"vars": [v for v, _ in var_use.most_common(12)],
"negatives": [n for n, _ in negatives.most_common(12)],
"reuse": reuse[:MAX_REUSE],
}
def note(summary: dict[str, Any], *, reuse: bool = True) -> str:
"""The summary as a few lines for a prompt. Empty for an empty library."""
lines: list[str] = []
if summary["style"]:
how = ("comma-separated tags" if summary["style"] == "tags"
else "natural phrases")
lines.append(f"Options are written as {how}, about {summary['words']:g} words each.")
if summary["tags"]:
lines.append("Option tags in use: " + ", ".join(summary["tags"]))
if summary["axes"]:
lines.append("Tag groups (axes) in use: " + ", ".join(summary["axes"]))
if summary["vars"]:
lines.append("Variables their stacks set most: $" + ", $".join(summary["vars"]))
if summary["negatives"]:
lines.append("Standing negatives in their templates: " + ", ".join(summary["negatives"]))
if reuse and summary["reuse"]:
lines.append("Wildcards they have (name · $var · options · tags):")
lines += [f"- {r['name']} · ${r['var']} · {r['count']}"
+ (f" · {', '.join(r['tags'])}" if r["tags"] else "")
for r in summary["reuse"]]
if not lines:
return ""
return "The user's library (match its conventions; reuse what fits):\n" + "\n".join(lines)