Files
DemonGatanjieu-Anomalous_Mo…/api/ai_sealed_models.py
T
DemonGatanjieuandClaude Fable 5.2 99ee111c81 Give a private task's AI the gist to read, keep its model names as tokens
Privacy mode left the AI blind twice over: the whole prompt was a token, so
it could not help with light, lens or style, and model and LoRA file names
still went out as written.

- The prompt box has a first field, the gist: the picture in words the user
  writes for the AI to read. seal_answer hands it on as written beside the
  tokens of the private prompts; either alone can be handed in.
- ai_sealed_models.py: while the Gates window's "Model names in a private
  task" is on (the default), each model of the library is one token ([M1],
  sealed as its model_id) however an answer writes it: model_id, loader
  name, file name in a text, card name. A model's own entry drops name,
  file, folder, links and hash and keeps type, base model, the author's
  settings and the user's note. expand writes a model token back as the
  model_id, as the loader's name among a node's values, as the card's name
  inside the AI's words. The task's menu lists what each token stands for.
- The model editor has "Note for the AI" (ai_note, note_for_ai in the
  tools, searched by search_models): what a model is for, in words the AI
  may read.
- A combo's name is sealed too: unless renamed it is made of its model's
  name and the start of its prompt.
- The privacy skill says how to write around a gist and how to pick and
  pass on model tokens.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 18:36:20 +08:00

115 lines
5.8 KiB
Python

"""Model names of a private task as tokens, while the user keeps those too (ai_privacy.hides_models):
a file name, its folder, the name on its card and its pages can say as much as a prompt does. Each
model of the library is one token ("[M1]", sealed in the task as its model_id, ai_sealed.py) however
a tool's answer writes it: its model_id, the name a loader lists it by, its file name inside a text;
its card's name where a name stands (NAME_KEYS), and inside a text when it is more than one plain
word (a model called "Anima" is not taken out of every sentence about Anima). A model's own entry
(anything with a model_id) loses what tells it apart (ENTRY_DROPS); its type, base model, author's
settings and the user's note for the AI stay, to pick by. A model file the library does not have
(one a workflow names) is a token of its own. written() and named() are the way back: the model_id
where a tool takes one, the loader's name among a node's values, the card's name inside the AI's words."""
import os
import re
from . import ai_toolbox_catalog
MODEL_FILE = (".safetensors", ".gguf", ".ckpt", ".pt", ".pth", ".bin", ".sft", ".onnx")
MODEL_ID = re.compile(r"[\w-]+:\d+:.+")
LOOSE = re.compile(r"[^\s\"'<>|*?,;()\[\]{}]+\.(?:safetensors|gguf|ckpt|pth|pt|bin|sft|onnx)\b", re.IGNORECASE)
ENTRY_DROPS = ("name", "file", "folder", "rel", "cover", "civitai_name", "version", "civitai_url", "download_link", "sha256")
NAME_KEYS = {"name", "names", "more_names", "model", "models", "main_model", "loras", "label", "labels"}
PLAIN_KEYS = {"type", "kind", "base", "base_model", "baseModel", "family", "source"} # never a model's name
WIDGET_KEYS = {"values", "settings", "inputs", "widgets", "widgets_values"} # a node's values: models by their loader's names
MAX_NAME = 260
def _library():
"""The library's models [{id, name, file}] as the materials picker lists them (read at most every few seconds)."""
return ai_toolbox_catalog.everything()["models"]
def _distinct(name):
"""Whether card name `name` is looked for inside a text: not one plain word, which a sentence may use as itself."""
return len(name) >= 4 and not (name.isascii() and name.isalpha())
def _cut(words):
"""The model whose card name begins with `words`, a name a tool cut short (mcp_fit.NAME), or None."""
return next((model["id"] for model in _library() if str(model["name"] or "").startswith(words)), None) if len(words) >= 20 else None
def _gather(value, out):
if isinstance(value, dict):
for key, item in value.items():
out.append(str(key))
_gather(item, out)
elif isinstance(value, list):
for item in value:
_gather(item, out)
elif isinstance(value, str):
out.append(value)
def _forms(blob):
"""What the strings of an answer (`blob`) may call the library's models: {form: model_id} for anywhere
in a text, {name: model_id} for where a name stands. Only the models `blob` can be naming."""
anywhere, names = {}, {}
for model in _library():
shown, stem = str(model["name"] or ""), os.path.splitext(model["file"])[0]
if not any(form and form in blob for form in (shown, stem)):
continue
place = model["id"].split(":", 2)[2]
for form in (model["id"], place, place.replace("/", "\\"), model["file"]):
anywhere.setdefault(form, model["id"])
for form in (shown, stem):
if form:
names.setdefault(form, model["id"])
if _distinct(form):
anywhere.setdefault(form, model["id"])
return anywhere, names
def hide(value, seals):
"""Tool answer `value` (its data, or a text of it) with every model as its token of `seals`."""
strings = []
_gather(value, strings)
blob = "\n".join(strings)
anywhere, names = _forms(blob)
found = sorted((form for form in anywhere if form in blob), key=len, reverse=True)
# A form counts where it stands by itself: "anima.safetensors" is not taken out of "my_anima.safetensors".
known = re.compile(r"(?<![\w.-])(?:" + "|".join(re.escape(form) for form in found) + r")(?!\w)") if found else None
def text(words, key):
if key in PLAIN_KEYS:
return words
whole = anywhere.get(words) or ((names.get(words) or _cut(words)) if key in NAME_KEYS else None)
if whole:
return seals.seal(whole, "M")
swapped = known.sub(lambda match: seals.seal(anywhere[match.group(0)], "M"), words) if known else words
if swapped == words and words.lower().endswith(MODEL_FILE) and len(words) <= MAX_NAME and "\n" not in words and ": " not in words:
return seals.seal(words, "M") # a model file the library does not have, spaces in its name and all
return LOOSE.sub(lambda match: seals.seal(match.group(0), "M"), swapped)
def walk(item, key=""):
if isinstance(item, dict):
entry = isinstance(item.get("model_id"), str)
return {text(name, "") if isinstance(name, str) else name: walk(part, name)
for name, part in item.items() if not (entry and name in ENTRY_DROPS)}
if isinstance(item, list):
return [walk(part, key) for part in item]
return text(item, key) if isinstance(item, str) else item
return walk(value)
def written(text, widget):
"""What the token of the model sealed as `text` puts into a call: the name its loader lists it by among
a node's values (`widget`), else the model_id the tools take."""
return text.split(":", 2)[2].replace("/", os.sep) if widget and MODEL_ID.fullmatch(text) else text
def named(text):
"""The card's name of the model sealed as `text`, for where the AI wrote its token inside its own words."""
found = next((model for model in _library() if model["id"] == text), None)
return str(found["name"] or found["file"]) if found else text