The message box stays at the bottom of the task column however the timeline scrolls. Over the input, what the message carries (ui_ai_tray.js): pictures with ✎ to mark up (sent on their own) and ×, chips for what "@" names; gallery pictures are attached, not typed in as "@name". Beside it the gallery and materials buttons with their counts; the input grows (Enter sends, Shift+Enter breaks the line) and pictures alone may be sent. "New steps below" appears when the AI adds steps while the user reads further up; one's own message scrolls into view. Pictures a message carries show as small image cards with the usual tools. The ready page keeps the big gallery and what is ready; the box below takes its pictures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
3.7 KiB
Python
89 lines
3.7 KiB
Python
"""What the user points at in a message to the AI ("@" in the AI workbench's message box,
|
|
web/modules/ui_ai_mention.js): models, combos, workflows, prompts, node types, custom node packs
|
|
and output pictures, by the names the AI's tools use. Checked when the message is left (clean)
|
|
and given to the AI with it (message_of): each thing as get_toolbox gives a pick (lookup), the
|
|
pictures attached with what made them, as ai_pictures.py hands them over, so the AI needs no
|
|
guessing which "that LoRA" is."""
|
|
|
|
from . import ai_pictures
|
|
from .mcp_tools import ToolError, _resolve_image
|
|
|
|
KINDS = ("models", "combos", "workflows", "prompts", "nodes", "packs", "pictures")
|
|
MAX_REFS = 12
|
|
MAX_NAME = 300
|
|
|
|
|
|
class RefsError(ValueError):
|
|
pass
|
|
|
|
|
|
def clean(refs):
|
|
"""[{kind, id, label}] from the page: known kinds, text names, each thing once, at most MAX_REFS."""
|
|
if refs in (None, []):
|
|
return []
|
|
if not isinstance(refs, list) or len(refs) > MAX_REFS:
|
|
raise RefsError(f"refs: a list of up to {MAX_REFS} things")
|
|
out = []
|
|
for ref in refs:
|
|
if not isinstance(ref, dict) or ref.get("kind") not in KINDS:
|
|
raise RefsError(f"refs: each is {{kind, id}} with kind one of {', '.join(KINDS)}")
|
|
name = ref.get("id")
|
|
if not isinstance(name, str) or not name.strip() or len(name) > MAX_NAME:
|
|
raise RefsError(f"refs: an id is text of up to {MAX_NAME} characters")
|
|
label = str(ref.get("label") or name).strip()[:MAX_NAME]
|
|
item = {"kind": ref["kind"], "id": name.strip(), "label": label}
|
|
if not any(have["kind"] == item["kind"] and have["id"] == item["id"] for have in out):
|
|
out.append(item)
|
|
return out
|
|
|
|
|
|
def lookup(kind, name):
|
|
"""A combo, workflow, prompt or model as the AI's tools give it, or None when it is gone."""
|
|
from . import mcp_tools
|
|
|
|
if kind in ("combos", "workflows"):
|
|
lister = mcp_tools.list_combos if kind == "combos" else mcp_tools.list_workflows
|
|
return next(iter(lister(name, 1, exact_name=True)[kind]), None)
|
|
if kind == "prompts":
|
|
from .ai_toolbox_catalog import everything
|
|
|
|
return next((entry for entry in everything()["prompts"] if entry["id"] == name), None)
|
|
if kind == "models":
|
|
try:
|
|
found = mcp_tools.get_model(name)
|
|
except ToolError:
|
|
return None
|
|
return found[0] if isinstance(found, tuple) else found # without its cover picture
|
|
return None
|
|
|
|
|
|
def message_of(step):
|
|
"""The refs of a message step for the AI ("refs", each in full or marked missing) and the
|
|
pictures' files in "_pictures" for ai_marks.image_items to attach."""
|
|
items, files = [], []
|
|
for ref in step.get("refs") or []:
|
|
item = {"kind": ref["kind"], "id": ref["id"], "said_as": f"@{ref['label']}"}
|
|
if ref["kind"] == "pictures":
|
|
try:
|
|
rel, path = _resolve_image(ref["id"])[:2]
|
|
except ToolError:
|
|
item["missing"] = True
|
|
else:
|
|
item.update(image=rel, made_with=ai_pictures.brief(path))
|
|
files.append(path)
|
|
elif ref["kind"] == "nodes":
|
|
item["note"] = "a node type: find_nodes(types=[id]) gives it in full, add_node adds it"
|
|
elif ref["kind"] == "packs":
|
|
item["note"] = "a custom node pack (folder): its node types are those find_nodes lists with this source"
|
|
else:
|
|
found = lookup(ref["kind"], ref["id"])
|
|
if found is None:
|
|
item["missing"] = True
|
|
else:
|
|
item["details"] = found
|
|
items.append(item)
|
|
out = {"refs": items, "refs_note": "What the user's @ names in their text point at, and the pictures they attached, exactly."}
|
|
if files:
|
|
out["_pictures"] = files
|
|
return out
|