# APNext context sockets for the Claude Code nodes. # # The APNext generator nodes (APNext Time, Scene, Poses, Plots, Feelings, # Cinematic, Science, Geography, Architecture, ...) all emit a plain STRING. # Wired straight into an `idea` field they are just words; the writer cannot # tell a time period from a pose. These helpers give every Claude Code node a # row of `context_N` sockets and, using ComfyUI's hidden PROMPT / UNIQUE_ID # inputs, look up *which* node feeds each socket. The category then selects a # short instruction on how that kind of input should steer the scene, and the # whole thing is handed to Claude as a labelled context block. # # Everything degrades gracefully: with no graph access the text is still used, # labelled as generic context. import re MAX_CONTEXT = 8 CONTEXT_SOCKETS = tuple(f"context_{i}" for i in range(1, MAX_CONTEXT + 1)) HIDDEN_GRAPH = "apnext_graph" HIDDEN_NODE_ID = "apnext_node_id" # Widgets on the APNext generator nodes that are not category fields. _NON_FIELD_INPUTS = {"prompt", "separator", "string", "seed", "attributes"} _INACTIVE_VALUES = {"None", "", None} # How each APNext category should steer a video prompt. Keys are the data/next # folder names (== the node's `_subcategory`); a few extra keys cover our own # nodes and the fallback. CATEGORY_GUIDANCE = { "time": ( "Period, era, decade or time of day. Make it visible: period-correct wardrobe, " "props, vehicles, technology, signage and architecture; the light and colour of " "that hour. State the time of day in every shot's opening line and keep it " "consistent across shots." ), "scene": ( "Setting and environment: location type, weather, textures, plants, modifiers. " "Use it as the place the action happens - describe it concretely in [Shot 1] " "and keep its light, weather and materials consistent." ), "poses": ( "Physical staging of the subject(s): body position, gesture, portrait pose. Turn " "it into what the character is physically doing at the start of the shot and how " "they move out of it; never leave a pose as a static hold." ), "plots": ( "Story beats and genre. Dramatise them as on-screen events - cause and effect, " "an arc across the shots (and across scenes when several are written). Show, " "don't narrate." ), "feelings": ( "Emotional tone. Express it through performance, delivery, body language, " "lighting temperature, colour, pacing and sound - not by naming the emotion." ), "cinematic": ( "Camera and film language: shot type, genre, effects, colour grading, film stock, " "director/movie references. Translate into the stated visual style, framing, " "camera moves and grade; a director or movie reference means their observable " "craft (lens, palette, blocking, rhythm), never a name-drop." ), "photography": ( "Photographic look: camera, lens, film, lighting setup, grade. Fold into the " "visual style and lighting description as observable qualities (depth of field, " "grain, key/fill, colour)." ), "science": ( "Scientific subject matter (astronomy, elements, mathematics, medical). Use it as " "accurate, concrete visual content - real phenomena, instruments, environments - " "and get the details right." ), "geography": ( "Country, nationality, territory. Set the place and its real look: landscape, " "architecture, climate, signage language, people and dress typical of it." ), "architecture": ( "Buildings, interiors, materials, architectural styles. Use as the built " "environment - describe structure, materials, scale and light behaviour " "concretely; keep it consistent across shots." ), "art": "Artistic medium, style, palette, technique. Drives the stated visual style and how surfaces and light are rendered.", "artist": "Named artists / illustrators as style anchors. Translate into their observable visual traits (palette, line, light, composition); do not just name them.", "brands": "Brand names. Show them as products, logos on props or wardrobe, storefronts and signage (in double quotes when text is readable), only where the idea allows.", "character": "Character archetypes / fictional figures. Cast them as the on-screen subject(s) with a concrete physical description, wardrobe and behaviour.", "fashion": "Wardrobe, accessories, hairstyles, designers. Dress the subjects accordingly and restate the wardrobe anchors in every shot.", "human": "Occupations, hobbies, groups, festivities. Use to define who the people are, what they are doing and the social setting.", "interaction": "How people interact (couples, groups, crowds, individuals). Stage the physical interaction beat by beat, with clear positions.", "keywords": "Loose modifiers / genre / trending keywords. Treat as flavour: fold the useful ones into style, mood and detail; ignore anything that contradicts the idea.", "people": "Archetypes, body types, expressions, eye colour. Use as concrete physical description of the subject(s), restated per shot.", "stuff": "Objects and props (everyday, seasonal, sci-fi, gadgets). Put them in the scene as physical props the characters handle or that dress the set.", "typography": "Fonts, word art, text styles. Only for readable on-screen text (titles, signs) - write the text in double quotes and describe the lettering style.", "vehicle": "Vehicles. Put them in the scene as physical objects (make, era, colour, condition) that are driven, ridden or parked, with correct scale.", "video_game": "Games, engines, designers, actions. Use as visual/gameplay reference: the look, the world, the kind of action - not UI overlays unless asked.", # Our own nodes "cast": "Cast lines `Character (played by Actor) from Show`. These are the characters to write for; use the strings verbatim in subject_definitions and play them in character.", "h3_prompt": "An existing H3 prompt. Treat it as reference material for style, subjects and continuity - do not copy it back unless asked to revise it.", "context": "Free-form context. Use whatever in it helps make the scene concrete and consistent; the idea/direction still leads.", } _KNOWN_CLASSES = { "H3Characters": "cast", "H3ClaudeCodeBaseWriter": "h3_prompt", "H3ClaudeCodeRefWriter": "h3_prompt", "H3ClaudeCodeRefiner": "h3_prompt", "H3ClaudeCodeContinueWriter": "h3_prompt", "H3BasePromptWriter": "h3_prompt", "H3RefPromptWriter": "h3_prompt", "H3ScenePick": "h3_prompt", } def context_inputs(): """The optional context sockets, to merge into a node's `optional` inputs.""" return { name: ("STRING", { "forceInput": True, "tooltip": ( "Steering input from another APNext node (Time, Scene, Poses, Plots, " "Feelings, Cinematic, Science, Geography, Architecture, Fashion, ...). " "The node detects which kind it is and tells Claude how to use it. " "Sockets grow as you connect them." ), }) for name in CONTEXT_SOCKETS } def context_hidden_inputs(): """Hidden inputs that let the node inspect the graph it runs in.""" return {HIDDEN_GRAPH: "PROMPT", HIDDEN_NODE_ID: "UNIQUE_ID"} def pop_context(kwargs): """ Remove context sockets and hidden graph inputs from a **kwargs dict, so other consumers of that dict (typed references, image slots) stay clean. Returns (context_values_by_socket, graph, node_id). """ values = {name: kwargs.pop(name, None) for name in CONTEXT_SOCKETS} graph = kwargs.pop(HIDDEN_GRAPH, None) node_id = kwargs.pop(HIDDEN_NODE_ID, None) return values, graph, node_id def _category_for(class_type): if not class_type: return "context", "" if class_type in _KNOWN_CLASSES: return _KNOWN_CLASSES[class_type], class_type if class_type.endswith("PromptNode"): stem = class_type[: -len("PromptNode")] category = re.sub(r"(?= 1 and isinstance(graph, dict): src = graph.get(str(link[0])) or {} category, source = _category_for(src.get("class_type")) if source.startswith("APNext "): fields = _active_fields(src.get("inputs")) entries.append({ "socket": socket, "category": category, "source": source, "fields": fields, "text": text.strip(), "guidance": CATEGORY_GUIDANCE.get(category, CATEGORY_GUIDANCE["context"]), }) return entries def context_block(entries, target="the scene"): """The labelled block appended to a Claude Code user prompt; "" if nothing is wired.""" if not entries: return "" lines = [ "CONTEXT FROM CONNECTED APNEXT NODES", f"Each item below is a steering input of a specific kind. Reconcile them into one " f"coherent version of {target}; the user's own idea/direction still leads. Where an " "input lists several options, pick what fits and make it concrete and visible on " "screen. Never paste an input's wording back verbatim as a list of tags - translate " "it into filmed detail.", ] for i, e in enumerate(entries, 1): header = f"{i}. [{e['category']}]" if e["source"]: header += f" from {e['source']}" if e["fields"]: header += f" (fields: {', '.join(e['fields'])})" lines.append(header) lines.append(f" Input: {e['text']}") lines.append(f" Use it as: {e['guidance']}") return "\n".join(lines) def build_context(kwargs, target="the scene"): """ One-call helper for node functions: pops the sockets from **kwargs, resolves them against the graph and returns (block_text, entries). """ values, graph, node_id = pop_context(kwargs) entries = resolve_context(values, graph, node_id) return context_block(entries, target), entries def with_context(user_prompt, block): """Append the context block to a finished user prompt (before nothing else).""" if not block: return user_prompt return f"{user_prompt.rstrip()}\n\n{block}\n" def context_summary(entries): """Short log line: 'time, scene, poses' or 'none'.""" return ", ".join(e["category"] for e in entries) or "none"