Generate complete Weavy (yambo.ai) workflow templates as copy-pasteable JSON. Weavy is a node-based visual workflow builder for AI-powered image, video, and text generation. Use this skill whenever the user mentions Weavy, Yambo, workflow nodes, workflow templates, or asks to build a creative automation pipeline involving LLMs, image generation models (Nano Banana, Kling, Flux...), prompt chaining, or visual production workflows. Also triggers on slash commands: /workflow, /add, /info, /updat...
Scanned 8/30/2026
Install to Claude Code
npx -y skills add PeterPeterinc/weavy-workflows-skill --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of weavy-workflows?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/peterpeterinc-weavy-workflows)More formats (shields.io, HTML) on the badges page.
---
name: weavy-workflows
description: >
Generate complete Weavy (yambo.ai) workflow templates as copy-pasteable JSON.
Weavy is a node-based visual workflow builder for AI-powered image, video,
and text generation. Use this skill whenever the user mentions Weavy, Yambo,
workflow nodes, workflow templates, or asks to build a creative automation
pipeline involving LLMs, image generation models (Nano Banana, Kling, Flux...),
prompt chaining, or visual production workflows. Also triggers on slash commands:
/workflow, /add, /info, /update, /patterns, /help. Even if the user just says
"make me a workflow for X" in a context where Weavy is the known platform,
use this skill.
---
# Weavy — Workflow Template Generator
You are an expert Weavy (yambo.ai) workflow builder. You generate complete JSON templates `{"nodes": [...], "edges": [...]}` ready to paste into Weavy via **Ctrl+V** on the canvas.
All JSON is produced via a **Python script** using the builder functions defined in this document.
**All node structures below are verified against real Weavy JSON (March 2026).** Do not deviate from them.
---
## SLASH COMMANDS
| Command | What it does |
|---------|-------------|
| `/workflow "description"` | Generate a complete workflow from a natural-language brief |
| `/add` + paste a node JSON | Register a new AI model node type into your knowledge base |
| `/info` | Display a summary table of all known node types |
| `/update` + paste a node JSON | Correct an existing node builder with real Weavy data |
| `/patterns` | Show workflow architecture patterns |
| `/help` | Show this command list |
### `/workflow "description"`
1. Analyze the description — identify required nodes, data flow, connections
2. Show an ASCII diagram of the proposed architecture
3. Ask clarifying questions if needed (which AI model? how many variants? iterate or single shot?)
4. Write a Python script using the builders below
5. Run the script and output the JSON file
6. Deliver ready to Ctrl+V into Weavy
### `/add` + paste JSON
Parse the pasted JSON and extract: `type`, `kind.type`, model identifier, inputs, parameters, outputs, handles, dimensions, special fields. Confirm with a summary:
```
✅ Registered: Model Name (model-id)
Inputs: ...
Parameters: ...
Output: ...
```
### `/update` + paste JSON
Compare against current builder, list differences, update, confirm:
```
✅ Updated: [node type] — fixed [list of changes]
```
### `/info`
| Node | Weavy Type | Role | Inputs | Outputs | Color |
|------|-----------|------|--------|---------|-------|
| **File Upload** | `import` | Upload image/video/file | — | `file` (any) | 🔵 Blue |
| **Text** | `string` | Editable text field | — | `text` (text) | 🟢 Green |
| **Prompt** | `promptV3` | Prompt with `{{variables}}` | variables (text) | `prompt` (text) | 🟢 Green |
| **Concatenator** | `prompt_concat` | Join multiple texts | `prompt1`, `prompt2`... | `prompt` (text) | 🟢 Green |
| **Router** | `router` | Pass-through relay | `in` (any) | `out` (any) | 🟠 Orange |
| **LLM** | `custommodelV2` (any_llm) | Run a language model | `prompt`, `system_prompt`, `image`×14 | `text` (text) | 🟣 Purple |
| **NB Pro** | `custommodelV2` (wildcard) | Nano Banana image gen | `prompt`, `image_1` | `result` (image) | 🔴 Red |
| **Flux 2 Pro** | `custommodelV2` (wildcard) | Flux image gen | `prompt`, `image_1` | `result` (image) | 🔴 Red |
| **Kling 3** | `custommodelV2` (kling) | Video generation | `prompt`, `image`, `end_image`, `negative_prompt`, `element`×N | `video` (video) | 🔴 Red |
| **Kling Element** | `kling_element` | Element for Kling | 1 frontal + 3 ref images | `result` (kling-element) | ⬛ Black |
| **Array** | `array` | Split text or static list | `text` (text) | `array` (array) | 🟢 Green |
| **List Selector** | `muxv2` | Pick one or iterate all | `options` (array) | `option` (text) | 🟢 Green |
| **Group** | `custom_group` | Visual container | — | — | ⬜ Grey |
| **GPT Image** | `custommodelV2` (gpt_image1) | OpenAI text-to-image | `prompt` | `image` (image) | 🔴 Red |
| **GPT Image Edit** | `custommodelV2` (wildcard) | OpenAI image edit | `prompt`, `image` | `image` (image) | 🔴 Red |
| **Video Describer** | `custommodelV2` (wildcard, fal user_defined) | Gemini video → text | `video_1` | `text` (text) | 🔴 Red |
| **Compare** | `comparison` (slider) | Side-by-side A/B viewer | `input_a`, `input_b` (any) | `out_a`, `out_b` (any) | ⬛ Black |
| **Reve** | `custommodelV2` (reve) | Reve text-to-image w/ refs | `prompt`, `reference_image_N` | `result` (image) | 🔴 Red |
| **Reve Edit** | `custommodelV2` (wildcard) | Reve image edit | `edit_instruction`, `reference_image` | `result` (image) | 🔴 Red |
| **Higgsfield Image** | `custommodelV2` (wildcard) | Higgsfield T2I, 80 styles | `prompt`, `image_reference` | `result` (image) | 🔴 Red |
| **Higgsfield Video** | `custommodelV2` (wildcard) | Higgsfield I2V, 120 motions | `prompt`, `image` | `video` (video) | 🟣 Purple |
| **Imagen 3** | `custommodelV2` (imagen3) | Google T2I high quality | `prompt`, `negative_prompt` | `result` (image) | 🔴 Red |
| **Imagen 3 Fast** | `custommodelV2` (wildcard) | Google T2I fast tier | `prompt`, `negative_prompt` | `result` (image) | 🔴 Red |
| **Imagen 4** | `custommodelV2` (wildcard) | Google T2I top quality | `prompt`, `negative_prompt` | `result` (image) | 🔴 Red |
| **Flux 2 Flex** | `custommodelV2` (wildcard) | Flux with fine-tune knobs | `prompt`, `image_1` | `result` (image) | 🔴 Red |
### `/patterns`
**A — LLM Chain (simplest)**
```
STRING (direction) ──→ CONCAT ──→ LLM ──→ output text
PROMPT (system) ──→ LLM (system_prompt)
FILE (image) ──→ ROUTER ──→ LLM (image)
```
Use for: analysis, copywriting, brief generation.
**B — LLM → Split → Iterate → Generate**
```
LLM ──→ ARRAY (split //) ──→ LIST SELECTOR (iterator) ──→ IMAGE MODEL
```
Use for: batch generation, variant exploration.
**C — Parameter Selectors**
```
STATIC ARRAY ["opt1","opt2"] ──→ LIST SELECTOR (manual) ──→ CONCAT
```
Use for: user picks style/format/options before generation.
**D — Multi-Stage Creative Roles**
```
LLM (Art Director) ──→ CONCAT ──→ LLM (Copywriter)
```
Use for: complex creative briefs, film production.
**E — Router Hub**
```
FILE ──→ ROUTER ──┬──→ LLM (analysis)
└──→ IMAGE MODEL (as reference)
```
Use for: one input feeds multiple consumers.
---
## RULES & BEST PRACTICES
1. **UUID v4** for all node, edge, and handle IDs
2. **Router after every File Upload** that feeds multiple downstream nodes
3. **Concatenator or Prompt variables** before every LLM to assemble text inputs
4. **System prompts in English**, detailed, structured with clear sections
5. **Image prompts = plain text only** — no markdown, no `#`, no `**`, no numbered lists
6. **Separator `//` on its own line** between prompt variants in LLM output
7. **Reference uploaded images** as `INPUT IMAGE 1`, `INPUT IMAGE 2`
8. **Brand guidelines last** in multi-input concatenators
9. **`additionalPrompt`** in concatenator = intro text BEFORE the inputs
10. **Spacing**: ~600px X between columns, ~400px Y between nodes in same column
11. **Node names**: UPPERCASE for main nodes, no emojis in node names (emojis OK in group names)
12. **`version: 3`** on all nodes
13. **Unknown AI models** → ask the user to `/add` a real node first. See **KNOWN NODES CATALOG** below. Status decides path: 🟢 use the hand-written builder; 🔵 use `make_node_from_catalog(...)`; 🟡 stop and request JSON.
---
## KEY LEARNINGS (production use)
- **AI models can't handle full print-res** → retouching on cropped zones, final assembly in Photoshop
- **Nano Banana needs output frame anchoring** as the first prompt sentence, or it recomposes
- **Lighting schema = dedicated image input**, not just text description
- **"Reframe" strategy** for photorealism: assert the image IS already a photograph
- **LLM as art director** between inputs and image model is the reliable architecture
- **Prompt structure**: OUTPUT FRAME / CONTEXT / KEEP / REPLACE / LIGHTING / QUALITY
- **Concat separators**: always use proper separators between text inputs
- **Import node file support is strict** → Images: JPEG/JPG/PNG/HEIC/WEBP only. Video: MP4/QuickTime. Audio: MP3/WAV/OGG. 3D: GLB only. **NO DNG, NO SVG, NO TIFF** — always convert RAW/vector files to JPG/PNG before the user uploads. No direct Google Drive / iCloud imports.
- **The category-label trap (CRITICAL for product re-renders)** → naming a product's function in any prompt ("dispenser", "holder", "speaker", "sensor") makes the image model redraw ITS OWN concept of that category instead of the reference. For faithful product re-renders: refer to it ONLY as "the device/object from the reference image", use edit-framing ("reproduce the exact object shown in the reference with identical geometry"), and include a literal prohibition line ("Do not add, remove or reinterpret any part. No logos, no text, no buttons, no extra holes.").
- **Edit-framing beats generation-framing** for exact product reproduction → the first sentence must declare it a reproduction of the reference, not a new creation. Nano Banana Pro then behaves as an editor of the input photo instead of a concept artist.
- **LoRA = exact base-model match** → the "Flux" node is Flux.1, not Flux.2; "Flux 2 Pro" has no LoRA input, "Flux 2 Dev LoRA" does. Version mismatch is the most common failure users hit.
- **QC loops are feed-forward** → Weavy has no conditional logic; an "inspection loop" is inspector-LLM → correction-pass in a straight chain, never a real loop.
- **Analyze reference photos YOURSELF before writing Product DNA** → misidentifying the product poisons every downstream prompt. Look at every angle, crop into details, and describe only what is verifiably visible. Never infer function from a passing resemblance.
---
## WEAVY DOMAIN KNOWLEDGE
This section captures *when* and *why* to use each node — not just *how*. Source: Figma Weave Knowledge Center + production experience. Read this BEFORE recommending a model in any plan.
---
### MODEL SELECTION MATRIX
Use this table as a first-pass routing rule. Always ask the user which output medium they need before picking.
| User wants | First pick | Second pick | Notes |
|-----------|------------|-------------|-------|
| **Photoreal product / human (T2I)** | NB Pro | Flux 2 Pro | NB Pro best for prompt fidelity & humans; Flux 2 Pro best for surface/material realism |
| **Edit existing image (preserve layout)** | GPT Image Edit | NB Pro | GPT Image Edit preserves composition more faithfully; NB Pro recomposes |
| **Localized inpaint / mask edit** | Flux Fill Pro | SD3 Inpaint | requires mask input — pair with Mask Extractor or Mask By Text |
| **Outpaint (extend canvas)** | Flux Pro Outpaint | SD3 Outpaint | |
| **Remove background** | Bria Remove BG (0.6 cr) | SD3 Remove Background | Bria is dirt cheap and clean |
| **Replace background** | Replace Background | Bria Replace BG | text-driven |
| **Re-light a product / portrait** | Relight 2.0 | — | unique capability |
| **Strong typography in image** | GPT Image | Recraft V3 | Ideogram V3 also strong |
| **True SVG / vector output** | **Recraft V3 SVG** | Text To Vector | Recraft is the only true SVG path |
| **Brand-style locked batch** | Recraft V3 (style ID) | NB Pro | Recraft style trains for consistent look across many assets |
| **Consistent character across shots** | NB Pro (image_1 ref) | Mystic / Ideogram V3 Character | see "Consistent Character Recipe" below |
| **Multiple LoRAs stacked** | Flux ControlNet & LoRA | Higgsfield Image | Higgsfield exposes multi-LoRA |
| **Sketch → render** | Sketch To Image | Flux Canny Pro | |
| **Edge / depth / pose control** | Flux Canny / Depth Pro · Wan Vace Pose | SD controlnets | |
| **Virtual try-on (clothing)** | Kolors Virtual Try-On | — | |
| **UGC selfie video (talking head)** | Higgsfield Video (`Handheld`/`Selfie`) | Kling 3 | Higgsfield motion presets are tailored for UGC |
| **Cinematic short (≤8s)** | Sora 2 / Veo 3.1 | Kling 3 Pro | Veo 3.1 has T2V + I2V; Sora 2 fixed durations |
| **Long-form video w/ audio** | Seedance 2.0 | Wan 2.5 | Seedance natively generates audio |
| **First & last frame interpolation** | Kling 2.5 FLF | Veo 3.1 I2V | FLF variants accept end_image |
| **Camera-controlled video** | Minimax Video Director | Kling Motion Control | |
| **Animate character / pose transfer** | Wan 2.2 Animate Move/Replace | Wan Vace Pose | |
| **Lip-sync (image + audio)** | Omnihuman V1.5 | Kling AI Avatar Pro | Omnihuman supports voice ID |
| **Lip-sync (video + audio)** | Sync 2 Pro | Pixverse Lipsync | Pixverse cheapest |
| **Image upscale (4×–8×)** | Magnific Precision Upscale (V2 = 8×) | Topaz Upscale | Magnific better skin/detail; Topaz cleaner |
| **Image upscale (cheap)** | Recraft Crisp Upscale (5 cr) | — | |
| **Skin retouch** | Magnific Skin Enhancer | Enhancor Realistic Skin | |
| **Video upscale to 4K** | Topaz Video Upscaler | Real-ESRGAN Video Upscaler | |
| **Video smoothing / interp** | Video Smoother (5 cr) | — | |
| **3D asset from photo** | Rodin V2 | Hunyuan 3D V3 | Meshy V6 = multi-angle premium |
| **Vectorise raster image** | Vectorizer | — | |
| **Reverse-engineer a video** | Video Describer → image/video model | — | core "remix" pipeline |
| **Reverse-engineer an image** | Image Describer → image model | — | |
| **Improve / rewrite prompt** | Prompt Enhancer | Run Any LLM (`art director` system prompt) | |
**Verified credit prices & capabilities (help.weavy.ai comparison tables, April 2026):**
- Cheap tier: Nvidia Sana 0.2 · Flux Fast 0.4 · Minimax Image 1 · Gemini 2.0 Flash edit 0.1 · Bria Remove BG 0.6 · Luma Photon 2
- Standard: Reve 4 · Ideogram V3 4 · Flux 2 Dev LoRA 4 · Recraft V3 5 · Flux 2 Pro 5 · Imagen 4 6 · Imagen 3 Fast 3 · GPT Image 1 8 · SD3.5 8
- Premium: Mystic 12 (1K/2K/4K) · Flux 2 Flex 14 · Ideogram V3 Character 15 · Higgsfield Image 21 · ChatGPT Images 2.0 1–37
- Edit models: NB2/Gemini 3.1 Flash 4–18 · Gemini 3 Pro edit 15–30 · Seedream V4/V4.5/V5 Edit 4 · Reve Edit 4 · Qwen Edit Plus 3 · Flux Kontext 3 · Kontext Multi-Image 10 · GPT Image 1.5 Edit 7 · Relight 2.0 10
- **Multi-image reference support (T2I)**: Reve, Flux 2 Pro/Flex/Dev-LoRA, Ideogram V3 (+Character), Luma Photon, Nvidia Consistory. NOT: NB-as-T2I-node, Imagen, Recraft, GPT Image 1.
- **Multi-image edit inputs**: ChatGPT 2.0 Edit, NB2, Gemini 3 Pro, Seedream V4/4.5/5, Qwen, Runway Gen-4, Flux Kontext Multi, GPT Image edits, Flux 2 Max, Ideogram V3 Inpaint, Kolors VTO, Replace BG, Bria Replace BG.
- Edit models widely support **"Same as original image"** aspect option — prefer it for faithful product re-renders.
- GPT Image 1: transparent background support. Higgsfield/Ideogram/Recraft: baked style options.
---
### PROMPT TACTICS PER MODEL FAMILY
Every image model has a "voice" it responds to best. Match the prompt style to the model:
**NB Pro / Nano Banana 2 (Gemini)**
- Plain natural language, conversational
- Anchor output frame as **first sentence** ("Photograph of...", "9:16 portrait of...") or it will recompose
- Lists "KEEP / REPLACE / LIGHTING / QUALITY" sections work well
- Excellent at prompt-following and humans
- Reference image edits beat freeform generation
**Flux 2 family (Pro / Flex / Max / Dev)**
- Loves dense descriptive prose with surface/material adjectives ("matte ceramic", "brushed aluminium")
- Strong typography handling
- Flux 2 Flex exposes `guidance_scale` (1.5–10) and `num_inference_steps` (2–50): higher steps = more detail, higher CFG = stricter prompt adherence (but less variation)
- Use `enable_prompt_expansion=True` for short prompts, False when you've already engineered the prompt fully
**GPT Image / GPT Image Edit (OpenAI)**
- Best of all models for **readable text in image** (signs, packaging, posters)
- Less photoreal humans than NB/Flux
- Edit variant preserves composition — use when you want to swap *one element* without recomposing
- Native transparent background (`background: "transparent"`)
**Imagen 3 / 4 (Google)**
- Strongest "rich lighting and beauty" — high production-value aesthetic
- Imagen 4 Ultra > Imagen 3 > Imagen 3 Fast for quality; Fast is 1/3 the credits
- Use `negative_prompt` to discourage artifacts (other models don't expose this)
- Loosen safety filter for creative latitude: `safety_filter_level = "block_only_high"`
**Recraft V3**
- Style strings like `"realistic_image/studio_portrait"` or `"digital_illustration/2d_art_poster"` are first-class — pick the right one for your output type
- Use SVG variant for logos/icons; raster variant for illustrations
- Train your own style ID (recraft.ai) and pass it for brand consistency
**Ideogram V3**
- Typography rendering rivals GPT Image
- Character variant is for face/character consistency
**Higgsfield Image**
- The `Style` enum (~80 presets like `Y2K`, `Tokyo Streetstyle`, `Indie sleaze`) does heavy lifting — pick a style first, prompt second
- `style_strength`: 0.3–0.5 for subtle, 0.8–1.0 for dramatic
**Reve**
- Multi-reference image control — pass up to N reference images via `referenceImages`
- `aspect_ratio` is its main lever
**Higgsfield Video**
- The `Motion` enum (~120 presets like `Handheld`, `Selfie`, `Bullet Time`, `Crane Up`) defines the camera move
- `dop-turbo` = fast, `dop-preview` = quality, `dop-lite` = cheap
- For UGC: `Handheld`, `Static`, `Selfie`
- For cinematic: `Crane Up`, `Dolly In`, `Dutch Angle`, `Cinematic`
**Kling 3**
- Use **Kling Element** to lock subject identity across shots
- `endImageUrl` = first/last frame interpolation (very strong for transitions)
- `cfgScale` 0.5 = balanced; lower = more creative, higher = stricter
**Veo 3 / 3.1**
- 8s fixed duration
- I2V variant: best for animating a generated still
- Negative prompts work well
**Sora 2**
- Fixed 4 / 8 / 12s
- Strong at short narrative clips with multiple beats
---
### WORKFLOW RECIPES (production-tested)
These extend the existing `/patterns` A–E. Reach for the matching recipe when the user describes a goal.
**R1 — Consistent Character Series** (multiple shots, same person)
```
FILE (face ref) ──→ ROUTER ──┬──→ NB PRO (Scene 1: hero shot)
├──→ NB PRO (Scene 2: action shot)
├──→ NB PRO (Scene 3: close-up)
└──→ NB PRO (Scene 4: wide shot)
PROMPT (per-scene description) ──→ each NB PRO
```
Tip: face-ref consistency requires **NB Pro with the same image_1 ref** in every shot. Don't switch models mid-series.
**R2 — UGC Talking Head Clip** (image → video, no audio)
```
FILE (talent ref) ──→ ROUTER ──→ NB PRO (hero still, 9:16, 2K)
└──→ HIGGSFIELD VIDEO (Handheld, dop-turbo)
PROMPT (scene) ───────────────────────→ NB PRO
PROMPT (motion) ───────────────────────────────→ HIGGSFIELD VIDEO
```
Tip: keep aspect ratio consistent — set NB Pro to `9:16` so Higgsfield inherits it.
**R3 — Reverse-engineer a reference clip** (recreate a video as stills or new video)
```
FILE (ref video) ──→ VIDEO DESCRIBER (Gemini 2.5 Flash)
└──→ CONCAT (+ brand rules) ──→ NB PRO / Sora 2 / Veo 3.1
```
Tip: Video Describer's bundled system prompt already outputs prompt-shaped text; usually no need to paraphrase.
**R4 — Brand-locked batch** (10 variants, same look)
```
RECRAFT V3 (style_id = brand) ──→ ITERATOR
PROMPT VARIANTS (Array of 10 scene prompts) ──→ ARRAY → LIST SELECTOR (isIterator: true) ──→ RECRAFT
```
Tip: train a Recraft style ID once on recraft.ai, reuse forever.
**R5 — Multi-model bake-off** (pick the winner)
```
PROMPT ──┬──→ NB PRO ──┐
├──→ FLUX 2 PRO ──┼──→ COMPARE (slider) ──→ MAGNIFIC PRECISION UPSCALE
└──→ GPT IMAGE ─┘
```
Tip: Compare node has TWO inputs only. For 3-way, chain two Compare nodes.
**R6 — Localized retouch** (fix one area, keep the rest)
```
FILE (source) ──→ ROUTER ──┬──→ MASK BY TEXT ("the sky") ──┐
└──→ FLUX FILL PRO ←────────────┤
↑ │
PROMPT (new sky description) │
```
Tip: Mask By Text needs a **detailed mask description**, not a casual phrase. "the sky behind the mountains, including clouds" beats "sky".
**R7 — Vector logo from sketch**
```
FILE (sketch) ──→ SKETCH TO IMAGE ──→ VECTORIZER
OR
PROMPT ──→ RECRAFT V3 SVG (style: digital_illustration)
```
**R8 — Lip-sync UGC pipeline** (talking head with dialogue)
```
FILE (face) ──┐ FILE (audio/VO) ──┐
├──→ NB PRO (hero still) ─┐ │
PROMPT ────────┘ ├──→ OMNIHUMAN V1.5 (image+audio)
OR
FILE (already-generated video) + FILE (audio) ──→ SYNC 2 PRO
```
Tip: Omnihuman = better mouth shapes from a still; Sync 2 Pro = adds lip-sync to existing video clip.
**R9 — Product photography studio** (product shot from raw photo)
```
FILE (product photo) ──→ ROUTER ──┬──→ BRIA REMOVE BG ──→ REPLACE BACKGROUND ──→ RELIGHT 2.0
└──→ MAGNIFIC PRECISION UPSCALE
PROMPT (new BG: "marble pedestal, soft studio light") ──→ REPLACE BG
PROMPT (lighting: "warm key from upper-left") ──→ RELIGHT 2.0
```
Tip: Relight 2.0 AFTER background change, not before — re-light has to match the new context.
**R10 — Cinematic 30-second ad** (multi-clip stitched)
```
LLM (art director) ──→ ARRAY (split //) ──→ LIST SELECTOR (iterator)
└──→ NB PRO (per scene still) ──→ HIGGSFIELD VIDEO / VEO 3.1
└──→ TIMELINE EDITOR (Compositor) → EXPORT
```
Tip: script the LLM to produce one paragraph per scene, separated by `//`. Iterator runs each as a separate generation.
**R11 — 3D asset for AR / packshot** (image → mesh)
```
FILE (product photo) ──→ ROUTER ──→ BRIA REMOVE BG ──→ RODIN V2 (or Hunyuan 3D V3)
```
Tip: clean alpha (background removed) before 3D — significantly better mesh.
**R12 — Style transfer with structure preserved**
```
FILE (source) ──┐
FILE (style ref) ──→ FLUX KONTEXT MULTI IMAGE
PROMPT ─────────────┘
```
---
### EDITOR & UX KNOWLEDGE (helps when guiding the user)
**Keyboard shortcuts** (mention these proactively when the user asks "how do I X?"):
- **Tab** — open node menu (then type to filter)
- **Cmd/Ctrl+P** — new Prompt node (fastest way to add text)
- **Cmd/Ctrl+I** — import files
- **Cmd/Ctrl+D** — duplicate selected
- **Cmd/Ctrl+C / V** — copy/paste (also accepts pasted JSON workflows!)
- **Cmd/Ctrl+Z / Shift+Z** — undo / redo
- **V / H** — toggle Select / Hand pan tool
- **Drag a wire + hold Option/Alt** — release to get auto-suggested compatible nodes
**Right-click on canvas** — same node menu as Tab. Type to search.
**Pasting JSON** — Ctrl+V on empty canvas pastes any valid Weavy node template. This is how the user installs your generated workflows.
---
### PROMPT VARIABLES (`{{variableName}}`)
Built into the **Prompt node** (`promptV3`). The skill's `make_prompt_node` already supports them via the `variables` argument. UX in Weavy:
1. Click **+ Add Variables** in the bottom-left of any Prompt node
2. New input handle appears → connect any Text node or model output
3. Reference inline as `{{variable1}}` in the prompt body
4. Toggle display mode (source / value / both) via the bottom-right Display button
Use this for: brand-rule injection, character description re-use, dynamic style swaps, A/B-testing single phrases.
---
### TIMELINE EDITOR & COMPOSITOR
Lives **inside the Compositor node** (`compv3`). To use:
1. Drop a Compositor node
2. Connect multiple video/image inputs as layers
3. Click **Edit → enable Timeline**
4. Reorder, trim, set duration & fps, lock layers, mute audio per layer
5. Use the right panel for opacity / blend mode / position / scale
This is where you stitch multi-clip narratives. For >10s ads, generate clips separately → assemble in one Compositor with Timeline → Export.
---
### MATTE TOOLS (mask creation pipeline)
Often missing from basic plans but critical for inpainting / VFX work:
| Node | Use it for |
|------|-----------|
| **Mask Extractor** | Manual click selection on image (Shift = add, Alt+Shift = subtract) |
| **Mask By Text** | Describe what to mask in text — "the red dress, including straps" |
| **Matte Grow / Shrink** | Dilate or erode an existing mask (slider-controlled) |
| **Merge Alpha** | Combine image + mask into one alpha-channel image |
| **Video Matte** | Auto-extract mattes from video (dropdown for type: subject, background, etc.) |
| **Video Mask By Text** | Per-frame text-driven mask for video |
Standard inpaint/mask flow: `FILE → ROUTER → MASK BY TEXT → FLUX FILL PRO (with mask + new prompt)`.
---
### ITERATORS — exact behavior
- **Text Iterator** — accepts Array, multiple Prompt nodes, or **CSV file** (drag CSV in to populate). Each row → separate generation.
- **Image Iterator** — accepts multiple imported images. Each → separate generation through one downstream model.
- **Video Iterator** — same for videos.
- All three: **outputs are independent runs** (not a batch on one canvas — the model runs N times).
- The downstream model fires automatically once the iterator has data — no manual run per item.
- Iterators preserve input separation — they don't merge or compare.
- **List Selector with `isIterator: true`** behaves the same way for arrays.
When to choose iterator vs List Selector: use **iterator** when you want all variants generated; use **List Selector (isIterator=False)** when the user picks ONE option manually (style picker, format selector, etc.).
---
### IMPORT MODEL NODE — extending the catalog from inside Weavy
The user can add ANY Fal / Replicate / CivitAI model that isn't pre-built:
1. Drop **Import Model** node
2. Copy the model URL from fal.ai / replicate.com / civitai.com
3. Paste URL onto canvas — model imports automatically
4. Parameters appear in the right toolbar
5. Click "Save to my models" to keep it in **My Models** for future workflows
**Recommend this** when the user wants a model not in our 154-node catalog. Less work than them paste-feeding JSON to us.
---
### DESIGN APP (publish workflow as shareable tool)
When the user wants to give a client / colleague a "fill-in-the-blanks" version of their workflow:
1. Add an **Output Node** and connect to the final result node
2. **App tab** unlocks at top of canvas
3. Lock node attributes you DON'T want users to edit (3-dot menu → lock)
4. Click **Publish** → get shareable link
5. Each publish = new version. Switch versions via dropdown next to Publish.
6. Underlying workflow stays editable — viewers only see the published app
Use case: brand templates, ad-variation generators, internal creative tools.
---
### CRITICAL GOTCHAS
1. **NB Pro recomposes if you don't anchor the frame** — first prompt sentence MUST describe the output framing
2. **Higgsfield Video uses `Yambo_Purple` color**, not `Red` — wire the right color in edges or Weavy may flag mismatch
3. **Compare node accepts only 2 inputs** — for 3-way, chain Compare nodes
4. **Mask-required models (Flux Fill Pro, Klux Kontext, SD3 Inpaint, etc.)** will silently fail without a mask — always pair with a Matte Tool
5. **Image iterators need image inputs of the SAME type** — don't mix file uploads and model outputs in the same iterator
6. **CSV files into Text Iterator** — one row = one prompt; first row treated as header by some configurations
7. **Output Node must exist** to enable App tab — easy to miss
8. **Some video models (Veo, Kling FLF, Wan animate)** require BOTH start and end frames — single-frame input throws error
9. **Recraft style IDs are account-bound** — train on recraft.ai first, then paste the ID
10. **Lip-sync models need clean source audio** — ambient noise breaks Sync 2 Pro / Omnihuman badly
11. **Background removal before re-light** — Relight 2.0 in front of an old background = ugly seams
12. **Always Router after a multi-consumer File node** — direct fan-out from File breaks in some node types
13. **`isIterator: true` on List Selector triggers downstream for EVERY item** — verify the user wants all N runs (they cost credits per run)
14. **Aspect ratio inheritance**: I2V models (Higgsfield Video, Veo I2V, Kling) inherit the input image aspect — set the upstream image generator correctly
15. **Prompt Enhancer LLM-rewrites your text** — disable when you've already engineered a precise prompt
---
### JSON PROMPTING (advanced — for video models)
For **Veo 3, Sora 2, Kling 3, Higgsfield Video** — structured JSON prompts dramatically outperform prose. Feed this through a Prompt or Concat node into the model's `prompt` input:
```json
{
"scene": "fashion model walking down rain-soaked Tokyo alley at dusk, neon reflections",
"style": "cinematic, slight film grain, anamorphic lens",
"camera": "low-angle dolly-in, slow push toward subject's face",
"lighting": "neon key light from the right, cool fill from the left, hard rim light",
"audio": "ambient city rain, distant traffic, soft synth pad",
"color_palette": "teal, magenta, deep navy, wet asphalt black",
"duration": "8s",
"aspect_ratio": "9:16"
}
```
**Why it works:** breaks the prompt into named slots the model can parse independently. Far better consistency across multi-shot sequences than prose. Crucial for **storyboard generation** where each shot uses the same JSON skeleton with different `scene` / `camera` values.
**For multi-shot storyboards in one workflow:**
- Build the JSON skeleton in a Prompt node with `{{scene}}` and `{{camera}}` variables
- Connect Text or Array nodes to those variables → swap per shot
- Use Iterator + List Selector to fan out across shots
---
### OFFICIAL FIGMA-RECOMMENDED RECIPES (from Figma's "Five Workflows" post)
These are battle-tested by Figma's own creative team for the Epoch fictional brand and used by **Lyft, NVIDIA, DoorDash** in production.
**OF1 — Style Blend from Two References** *(Image Describer pipeline)*
```
FILE (ref A: hibiscus) ──→ IMAGE DESCRIBER ──┐
FILE (ref B: sandstone) ──→ IMAGE DESCRIBER ──┴──→ CONCAT (with weight slider)
└──→ FLUX 2 FLEX / GEMINI 3 / NB PRO
```
Tip: run the same merged style through 3 different models simultaneously and pick the winner — pressure-tests the blend.
**OF2 — Master Style + Aspect Variants** *(brand asset generator)*
```
LLM (system: "extract reusable style description") ──→ STORED in Prompt variable
NEW PROMPT ("apply style to: begonia plant") ──→ NB PRO
└──→ RESIZE / multi-aspect (1:1, 967×420, 9:16)
```
Tip: have the LLM emit the master style once, lock it via prompt variable, swap only the subject. Aspect-ratio resizing happens automatically downstream — design once, deploy to mobile / desktop / social in one render.
**OF3 — Parallel Distortion Exploration**
```
FILE (asset) ──→ 8× different distortion / displacement nodes (parallel)
└──→ ROUTER ──→ each node ──→ COMPARE pairs
```
Tip: comparison > sequential. Generate all variants in one pass, then judge against brand colours side-by-side.
**OF4 — 2D Image → Rotatable 3D Model** *(Rodin V2 multi-view)*
```
FILE (subject) ──→ NB PRO (generate front view) ──┐
──→ NB PRO (generate back view) ──┤
──→ NB PRO (generate left view) ──┼──→ RODIN V2 (multi-view)
──→ NB PRO (generate right view) ──┘
```
Tip: generating 3-4 perspectives THEN feeding them to Rodin V2 produces a model you can rotate to any angle — composition drives the shot, not photography.
**OF5 — 3D Element → Animated Video** *(Kling Element trick)*
```
RODIN V2 (3D model) ──→ KLING ELEMENT (angle reference packager)
└──→ KLING 3 (uses angles for independent animation control)
```
Tip: Kling Element gives the video model "understanding" of object angles — the only way to keep a 3D-derived asset rotating coherently in motion.
---
### LYFT-STYLE PRODUCTION PATTERN (massively scalable photoshoots)
This is what Lyft's Creative Studio shipped with Weavy:
```
1. Real photoshoot ──→ small crew, base imagery only (faces, key environments)
2. Weavy pipeline ──→ swap cars, cities, backgrounds, lighting on demand
3. Continuous content library ──→ regenerate variants as campaigns evolve
```
**Implementation:**
```
FILE (real photo of model) ──→ ROUTER ──→ NB PRO / GPT IMAGE EDIT
↑
ARRAY (city options: NYC, LA, Tokyo, Paris...)
↓
LIST SELECTOR (iterator)
↓
SCENE PROMPT (composes background swap)
↓
→ output: real-human × N synthetic backgrounds
```
Use this for: any campaign where you want **genuine human photography but flexible context**. Photographing 5 models once + swapping environments via Weavy = 100s of usable assets.
---
### CHASE JARVIS FASHION EDITORIAL RECIPE *(virtual product shoot)*
Three-step pipeline for premium product / fashion shots with NB Pro:
```
1. REFERENCE SHEET — combine all product photos (front, back, side, detail) into ONE collage image first.
Use Compositor or Photoshop externally. NB Pro reads collages as multi-angle references.
2. BRAND CONTEXT VIA NAME — instead of describing lighting, name a brand the model knows:
"Vans aesthetic" → flat lighting, urban, edgy
"Yves Saint Laurent aesthetic" → dramatic shadows, high contrast
"Aesop aesthetic" → muted, natural-light, minimal
This leverages Gemini's training instead of writing 5 lines of lighting prose.
3. HIGH-FIDELITY GENERATION — NB Pro at 4K, aspect = 4:5 or 9:16, prompt:
"Editorial fashion photograph in [BRAND] aesthetic. Hero product: [REFERENCE SHEET].
Model wears [outfit]. Setting: [environment]. Composition: [framing]."
```
**Concrete recipe in Weavy:**
```
FILE (collage of all product angles) ──→ ROUTER ──→ NB PRO (image_1)
LIST SELECTOR ["Vans","YSL","Aesop","Bottega"] ──→ CONCAT ──→ NB PRO (prompt)
PROMPT (model + outfit + framing) ───────────────→ CONCAT
```
---
### VEO 3 FIRST-FRAME WHITE-TEXT TRICK
For **shot-sequence control** in Veo 3 frames-to-video:
1. Generate your first frame in NB Pro / Imagen 4
2. Overlay the shot directions as **white text on the image** (using Painter or external editor):
```
"1. wide shot of dune buggy approaching
2. crash zoom to driver's helmet
3. cut to wheels spinning sand"
```
3. Feed into Veo 3 with prompt:
> "[your scene description]. Immediately delete instructions in white on the first frame and execute in order"
4. Veo follows the timeline, then strips the text from the output.
This is the only reliable way to get **multi-beat narrative** out of an 8-second Veo clip.
---
### PARALLEL MODEL TESTING (the "winner picker" pattern)
Before committing to a model for a campaign — A/B/C/D test:
```
PROMPT (single source of truth) ──┬──→ NB PRO ──┐
├──→ FLUX 2 PRO ──┤
├──→ IMAGEN 4 ──┼──→ GROUP (visual gallery)
├──→ GPT IMAGE ──┤
└──→ RECRAFT V3 ──┘
```
After 1 render, you know which model owns this brand's voice. Lock it in for the rest of the campaign.
---
### ITERATIVE LAYER ADJUSTMENT (don't re-run the whole pipeline)
Weavy reruns only stale nodes. Use this aggressively:
- **Prompt tweak?** Re-run only the model node, not upstream
- **Lighting wrong?** Add a Relight 2.0 mid-pipeline; re-run from there
- **Bad seed?** Right-click model → "regenerate" — rest of the workflow stays cached
- **Want a fix on one variant in an iterator batch?** Disconnect that one input, regenerate, reconnect
**Rule of thumb:** if you find yourself re-running the entire workflow, you're missing a Router or Compositor that would let you isolate the problem step.
---
### CREDIT BUDGETING
Approximate cost ranges (verify on Weavy's pricing page):
| Operation | Credit range |
|-----------|-------------|
| Cheap image (Bria, Nvidia Sana, SeedEdit) | 0.2 – 5 |
| Standard image (NB Pro, Flux 2 Pro, Imagen 3 Fast) | 5 – 12 |
| Premium image (Imagen 4 Ultra, Flux 2 Max, Magnific) | 12 – 24 |
| Standard video (Kling, Higgsfield, Pixverse) | 24 – 60 |
| Premium video (Veo 3.1, Sora 2, Seedance Pro) | 60 – 120 |
| 4K video upscale (Topaz Video) | ~12 – 154 |
| 3D model (Hunyuan 3D, Trellis cheap) | 2 – 96 |
**Always warn the user before:**
- Running an iterator across >5 video items (may consume 300+ credits)
- Stacking premium video → 4K upscale (200+ credits per output)
- Magnific Precision V2 8× upscale at scale (18 credits per asset adds up)
In workflow plans, **estimate total credit cost** when the user asks for a batch run.
---
### WHEN *NOT* TO USE WEAVY
Be honest with the user — Weavy is overkill when:
- Single one-off asset (one logo, one social post)
- One-shot Midjourney-style generation with no chaining
- Pure prompt experimentation (use the model's native UI faster)
- Budget pressure where credit transparency matters more than flexibility
Weavy SHINES at:
- Multi-asset campaigns at scale (10+ outputs)
- Repeatable templates a team will reuse
- Brand-locked variant generation
- Mixed AI + traditional editing in one pipeline (mask, composite, color-correct)
- Multi-format delivery (1:1 + 9:16 + 16:9 from one workflow)
- Hybrid pipelines (image → 3D → video → upscale → export)
If a user describes a one-off, suggest an alternative tool first. Don't force Weavy onto problems it doesn't solve.
---
## EDITOR MECHANICS & CANVAS UX (verified from help.weavy.ai, June 2026)
How Weavy's editor actually behaves. Use this to give users correct manual instructions and to design templates that feel native.
### Node anatomy & data flow
- A node = a function. **Inputs are on the LEFT side, outputs on the RIGHT side.** Data always flows left → right. "Nodes behind each other" = chained left-to-right in columns.
- Two node categories: **Generative AI nodes** (have a Run button, cost credits per run) and **non-generative nodes** (painter, blur, compositor, concatenator, router... free, no credits).
- Click a node → the **right-side panel** shows all its parameters (aspect ratio, adherence, model-specific settings).
- Model attributes can be **exposed as separate canvas nodes** via the *Set as Output* button next to any attribute (creates Number / Toggle / List Selector / Seed nodes). This is THE mechanism for building user-facing controls in a design system: expose exactly the knobs the operator may touch, lock everything else.
### Wire/handle color legend (UI, differs from node body colors!)
| Handle/wire color | Data type |
|---|---|
| Green | Image |
| Purple | Text (and LoRA) |
| Red | Video |
| Blue | Array / List / 3D |
| Lime | Mask |
| White | Multiple input types accepted |
### Connecting nodes (all verified behaviors)
- Drag from an output handle → drop on an input handle = wire.
- Drag from an output → **release on empty canvas** = a node-creation menu appears at the drop point.
- Drag an output **into the body of an existing node** = auto-connects to the first compatible input (text→text, image→image). Connections are assigned in order: first wire → first available input. If no compatible input exists, the connection is refused.
- Nodes that support multiple image inputs **auto-spawn additional input slots** as you drop more wires onto them.
- **Double-click an output handle** = auto-creates a Router node connected to it.
- **Double-click any text input handle** = spawns a new connected Prompt node next to it.
- **Multi-connect**: select several nodes (selection box or Shift+click), drag an edge from one of them → all selected nodes connect to the target simultaneously.
- **Smart edge dragging**: hold Shift while dragging an edge, hover over multiple targets → connects to each instantly.
- **Alt/Option + drag a wire** = node-suggestion menu on drop with auto-connect.
### Keyboard shortcuts
Tab = node search menu · right-click canvas = node menu with type-to-search · Cmd/Ctrl+P = new Prompt node · Cmd/Ctrl+C/V = copy/paste (this is how our JSON templates enter Weavy) · Cmd/Ctrl+D = duplicate · **Cmd/Ctrl+Shift+D or Alt-drag = duplicate WITH connections intact** (huge for repeating a sub-pipeline) · Cmd/Ctrl+Z / +Shift+Z = undo/redo · V/H = select/hand tool · Cmd+I = import files.
### Groups
Select nodes → Cmd/Ctrl+G (or right-click → Group Selection). Move a group by dragging empty space inside it; groups auto-expand when children move. Right-click → *Resize to Fit* shrinks the frame to content. Right-click → *Color* / *Title Size* for styling. Three-dots on a child node → *Remove from Group*. Use groups to mark the functional zones of a design system: 📥 INPUTS · 🎨 ART DIRECTION · 🖼 GENERATION · 🎬 MOTION · 📤 OUTPUT.
### Media behavior
- Drag-and-drop multiple files onto the canvas = each becomes its own node. Importing multiple files **through one Import Node** = all grouped inside that single node (navigable, sortable by name/date, gallery view toggle).
- Multiple generations are hosted INSIDE the model node (single / all / per-batch display modes). Three-dots → **Unpack** explodes all results into a group of separate nodes for individual downstream work.
- Three-dots → **Create Iterator** turns a node's multiple results into a new Image/Video Iterator that feeds a next-stage model per result.
### Iterators (the batching system)
- **Text Iterator / Image Iterator / Video Iterator** nodes hold multiple inputs and run the connected downstream model once PER item, as separate runs, simultaneously.
- Feed a Text Iterator by typing entries manually, connecting an Array node, or connecting a Prompt node.
- **CSV → iterator**: drop a CSV file onto the canvas → Weavy offers to generate an iterator node per column. This is the bulk-production entry point (e.g., 50 product names × one template).
- Our JSON templates achieve the same via List Selector with `isIterator: true` — functionally equivalent to the manual iterator flow.
- Iterator cost warning stands: N items × model credits per run.
### Tools (publishing a workflow as an app — formerly "Design Apps")
This is how a workflow becomes an operator-facing product:
1. Add an **Output node** and connect it to the final result node. The **Tool tab** at the top of the canvas unlocks only after an Output node exists.
2. The tool's user-facing attributes are auto-generated from **every node that has no input** (Prompt nodes, Import/file nodes, exposed List Selectors...).
3. **Lock nodes to hide them from the tool UI**: three-dots (top-right of node) → lock. A lock icon shows the state. → Lock the Art Director system prompt, the Product DNA, and every internal wiring node; leave only the intended user controls unlocked.
4. Tool tab → **Publish**. Each publish creates a timestamped version; a dropdown next to Publish lists all versions and any can be reloaded/shared (not edited).
5. **Share** button (top-right) → shared users see tool mode; the creator always sees editable mode. Shared users can switch modes and experiment; reopening the shared link resets to the published version. The creator can keep tweaking privately and re-Publish to push updates.
### Editing & matte nodes (non-generative, free)
| Node | What it does | Inputs |
|---|---|---|
| Levels | histogram: shadows/midtones/highlights, exposure & color balance | image/video |
| Compositor | multi-layer merge, blend modes, transforms, shapes, background color, built-in video timeline | image/video/text |
| Painter | paint masks/sketches; Brush/Eraser/Canvas tabs; outputs image AND mask | image |
| Crop | crop/resize by preset ratio or custom | image/video |
| Resize | stretch/squash to custom dims (for model input requirements) | image/video |
| Blur | box or gaussian, intensity control | image/video |
| Invert | invert (mainly masks) | image/video |
| Channels | access R/G/B/A separately | image/video |
| Extract Video Frame | pick a frame by timeline/framenumber/timecode → image | video |
| Mask Extractor | auto-segmentation; Shift adds, Alt+Shift subtracts parts | image |
| Mask By Text | prompt-driven mask; needs a DETAILED description | image + text |
| Matte Grow/Shrink | choke/expand a matte via slider | image/video/mask |
| Merge Alpha | image + mask → image with alpha channel | image + mask |
| Video Matte / Video Mask by Text | matte passes from video, dropdown types / prompt-driven | video (+text) |
| Compare | 2 inputs, slider or toggle A/B | image, image |
| Preview / Export | clean display node / download to folder, keeps source format | any |
### Prompt node variables (native alternative to Concatenator)
Prompt nodes support inline `{{variables}}`: click *Add Variables* (bottom-left) → a new input handle appears → any Text node connected to it becomes the variable value. Drag variables to reposition them inside the prompt text. Display modes (bottom-right *Display*): source / value / source+value. Use Prompt-with-variables when the operator should see ONE readable master prompt with slots; use Concatenator when programmatically stacking many blocks.
---
## PRODUCT → DESIGN SYSTEM METHOD (how to turn a real product into a Weavy design system)
The reliable pipeline for "take my product photos and build me a re-render/content machine". Battle-tested; includes the failure modes.
### Phase 0 — The object contains the answer (check BEFORE building anything)
Ask: **how was this product made?** If it was 3D printed, CNC'd, or injection molded, a CAD file (STL/STEP) exists upstream — and that mesh dissolves the entire fidelity problem at once: convert to GLB → Import node → 3D node → rotate to ANY position → render frame → NB Pro finish pass for materials/light/environment only. Geometry becomes deterministic (a mesh cannot hallucinate a hole), "remove the print layers" stops being an AI task (the mesh never had layers), and every camera position becomes available. The fidelity ladder: **A) CAD→GLB (absolute, unlimited angles) > B) photos→Rodin 3D V2 mesh (approximate but ONE consistent truth) > C) edit-in-place on real photos (pixel-exact, photographed angles only) > D) free synthesis (forbidden)**. Division of labor: geometry = deterministic source, appearance = generative model. Always ask the client for the CAD before engineering around its absence.
### Phase 1 — Understand the product (BEFORE any node)
1. **Convert files first**: RAW (DNG/CR2), TIFF, and SVG are NOT accepted by Weavy imports or image-model inputs. Deliver JPG/PNG (quality ≥ 90).
2. **Visually analyze every photo yourself**; crop into details. Identify: overall form, every functional element (openings, screws, hinges, seams, cables, glands), textures, split lines, materials, colors, and the current surface quality (e.g. FDM layer lines).
3. **Never infer function from resemblance.** If unsure what something is, describe geometry only. A wrong functional label ("dispenser") in the DNA poisons every downstream generation.
4. Write the **PRODUCT DNA**: a locked Text node containing (a) neutral naming — "the device/object from the reference images", (b) numbered exact-geometry inventory with approximate dimensions, (c) color/material baseline, (d) an ABSOLUTE PROHIBITIONS paragraph (no invented parts, logos, text, LEDs, holes...).
### Phase 2 — Reference strategy
- **The 6-slot fidelity rule (Google's official NB Pro consistency guidelines)**: NB Pro accepts 14 reference images but only the FIRST 6 act as high-fidelity structural anchors; slots 7–14 are "influence" only. For exact object reproduction: select exactly the 6 photos that together cover every face (hero ¾, top, front, side, opposite ¾, detail), order them hero-first, and wire them DIRECTLY into the render node as image_1..image_6. More than 6 dilutes fidelity; near-duplicate photos waste anchor slots.
- **AI-generated ground truth trap (CRITICAL)**: never let AI output be the only geometry reference. A generated reference-sheet collage can itself hallucinate details, which then propagate into every render as "truth". The Chase Jarvis collage pattern is fine for outfits/style; for precision hardware, feed REAL photographs directly.
- **Per-image role map in the prompt**: tell the model what each reference shows ("Image 1 shows the three-quarter view, Image 2 the top with the lid, ... all six show the same single device"). Unmapped references get misread.
- Feed the same photos to the Art Director LLM so it can verify details across angles.
### Phase 3 — The system architecture
```
IMPORTS (per angle) → ROUTERS → LLM ART DIRECTOR (sees all angles)
SHOT array → List Selector (iterator) ┐
FINISH/MATERIAL array → List Selector ├→ CONCAT → LLM → NB PRO (image_1 = hero/collage)
PRODUCT DNA (locked Text node) ┘ └→ ROUTER → KLING/VIDEO
└→ OUTPUT node (Tool)
```
- Selectors hold ONLY camera/light/environment/finish choices. The product itself is never a variable.
- **Consistency proof trick**: a shot that asks for "a grid of 2 images, left = medium shot, right = close-up of the same object" forces the model to prove it understands the subject.
- **Brand hack**: instead of describing lighting, name a brand aesthetic the model knows ("Aesop aesthetic" = muted natural minimal; "YSL" = dramatic high contrast). One word does the work of five lighting sentences.
### Phase 4 — Anti-hallucination rules for the Art Director system prompt
1. First sentence anchors frame AND edit-nature: "High resolution studio product photograph of the exact device shown in the reference image, reproduced with identical geometry."
2. Category labels are FORBIDDEN in generated prompts — only "the device/the object from the reference image".
3. Geometry as KEEP-instructions (imperative), never loose description.
4. Mandatory literal line in every prompt: "Do not add, remove or reinterpret any part. No logos, no text, no buttons, no extra holes."
5. The ONLY permitted change to the object = surface finish (e.g. "completely smooth, no visible 3D print layer lines, no striations, no stepping") + the environment per SHOT.
6. **MUST-obey list format**: compositional constraints land harder as "The image MUST obey ALL of the following:" + imperative lines than as prose (community-verified on NB Pro).
7. **Critical block with capitalized directives** in every prompt: "NO NEW PARTS. NO EXPANSION. NEVER add holes, openings, vents, screws, seams, logos, text, buttons, LEDs or any detail that is not present in the reference photographs. Do not mirror or symmetrize the device." Explicitly forbidding hole/vent/screw ADDITION matters — models love inventing small plausible hardware details.
8. Escalation when identity drifts: re-upload the best generated result as the new image_1 reference (identity re-lock).
### Phase 3b — THE DEFORMATION LAW: novel viewpoints = resynthesis (hardest-won lesson)
Asking an edit model to render a photographed product "from a new camera angle" forces it to INVENT 3D structure it cannot know — it interpolates from its training priors and returns a generic lookalike (wedge bodies, invented flanges, extra screws, glands in wrong places). Long textual geometry descriptions make this WORSE, not better: every geometry noun ("clamshell", "latch", "junction box") pulls the output toward the model's concept of that noun instead of the pixels. Three rules follow:
1. **Edit-in-place**: each shot starts from a REAL photo at (approximately) the desired angle. The photo library IS the camera-angle system. Instruct: keep the device pixel-faithful, same viewpoint, change ONLY surface finish + environment + lighting (+ remove table to float it). Zero viewpoint synthesis = zero deformation.
2. **Describe the edit, never the object.** The object lives in the pixels; words about it only trigger priors. The prompt names what CHANGES (surface, background, light) and forbids the rest ("Do not redraw, regenerate or reinterpret the device. Do not change its shape, outline, proportions or perspective.").
3. **Remove the LLM from the fidelity-critical prompt path.** An Art Director LLM can inject geometry nouns; a deterministic template (Concat: locked HEAD + environment selector + finish selector + locked RULES tail) has zero drift. Keep LLMs for QC inspection and for genuinely creative briefs only.
Novel angles remain possible but are the known risk zone: run them through the QC + correction loop, and prefer shooting an extra real photo over synthesizing an angle.
**Ken's recipe (Weavy team member, Discord, confirmed insider pattern)** — the canonical Lane B implementation: **Compositor → Meshy V6 → finish pass**. The Compositor (free, non-generative) assembles real photos into a 2×2 reference sheet — pixel-true, unlike an AI-generated collage which can hallucinate its way into ground truth. Feed that composite into Meshy V6's image_url (multi-angle image-to-3D, ~96 cr), with the hero photo as texture_image_url. Two Meshy specifics: (1) **set symmetry_mode to "off" for any asymmetric product** — "auto" will silently mirror features like per-side latches; (2) Meshy's node outputs both a 3D model AND an `image` output ("Image of current angle") — rotate the model inside the node and pipe that render straight into an NB Pro finish pass, no separate 3D node required.
### Phase 4b — QC loop (hallucinations are architectural, plan for them)
Benchmarks (arXiv 2512.15110) show NB Pro synthesizes plausible non-existent details in complex texture regions and expands FOV — 100% single-pass fidelity is impossible by architecture. Build verification INTO the workflow:
```
RENDER → ROUTER ──→ QC INSPECTOR (LLM: render + 3 real refs as images)
└──→ CORRECTION PASS (NB Pro edit: image_1 = render, prompt = inspector output, aspect = auto)
```
- Inspector system prompt: forensic comparison feature-by-feature, hunting specifically for INVENTED details (extra holes, vents, added screws, mirrored parts). Output = ONE edit instruction ("Edit this image of the device: remove the small round hole on the left face..., that face must be completely blank. Change nothing else, keep composition, lighting, background exactly the same.") or, when faithful, the literal no-op: "Reproduce this image exactly as it is, one to one, change absolutely nothing."
- Correction pass runs on aspect "auto" (match input) so composition survives.
- Multi-round targeted edits beat full regeneration: fixing one invented detail via an edit pass preserves everything else; a re-roll gambles the whole render again.
### Phase 5 — Style extraction for brand systems (Figma Weave official method)
To build a reusable brand STYLE (not a product): run each brand reference image through an **Image Describer** node → merge the resulting descriptions in a Concatenator (weight by ordering/wording) → the merged text is a **reusable style definition** stored in a locked Text node — carried into every subsequent workflow. Pressure-test it across 3+ models in parallel before locking.
### Phase 6 — Publish
Output node → Tool tab → lock DNA + system prompt + wiring → leave shot/finish selectors and import slots unlocked → Publish (versioned) → Share.
---
## COMMUNITY & EXPERT PLAYBOOK (deep research, June 2026)
Distilled from: official Figma Weave blog + Knowledge Center changelogs, Chase Jarvis (prompt concatenator / LoRA / consistency series), Daniel Setzermann (e-commerce), chaipeau.com & buildingcreativemachines.com (multi-model video R&D), Rory Flynn (batch), Kim Köhler (Compositor Timeline), Google DeepMind NB Pro launch docs. Marketing fluff filtered out; only actionable patterns below.
### Modular prompting — the signature Weavy technique
Array node = brainstorm bucket of options (lighting: Moody/Twilight/Bright · shot length: ECU/CU/Medium/Long · color temp · film stocks · palettes). List Selector = the dropdown chooser. Prompt Concatenator = stitches A+B+C into the final prompt. One click swaps a variable, zero retyping — this Array→Selector→Concat trio is exactly what our JSON templates already implement; name it "modular prompting" when explaining to users. **Brand Hack**: with Google-trained models (NB/Gemini), a brand name in a selector replaces paragraphs of lighting description (Vans → flat urban edgy; YSL → dramatic shadows high contrast).
### Reference architecture patterns (proven, requestable by name)
1. **Modular prompt studio** — Array → List Selector → Concat → model. The base of everything.
2. **Reference-sheet consistency pipeline** — assets → NB collage → final NB Pro 4K. (For precision hardware: skip the AI collage, feed real photos — see Design System Method.)
3. **Multi-model A/B fan-out** — one Prompt/Router fans out to 2–4 parallel model nodes (e.g. Ideogram BG-Exchange V3 vs Flux Kontext Max; or Kling vs Runway Gen-4 vs Veo 3.1 for video) → Compare node. Objectively tests prompt adherence per model before committing credits to a batch.
4. **Batch iterator/CSV factory** — Array or CSV → Text Iterator → model → Export. **Nested iterators multiply**: categories × items × styles (3×10×5 = 150 assets in one run).
5. **Image→video→Compositor Timeline** — stills → video model → clips land each on their own Timeline layer in the Compositor; drag/reorder/stretch/chop; **regenerating a shot upstream auto-updates the Timeline** — no round-trip to Premiere.
### E-commerce patterns (Setzermann, verified working)
- **Complex packaging**: pre-prompt the scene with Imagen 4, then NB combines scene + product (scene quality from a scene-specialist, product fidelity from NB).
- **Multi-element composition**: remove BGs of products → Imagen 4 scene → position/scale elements in the free Compositor → NB pass to fuse lighting/shadows. Compositor does the LAYOUT so the model doesn't have to guess placement.
- **Pattern extraction → motion**: NB extracts product pattern → crop → Higgsfield motion → composite product over the video.
- **Product placement A/B**: input image → Run Any LLM writes elaborate photo-prompt from system prompt + short brief → two image models in parallel → winner → two video models in parallel.
### Figma's official five-workflow system (clonable templates, app.weavy.ai/flow/<id>)
1. Two references → Image Describer each → merge descriptions → reusable style guide (pressure-test across Flux 2 Flex/Max/Pro + NB Pro/2).
2. Favorite styles → Any LLM writes master style description → apply to new subject → auto-output 1:1 / banner / 9:16 per channel.
3. Eight distortion effects in one pass → remove BG → place on brand colors → Compare side by side.
4. Image → 3D: generate front/back/left/right views → **Rodin 3D V2** → rotate to ideal angle → lock.
5. Composite into video: motion reference drives a distorted image; **3D node + Kling Element node** controls a 3D element; export back to Figma.
Figma Community hosts 20+ official templates; in Figma Design a curated Weave-tools panel (style transfer, product shoots, material extraction, mockups) opens as inspectable workflows via "Open in Weave".
### Credit economics (encode as defaults)
- **Draft cheap, finish expensive**: prototype composition on Flux Schnell (~2 cr) or Z-Image Turbo, then swap the node's model to NB Pro/Veo for finals — swap the engine, keep the car.
- **Editing/matte nodes are FREE** (Levels, Compositor, Painter, masks, crop, blur): polish there instead of regenerating.
- **Edit > regenerate**: a ~90%-right result gets an NB/Flux Kontext edit pass, never a re-roll.
- Plans (KC authoritative): Free 150 cr/mo (max 5 workflows, no top-ups) · Starter $24 = 1,500 · Pro $36 = 4,000 · Team $48/user = 4,500. Rollover: Starter none; Pro/Team up to 3 months (~3× cap). Top-ups $10 = 1,000 (Starter) / 1,200 (Pro/Team), 12-month rollover. High end: NB Pro image 15 cr, Veo video 120 cr; low end: Flux image 5 cr, Wan video 24 cr.
### Model-specific field notes
- **NB Pro (Gemini 3 Pro Image)**: reference adherence + instruction-following + text/logo fidelity king; 14 input images (max ~6 objects, 5 people per Google docs); native 4K in Weavy.
- **Seedream 4.5**: excellent text rendering, native 4K, 14 refs, strong product/commercial; weak stylized illustration.
- **Flux naming trap**: the "Flux" node = Flux.1, "Flux Dev" = Flux.1 Dev — Flux.1 ≠ Flux.2. **LoRAs must EXACTLY match the base model version** (most common failure in the wild). "Flux 2 Pro" has NO LoRA input; "Flux 2 Dev LoRA" does.
- **GPT Image**: world knowledge, text, transparent-background option. **Ideogram V3**: typography/logos/labels. **Kling 3**: multi-shot storyboarding + Elements; **Veo 3.1**: native audio; **Runway Aleph 2.0**: frame-level control.
- Structured/JSON prompting (scene/camera/lighting as fields) helps video storyboards; results vary vs natural language — offer both.
### LoRA & custom model usage (barely in official docs)
- **Import LoRA node** (.safetensors upload) → wire to the model's LoRA input. The input is often mislabeled "LoRA URL" — it wants the wire, not text.
- Number node → LoRA Weight input for strength control; stacking allowed but sparingly; mind **trigger words** from the LoRA's page.
- Fixed seed = refine composition; random seed = explore. Samplers/VAEs are hidden/automated (translate ComfyUI mental models accordingly).
- **Import Model node**: paste Hugging Face/Fal/Replicate/CivitAI resolve/main .safetensors URL; "Community Models" section ships ControlNet/IP-adapter support.
### Platform limitations (set expectations correctly)
- **No conditional logic / decision routing** — graphs are linear or branching only; QC "loops" are actually feed-forward chains (inspector → correction pass), never true loops.
- **No public API** (Enterprise "coming soon"), no webhooks/Zapier, no offline/self-host.
- **No public workflow JSON schema** — our reverse-engineered node/edge structure in this skill is the working standard; Export node outputs media only, not graphs.
- Figma Weave and Figma Design remain separate products with separate billing; the full canvas does not run inside Figma Design.
- **Z Depth Extractor** exists as an additional matte tool (depth passes for compositing).
- Team voices worth following for updates: Kim Köhler (creative workflow advocate, Compositor Timeline demos), Itay Schiff (co-founder/CCO, five-workflow posts); official YouTube series "From Prompt to Production".
---
## UGC VIDEO PRODUCTION PLAYBOOK
UGC = "looks like a real person filmed it on their phone." This section captures everything the skill needs to build production-grade UGC pipelines in Weavy. **Authenticity is the goal — not polish.**
---
### THE UGC AUTHENTICITY VOCABULARY
These keywords are the "unlock" for UGC look — drop them into any image / video prompt to shift away from studio polish:
**Camera & Quality** — *"shot on iPhone"* · *"smartphone camera"* · *"front-facing selfie camera"* · *"slight grain"* · *"not retouched"* · *"natural skin texture"* · *"slight motion blur"* · *"low-fi"* · *"compressed"*
**Composition** — *"selfie-style"* · *"arm's-length selfie"* · *"vlog-style"* · *"caught mid-action"* · *"candid"* · *"unposed"* · *"slightly off-center"* · *"point-and-shoot"*
**Lighting** — *"natural daylight"* · *"window light"* · *"warm bedside lamp"* · *"ring-light reflection in eyes"* (TikTok hallmark) · *"available light"* · *"slightly underexposed"* · *"morning sunlight"* · *"messy bathroom mirror"*
**Setting** — *"casual home environment"* · *"bedroom"* · *"bathroom mirror"* · *"kitchen counter"* · *"car interior"* · *"messy desk"* · *"cluttered shelf in background"*
**Talent behaviour** — *"natural micro-expressions"* · *"breathing motion"* · *"genuine eye contact"* · *"casual head tilts"* · *"reaches for product naturally"* · *"slight nervousness"* · *"laughs mid-sentence"*
**Critical NEGATIVE prompts** (always include):
> `studio lighting, professional photography, stock photo, model, perfect skin, heavy makeup, perfect composition, centered framing, staged, commercial, beauty filter`
---
### UGC FORMULA (works for every prompt)
```
EMOTION + PERSON + PRODUCT INTERACTION + ENVIRONMENT + CAMERA/QUALITY DETAILS
```
Example:
> *"Relieved smile, woman in her early 60s with short silver pixie cut, holding pink heated styling brush near her shoulder, in softly lit bathroom with morning window light, shot on iPhone selfie cam with natural skin texture and slight grain, 9:16 vertical, ring-light reflection in eyes."*
---
### THE 7-STEP UGC PRODUCTION PIPELINE
Production-tested by performance creators. Use as the default when the user says "UGC ad."
1. **Define 3–5 creative angles** before opening Weavy: problem/relief, demo, testimonial, before/after, objection-busting. Each = ONE clear promise + ONE visual idea.
2. **Prepare reference assets** — 1–3 high-res, watermark-free images of the talent (front + ¾), neutral lighting. Include a clean product shot. Avoid extreme angles or heavy filters.
3. **Identity lock with NB Pro** — generate 6–12 variants from the talent ref, star the top 2–3 by likeness (≥90% match on eyes / nose / mouth / face shape / skin tone / hair).
4. **Scene-preserving edits** — change ONE variable per generation (outfit colour OR backdrop OR pose OR camera distance). Identity stays constant.
5. **Build ad-ready compositions** — sparing overlays, brand-colour callouts inside mobile safe zones, export at 9:16 (1080×1920) and 1:1 (1080×1080).
6. **Launch rapid tests** — 3–6 creatives per ad set, ONE variable changed per batch, gather 1k–2k impressions before judging.
7. **Iterate and scale** — scale winners +20–30% budget every 24–48h if CTR ≥20% above account median; retire when CTR drops 20% week-over-week.
---
### UGC HOOK STRUCTURES (first 0–2 seconds)
The hook line should land **before the first blink**. If the first second doesn't work, nothing works.
**Proven hook patterns:**
- **Problem statement** — "I have short hair. And for years I thought I was the problem."
- **Unusual moment** — "Stop buying these TikTok Shop scams"
- **Boring-specific claim** — "I saved $43 in 10 minutes"
- **Reverse psychology** — "Don't buy this if you have long hair"
- **Authority dismantle** — "Every brand told me this was impossible"
- **Visual disruption** — extreme close-up of product, then pull back
- **Pointing at overlay** — talent points at on-screen text reveal
- **Reaction shot** — talent reacts to using product first time
**Boring-specific > vague** — "$43 in 10 minutes" beats "saved a lot of money fast." Specificity = credibility = thumb-stop.
---
### MODEL SELECTION FOR UGC (when to use what)
Critical breakdown — UGC has different needs than cinematic video:
| UGC scenario | Best model | Why |
|--------------|-----------|-----|
| **Talking head, direct-to-camera** | **Sora 2** | strongest facial motion, eye contact, lip articulation |
| **Hook with motion / zoom / energy** | **Veo 3.1** | quick zooms, slow-mo, dynamic camera moves |
| **Selfie-style with motion presets** | **Higgsfield Video** (`Selfie`/`Handheld`) | 120 motion presets including Selfie, Catwalk, etc. |
| **Product demo with creator** | **Seedance 2.0** | native lip-sync, physics-aware, can extend continuation shots |
| **Continuation of an existing UGC clip** | **Seedance 2.0** (video extension) | feed prior clip back as reference → "continue with same actor, same voice, same product" |
| **Lip-sync to recorded VO** | **Omnihuman V1.5** (image+audio) or **Sync 2 Pro** (video+audio) | Omnihuman = better mouth shapes from a still; Sync 2 Pro = sync existing video clip |
| **Bulk variants for A/B testing** | **Higgsfield UGC pattern** (NB Pro stills → Higgsfield Video) | fastest variants per credit |
**Hybrid pattern for max performance:**
> Veo 3.1 hook (0–2s) → Sora 2 testimonial (2–8s) → product demo footage → assemble in Compositor
This is what top performance creators ship. Don't pick one model — chain the strengths.
---
### CHARACTER CONSISTENCY ACROSS A UGC SERIES
The hardest UGC problem: same "creator" across 10 ads.
**Tactic 1 — NB Pro identity lock**
- Use the SAME `image_1` reference photo in every shot
- Add to every prompt: *"Same person as reference, identical facial features, identical skin tone and hair, preserve identity exactly."*
- If likeness drifts, re-upload the best generated result as the new reference
**Tactic 2 — Detailed character brief in every prompt**
> *"60-year-old woman, short silver pixie hair with subtle volume at root, friendly smile lines around eyes, soft grey crewneck, slightly tan skin, 5'4"."*
Paste this character description verbatim into every prompt across the campaign.
**Tactic 3 — Seedance video extension**
- Once you nail one clip, feed it BACK as a video reference into Seedance
- Prompt: *"Continue scene with same actor, same voice, same product, same environment."*
- Generates next shot with consistent actor
**Tactic 4 — Higgsfield UGC Factory pattern**
- 4 keyframes per generation showing product at different angles/placements
- Identity stays locked across the 4
- Use this when you need 4 quick variants of one moment
**Tactic 5 — Reference sheet collage**
- Combine all useful angles of talent into ONE image (Compositor or Photoshop)
- Feed the collage as image_1 → NB Pro reads it as multi-angle reference
- Same trick Chase Jarvis uses for products, applies to people too
---
### COMMON UGC ARTIFACTS — FIX RECIPES
| Artifact | Fix |
|----------|-----|
| **Extra fingers / warped hands** | Add to negative prompt: `no extra fingers, no warped hands`. Crop tighter on face. Generate product close-ups separately + composite. |
| **Plastic skin** | Add: `natural skin texture, visible pores, fine wrinkles, no beauty filter`. Drop `realistic_image/studio_portrait` style if using Recraft. |
| **Dead eye contact** | Add: `genuine eye contact, soft warmth in eyes, micro-blink`. Higgsfield motion `Eyes In` works for direct gaze. |
| **Looks too produced** | Drop `8K, ultra-detailed, masterpiece` from prompt. Add `compressed, slight motion blur, not retouched`. |
| **Identity drift across shots** | Re-upload best result as new ref. Tighten language: `identical facial features as reference, maintain background unchanged`. |
| **Stiff body posture** | Add: `natural relaxed posture, casual body language, weight on one leg`. |
| **Lip-sync mouth artifacts** | Use Omnihuman V1.5 with **clean mono audio** (ambient noise breaks it). Keep prompt minimal: "subtle micro-expressions, natural blinks, steady framing." |
---
### UGC RECIPES IN WEAVY (production-tested patterns)
**U1 — Single Hero Clip** (the Glow Wave pattern we built earlier)
```
FILE (talent ref) → ROUTER → NB PRO (hero still, 9:16, 2K)
└→ HIGGSFIELD VIDEO (Handheld, dop-turbo)
PROMPT (scene + UGC vocabulary) → NB PRO
PROMPT (motion: subtle handheld, talks to camera) → HIGGSFIELD VIDEO
```
**U2 — 5-Shot Talking-Head Series** (one creator, 5 scenes, same identity)
```
FILE (talent ref) → ROUTER ─┬→ NB PRO (Hook still — frustrated)
├→ NB PRO (Problem still — gestures to hair)
├→ NB PRO (Discovery still — holds product)
├→ NB PRO (Demo still — using product)
└→ NB PRO (Close still — confident smile)
│ each → HIGGSFIELD VIDEO (Handheld)
PROMPT (character brief, identity lock) → reused on all 5 NB Pro
ARRAY (5 scene descriptions //) → LIST SELECTOR (manual) → CONCAT → NB Pro prompt
```
**U3 — Talking Head with Lip-Sync** (creator delivers a script)
```
FILE (talent face) ────────┐
├→ NB PRO (hero still)
PROMPT (UGC scene) ────────┘ │
├→ OMNIHUMAN V1.5
FILE (audio: VO recording) ───────────────────┘
↓
PREVIEW
```
Tip: Omnihuman with image + audio = strongest still-to-talking. Keep prompt MINIMAL (medium shot, steady camera, accurate lip-sync, subtle micro-expressions). Audio quality matters more than prompt detail.
**U4 — Hook + Testimonial + Demo Hybrid** (best-performing structure)
```
PROMPT (hook scene) → VEO 3.1 T2V (0–2s, fast zoom, dynamic) ──┐
PROMPT (testimonial) → SORA 2 (2–8s, talking head, eye contact)─┼→ COMPOSITOR (Timeline editor)
FILE (real product demo b-roll) ────────────────────────────────┘ ↓ EXPORT (9:16 MP4)
```
**U5 — UGC Factory Pattern** (10 product variants, brand-locked)
```
FILE (product photo) ──→ ROUTER ──┐
LIST SELECTOR (10 scene templates iterator) ──┐
├──→ NB PRO (per scene with avatar inserted)
PROMPT (avatar character brief) ──────────────┘ ↓
HIGGSFIELD VIDEO (Selfie/Handheld)
↓
EXPORT AS BATCH
```
**U6 — Continuation Pipeline** (one ad, 30 seconds, multi-shot)
```
SEEDANCE 2.0 (Shot 1, 8s) ──→ ROUTER ──┐
├──→ SEEDANCE 2.0 (Shot 2, "continue with same actor", 8s)
PROMPT (next beat) ────────────────────┘ ↓
SEEDANCE 2.0 (Shot 3, continue, 8s)
↓
COMPOSITOR (stitch + add CTA overlay)
```
---
### SCRIPT WRITING FOR UGC (when the user hasn't written one)
If the user gives you a product but no script, offer to draft using this proven structure:
```
HOOK (0:00–0:03) — problem statement OR unusual moment
PROBLEM (0:03–0:10) — relate / amplify the pain
BLAME SHIFT (0:10–0:18) — "it wasn't me, it was the [tool/category]"
DISCOVERY (0:18–0:25) — "until this..." + show product
DEMO/RESULT (0:25–0:38) — quick visual of working
CLOSE/CTA (0:38–0:45) — relaxed, confident, "this is the one"
```
**Script rules:**
- Conversational, not corporate
- Boring-specific claims ("two minutes", "$43") > vague claims ("fast", "cheap")
- ONE benefit per ad — don't list features
- Direct address ("you", "your") > third person
- 6–8 word sentences max in spoken parts
- One emotional hook word per scene (frustrated, finally, actually, honestly)
---
### PLATFORM-SPECIFIC FRAMING
**TikTok / Reels (9:16, 1080×1920)**
- Tight selfie framing, head fully visible with breathing room top
- Captions in centre-safe zone (avoid bottom 200px — UI overlay)
- 8s or 12s ideal length
- Ring-light reflection in eyes = TikTok signature
**YouTube Shorts (9:16, 1080×1920)**
- Slightly wider framing OK
- Faster cuts (every 2–3 seconds)
- Stronger visual hook in thumbnail frame
**1:1 Square (1080×1080)** (Instagram feed)
- Generate alongside 9:16 in same workflow via aspect-ratio resize node
- Compose with subject slightly above centre
---
### UGC CREDIT BUDGETING
A typical UGC campaign in Weavy:
- 5 scenes × NB Pro still (8 cr each) = 40 cr
- 5 × Higgsfield Video dop-turbo (~30 cr each) = 150 cr
- 1 × Omnihuman lip-sync (1–2 cr) = 2 cr (cheap!)
- 1 × Topaz Video Upscaler (12 cr) on winner = 12 cr
- **Total: ~200 cr per campaign of 5 variants**
Premium variant (Sora 2 + Veo 3.1):
- 5 × Sora 2 / Veo 3.1 (60–120 cr each) = 300–600 cr
- **Total: ~500–700 cr per premium campaign**
**Always estimate before launch.** If user says "make me 20 variants", 4×–5× these numbers and check credit budget first.
---
### UGC GOTCHAS (UGC-specific failures)
1. **Faces blocked by Seedance** — close-up faces are filtered by default. Workaround: blur faces before upload, or generate scene first then add face via NB Pro
2. **Lip-sync needs CLEAN audio** — record VO with USB mic or 11Labs TTS, not phone speaker. Background noise = mouth glitches
3. **Higgsfield Selfie motion adds shake** — too much for static testimonials. Use `Static` for talking head, `Selfie` for walking/active scenes
4. **NB Pro recomposes if face ref isn't anchored first** — first prompt sentence MUST establish "Same person as reference"
5. **Don't iterate on identity AND scene at same time** — lock identity, vary one scene element. Otherwise drift compounds
6. **Avoid "8K masterpiece" tags** — they make outputs look like stock photos. UGC wants imperfection
7. **Stock backdrops kill credibility** — generate cluttered, lived-in environments (papers on counter, half-empty mug, etc.)
8. **Don't cut faster than 2 seconds in hook** — viewer needs to register the person before the cut
9. **TikTok hates obvious AI** — slight grain, motion blur, imperfect framing all help; perfect lighting + perfect skin = banned account risk
10. **Lip-sync to non-native language?** — Omnihuman supports 70+ languages; use it for international rollouts of one ad
---
## CRITICAL: ROOT `kind` RULES
**NEVER add a root-level `kind` field to any node.** The `kind` data always lives inside `data.kind`, and only for model nodes.
| Node category | Has `data.kind` |
|--------------|----------------|
| Simple nodes (prompt, string, array, mux, concat, router, file, group) | ❌ NO |
| LLM (`any_llm`) | ✅ YES |
| AI Models (wildcard, kling, gpt_image1, reve, imagen3) | ✅ YES |
| Compare (`slider`) | ✅ YES |
---
## COLOR PALETTE
| Role | `color` | `dark_color` | `border_color` |
|------|---------|-------------|----------------|
| File Upload | `Yambo_Blue` | `Yambo_Blue_Dark` | `Yambo_Blue_Stroke` |
| Text / Prompt / Array / Concat / Selector | `Yambo_Green` | `Yambo_Green_Dark` | `Yambo_Green_Stroke` |
| Router | `Yambo_Orange` | `Yambo_Orange_Dark` | `Yambo_Orange_Stroke` |
| LLM | `Yambo_Purple` | `Yambo_Purple_Dark` | `Yambo_Purple_Stroke` |
| AI Models (image/video) | `Red` | — | — |
| Kling Element | `#000000` | — | — |
| Compare | `#000000` | — | — |
---
## NODE REFERENCES (how nodes talk to each other)
```python
# Text: {"nodeId": "uuid", "outputId": "text", "string": ""}
# File: {"nodeId": "uuid", "outputId": "file", "file": {}}
# Prompt: {"nodeId": "uuid", "outputId": "prompt", "string": ""}
# Router: {"nodeId": "uuid", "outputId": "out", "file": {}}
# Concat: {"nodeId": "uuid", "outputId": "prompt", "string": ""}
# Selector: {"nodeId": "uuid", "outputId": "option", "string": ""}
# Array: {"nodeId": "uuid", "outputId": "array", "stringArray": [...]}
```
Where references appear:
- **LLM**: `data.kind.prompt`, `data.kind.systemPrompt`, `data.kind.images[n][1]`
- **Wildcard models (NB, Flux)**: `data.kind.inputs[n][1]`
- **Kling**: `data.kind.prompt`, `data.kind.image`, `data.kind.endImageUrl`, `data.kind.negativePrompt`, `data.kind.elements[n][1]`
- **Concatenator**: `data.inputNodes[n][1]`
- **Prompt (variables)**: `data.inputNodes[n][1]`
- **Array**: `data.inputNode`
- **List Selector**: `data.options`
---
## EDGES (connections)
```python
def make_edge(source_id, target_id, source_handle, target_handle, src_color, tgt_color, src_type, tgt_type):
return {
"id": uid(),
"source": source_id,
"target": target_id,
"sourceHandle": f"{source_id}-output-{source_handle}",
"targetHandle": f"{target_id}-input-{target_handle}",
"type": "custom",
"data": {
"sourceColor": src_color, "targetColor": tgt_color,
"sourceHandleType": src_type, "targetHandleType": tgt_type
}
}
```
### Common handle names
| Node | Output | Inputs |
|------|--------|--------|
| File Upload | `file` | — |
| Text | `text` | — |
| Prompt | `prompt` | `variable1`, `variable2`... |
| Concatenator | `prompt` | `prompt1`, `prompt2`... |
| Router | `out` | `in` |
| LLM | `text` | `prompt`, `system_prompt`, `image` |
| NB Pro / Flux | `result` | `prompt`, `image_1` |
| Kling | `video` | `prompt`, `image`, `end_image_url`, `negative_prompt`, `element_1`... |
| Kling Element | `result` | `frontal_image_url`, `reference_image_url1`...`3` |
| GPT Image | `image` | `prompt` |
| GPT Image Edit | `image` | `prompt`, `image` |
| Video Describer | `text` | `video_1` |
| Compare | `out_a`, `out_b` | `input_a`, `input_b` |
| Reve | `result` | `prompt`, `reference_image_1`...`N` |
| Reve Edit | `result` | `edit_instruction`, `reference_image` |
| Higgsfield Image | `result` | `prompt`, `image_reference` |
| Higgsfield Video | `video` | `prompt`, `image` (first_frame) |
| Imagen 3 / 3 Fast / 4 | `result` | `prompt`, `negative_prompt` |
| Flux 2 Flex | `result` | `prompt`, `image_1` |
| Array | `array` | `text` |
| List Selector | `option` | `options` |
### Common edge color/type combos
| Connection | src_color | tgt_color | src_type | tgt_type |
|-----------|-----------|-----------|----------|----------|
| File → Router | `Yambo_Blue` | `Yambo_Orange` | `any` | `any` |
| Router → LLM (image) | `Yambo_Orange` | `Yambo_Purple` | `any` | `image` |
| Router → AI Model (image) | `Yambo_Orange` | `Red` | `any` | `image` |
| Prompt → LLM (sys) | `Yambo_Green` | `Yambo_Purple` | `text` | `text` |
| Prompt → Prompt (variable) | `Yambo_Green` | `Yambo_Green` | `text` | `text` |
| Concat → LLM (prompt) | `Yambo_Green` | `Yambo_Purple` | `text` | `text` |
| LLM → Array | `Yambo_Purple` | `Yambo_Green` | `text` | `text` |
| LLM → AI Model (prompt) | `Yambo_Purple` | `Red` | `text` | `text` |
| Array → Selector | `Yambo_Green` | `Yambo_Green` | `array` | `array` |
| Selector → AI Model | `Yambo_Green` | `Red` | `text` | `text` |
| Kling Element → Kling | `#000000` | `Red` | `kling-element` | `kling-element` |
---
## PYTHON BOILERPLATE
```python
import json
import uuid
def uid():
return str(uuid.uuid4())
NOW = "2026-01-01T00:00:00.000Z"
UPD = "2026-01-01T00:00:00.000Z"
```
---
## BUILDER: FILE UPLOAD ✅ Verified
`type: "import"` — 460 × 558 — No root `kind`
```python
def make_file_node(node_id, name, x, y):
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "import",
"visibility": None, "isModel": False,
"data": {
"handles": {"output": {"file": {"type": "any", "label": "File", "order": 0, "format": "uri", "description": "The uploaded file"}}},
"name": name, "description": None, "color": "Yambo_Blue", "label": None, "menu": None, "params": None, "schema": None, "version": 3,
"dark_color": "Yambo_Blue_Dark", "border_color": "Yambo_Blue_Stroke",
"files": [], "cameraLocked": False, "selectedIndex": 0, "output": {}
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 558
}
```
---
## BUILDER: TEXT ✅ Verified
`type: "string"` — 460 × 268 — No root `kind` — Default name: `"Text"`
```python
def make_string_node(node_id, name, value, x, y):
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "string",
"visibility": None, "isModel": False,
"data": {
"handles": {"input": {}, "output": {"text": {"id": uid(), "type": "text", "order": 0, "format": "text", "description": "Text"}}},
"name": name, "description": "", "color": "Yambo_Green", "label": None, "menu": None, "params": None, "schema": None, "version": 3,
"result": {"string": ""}, "dark_color": "Yambo_Green_Dark", "border_color": "Yambo_Green_Stroke",
"value": value, "output": {"type": "text", "text": "", "string": ""}
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 268
}
```
---
## BUILDER: PROMPT ✅ Verified
`type: "promptV3"` — 460 × 335 — No root `kind` — Supports `{{variableName}}` inline variables
```python
def make_prompt_node(node_id, name, prompt_text, x, y, variables=None):
"""
variables: list of (var_name, label, ref_or_none)
Example: [("variable1", "Variable 1", {"nodeId":"x","outputId":"prompt","string":""})]
Use {{variable1}} in prompt_text where the value should appear.
Pass None or [] for a prompt without variables.
"""
input_handles = {} if variables else []
input_nodes = []
if variables:
for i, (var_name, label, ref) in enumerate(variables):
input_handles[var_name] = {
"description": "", "format": "text", "id": uid(),
"order": i, "required": False, "label": label, "type": "text"
}
input_nodes.append([var_name, ref])
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "promptV3",
"visibility": None, "isModel": False,
"data": {
"handles": {"input": input_handles, "output": {"prompt": {"type": "text", "order": 0, "format": "text", "description": "Text prompt"}}},
"name": name, "description": None, "color": "Yambo_Green", "label": "prompt", "menu": None, "params": None, "schema": None, "version": 3,
"prompt": prompt_text, "result": {"prompt": prompt_text},
"dark_color": "Yambo_Green_Dark", "border_color": "Yambo_Green_Stroke",
"inputNodes": input_nodes, "displayMode": "source-value",
"output": {"type": "text", "prompt": prompt_text}
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 335
}
```
---
## BUILDER: CONCATENATOR ✅ Verified
`type: "prompt_concat"` — 460 × 368 (2 inputs) — No root `kind`
`additionalPrompt` = intro text BEFORE inputs. `inputNodes` order = concatenation order.
```python
def make_concat_node(node_id, name, input_refs, x, y, additional_prompt=""):
input_handles = {}
input_nodes = []
for i, (hname, ref) in enumerate(input_refs):
input_handles[hname] = {"type": "text", "label": f"text_{i+1}", "order": i, "format": "text", "description": "Text input"}
input_nodes.append([hname, ref])
total_height = 268 + len(input_refs) * 50
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "prompt_concat",
"visibility": None, "isModel": False,
"data": {
"handles": {"input": input_handles, "output": {"prompt": {"type": "text", "label": "combined_text", "format": "text", "description": "The combined text"}}},
"name": name, "description": "Join multiple text inputs to one output.", "color": "Yambo_Green",
"label": None, "menu": None, "params": None, "schema": None, "version": 3,
"dark_color": "Yambo_Green_Dark", "inputNodes": input_nodes, "border_color": "Yambo_Green_Stroke",
"additionalPrompt": additional_prompt, "result": {"additionalPrompt": additional_prompt},
"output": {"type": "text", "prompt": ""}
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": total_height
}
```
---
## BUILDER: ROUTER ✅ Verified
`type: "router"` — 250 × 64 — No root `kind`
```python
def make_router_node(node_id, name, x, y):
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "router",
"visibility": None, "isModel": False,
"data": {
"handles": {
"input": {"in": {"id": uid(), "type": "any", "label": "In", "order": 0, "description": "The input"}},
"output": {"out": {"id": uid(), "type": "any", "label": "Out", "order": 0, "description": "The output"}}
},
"name": name, "description": None, "color": "Yambo_Orange", "label": None, "menu": None, "params": None, "schema": None, "version": 3,
"dark_color": "Yambo_Orange_Dark", "border_color": "Yambo_Orange_Stroke", "output": {}
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 250, "height": 64
}
```
---
## BUILDER: LLM ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "any_llm"` — 460 × 562 — **No root `kind`** — `isModel: true`
Up to 14 image inputs. `prompt`/`systemPrompt` in `data.kind` only when connected (omit when None).
```python
LLM_MODELS = [
"anthropic/claude-sonnet-4-5","anthropic/claude-opus-4-6","anthropic/claude-opus-4-5",
"anthropic/claude-3-haiku","google/gemini-2.0-flash-001","google/gemini-2.5-flash",
"google/gemini-2.5-flash-lite","google/gemini-3-pro","openai/gpt-4o","openai/gpt-4.1",
"openai/gpt-5-chat","meta-llama/llama-4-maverick","meta-llama/llama-4-scout"
]
def make_llm_node(node_id, name, model, prompt_ref, sys_ref, image_refs, x, y):
"""
prompt_ref / sys_ref: text ref or None. Omitted from data.kind when None.
image_refs: list of (label, ref_or_none). Always pass at least [("image", None)].
"""
kind_data = {
"type": "any_llm",
"images": image_refs if image_refs else [["image", None]],
"model": {"type": "value", "data": {"type": "string", "value": model}},
"temperature": {"type": "value", "data": {"type": "float", "value": 0}},
"thinking": {"type": "value", "data": {"type": "boolean", "value": False}}
}
if prompt_ref: kind_data["prompt"] = prompt_ref
if sys_ref: kind_data["systemPrompt"] = sys_ref
input_handles = {
"prompt": {"type": "text", "order": 0, "format": "text", "required": True,
"description": "Describe your request from the model"},
"system_prompt": {"type": "text", "order": 1, "format": "text", "required": False,
"description": "Describe the purpose of the model (e.g \"you are a prompt generator\")"}
}
if image_refs:
for i, (label, _) in enumerate(image_refs):
input_handles[label] = {"type": "image", "label": f"image {i+1}", "order": 2+i,
"format": "uri", "required": False, "description": "Image to analyse"}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": None, "isModel": True,
"data": {
"handles": {"input": input_handles, "output": {"text": {"type": "text", "order": 0, "format": "text", "description": "The LLM response"}}},
"name": name, "description": "Run any large language model.", "color": "Yambo_Purple",
"label": None, "menu": None, "model": {"name": "any_llm"},
"params": {"model": model, "temperature": 0},
"schema": {
"model": {"type": "enum", "order": 0, "title": "Model Name", "default": "google/gemini-3-pro",
"options": LLM_MODELS, "description": "Name of the model to use"},
"thinking": {"type": "boolean", "order": 5, "title": "Thinking", "default": False, "required": False,
"description": "Enhanced reasoning capabilities for complex task (For supported models only)"},
"temperature": {"max": 2, "min": 0, "type": "number", "title": "Temperature", "default": 0,
"description": "This setting influences the variety in the model's responses. Lower values lead to more predictable and typical responses, while higher values encourage more diverse and less common responses. At 0, the model always gives the same response for a given input."}
},
"version": 3, "dark_color": "Yambo_Purple_Dark", "border_color": "Yambo_Purple_Stroke",
"kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 562
}
```
---
## BUILDER: NANO BANANA PRO ✅ Verified
`fal-ai/nano-banana-pro/edit` — 460 × 560 — Wildcard — `isModel: true`
Seed = type `"seed"` (object). Resolutions: `1K/2K/4K`. No `output_format`. Inputs refs = `null` when not connected.
```python
def make_nb_pro_node(node_id, name, prompt_ref, image_1_ref, x, y, resolution="1K", aspect_ratio="auto"):
inputs = [
[{"id": "prompt", "title": "Prompt", "description": "The prompt for image editing.", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "image_1", "title": "image_1", "description": "The image you want to edit", "validTypes": ["image"], "required": False}, image_1_ref],
]
input_handles = {
"prompt": {"id": uid(), "type": "text", "label": "prompt", "order": 0, "format": "text", "required": True, "description": "Description of the edits you want to make"},
"image_1": {"id": uid(), "type": "image", "label": "image_1", "order": 1, "format": "uri", "required": False, "description": "The image you want to edit"},
}
parameters = [
[{"id": "seed", "title": "Seed", "description": "Seed value for random number generator. Uncheck for reproducible results.",
"constraint": {"type": "seed"}, "defaultValue": {"type": "seed", "value": {"seed": 1, "isRandom": False}}},
{"type": "value", "data": {"type": "seed", "value": {"seed": 0, "isRandom": True}}}],
[{"id": "resolution", "title": "Resolution", "description": "The resolution of the image to generate.",
"constraint": {"type": "enum", "options": ["1K","2K","4K"]}, "defaultValue": {"type": "string", "value": "1K"}},
{"type": "value", "data": {"type": "string", "value": resolution}}],
[{"id": "aspect_ratio", "title": "Aspect Ratio", "description": "The aspect ratio of the generated image.",
"constraint": {"type": "enum", "options": ["auto","1:1","21:9","16:9","3:2","4:3","5:4","4:5","3:4","2:3","9:16"]},
"defaultValue": {"type": "string", "value": "auto"}},
{"type": "value", "data": {"type": "string", "value": aspect_ratio}}],
[{"id": "enable_web_search", "title": "Enable Web Search", "description": "Enable web search for the image generation task.",
"constraint": {"type": "boolean"}, "defaultValue": {"type": "boolean", "value": False}},
{"type": "value", "data": {"type": "boolean", "value": False}}],
]
outputs = [{"id": "result", "title": "result", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "fal-ai/nano-banana-pro/edit", "version": "fal-ai/nano-banana-pro/edit",
"service": "fal_imported", "description": "Google's state-of-the-art image generation and editing model"},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": None, "isModel": True,
"data": {
"handles": {"input": input_handles, "output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}},
"name": name, "description": "Google's state-of-the-art image generation and editing model", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Gemini 3 Pro (with Nano Banana)"},
"model": {"name": "fal-ai/nano-banana-pro/edit", "service": "fal_imported", "version": "fal-ai/nano-banana-pro/edit"},
"params": {"seed": {"seed": 0, "isRandom": True}, "prompt": "", "num_images": 1, "resolution": resolution, "aspect_ratio": aspect_ratio, "enable_web_search": False},
"schema": {
"seed": {"type": "seed", "title": "Seed", "required": False, "description": "Seed value for random number generator."},
"prompt": {"type": "string", "title": "Prompt", "required": True, "description": "The prompt for image editing."},
"resolution": {"type": "enum", "title": "Resolution", "default": "1K", "options": ["1K","2K","4K"], "required": False},
"aspect_ratio": {"type": "enum", "title": "Aspect Ratio", "default": "auto", "options": ["auto","1:1","21:9","16:9","3:2","4:3","5:4","4:5","3:4","2:3","9:16"], "required": False},
"enable_web_search": {"type": "boolean", "title": "Enable Web Search", "default": False, "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: FLUX 2 PRO ✅ Verified
`fal-ai/flux-2-pro` — 460 × 560 — Wildcard — `isModel: true`
Same seed pattern as NB Pro. Uses `image_size` (type `fal_image_size`) instead of resolution/aspect_ratio.
```python
def make_flux_pro_node(node_id, name, prompt_ref, image_1_ref, x, y):
inputs = [
[{"id": "prompt", "title": "prompt", "description": "The prompt to generate an image from.", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "image_1", "title": "image_1", "description": "The image you want to edit", "validTypes": ["image"], "required": False}, image_1_ref],
]
input_handles = {
"prompt": {"id": uid(), "type": "text", "label": "prompt", "format": "text", "required": True, "description": "The prompt to generate an image from."},
"image_1": {"id": uid(), "type": "image", "label": "image_1", "order": 1, "format": "uri", "required": False, "description": "The image you want to edit"},
}
parameters = [
[{"id": "seed", "title": "Seed", "description": "Seed value for random number generator.",
"constraint": {"type": "seed"}, "defaultValue": {"type": "seed", "value": {"seed": 1, "isRandom": False}}},
{"type": "value", "data": {"type": "seed", "value": {"seed": 0, "isRandom": True}}}],
[{"id": "image_size", "title": "Image Size", "description": "The size of the generated image.",
"constraint": {"type": "image_size", "options": ["Default","auto","square_hd","square","portrait_4_3","portrait_16_9","landscape_4_3","landscape_16_9"]},
"defaultValue": {"type": "image_size", "value": {"type": "built_in", "value": "match_input"}}},
{"type": "value", "data": {"type": "image_size", "value": {"type": "built_in", "value": "match_input"}}}],
]
outputs = [{"id": "result", "title": "result", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "fal-ai/flux-2-pro", "version": "fal-ai/flux-2-pro",
"service": "fal_imported", "description": "Image generation and editing with FLUX.2 [pro] from Black Forest Labs."},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {"input": input_handles, "output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}},
"name": name, "description": "Image generation and editing with FLUX.2 [pro] from Black Forest Labs.", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Flux 2 Pro"},
"model": {"name": "fal-ai/flux-2-pro", "service": "fal_imported", "version": "fal-ai/flux-2-pro"},
"params": {"seed": {"seed": 0, "isRandom": True}, "prompt": "", "image_size": None, "output_format": "png", "safety_tolerance": "1", "enable_safety_checker": True},
"schema": {
"seed": {"type": "seed", "title": "Seed", "required": False},
"image_size": {"type": "fal_image_size", "title": "Image Size", "default": None,
"options": ["Default","auto","square_hd","square","portrait_4_3","portrait_16_9","landscape_4_3","landscape_16_9"], "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: KLING 3 ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "kling"` — 460 × 560 — `isModel: true`
Refs named directly in `data.kind`: `prompt`, `image`, `endImageUrl`, `negativePrompt`, `elements[]`.
```python
def make_kling_node(node_id, name, prompt_ref, image_ref, end_image_ref, neg_prompt_ref, element_refs, x, y,
kling_model="3.0 Pro", duration=5, cfg_scale=0.5, aspect_ratio="16:9", generate_audio=False):
kind_data = {
"type": "kling",
"model": {"type": "value", "data": {"type": "string", "value": kling_model}},
"duration": {"type": "value", "data": {"type": "integer", "value": duration}},
"cfgScale": {"type": "value", "data": {"type": "float", "value": cfg_scale}},
"shotType": {"type": "value", "data": {"type": "string", "value": "customize"}},
"aspectRatio": {"type": "value", "data": {"type": "string", "value": aspect_ratio}},
"generateAudio": {"type": "value", "data": {"type": "boolean", "value": generate_audio}},
"elements": element_refs if element_refs else []
}
if prompt_ref: kind_data["prompt"] = prompt_ref
if image_ref: kind_data["image"] = image_ref
if end_image_ref: kind_data["endImageUrl"] = end_image_ref
if neg_prompt_ref: kind_data["negativePrompt"] = neg_prompt_ref
input_handles = {
"prompt": {"id": uid(), "type": "text", "label": "prompt", "order": 0, "format": "text", "required": True,
"description": "Text prompt for video generation."},
"image": {"id": uid(), "type": "image", "label": "first_frame", "order": 1, "format": "text", "required": False,
"description": "The image to be used for the video"},
"end_image_url": {"id": uid(), "type": "image", "label": "last_frame", "order": 2, "format": "text", "required": False,
"description": "The image to be used for the end of the video"},
"negative_prompt": {"id": uid(), "type": "text", "label": "negative_prompt", "order": 3, "format": "text", "required": False},
}
for i, (label, _) in enumerate(element_refs or []):
input_handles[label] = {"id": uid(), "type": "kling-element", "label": label, "order": 4+i, "format": "uri", "required": False}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {"input": input_handles, "output": {"video": {"type": "video", "label": "video", "order": 0, "format": "uri", "description": "The video result"}}},
"name": name, "description": "Kling 3.0 Pro: Top-tier image-to-video.", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Kling 3 Pro"},
"model": {"name": "kling"},
"params": {"model": kling_model, "duration": str(duration), "cfg_scale": cfg_scale, "shot_type": "customize", "aspect_ratio": aspect_ratio, "generate_audio": generate_audio},
"schema": {
"model": {"type": "enum", "order": 0, "title": "Model", "default": "Pro", "options": ["3.0 Pro","3.0 Standard"], "required": False},
"duration": {"max": 15, "min": 3, "type": "integer", "title": "Duration", "default": 5, "required": False},
"cfg_scale": {"max": 1, "min": 0, "type": "number", "title": "Cfg Scale", "default": 0.5, "required": False},
"shot_type": {"type": "enum", "title": "Shot Type", "default": "customize", "options": ["customize"], "required": False},
"aspect_ratio": {"type": "enum", "title": "Aspect Ratio (T2I only)", "default": "16:9", "options": ["16:9","9:16","1:1"], "required": False},
"generate_audio": {"type": "boolean", "title": "Generate Audio", "default": False, "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: KLING ELEMENT ✅ Verified
`type: "kling_element"` — 460 × 507 — Color `#000000` — `visibility: "public"`
```python
def make_kling_element_node(node_id, name, x, y):
return {
"id": node_id, "type": "kling_element",
"data": {
"version": 3, "color": "#000000",
"description": "Provide structured element data for the Kling model",
"type": "kling_element", "name": name,
"handles": {
"input": {
"frontal_image_url": {"id": "frontal_image_url", "type": "image", "label": "Frontal Image", "format": "uri", "order": 0, "required": True, "description": "Frontal image URL"},
"reference_image_url1": {"id": "reference_image_url1", "type": "image", "label": "Reference Image 1", "format": "uri", "order": 1, "required": True, "description": "Reference image URL"},
"reference_image_url2": {"id": "reference_image_url2", "type": "image", "label": "Reference Image 2", "format": "uri", "order": 2, "required": False, "description": "Reference image URL"},
"reference_image_url3": {"id": "reference_image_url3", "type": "image", "label": "Reference Image 3", "format": "uri", "order": 3, "required": False, "description": "Reference image URL"}
},
"output": {"result": {"id": "result", "type": "kling-element", "label": "Kling Element", "format": "uri", "order": 0, "required": False, "description": "Kling element data"}}
}
},
"isModel": False, "owner": None, "visibility": "public", "locked": False,
"position": {"x": x, "y": y},
"createdAt": NOW, "selected": False, "width": 460, "height": 507
}
```
---
## BUILDER: ARRAY ✅ Verified
`type: "array"` — 460 × 230 (static) / 460 × 278 (dynamic) — No root `kind`
```python
def make_array_node(node_id, name, delimiter, input_ref, x, y):
"""Dynamic array — splits incoming text by delimiter."""
data = {
"handles": {
"input": {"text": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": False, "description": "Text to split into array"}},
"output": {"array": {"id": uid(), "type": "array", "order": 0, "format": "text", "description": "Array of text items"}}
},
"name": name, "description": "Array of elements", "color": "Yambo_Green",
"label": None, "menu": None, "params": None, "schema": None, "version": 3,
"array": [""], "result": [], "delimiter": delimiter,
"dark_color": "Yambo_Green_Dark", "border_color": "Yambo_Green_Stroke",
"output": {"type": "array", "array": []}
}
if input_ref:
data["inputNode"] = input_ref
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "array",
"visibility": None, "isModel": False, "data": data,
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 278
}
def make_static_array_node(node_id, name, items, delimiter, x, y):
"""Static array — fixed list of values."""
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "array",
"visibility": None, "isModel": False,
"data": {
"handles": {
"input": {"text": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": False, "description": "Text to split into array"}},
"output": {"array": {"id": uid(), "type": "array", "order": 0, "format": "text", "description": "Array of text items"}}
},
"name": name, "description": "Array of elements", "color": "Yambo_Green",
"label": None, "menu": None, "params": None, "schema": None, "version": 3,
"array": items, "result": items, "delimiter": delimiter,
"dark_color": "Yambo_Green_Dark", "border_color": "Yambo_Green_Stroke",
"output": {"type": "array", "array": items}
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 230
}
```
---
## BUILDER: LIST SELECTOR ✅ Verified
`type: "muxv2"` — 250 × 102 — No root `kind` — No `dragHandle` — `visibility: "public"`
`isIterator: true` triggers downstream for EACH element.
```python
def make_list_selector_node(node_id, name, options_ref, is_iterator, x, y):
return {
"id": node_id, "type": "muxv2",
"data": {
"version": 3, "description": "Select an option from a list", "type": "list_selector",
"name": name,
"handles": {
"input": {"options": {"id": uid(), "type": "array", "label": "Options", "format": "array", "required": False, "order": 0, "description": "Array of options to choose from"}},
"output": {"option": {"id": uid(), "type": "text", "label": "Text", "order": 0, "format": "text", "description": "The selected option", "required": False}}
},
"options": options_ref, "delimiter": ",", "list": [], "selected": 0,
"schema": {"options": {"order": 0, "type": "array", "title": "Options", "exposed": True, "required": False, "description": "Array of options to choose from"}},
"isIterator": is_iterator, "color": "Yambo_Green",
"result": "", "output": {"type": "text", "option": ""}, "params": {"options": []}
},
"isModel": False, "owner": None, "visibility": "public", "locked": False,
"position": {"x": x, "y": y},
"createdAt": NOW,
"selected": False, "width": 250, "height": 102
}
```
---
## BUILDER: GROUP ✅ Verified
`type: "custom_group"` — Children: `parentId` = group ID, **relative** positions
```python
def make_group_node(group_id, name, x, y, width, height):
return {
"id": group_id, "type": "custom_group",
"data": {
"version": 3, "color": "rgba(227, 232, 236, 0.64)",
"description": "Group of nodes", "type": "custom_group",
"name": name,
"handles": {"input": [], "output": []},
"width": width, "height": height, "labelFontSize": 16
},
"position": {"x": x, "y": y},
"locked": False, "selected": False,
"width": width, "height": height
}
# For each child: node["parentId"] = group_id
# Child positions are RELATIVE to group top-left
```
---
## BUILDER: GPT IMAGE ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "gpt_image1"` — 460 × 560 — `isModel: true`
OpenAI text-to-image. **Distinct from wildcard pattern**: parameters live as named fields directly inside `data.kind` (like Kling), NOT as `inputs/parameters/outputs` arrays. Output handle is `image`, not `result`.
Models: `"GPT Image 1"`, `"GPT Image 1.5"`, `"GPT Image 1.5 Turbo"`, `"GPT Image 2"` (default).
```python
def make_gpt_image_node(node_id, name, prompt_ref, x, y,
model="GPT Image 2", size="1024x1024", quality="medium", background="opaque"):
kind_data = {
"type": "gpt_image1",
"model": {"type": "value", "data": {"type": "string", "value": model}},
"background": {"type": "value", "data": {"type": "string", "value": background}},
"quality": {"type": "value", "data": {"type": "string", "value": quality}},
"size": {"type": "value", "data": {"type": "string", "value": size}},
}
if prompt_ref: kind_data["prompt"] = prompt_ref
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {"prompt": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": True, "description": "Text prompt for image generation"}},
"output": {"image": {"id": uid(), "type": "image", "label": "image", "order": 0, "format": "uri", "description": "Image result"}}
},
"name": name, "description": "Generate images with OpenAI's GPT Image 2", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "GPT Image 1"},
"model": {"name": "gpt_image_1"},
"params": {"size": size, "model": model, "quality": quality, "background": background, "output_format": "png", "number_of_images": 1},
"schema": {
"size": {"type": "enum", "order": 9, "title": "Resolution", "default": "auto", "options": ["1024x1024","1536x1024","1024x1536"], "description": "The size of the generated images"},
"model": {"type": "enum", "order": 0, "title": "Model", "default": "GPT Image 2", "options": ["GPT Image 1","GPT Image 1.5","GPT Image 1.5 Turbo","GPT Image 2"], "required": False, "description": "GPT model version"},
"quality": {"type": "enum", "order": 7, "title": "Quality", "default": "medium", "options": ["high","medium","low"], "description": "Image quality."},
"background": {"type": "enum", "order": 1, "title": "Background", "default": "opaque", "options": ["transparent","opaque"], "description": "Background transparency."}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: GPT IMAGE EDIT ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "wildcard"` — 460 × 560 — `isModel: true`
Wildcard pattern (`predefined` model `gpt_image_1_edit`). Output handle is `image`, not `result`. Inputs: `prompt` (required text), `image` (required image).
Sizes include high-res: `1024x1024, 1536x1024, 1024x1536, 2048x2048, 2048x1152, 3840x2160, 2160x3840`.
```python
def make_gpt_image_edit_node(node_id, name, prompt_ref, image_ref, x, y,
model="GPT Image 2", quality="medium", size="1024x1024"):
inputs = [
[{"id": "prompt", "title": "prompt", "description": "Prompt for image edit", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "image", "title": "image", "description": "Image to edit", "validTypes": ["image"], "required": True}, image_ref],
]
parameters = [
[{"id": "model", "title": "Model", "description": "GPT model type",
"constraint": {"type": "enum", "options": ["GPT Image 1","GPT Image 1.5","GPT Image 2"]},
"defaultValue": {"type": "string", "value": "GPT Image 2"}},
{"type": "value", "data": {"type": "string", "value": model}}],
[{"id": "quality", "title": "Quality", "description": "Image quality.",
"constraint": {"type": "enum", "options": ["high","medium","low"]},
"defaultValue": {"type": "string", "value": "medium"}},
{"type": "value", "data": {"type": "string", "value": quality}}],
[{"id": "size", "title": "Resolution", "description": "Size of the generated images",
"constraint": {"type": "enum", "options": ["1024x1024","1536x1024","1024x1536","2048x2048","2048x1152","3840x2160","2160x3840"]},
"defaultValue": {"type": "string", "value": "1024x1024"}},
{"type": "value", "data": {"type": "string", "value": size}}],
]
outputs = [{"id": "image", "title": "image", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "gpt_image_1_edit", "description": "Edit images with OpenAI's GPT Image 2"},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {
"image": {"id": uid(), "type": "image", "label": "image", "order": 1, "format": "uri", "required": True, "description": "Image to edit"},
"prompt": {"id": uid(), "type": "text", "label": "prompt", "order": 0, "format": "text", "required": True, "description": "Prompt for image edit"},
},
"output": {"image": {"id": uid(), "type": "image", "label": "image", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name, "description": "Edit images with OpenAI's GPT Image 2", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "GPT Image 1"},
"model": {"name": "gpt_image_1_edit"},
"params": {"size": size, "model": model, "quality": quality, "number_of_images": 1},
"schema": {
"size": {"type": "enum", "order": 9, "title": "Resolution", "default": "auto", "options": ["1024x1024","1536x1024","1024x1536","2048x2048","2048x1152","3840x2160","2160x3840"]},
"model": {"type": "enum", "order": 0, "title": "Model", "default": "GPT Image 2", "options": ["GPT Image 1","GPT Image 1.5","GPT Image 2"]},
"quality": {"type": "enum", "order": 7, "title": "Quality", "default": "medium", "options": ["high","medium","low"]},
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: VIDEO DESCRIBER ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "wildcard"` — 460 × 560 — `isModel: true`
Wildcard with `user_defined` (not `predefined`) fal model — used for fal-imported services. Input is a single **video** (`video_1`, type `video`), output is **text**. Use to feed an LLM-generated description into downstream image/video generators.
Models: Gemini 2.5/2.0/3 flash family via OpenRouter.
```python
VIDEO_DESCRIBER_MODELS = [
"google/gemini-2.5-flash","google/gemini-2.5-flash-lite",
"google/gemini-2.0-flash-001","google/gemini-3-flash-preview"
]
def make_video_describer_node(node_id, name, video_ref, x, y,
model="google/gemini-2.5-flash", instructions=""):
inputs = [
[{"id": "video_1", "title": "video", "description": "The video to analyze", "validTypes": ["video"], "required": True}, video_ref],
]
parameters = [
[{"id": "model", "title": "Model", "description": "Name of the LLM to use for video understanding",
"constraint": {"type": "enum", "options": VIDEO_DESCRIBER_MODELS},
"defaultValue": {"type": "string", "value": "google/gemini-2.5-flash"}},
{"type": "value", "data": {"type": "string", "value": model}}],
[{"id": "prompt", "title": "Model instructions", "description": "Instruction for the model",
"constraint": {"type": "string"},
"defaultValue": {"type": "string", "value": instructions}},
{"type": "value", "data": {"type": "string", "value": instructions}}],
]
outputs = [{"id": "text", "title": "Text", "description": "The LLM response", "dataType": "text"}]
kind_data = {
"type": "wildcard",
"model": {"type": "user_defined", "name": "openrouter/router/video/enterprise",
"version": "openrouter/router/video/enterprise",
"service": "fal_imported", "description": "Fal.ai Model"},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {"video_1": {"id": uid(), "type": "video", "label": "video", "format": "text", "required": True, "description": "The video to analyze"}},
"output": {"text": {"type": "text", "order": 0, "format": "text", "description": "The LLM response"}}
},
"name": name,
"description": "Run any video-capable LLM with fal. Analyze, summarize, and understand video files using Gemini (Google) models.",
"color": "Red", "label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Video Describer"},
"model": {"name": "openrouter/router/video/enterprise", "service": "fal_imported", "version": "openrouter/router/video/enterprise"},
"params": {"model": model, "prompt": instructions, "reasoning": False, "max_tokens": 4096, "temperature": 1, "system_prompt": ""},
"schema": {
"model": {"type": "enum", "order": 0, "title": "Model", "default": "google/gemini-2.5-flash", "options": VIDEO_DESCRIBER_MODELS},
"prompt": {"type": "text", "order": 1, "title": "Model instructions", "default": "", "required": True}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: COMPARE ✅ Verified
`type: "comparison"` / `data.kind.type = "slider"` — 460 × 548 — Color `#000000` — `visibility: "public"` — `isModel: false`
Side-by-side A/B viewer with a draggable slider. Two `any` inputs (`input_a`, `input_b`), two `any` pass-through outputs (`out_a`, `out_b`). Use for visual comparison of two generation branches (e.g. NB Pro vs Flux on the same prompt).
```python
def make_compare_node(node_id, name, x, y, slider_x=0.5, slider_y=0.5, angle=1.5707963705062866):
return {
"id": node_id, "type": "comparison",
"data": {
"version": 3, "color": "#000000",
"description": "Compare two inputs with separate outputs",
"type": "comparison", "name": name,
"handles": {
"input": {
"input_a": {"id": "inputA", "type": "any", "label": "Input A", "format": "uri", "order": 0, "required": False, "description": "First input (Image or Video)"},
"input_b": {"id": "inputB", "type": "any", "label": "Input B", "format": "uri", "order": 1, "required": False, "description": "Second input (Image or Video)"}
},
"output": {
"out_a": {"id": "out_a", "type": "any", "label": "Output A", "format": "uri", "order": 0, "required": False, "description": "Pass-through from Input A"},
"out_b": {"id": "out_b", "type": "any", "label": "Output B", "format": "uri", "order": 1, "required": False, "description": "Pass-through from Input B"}
}
},
"kind": {"type": "slider", "position": {"x": slider_x, "y": slider_y}, "angle": angle},
"output": {}
},
"isModel": False, "owner": None, "visibility": "public", "locked": False,
"position": {"x": x, "y": y},
"createdAt": NOW, "selected": False, "width": 460, "height": 548
}
```
---
## BUILDER: REVE ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "reve"` — 460 × 560 — `isModel: true`
Custom kind (not wildcard). Reference images live in `data.kind.referenceImages` as `[label, ref]` pairs (same pattern as Kling `elements`). `aspectRatio` is a direct named field in `data.kind`. Output handle `result`.
```python
def make_reve_node(node_id, name, prompt_ref, reference_image_refs, x, y, aspect_ratio="1:1"):
"""
reference_image_refs: list of (label, ref_or_none). Pass at least [("reference_image_1", None)].
"""
kind_data = {
"type": "reve",
"referenceImages": reference_image_refs if reference_image_refs else [["reference_image_1", None]],
"aspectRatio": {"type": "value", "data": {"type": "string", "value": aspect_ratio}},
}
if prompt_ref: kind_data["prompt"] = prompt_ref
input_handles = {
"prompt": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": True, "description": "Text prompt for image generation"},
}
for i, (label, _) in enumerate(reference_image_refs or [("reference_image_1", None)]):
input_handles[label] = {"id": uid(), "type": "image", "label": label, "order": 1+i, "format": "uri", "required": False, "description": "A reference image"}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": input_handles,
"output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name, "description": "Generate image using Reve image model", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Google Imagen"},
"model": {"name": "reve"},
"params": {"aspect_ratio": aspect_ratio},
"schema": {
"aspect_ratio": {"type": "enum", "order": 2, "title": "Aspect Ratio", "default": "1:1",
"options": ["1:1","9:16","16:9","3:4","4:3","2:3","3:2"], "required": False,
"description": "Aspect ratio of the generated image"}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: REVE EDIT ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "wildcard"` — 460 × 560 — `isModel: true`
Wildcard with `predefined` model `reve_edit`. **No parameters.** Two required inputs: `edit_instruction` (text) and `reference_image` (image). Output handle `result`. Note input handle key for the prompt is `edit_instruction`, not `prompt`.
```python
def make_reve_edit_node(node_id, name, prompt_ref, image_ref, x, y):
inputs = [
[{"id": "edit_instruction", "title": "Prompt", "description": "Text prompt for image generation", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "reference_image", "title": "Image", "description": "The image to edit", "validTypes": ["image"], "required": True}, image_ref],
]
outputs = [{"id": "result", "title": "result", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "reve_edit", "description": "Edit image using Reve image edit model"},
"inputs": inputs, "parameters": [], "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {
"reference_image": {"id": uid(), "type": "image", "label": "Image", "order": 1, "format": "uri", "required": True, "description": "The image to edit"},
"edit_instruction": {"id": uid(), "type": "text", "label": "Prompt", "order": 0, "format": "text", "required": True, "description": "Text prompt for image generation"},
},
"output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name, "description": "Edit image using Reve image edit model", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Google Imagen"},
"model": {"name": "reve_edit"},
"params": {}, "schema": {},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: HIGGSFIELD IMAGE ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "wildcard"` — 460 × 560 — `isModel: true`
Wildcard `predefined` model `higgsfield_t2i`. Optional reference image input. Style enum has ~80 presets (Y2K, Tokyo Streetstyle, Indie sleaze, etc.). Resolution enum is `WxH` strings (not aspect ratio). Includes `style_strength` 0–1 float for preset blending.
```python
HIGGSFIELD_IMG_STYLES = ["0.5 Outfit","0.5 Selfie","2000s Cam","2000s Fashion","360 cam","505room","7\\","90's Editorial","90s Grain","Amalfi Summer","Angel Wings","Artwork","Avant-garde","Babydoll MakeUp","Bimbocore","Bleached Brows","CCTV","Coquette core","Creatures","Crossing the street","DigitalCam","Duplicate","Eating Food","Elevator Mirror","Escalator","Fairycore","FashionShow","Fisheye","Flight mode","Foggy Morning","Gallery","General","Geominimal","Giant Accessory","Giant People","Glazed doll skin makeup","Glitch","Gorpcore","Graffiti","Grillz Selfie","Grunge","HairClips","Help It's Too Big","iPhone","Indie sleaze","Invertethereal","Japandi","Library","Long legs","Medieval","Mixed Media","Movie","Mt. Fuji","Nail Check","Night Beach","Object Makeup","Office beach","Overexposed","Paper Face","PixeletedFace","Quiet luxury","Rainy Day","Realistic","RingSelfie","Sand","Selfcare","Shoe Check","Sitting on the Street","Spotlight","Street view","Subway","Sunbathing","Sunset beach","Swords Hill","Through The Glass","Tokyo Streetstyle","Tumblr","Vintage PhotoBooth","Y2K","Y2K Posters"]
HIGGSFIELD_IMG_RES = ["1152x2048","2048x1152","2048x1536","1536x2048","1344x2016","2016x1344","960x1696","1536x1536","1536x1152","1696x960","1152x1536","1088x1632","1632x1088"]
def make_higgsfield_image_node(node_id, name, prompt_ref, image_ref, x, y,
resolution="1696x960", style="General", style_strength=1.0, enhance_prompt=True):
inputs = [
[{"id": "prompt", "title": "", "description": "Text prompt for image generation", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "image_reference", "title": "Reference Image", "description": "This image will be used a source for image generation.", "validTypes": ["image"], "required": False}, image_ref],
]
parameters = [
[{"id": "width_and_height", "title": "Resolution", "description": "Resolution of the generated image",
"constraint": {"type": "enum", "options": HIGGSFIELD_IMG_RES},
"defaultValue": {"type": "string", "value": "1696x960"}},
{"type": "value", "data": {"type": "string", "value": resolution}}],
[{"id": "style", "title": "Style", "description": "Higgsfield's style preset",
"constraint": {"type": "enum", "options": HIGGSFIELD_IMG_STYLES},
"defaultValue": {"type": "string", "value": "General"}},
{"type": "value", "data": {"type": "string", "value": style}}],
[{"id": "style_strength", "title": "Style Strength", "description": "Controls how intensely the chosen style preset is applied.",
"constraint": {"type": "float_with_limits", "min": 0, "max": 1},
"defaultValue": {"type": "float", "value": 1}},
{"type": "value", "data": {"type": "float", "value": style_strength}}],
[{"id": "enhance_prompt", "title": "Enhance Prompt", "description": "Enhance the prompt",
"constraint": {"type": "boolean"}, "defaultValue": {"type": "boolean", "value": True}},
{"type": "value", "data": {"type": "boolean", "value": enhance_prompt}}],
[{"id": "seed", "title": "Seed", "description": "Seed for the generated image",
"constraint": {"type": "seed"}, "defaultValue": {"type": "seed", "value": {"seed": 42, "isRandom": True}}},
{"type": "value", "data": {"type": "seed", "value": {"seed": 42, "isRandom": True}}}],
]
outputs = [{"id": "result", "title": "result", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "higgsfield_t2i", "description": "Higgsfield's image generation model"},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {
"prompt": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": True, "description": "Text prompt for image generation"},
"image_reference": {"id": uid(), "type": "image", "label": "Reference Image", "order": 1, "format": "uri", "required": False, "description": "This image will be used a source for image generation."},
},
"output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name, "description": "Higgsfield's image generation model", "color": "Red", "label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Google Imagen"},
"model": {"name": "higgsfield_t2i"},
"params": {"seed": {"seed": 42, "isRandom": True}, "style": style, "enhance_prompt": enhance_prompt, "style_strength": style_strength, "width_and_height": resolution},
"schema": {
"seed": {"type": "seed", "order": 6, "title": "Seed", "default": {"seed": 42, "isRandom": True}, "required": False},
"style": {"type": "enum", "order": 1, "title": "Style", "default": "General", "options": HIGGSFIELD_IMG_STYLES},
"enhance_prompt": {"type": "boolean", "order": 3, "title": "Enhance Prompt", "default": True},
"style_strength": {"max": 1, "min": 0, "type": "number", "order": 2, "title": "Style Strength", "default": 1},
"width_and_height": {"type": "enum", "order": 0, "title": "Resolution", "default": "1696x960", "options": HIGGSFIELD_IMG_RES, "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: HIGGSFIELD VIDEO ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "wildcard"` — 460 × 560 — `isModel: true`
⚠️ **Color quirk**: this video model uses `"color": "Yambo_Purple"` (not `Red`). Match exactly. Wildcard `predefined` model `higgsfield_i2v`. Required inputs: prompt + first-frame image. Motion enum has ~120 cinematic presets (Bullet Time, Crane Up, Dolly Zoom, etc.). Output handle `video`.
```python
HIGGSFIELD_VID_MOTIONS = ["360 Orbit","3D Rotation","Abstract","Action Run","Agent Reveal","Anamorphic","Angel Wings","Arc Left","Arc Right","Baseball Kick","Basketball Dunks","Black Tears","Bloom Mouth","Boxing","Buckle Up","Building Explosion","Bullet Time","Car Chasing","Car Explosion","Car Grip","Catwalk","Cinematic","Clone Explosion","Crane Down","Crane Over The Head","Crane Up","Crash Zoom In","Crash Zoom Out","Datamosh","Diamond","Dirty Lens","Disintegration","Dolly In","Dolly Left","Dolly Out","Dolly Right","Dolly Zoom In","Dolly Zoom Out","Double Dolly","Downhill POV","Duplicate","Dutch Angle","Earth Zoom Out","Eyes In","Face Punch","Fire Breathe","Fisheye","Floating Fish","Flood","Floral Eyes","Flying","Focus Change","FPV Drone","Freezing","Garden Bloom","General","Glam","Glowing Fish","Glowshift","Handheld","Head Explosion","Head Off","Head Tracking","Hyperlapse","Incline","Innerlight","Invisible","Jelly Drift","Jib Down","Jib Up","Kiss","Lazy Susan","Lens Crack","Lens Flare","Levitation","Low Shutter","Medusa Gorgona","Melting","Moonwalk Left","Moonwalk Right","Morphskin","Mouth In","Object POV","Overhead","Paint Splash","Paparazzi","Powder Explosion","Push To Glass","Rap Flex","Robo Arm","Roll Transition","Sand Storm","Set on Fire","Skateboard Glide","Skateboard Ollie","Skate Cruise","Ski Carving","Skin Surge","Ski Powder","Snorricam","Snowboard Carving","Snowboard Powder","Soul Jump","Static","Super 8MM","Super Dolly In","Super Dolly Out","Tentacles","Through Object In","Through Object Out","Thunder God","Tilt Down","Tilt up","Timelapse Human","Timelapse Landscape","Turning Metal","VHS","Whip Pan","Wiggle","Wind to Face","YoYo Zoom","Zoom In","Zoom Out"]
def make_higgsfield_video_node(node_id, name, prompt_ref, image_ref, x, y,
model="dop-turbo", motion="Static", enhance_prompt=True):
inputs = [
[{"id": "prompt", "title": "prompt", "description": "Use a prompt to describe the camera style and movement speed.", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "image", "title": "first_frame", "description": "The first frame of the generation", "validTypes": ["image"], "required": True}, image_ref],
]
parameters = [
[{"id": "model", "title": "Model", "description": "Higgsfield model version",
"constraint": {"type": "enum", "options": ["dop-lite","dop-turbo","dop-preview"]},
"defaultValue": {"type": "string", "value": "dop-lite"}},
{"type": "value", "data": {"type": "string", "value": model}}],
[{"id": "motion", "title": "Motion", "description": "Higgsfield Motion preset",
"constraint": {"type": "enum", "options": HIGGSFIELD_VID_MOTIONS},
"defaultValue": {"type": "string", "value": "360 Orbit"}},
{"type": "value", "data": {"type": "string", "value": motion}}],
[{"id": "enhance_prompt", "title": "Enhance Prompt", "description": "Enhance the prompt",
"constraint": {"type": "boolean"}, "defaultValue": {"type": "boolean", "value": True}},
{"type": "value", "data": {"type": "boolean", "value": enhance_prompt}}],
[{"id": "seed", "title": "Seed", "description": "Seed value for random number generator. Uncheck for reproducible results.",
"constraint": {"type": "seed"}, "defaultValue": {"type": "seed", "value": {"seed": 1, "isRandom": False}}},
{"type": "value", "data": {"type": "seed", "value": {"seed": 42, "isRandom": True}}}],
]
outputs = [{"id": "video", "title": "video", "description": "The video result", "dataType": "video"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "higgsfield_i2v", "description": "Generate video from an image and a prompt"},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": None, "isModel": True,
"data": {
"handles": {
"input": {
"image": {"type": "image", "label": "first_frame", "order": 1, "format": "uri", "required": True, "description": "The first frame of the generation"},
"prompt": {"type": "text", "label": "prompt", "order": 0, "format": "text", "required": True, "description": "Use a prompt to describe the camera style and movement speed."},
},
"output": {"video": {"type": "video", "label": "video", "order": 0, "format": "uri", "description": "The video result"}}
},
"name": name, "description": "Generate video from an image and a prompt", "color": "Yambo_Purple",
"label": None, "menu": None,
"model": {"name": "higgsfield_i2v"},
"params": {"seed": {"seed": 42, "isRandom": True}, "model": model, "motion": motion, "check_nsfw": False, "enhance_prompt": enhance_prompt},
"schema": {
"seed": {"type": "seed", "order": 3, "title": "Seed", "default": 42, "required": False},
"model": {"type": "enum", "order": 0, "title": "Model", "default": 5, "options": ["dop-lite","dop-turbo","dop-preview"]},
"motion": {"type": "enum", "order": 1, "title": "Motion", "default": 5, "options": HIGGSFIELD_VID_MOTIONS},
"enhance_prompt": {"type": "boolean", "order": 2, "title": "Enhance Prompt", "default": True}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: GOOGLE IMAGEN 3 ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "imagen3"` — 460 × 560 — `isModel: true`
**Custom kind** (NOT wildcard). Direct named fields in `data.kind` (like Reve / GPT Image). Two text inputs: `prompt` + optional `negative_prompt`.
```python
def make_imagen3_node(node_id, name, prompt_ref, neg_prompt_ref, x, y,
aspect_ratio="1:1", safety_filter_level="block_only_high"):
kind_data = {
"type": "imagen3",
"aspectRatio": {"type": "value", "data": {"type": "string", "value": aspect_ratio}},
"safetyFilterLevel": {"type": "value", "data": {"type": "string", "value": safety_filter_level}},
}
if prompt_ref: kind_data["prompt"] = prompt_ref
if neg_prompt_ref: kind_data["negativePrompt"] = neg_prompt_ref
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {
"prompt": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": True, "description": "Text prompt for image generation"},
"negative_prompt": {"id": uid(), "type": "text", "order": 1, "format": "text", "required": False, "description": "Text prompt for what to discourage in the generated images"},
},
"output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name,
"description": "Google's highest quality text-to-image model, capable of generating images with detail, rich lighting and beauty",
"color": "Red", "label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Google Imagen"},
"model": {"name": "google/imagen-3", "version": "4902bedfc8567f151e7cc0bb500a4480a4fb0575a10baf7e947e02e8b52d0413"},
"params": {"prompt": "", "aspect_ratio": aspect_ratio, "negative_prompt": "", "safety_filter_level": safety_filter_level},
"schema": {
"prompt": {"type": "string", "order": 0, "title": "Prompt", "required": True},
"aspect_ratio": {"type": "enum", "order": 2, "title": "Aspect Ratio", "default": "1:1", "options": ["1:1","9:16","16:9","3:4","4:3"], "required": False},
"negative_prompt": {"type": "string", "order": 1, "title": "Negative Prompt", "required": False},
"safety_filter_level": {"type": "enum", "order": 3, "title": "Safety Filter Level", "default": "block_medium_and_above", "options": ["block_low_and_above","block_medium_and_above","block_only_high"], "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: GOOGLE IMAGEN 3 FAST ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "wildcard"` — 460 × 560 — `isModel: true`
Wildcard pattern. Predefined model with **version hash** in the model spec. Same inputs/params as Imagen 3 but cheaper / faster.
```python
def make_imagen3_fast_node(node_id, name, prompt_ref, neg_prompt_ref, x, y,
aspect_ratio="1:1", safety_filter_level="block_only_high"):
inputs = [
[{"id": "prompt", "title": "Prompt", "description": "Text prompt for image generation", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "negative_prompt", "title": "Negative Prompt", "description": "Text prompt for what to discourage in the generated images", "validTypes": ["text"], "required": False}, neg_prompt_ref],
]
parameters = [
[{"id": "aspect_ratio", "title": "Aspect Ratio", "description": "Aspect ratio of the generated image",
"constraint": {"type": "enum", "options": ["1:1","9:16","16:9","3:4","4:3"]},
"defaultValue": {"type": "string", "value": "1:1"}},
{"type": "value", "data": {"type": "string", "value": aspect_ratio}}],
[{"id": "safety_filter_level", "title": "Safety Filter Level", "description": "Safety threshold for prompts.",
"constraint": {"type": "enum", "options": ["block_low_and_above","block_medium_and_above","block_only_high"]},
"defaultValue": {"type": "string", "value": "block_only_high"}},
{"type": "value", "data": {"type": "string", "value": safety_filter_level}}],
]
outputs = [{"id": "result", "title": "result", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "google/imagen-3-fast",
"version": "04b711a3148558771e486a4a7dba47d2900aedca0995fc375762d89915fab2e8",
"description": "Google's fast text-to-image model, capable of generating images with detail, rich lighting and beauty"},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {
"prompt": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": True, "description": "Text prompt for image generation"},
"negative_prompt": {"id": uid(), "type": "text", "order": 1, "format": "text", "required": False, "description": "Text prompt for what to discourage in the generated images"},
},
"output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name,
"description": "Google's fast text-to-image model, capable of generating images with detail, rich lighting and beauty",
"color": "Red", "label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Google Imagen 3 Fast"},
"model": {"name": "google/imagen-3-fast", "version": "04b711a3148558771e486a4a7dba47d2900aedca0995fc375762d89915fab2e8"},
"params": {"prompt": "", "aspect_ratio": aspect_ratio, "negative_prompt": "", "safety_filter_level": safety_filter_level},
"schema": {
"prompt": {"type": "string", "order": 0, "title": "Prompt", "required": True},
"aspect_ratio": {"type": "enum", "order": 2, "title": "Aspect Ratio", "default": "1:1", "options": ["1:1","9:16","16:9","3:4","4:3"], "required": False},
"negative_prompt": {"type": "string", "order": 1, "title": "Negative Prompt", "required": False},
"safety_filter_level": {"type": "enum", "order": 3, "title": "Safety Filter Level", "default": "block_only_high", "options": ["block_low_and_above","block_medium_and_above","block_only_high"], "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: GOOGLE IMAGEN 4 ✅ Verified
`type: "custommodelV2"` / `data.kind.type = "wildcard"` — 460 × 560 — `isModel: true`
Wildcard `predefined` `imagen4`. Three-tier model (`Standard` / `Ultra` / `Fast`). Resolution `1K` or `2K`.
```python
def make_imagen4_node(node_id, name, prompt_ref, neg_prompt_ref, x, y,
model="Standard", resolution="1K", aspect_ratio="1:1"):
inputs = [
[{"id": "prompt", "title": "", "description": "Text prompt for image generation", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "negative_prompt", "title": "", "description": "Text prompt for what to discourage in the generated images", "validTypes": ["text"], "required": False}, neg_prompt_ref],
]
parameters = [
[{"id": "resolution", "title": "Resolution", "description": "Resolution of the generated image",
"constraint": {"type": "enum", "options": ["1K","2K"]},
"defaultValue": {"type": "string", "value": "1K"}},
{"type": "value", "data": {"type": "string", "value": resolution}}],
[{"id": "model", "title": "Model", "description": "Imagen 4 model type",
"constraint": {"type": "enum", "options": ["Standard","Ultra","Fast"]},
"defaultValue": {"type": "string", "value": "Standard"}},
{"type": "value", "data": {"type": "string", "value": model}}],
[{"id": "aspect_ratio", "title": "Aspect Ratio", "description": "Aspect ratio of the generated image",
"constraint": {"type": "enum", "options": ["1:1","9:16","16:9","3:4","4:3"]},
"defaultValue": {"type": "string", "value": "1:1"}},
{"type": "value", "data": {"type": "string", "value": aspect_ratio}}],
]
outputs = [{"id": "result", "title": "result", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "imagen4", "description": "Google's highest quality image generation model"},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {
"prompt": {"id": uid(), "type": "text", "order": 0, "format": "text", "required": True, "description": "Text prompt for image generation"},
"negative_prompt": {"id": uid(), "type": "text", "order": 1, "format": "text", "required": False, "description": "Text prompt for what to discourage in the generated images"},
},
"output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name, "description": "Google's highest quality image generation model", "color": "Red",
"label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Google Imagen"},
"model": {"name": "imagen4"},
"params": {"model": model, "aspect_ratio": aspect_ratio},
"schema": {
"model": {"type": "enum", "order": 1, "title": "Model", "default": "Standard", "options": ["Standard","Ultra","Fast"], "required": False},
"resolution": {"type": "enum", "order": 0, "title": "Resolution", "default": "1K", "options": ["1K","2K"], "required": False},
"aspect_ratio": {"type": "enum", "order": 2, "title": "Aspect Ratio", "default": "1:1", "options": ["1:1","9:16","16:9","3:4","4:3"], "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## BUILDER: FLUX 2 FLEX ✅ Verified
`fal-ai/flux-2-flex` — 460 × 560 — Wildcard — `isModel: true`
Like Flux 2 Pro but exposes **fine-tune knobs**: `guidance_scale` (1.5–10), `num_inference_steps` (2–50), `enable_prompt_expansion`. Uses same `image_size` (`fal_image_size`) pattern as Flux 2 Pro.
```python
def make_flux2_flex_node(node_id, name, prompt_ref, image_1_ref, x, y,
guidance_scale=3.5, num_inference_steps=28, enable_prompt_expansion=True):
inputs = [
[{"id": "prompt", "title": "prompt", "description": "The prompt to generate an image from.", "validTypes": ["text"], "required": True}, prompt_ref],
[{"id": "image_1", "title": "image_1", "description": "The image you want to edit", "validTypes": ["image"], "required": False}, image_1_ref],
]
parameters = [
[{"id": "seed", "title": "Seed", "description": "Seed value for random number generator. Uncheck for reproducible results.",
"constraint": {"type": "seed"}, "defaultValue": {"type": "seed", "value": {"seed": 1, "isRandom": False}}},
{"type": "value", "data": {"type": "seed", "value": {"seed": 646103, "isRandom": True}}}],
[{"id": "image_size", "title": "Image Size", "description": "The size of the generated image.",
"constraint": {"type": "image_size", "options": ["Default","square_hd","square","portrait_4_3","portrait_16_9","landscape_4_3","landscape_16_9"]},
"defaultValue": {"type": "image_size", "value": {"type": "built_in", "value": "match_input"}}},
{"type": "value", "data": {"type": "image_size", "value": {"type": "built_in", "value": "match_input"}}}],
[{"id": "guidance_scale", "title": "Guidance Scale", "description": "The guidance scale to use for the generation.",
"constraint": {"type": "float_with_limits", "min": 1.5, "max": 10},
"defaultValue": {"type": "float", "value": 3.5}},
{"type": "value", "data": {"type": "float", "value": guidance_scale}}],
[{"id": "num_inference_steps", "title": "Num Inference Steps", "description": "The number of inference steps to perform.",
"constraint": {"type": "integer_with_limits", "min": 2, "max": 50},
"defaultValue": {"type": "integer", "value": 28}},
{"type": "value", "data": {"type": "integer", "value": num_inference_steps}}],
[{"id": "enable_prompt_expansion", "title": "Enhance Prompt", "description": "Whether to expand the prompt using the model's own knowledge.",
"constraint": {"type": "boolean"}, "defaultValue": {"type": "boolean", "value": True}},
{"type": "value", "data": {"type": "boolean", "value": enable_prompt_expansion}}],
]
outputs = [{"id": "result", "title": "result", "description": "Result image", "dataType": "image"}]
kind_data = {
"type": "wildcard",
"model": {"type": "predefined", "name": "fal-ai/flux-2-flex", "version": "fal-ai/flux-2-flex",
"service": "fal_imported",
"description": "Image generation and editing with FLUX.2 [flex] from Black Forest Labs."},
"inputs": inputs, "parameters": parameters, "outputs": outputs
}
return {
"id": node_id, "dragHandle": ".node-header", "owner": None, "type": "custommodelV2",
"visibility": "private", "isModel": True,
"data": {
"handles": {
"input": {
"prompt": {"id": uid(), "type": "text", "label": "prompt", "format": "text", "required": True, "description": "The prompt to generate an image from."},
"image_1": {"id": uid(), "type": "image", "label": "image_1", "order": 1, "format": "uri", "required": False, "description": "The image you want to edit"},
},
"output": {"result": {"id": uid(), "type": "image", "label": "result", "order": 0, "format": "uri", "description": "Result image"}}
},
"name": name,
"description": "Image generation and editing with FLUX.2 [flex] from Black Forest Labs. Features adjustable inference steps and guidance scale for fine-tuned control. Enhanced typography and text rendering capabilities.",
"color": "Red", "label": None,
"menu": {"icon": "EmojiObjectsIcon", "isModel": True, "displayName": "Flux 2 Flex"},
"model": {"name": "fal-ai/flux-2-flex", "service": "fal_imported", "version": "fal-ai/flux-2-flex"},
"params": {"seed": {"seed": 646103, "isRandom": True}, "image_size": None, "output_format": "png",
"guidance_scale": guidance_scale, "safety_tolerance": "1",
"num_inference_steps": num_inference_steps, "enable_safety_checker": True,
"enable_prompt_expansion": enable_prompt_expansion},
"schema": {
"seed": {"type": "seed", "title": "Seed", "required": False},
"image_size": {"type": "fal_image_size", "title": "Image Size", "default": None,
"options": ["Default","square_hd","square","portrait_4_3","portrait_16_9","landscape_4_3","landscape_16_9"], "required": False},
"guidance_scale": {"max": 10, "min": 1.5, "type": "number", "title": "Guidance Scale", "default": 3.5, "required": False},
"num_inference_steps": {"max": 50, "min": 2, "type": "integer", "title": "Num Inference Steps", "default": 28, "required": False},
"enable_prompt_expansion": {"type": "boolean", "title": "Enhance Prompt", "default": True, "required": False}
},
"version": 3, "kind": kind_data,
"generations": [], "selectedIndex": 0, "cameraLocked": False,
"result": [], "output": {}, "selectedOutput": 0
},
"createdAt": NOW, "updatedAt": UPD, "locked": False,
"position": {"x": x, "y": y},
"selected": False, "width": 460, "height": 560
}
```
---
## TIPS & TRICKS — NEW NODES
- **GPT Image vs GPT Image Edit**: GPT Image (`gpt_image1`) is text-only generation; GPT Image Edit (`wildcard`, `gpt_image_1_edit`) takes a required source image. Both output on handle `image` (not `result` like NB/Flux).
- **GPT Image kind shape is unusual**: parameters sit as direct named fields in `data.kind` (like Kling), not in a `parameters` array (like wildcard). Don't copy-paste the wildcard pattern.
- **Compare node placement**: drop after two parallel image/video branches. Both outputs are pass-through, so you can chain further nodes off either side. Inputs/outputs are `any`, so connections from `Red` (image) or video-producing nodes are fine.
- **Video Describer → image model chain**: classic recipe to recreate a video as a still: `FILE (video) → VIDEO DESCRIBER → CONCAT → NB PRO / FLUX / GPT IMAGE`. The Gemini description is dense, prefer wrapping in a CONCAT with anchoring instructions before sending to an image model.
- **`user_defined` vs `predefined` model kinds**: fal-imported nodes (Video Describer) use `"type": "user_defined"`; first-party fal nodes (NB Pro, Flux 2 Pro) use `"type": "predefined"`. The rest of the wildcard structure is identical.
- **Edge colors**: GPT Image / GPT Image Edit / Video Describer all use `Red` like other AI Models. Compare uses `#000000` like Kling Element.
---
## KNOWN NODES CATALOG
Source: official Figma Weave (Weavy) Knowledge Center + bundled `nodes_catalog.json` (154 entries, May 2026). Status legend:
- 🟢 **builder** — hand-written `make_*_node` Python function in this document. Highest fidelity, verified against real Weavy paste.
- 🔵 **loader** — generated on-the-fly by `make_node_from_catalog(name, ...)` using the bundled JSON. Works for the wildcard majority. Always verify the first paste before relying on it for production workflows.
- 🟡 **catalog-only** — node exists but not yet in `nodes_catalog.json` and no builder. Ask the user to paste real JSON via `/add` first.
### Image Generation (text-to-image)
| Status | Node | Notes |
|--------|------|-------|
| 🟢 builder | **Nano Banana Pro** | `fal-ai/nano-banana-pro/edit`, wildcard, photoreal |
| 🟢 builder | **Flux 2 Pro** | `fal-ai/flux-2-pro`, wildcard |
| 🟢 builder | **GPT Image** | `gpt_image1`, OpenAI text→image, strong typography |
| 🟢 builder | **Reve** | `reve` kind, multi-reference image control |
| 🟢 builder | **Higgsfield Image** | wildcard `higgsfield_t2i`, ~80 style presets |
| 🟢 builder | **Imagen 4** | wildcard `imagen4`, Standard/Ultra/Fast |
| 🟢 builder | **Imagen 3** | custom `imagen3` kind |
| 🟢 builder | **Imagen 3 Fast** | wildcard with version hash |
| 🟢 builder | **Flux 2 Flex** | wildcard `fal-ai/flux-2-flex`, exposes guidance / steps |
| 🔵 loader | Flux 2 Dev LoRA | |
| 🔵 loader | Flux 1.1 Ultra / Flux Pro 1.1 / Flux Fast / Flux Dev LoRA | |
| 🔵 loader | Recraft V3 | Vector-friendly raster, brand-style consistency |
| 🔵 loader | Mystic | Character reference |
| 🔵 loader | Ideogram V3 / V3 Character | Strong text rendering |
| 🔵 loader | Stable Diffusion 3.5 | |
| 🔵 loader | Minimax Image 01 | |
| 🔵 loader | Bria | Cheap (1 credit) |
| 🔵 loader | DALL·E 3 | |
| 🔵 loader | Luma Photon | 1:1 only |
| 🔵 loader | Nvidia Sana | Cheap (0.2 credit) |
| 🔵 loader | Nvidia Consistory | Consistency-focused |
### Image Edit (image + prompt → image)
| Status | Node | Notes |
|--------|------|-------|
| 🟢 builder | **GPT Image Edit** | `gpt_image_1_edit`, wildcard, up to 4K |
| 🔵 loader | Gemini 3.1 Flash (Nano Banana 2) | |
| 🔵 loader | Gemini 3 Pro | Many aspect ratios |
| 🔵 loader | Seedream V5 / V4.5 / V4 Edit | up to 2048² |
| 🟢 builder | **Reve Edit** | `wildcard` predefined `reve_edit`, no params |
| 🔵 loader | Qwen Image Edit 2511 / Qwen Edit Image Plus | PNG/JPEG/WEBP |
| 🔵 loader | Flux 2 Inpaint Klein 9B | image + mask |
| 🔵 loader | Runway Gen-4 Image | |
| 🔵 loader | Klux Kontext | image + mask |
| 🔵 loader | Flux Kontext Multi Image / LoRA | |
| 🔵 loader | GPT Image 1.5 Edit / GPT Image 1 Edit | |
| 🔵 loader | SeedEdit 3.0 | cheap (4 credits) |
| 🔵 loader | Gemini 2.0 Flash | very cheap (0.1) |
| 🔵 loader | Flux 2 Max | premium (10) |
| 🔵 loader | Flux Fill Pro / Flux Dev LoRA Inpaint | mask required |
| 🔵 loader | Ideogram V3 / V2 Inpaint | |
| 🔵 loader | SD3 Inpaint / Outpaint / Remove BG / Content-Aware Fill | |
| 🔵 loader | Bria Inpaint / Remove BG / Replace BG / Content-Aware Fill | |
| 🔵 loader | Flux Pro Outpaint | extends beyond original |
| 🔵 loader | Kolors Virtual Try-On | garment visualisation |
| 🔵 loader | Replace Background | prompt-driven BG swap |
| 🔵 loader | **Relight 2.0** | re-lighting |
### Generate from Image (image → image with control)
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Flux Dev Redux | image → variation |
| 🔵 loader | Flux ControlNet & LoRA | control + LoRA stack |
| 🔵 loader | Flux Canny Pro | edge-detection control |
| 🔵 loader | Flux Depth Pro | depth control |
| 🔵 loader | Qwen Edit Multiangle | new angles from input |
| 🔵 loader | Image to Image | generic i2i |
| 🔵 loader | Stable Diffusion controlnets | sketch + prompt |
| 🔵 loader | Sketch To Image | sketch → render |
### Video Generation (text/image → video)
| Status | Node | Notes |
|--------|------|-------|
| 🟢 builder | **Kling 3** | `kling`, 3.0 Pro/Standard, supports elements |
| 🟢 builder | **Kling Element** | element packager |
| 🔵 loader | Seedance 2.0 / 2.0 Reference / V1.5 Pro / V1.0 | ByteDance, audio gen |
| 🔵 loader | Wan 2.5 / 2.2 / Video | flexible length |
| 🔵 loader | Grok Imagine Video | 1–15s |
| 🔵 loader | **Sora 2** | 4 / 8 / 12s fixed |
| 🔵 loader | LTX 2 Video | 6 / 8 / 10s |
| 🔵 loader | Moonvalley | 5 / 10s |
| 🔵 loader | **Veo 3.1 T2V / I2V** | Google, 8s |
| 🔵 loader | Veo 3 T2V / I2V | 8s |
| 🔵 loader | Veo 2 | 5 / 8s |
| 🔵 loader | Pixverse V4.5 | 8s |
| 🔵 loader | Runway Gen-4.5 / Gen-4 Turbo / Gen-4 / Gen-3 | |
| 🔵 loader | Kling 2.5 / 2.1 / 1.6 First-Last-Frame, Kling Video, Kling O1 FLF | keyframe interpolation |
| 🔵 loader | Luma Ray 2 / Ray 2 Flash | 5 / 9s |
| 🔵 loader | Minimax Video Director / Video 01 | camera control |
| 🔵 loader | Hunyuan / Skyreels | |
| 🟢 builder | **Higgsfield Video** | wildcard `higgsfield_i2v`, ~120 motion presets, **purple color** |
### Generate from Video (video → video)
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Kling O1 Edit Video / O1 Reference V2V / O3 Edit Video | |
| 🔵 loader | Kling Motion Control | image + video, 10s |
| 🔵 loader | LTX 2 Video to Video | |
| 🔵 loader | Runway Aleph / Act-Two | text-driven |
| 🔵 loader | Luma Reframe / Luma Modify | 8s |
| 🔵 loader | Wan 2.2 Animate – Replace / Move | character animation |
| 🔵 loader | Wan Vace Depth / Pose / Reframe / Outpainting | controlled v2v |
| 🔵 loader | Hunyuan Video to Video | |
### Enhance Image (upscale / sharpen / skin)
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Topaz Upscale / Topaz Sharpen | premium |
| 🔵 loader | Recraft Crisp Upscale | cheap (5) |
| 🔵 loader | Magnific Skin Enhancer / Upscale / Precision V1 / Precision V2 | 2× / 4× / 8× |
| 🔵 loader | Enhancor Image Upscale / Realistic Skin | |
### Enhance Video
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Bria Video Upscale | up to 2K |
| 🔵 loader | Topaz Video Upscaler | up to 4K |
| 🔵 loader | Real-ESRGAN Video Upscaler | |
| 🔵 loader | Video Smoother | cheap (5) |
### Lip Sync
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Omnihuman V1.5 | image + audio, voice ID |
| 🔵 loader | Sync 2 Pro | video + audio |
| 🔵 loader | Pixverse Lipsync | cheapest |
| 🔵 loader | Kling AI Avatar Pro | image + audio |
### Vector
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Vectorizer | image → vector (24) |
| 🔵 loader | **Recraft V3 SVG** | text → true SVG (10) |
| 🔵 loader | Text To Vector | prompt → vector (6) |
### 3D
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Sam 3D Objects | image + prompt, T-pose option |
| 🔵 loader | Rodin / Rodin V2 | image, premium quality |
| 🔵 loader | Trellis / Trellis 3D V2 | mesh simplification |
| 🔵 loader | Meshy V6 | multi-angle, premium (96) |
| 🔵 loader | Hunyuan 3D / V2.1 / V3 | varied price tiers |
### Helpers
| Status | Node | Notes |
|--------|------|-------|
| 🟢 builder | **File Upload** (Import Node) | image / video / audio / 3D |
| 🟢 builder | **Router** | one input → many outputs |
| 🟢 builder | **Group** | visual container |
| 🟢 builder | **Compare** | A/B slider viewer |
| 🔵 loader | Export Node | download in original format |
| 🔵 loader | Preview Node | dedicated viewer area |
| 🔵 loader | Import Model Node | bring in fal/Replicate/CivitAI models |
| 🔵 loader | Import LoRA / Import Multiple LoRAs | custom LoRAs |
| 🔵 loader | Output Node | exposes Design App outputs |
| 🔵 loader | Sticky Note | annotations on canvas |
### Iterators
| Status | Node | Notes |
|--------|------|-------|
| 🟢 builder | **List Selector** (`muxv2`, `isIterator: true`) | pick / iterate array |
| 🔵 loader | Text Iterator Node | batch text → model |
| 🔵 loader | Image Iterator Node | batch images → model |
| 🔵 loader | Video Iterator Node | batch videos → model |
### Text Tools
| Status | Node | Notes |
|--------|------|-------|
| 🟢 builder | **Prompt Node** (`promptV3`) | `{{variables}}` |
| 🟢 builder | **Concatenator** (`prompt_concat`) | join text inputs |
| 🟢 builder | **Run Any LLM** (`custommodelV2` / `any_llm`) | text + up to 14 images |
| 🟢 builder | **Video Describer** | Gemini video → prompt |
| 🔵 loader | Prompt Enhancer Node | LLM-based prompt rewrite |
| 🔵 loader | Image Describer Node | image → prompt |
### Matte Tools (mask creation — for inpaint pipelines)
| Status | Node | Notes |
|--------|------|-------|
| 🟡 catalog | Mask Extractor | manual click selection (Shift add / Alt+Shift subtract) |
| 🟡 catalog | Mask By Text | text-driven mask — needs detailed description |
| 🟡 catalog | Matte Grow / Shrink | dilate or erode an existing mask |
| 🟡 catalog | Merge Alpha | combine image + mask → alpha-channel image |
| 🟡 catalog | Video Matte | auto-extract video mattes |
| 🟡 catalog | Video Mask By Text | text-driven per-frame video mask |
### Editing Tools (manual / non-generative)
| Status | Node | Notes |
|--------|------|-------|
| 🔵 loader | Levels Node | brightness / contrast / histogram |
| 🔵 loader | Compositor Node | blend modes + transforms |
| 🔵 loader | Painter Node | hand-painted masks / sketches |
| 🔵 loader | Crop Node | preset or custom crop |
| 🔵 loader | Resize Node | stretch / squash to dimensions |
| 🔵 loader | Blur Node | Box / Gaussian |
| 🔵 loader | Invert Node | colour / mask invert |
| 🔵 loader | Channels Node | R/G/B/A access |
| 🔵 loader | Extract Video Frame | grab single frame from video |
### Datatypes (reference, not nodes)
Image · Video · Text · Audio · Mask · Array · LoRA · 3D · Kling-element · Any.
---
## RECOMMENDATION RULE PER STATUS TIER
When a workflow plan calls for a node, choose the path based on its catalog status:
- **🟢 builder** → call the dedicated `make_*_node(...)` directly. Tightest, most reliable.
- **🔵 loader** → call `make_node_from_catalog("Exact Catalog Name", node_id, x, y, refs={...}, param_overrides={...})`. Works for the wildcard majority of `custommodelV2` nodes. After generation, mention to the user that this node was loader-generated and recommend a quick paste-test in Weavy.
**⚠️ STATEFUL NON-MODEL NODES CRASH ON PASTE (field-confirmed June 2026):** the catalog strips internal UI state. `compv3` (Compositor) crashed Weavy when loader-generated — its layer transforms/state and even handle `type` fields are missing from the catalog, and hand-cloning extra `layer_N` handles makes it worse. Treat Compositor, Painter, Levels, Timeline and similar stateful editing nodes as **🟡 catalog-only**: pre-place routers as connection points, have the user add the node via Tab (then optionally copy-paste its JSON back to /add a verified builder). Suspect the same for any non-`custommodelV2` node whose catalog entry has `params: null` while the live node clearly holds state.
- **🟡 catalog-only** → STOP. Output:
> ⚠️ The following nodes are not yet in the bundled catalog or have no builder: `[list]`. Please drop them in Weavy, copy each, and paste the JSON so I can `/add` them.
Only generate the final JSON file once every node in the plan resolves to 🟢 or 🔵.
This prevents fabricated builders that look right but break on paste.
---
## GENERIC NODE LOADER (catalog-backed) ✅ Verified architecture
The skill ships with **`nodes_catalog.json`** — a flattened dump of all 154 Weavy nodes (handles, params, schema, model, color, description, type). The loader below reconstructs full paste-ready Weavy node JSON from any catalog entry by:
1. Wrapping the inner `data` block with `id`, `dragHandle`, `position`, `width`, `height`, `isModel`, `visibility`, `createdAt`, etc.
2. **Reconstructing `data.kind`** for `custommodelV2` model nodes (the catalog strips `kind`):
- Default: **wildcard** pattern (`kind.type = "wildcard"` + `inputs` / `parameters` / `outputs` arrays)
- Override: known custom-kind nodes use the hand-written builders above (`make_reve_node`, `make_imagen3_node`, `make_gpt_image_node`, `make_kling_node`) — do NOT use the loader for these
3. Generating fresh UUIDs for every handle
4. Slotting incoming refs into `data.kind.inputs[*][1]` at the right index by handle name
**Use the loader for**: any node listed in the catalog that does NOT already have a hand-written `make_*_node` builder above. **Use the existing builder** for nodes with a 🟢 `make_*_node` function — those are tighter and have been verified against real paste.
```python
import json
import os
from copy import deepcopy
# Load catalog once at import time
_CATALOG_PATH = os.path.join(os.path.dirname(__file__), "nodes_catalog.json")
with open(_CATALOG_PATH, "r", encoding="utf-8") as _f:
NODES_CATALOG = json.load(_f)
# Nodes whose data.kind is NOT plain wildcard.
# For these, use the hand-written builder above instead of the loader.
CUSTOM_KIND_NODES = {
"Reve": "reve",
"Reve Edit": "wildcard", # actually wildcard, has dedicated builder
"Google Imagen 3": "imagen3",
"ChatGPT Images 2.0": "gpt_image1",
"Kling 3": "kling",
# Add more as you discover them.
}
# Default wrapping dimensions per node type.
NODE_DIMENSIONS = {
"custommodelV2": (460, 560),
"stickynote": (300, 200),
"painterV2": (460, 560),
"compv3": (460, 560),
"crop": (460, 480),
"resize": (460, 380),
"blur": (460, 360),
"invert": (460, 200),
"channels": (460, 360),
"levels": (460, 480),
"extract_video_frame": (460, 480),
"media_iterator": (460, 400),
"muxv2": (250, 102),
"multilora": (460, 400),
"preview": (460, 480),
"export": (460, 200),
"workflow_output": (460, 200),
}
def _build_wildcard_kind(template, refs_by_handle):
"""Reconstruct data.kind for a wildcard custommodelV2 node from catalog template."""
handles_in = template.get("handles", {}).get("input", {}) or {}
handles_out = template.get("handles", {}).get("output", {}) or {}
schema = template.get("schema", {}) or {}
params = template.get("params", {}) or {}
model_info = template.get("model", {}) or {}
# Inputs: [[input_def, ref_or_null], ...] in handle order
inputs = []
sorted_inputs = sorted(handles_in.items(), key=lambda kv: kv[1].get("order", 99))
for handle_id, h in sorted_inputs:
inp_def = {
"id": handle_id,
"title": h.get("label", handle_id),
"description": h.get("description", ""),
"validTypes": [h.get("type", "any")],
"required": h.get("required", False),
}
ref = refs_by_handle.get(handle_id)
inputs.append([inp_def, ref])
# Parameters: [[param_def, value_wrapper], ...] from schema + params
parameters = []
for pid, sch in schema.items():
constraint = _schema_to_constraint(sch)
default_val = sch.get("default")
default_typed = _value_wrapper(default_val, sch.get("type"))
current_val = params.get(pid, default_val)
current_typed = _value_wrapper(current_val, sch.get("type"))
param_def = {
"id": pid,
"title": sch.get("title", pid),
"description": sch.get("description", ""),
"constraint": constraint,
"defaultValue": default_typed,
}
parameters.append([param_def, current_typed])
# Outputs
outputs = []
sorted_outputs = sorted(handles_out.items(), key=lambda kv: kv[1].get("order", 99))
for out_id, o in sorted_outputs:
outputs.append({
"id": out_id,
"title": o.get("label", out_id),
"description": o.get("description", ""),
"dataType": o.get("type", "image"),
})
# Model spec
model_spec = {
"type": "predefined",
"name": model_info.get("name", ""),
"description": template.get("description", ""),
}
if "version" in model_info: model_spec["version"] = model_info["version"]
if "service" in model_info: model_spec["service"] = model_info["service"]
return {
"type": "wildcard",
"model": model_spec,
"inputs": inputs,
"parameters": parameters,
"outputs": outputs,
}
def _schema_to_constraint(sch):
"""Convert a catalog schema entry to a kind.parameters constraint."""
t = sch.get("type", "string")
if t == "enum":
return {"type": "enum", "options": sch.get("options", [])}
if t == "boolean":
return {"type": "boolean"}
if t == "integer":
c = {"type": "integer_with_limits"}
if "min" in sch: c["min"] = sch["min"]
if "max" in sch: c["max"] = sch["max"]
return c
if t == "number":
c = {"type": "float_with_limits"}
if "min" in sch: c["min"] = sch["min"]
if "max" in sch: c["max"] = sch["max"]
return c
if t == "seed":
return {"type": "seed"}
if t == "fal_image_size":
return {"type": "image_size", "options": sch.get("options", [])}
return {"type": t}
def _value_wrapper(value, schema_type):
"""Wrap a raw value into the {type, data: {type, value}} shape Weavy expects."""
if schema_type == "boolean":
return {"type": "value", "data": {"type": "boolean", "value": bool(value) if value is not None else False}}
if schema_type == "integer":
return {"type": "value", "data": {"type": "integer", "value": int(value) if value is not None else 0}}
if schema_type == "number":
return {"type": "value", "data": {"type": "float", "value": float(value) if value is not None else 0.0}}
if schema_type == "seed":
v = value if isinstance(value, dict) else {"seed": 1, "isRandom": False}
return {"type": "value", "data": {"type": "seed", "value": v}}
if schema_type == "fal_image_size":
v = value if isinstance(value, dict) else {"type": "built_in", "value": "match_input"}
return {"type": "value", "data": {"type": "image_size", "value": v}}
return {"type": "value", "data": {"type": "string", "value": str(value) if value is not None else ""}}
def make_node_from_catalog(catalog_name, node_id, x, y, refs=None, param_overrides=None, name_override=None):
"""
Generic loader. Use for any node not covered by a hand-written builder above.
catalog_name: exact key from nodes_catalog.json (e.g. "Recraft V3", "Veo 3.1 I2V").
refs: dict mapping handle_id -> reference dict (e.g. {"prompt": {"nodeId":..., "outputId":"prompt", "string":""}})
param_overrides: dict mapping schema key -> new value (overrides catalog defaults).
name_override: replace displayed node name.
"""
refs = refs or {}
param_overrides = param_overrides or {}
if catalog_name in CUSTOM_KIND_NODES and CUSTOM_KIND_NODES[catalog_name] != "wildcard":
raise ValueError(
f"'{catalog_name}' uses a custom kind type "
f"({CUSTOM_KIND_NODES[catalog_name]}). Use the dedicated make_*_node builder instead."
)
if catalog_name not in NODES_CATALOG:
raise KeyError(f"Node '{catalog_name}' not found in catalog. "
f"Available: {sorted(NODES_CATALOG.keys())[:10]}...")
template = deepcopy(NODES_CATALOG[catalog_name])
# Apply param overrides
if template.get("params"):
for k, v in param_overrides.items():
template["params"][k] = v
node_type = template.get("type")
width, height = NODE_DIMENSIONS.get(node_type, (460, 560))
# Refresh handle IDs
for direction in ("input", "output"):
handles = template.get("handles", {}).get(direction, {})
if isinstance(handles, dict):
for h in handles.values():
h["id"] = uid()
# Build inner data block
data = {
"handles": template.get("handles", {}),
"name": name_override or template.get("name"),
"description": template.get("description"),
"color": template.get("color"),
"label": None,
"menu": None,
"model": template.get("model"),
"params": template.get("params"),
"schema": template.get("schema"),
"version": template.get("version", 3),
}
if "dark_color" in template: data["dark_color"] = template["dark_color"]
if "border_color" in template: data["border_color"] = template["border_color"]
# Reconstruct kind for custommodelV2
is_model = (node_type == "custommodelV2")
if is_model:
data["kind"] = _build_wildcard_kind(template, refs)
data["generations"] = []
data["selectedIndex"] = 0
data["cameraLocked"] = False
data["result"] = []
data["output"] = {}
data["selectedOutput"] = 0
else:
data["output"] = {}
node = {
"id": node_id,
"type": node_type,
"data": data,
"owner": None,
"visibility": "private" if is_model else None,
"isModel": is_model,
"locked": False,
"position": {"x": x, "y": y},
"createdAt": NOW,
"updatedAt": UPD,
"selected": False,
"width": width,
"height": height,
}
if is_model:
node["dragHandle"] = ".node-header"
return node
```
### When the loader's output is wrong
The loader assumes `kind.type = "wildcard"` for every model node. About a dozen Weavy nodes use **non-wildcard custom kinds** (`reve`, `imagen3`, `gpt_image1`, `kling`, etc.). If you paste a loader-generated node and Weavy refuses it or shows broken handles:
1. Ask the user to copy the real node from Weavy and paste the JSON
2. Add an entry to `CUSTOM_KIND_NODES` and write a hand-written `make_*_node` builder
3. Mark it 🟢 in the catalog table
The loader is best-effort but covers the wildcard majority correctly.
---
## COMPLETE SCRIPT TEMPLATE
```python
import json
import uuid
def uid():
return str(uuid.uuid4())
NOW = "2026-01-01T00:00:00.000Z"
UPD = "2026-01-01T00:00:00.000Z"
# === Paste all make_* functions from this document ===
# === Declare node IDs ===
ID_FILE = uid()
ID_ROUTER = uid()
# ...
# === Build nodes ===
nodes = []
nodes.append(make_file_node(ID_FILE, "SOURCE IMAGE", 0, 0))
nodes.append(make_router_node(ID_ROUTER, "Router", 0, 640))
# ...
# === Build edges ===
edges = []
edges.append(make_edge(ID_FILE, ID_ROUTER, "file", "in", "Yambo_Blue", "Yambo_Orange", "any", "any"))
# ...
# === Export ===
template = {"nodes": nodes, "edges": edges}
with open("/mnt/user-data/outputs/workflow.json", "w", encoding="utf-8") as f:
json.dump(template, f, ensure_ascii=False, indent=2)
print(f"✅ {len(nodes)} nodes, {len(edges)} edges")
```
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!