$ cat workspace-template.yaml
Produzione Documenti Per Emblema
// A Document Production Automation with HTML slides for Emblema Platform.
// Canvas Preview
// Instruction
Presentazione Branded Emblema dalle Cartelle Drive
Template ID
<TBD — assign when the workspace template is published>
Overview
Composes a branded business deck from documents stored in a Drive Folder (or a single Drive file), using the Emblema theme and slide layouts. Designed for general-purpose corporate, product, and consulting decks — the output is a self-contained Reveal.js presentation exported to PPTX, styled with Emblema's teal-on-white brand system, IBM Plex typography, and 1920×1080 HD canvas. The workflow reads documents, then "Selezione dei Topic Principali" identifies the main topics and the slides for each topic. A centralized "Contenuto Presentazione" node generates ALL slide content for the entire deck in a single pass. For each (topic, slide) pair, the agent MUST clone a prototype node, rename it, wire its content input from "Contenuto Presentazione", and wire its output to "Make Array". All slides are assembled into the deck and exported to PPTX.
Terminology: A topic is a high-level subject area. Each topic produces one or more slides. By default each topic resolves to a single slide. When the user explicitly requests multiple slides per topic (e.g. "10 topic con 3 slide ciascuno"), each topic is split into N slide-sized sections. Total content slides = sum over all topics of M_x (where M_x is the number of slides for topic X). Plus cover + closing.
The template includes two prototype chains: a standalone Slide Generator ("Topic #x - Slide #1", emblema-bullets by default) AND a paired Slide-Generator + Image-Creation chain ("Topic #x - Slide #1 - [With Image Input]" + "Image Creation - for Topic #x - Slide #1 - [With Image Input]", emblema-image by default). At Phase 1 the agent asks the user (Gate 3) whether the deck should include generated images; the answer selects which prototype chain Phase 3 clones for EVERY (topic, slide) pair. The agent renames each clone and picks the best emblema layout for each slide based on the slide's content shape (text, image, KPI, comparison, timeline, code, etc.). Do NOT delete either prototype — both must remain on the canvas. Unless the user specifies a maximum at Gates 1 and 2, ALL topics and slides from the topic-selector output are used.
Emblema ships 34 slide layouts in v1, including image-rich layouts. The Emblema theme has 9 layouts that accept upstream image input via <img> tags — emblema-image, emblema-magazine, emblema-two-img, emblema-hero-centered, emblema-two-col, emblema-team, emblema-testimonial, emblema-logos, emblema-ui — pick them when the user provides imagery. The remaining layouts (including some visually photo-rich ones like emblema-grid-img / emblema-hero-card / emblema-collage) render with CSS placeholders and do NOT accept image input.
Workflow Chain
Final-execution preference: when the workspace contains a Public Share node downstream of Presentation Composer, that is the canonical target the agent should execute (Phase 4). PPTX Converter is the optional download branch. If no Public Share node is present, the agent must ask the user before falling back to PPTX Converter (see Phase 4).
Data Source Selection
The data source depends on what the user wants to process:
- Folder (multiple documents): Use Drive Folder Reader connected to Drive Document Reader (both nodes). The Folder Reader lists items, the Document Reader reads their content.
- Single file: Use only Drive Document Reader (no Folder Reader needed). The user selects the document directly. You must delete the "Seleziona Materiale Sorgente" node from the canvas using
deleteNode, then configure the Document Reader withupsertNode.
Node Reference
Data Source Nodes
"Seleziona Materiale Sorgente" (Drive Folder Reader):
- What it does: Lists all document items in a selected Drive folder.
- Configurable inputs:
driveItemId— the Drive folder ID (NOTfolderId). - Output:
driveItemIds→ list of document IDs.
"Leggi i Documenti disponibili" (Drive Document Reader):
- What it does: Reads the processed text content from documents.
- Input from: Drive Folder Reader
[driveItemIds](folder mode) or user-selected document (single file mode). - Configurable inputs:
driveItemId— the Drive item ID. - Output:
content→ full text content of the document(s). This content feeds "Selezione dei Topic Principali" AND "Contenuto Presentazione".
"Selezione dei Topic Principali" (Topic Selector)
What it does: Analyzes document content and identifies both the main topics AND the slides per topic in a single pass. This is the only node that determines the presentation structure.
Input from: "Leggi i Documenti disponibili" [content].
Configurable inputs:
llmModel: LLM model for topic extraction (default: auto-selected)prompt: Instruction for topic and slide selection. Default:"Seleziona i topic principali dei documenti e riassumi i punti chiave (topics), per ciascun topic riassumi i temi (slides) rilevanti". By default each topic resolves to a single slide. Can be customized to focus on specific areas or to request a specific number of topics or slides-per-topic. Output:summary→ structured text containing topics and the slides for each topic. The agent MUST read this output to extract both the topic names AND the slide names per topic (one or more). All subsequent node naming and prompts MUST use the exact topic and slide names from this output.
"Contenuto Presentazione" (Content Generator)
What it does: Generates the complete textual content for ALL slides in the presentation in a single pass. The agent MUST update the prompt with the full list of topics and slides (from "Selezione dei Topic Principali" output) before executing this node.
Input from: "Leggi i Documenti disponibili" [content].
Configurable inputs:
llmModel: LLM model (default:openai/gpt-oss-120b)prompt: MUST be updated by the agent to list ALL topics and their slides in the following format: When a topic resolves to a single slide (default mode), include only one##Slide 1: <name>line per topic. Output:summary→ complete presentation content organized by topic and slide. This feeds "#Cover", "#Closing", and ALL topic Slide Generator nodes.
"#Cover" and "#Closing" (Slide Generators)
"#Cover" (Slide Generator):
- What it does: Generates the opening title slide with Emblema's split layout — text on the left (eyebrow + headline + subtitle + meta line), full-bleed teal panel on the right.
- Input from: "Contenuto Presentazione"
[summary]→[content]. - Pre-connected: Output
slide_html→ Make Arrayitem_0. - Default config:
template_name: "emblema-cover",instruction: "titolo slide di apertura".
"#Closing" (Slide Generator):
- What it does: Generates the closing CTA slide with Emblema's teal split, eyebrow + headline + contact pairs.
- Input from: "Contenuto Presentazione"
[summary]→[content]. - NOT pre-connected to Make Array — the agent MUST wire it to the last pin after all topic slides are connected.
- Default config:
template_name: "emblema-closing".
Prototype Nodes (for cloning)
The template includes two prototype chains, one of which is selected at Gate 3 (Phase 1, step 3c) based on whether the user wants images on slides. Do NOT delete either prototype — both must remain on the canvas as reference nodes for future operations.
Prototype Node per Topic/Slide (no images)
"Topic #x - Slide #1" (standalone Slide Generator):
- 1 node: Slide Generator
- Default config:
template_name: "emblema-bullets",instruction: "Inserisci il contenuto del Topic #x - Slide #1" - Input:
content← from "Contenuto Presentazione"[summary](external edge — NOT preserved bycloneNodes) - Output:
slide_html— NOT pre-connected to Make Array - IMPORTANT: This is a standalone node, NOT inside a section. Use
nodeFinderto locate it (notsectionFinder). When cloning withcloneNodes({ nodeIds: [nodeId] }), the clone keeps the original label"Topic #x - Slide #1"— you MUST callupsertNodeto rename it (e.g."Topic #2 - Slide #1"or"Topic #1 - Slide #3"), set the instruction, and pick a specific emblema layout for that slide.
Prototype Node per Topic/Slide + image creation Node (with images)
"Topic #x - Slide #1 - [With Image Input]" (Slide Generator, paired with an Image Creation node):
- 2 nodes in a wired pair:
- Slide Generator — labeled
"Topic #x - Slide #1 - [With Image Input]". Default config:template_name: "emblema-image",instruction: "Inserisci il contenuto del Topic #x - Slide #1". Inputs:content← from "Contenuto Presentazione"[summary](external — NOT preserved bycloneNodes);image← from Image Creation[image](internal — IS preserved bycloneNodeswhen both nodes are passed innodeIds). - Image Creation — labeled
"Image Creation - for Topic #x - Slide #1 - [With Image Input]". Default config:prompt: "<set per-slide by the agent>". Output:image→ wired to Slide Generator'simageinput.
- Slide Generator — labeled
- Output:
slide_htmlfrom the Slide Generator — NOT pre-connected to Make Array. - IMPORTANT: To clone the pair, pass both prototype IDs together:
batchCloneNodes({ nodeIds: [<imgCreationProtoId>, <slideGenProtoId>], count: total_slides }). The internalimage_creation.image → slide_gen.imageedge IS preserved across the clone set per pair. Returnedclones[i].nodeIdMapwill map BOTH original IDs to their clone IDs for that pair. After cloning, the agent MUST rename both clones AND set a per-slide imageprompton the Image Creation clone. The slide-generator clone wiring to "Contenuto Presentazione" is identical to the simple prototype path.
Make Array
What it does: Collects all slide HTML outputs into a single ordered array. Pin order determines slide order in the final presentation.
Pin layout: item_0 = #Cover, then all topic slides grouped by topic in sequential order, last pin = #Closing.
Example (2 topics, 3 and 2 slides): item_0 = #Cover, item_1 = T1-S1, item_2 = T1-S2, item_3 = T1-S3, item_4 = T2-S1, item_5 = T2-S2, item_6 = #Closing.
Default pins: The template ships with 10 input pins. When the total exceeds 10, new pins are created automatically by upsertEdge.
Total pins formula: 1 (Cover) + sum of all slides across all topics (total_slides) + 1 (Closing).
Output: Array of slide HTML sections (ordered by pin index).
Presentation Composer
What it does: Assembles individual slides into a complete self-contained Reveal.js HTML presentation.
Pre-connected in template: Receives items array from Make Array.
Configurable inputs:
title: Presentation title (shown in browser tab)theme: default"emblema"(Emblema brand — teal#00485B+ white + IBM Plex Sans/Mono fonts inlined as base64). The emblema theme renders at a 1920×1080 internal canvas.transition: Slide transition effect —slide,fade,convex,concave,zoom,noneOutput: Complete HTML document (self-contained presentation).
PPTX Converter
What it does: Converts the Reveal.js HTML presentation into a downloadable PowerPoint .pptx file.
Pre-connected in template: Receives presentation HTML from Presentation Composer.
Configurable inputs:
title: PowerPoint metadata titletheme: default"emblema"— must match Presentation Composer theme. Note: the PPTX converter does NOT ship a dedicatedemblemacolor palette inTHEME_PALETTES(apps/workspace-service/app/templates/pptx_converter.py); it will fall back to the default palette for theme-aware color mapping. Visual fidelity is still preserved because the Emblema CSS is inlined in the HTML and styles cascade into the rendered slide elements. Coordinate system is fixed at 13.33 × 7.5 inches (LAYOUT_WIDE) — the 1920×1080 Reveal canvas does NOT affect PPTX output dimensions. Output: PowerPoint .pptx file.
Emblema Layout Selection
Emblema ships 34 slide layouts in v1. The agent MUST pick the best layout per slide based on the content shape — using only emblema-bullets for everything wastes the brand and loses visual pacing. Variety across slides is the whole point of having 34 layouts.
Reserved layouts (do NOT pick for topic slides)
emblema-cover— used by#Coveronly. (reserved for #Cover; does not support images)emblema-closing— used by#Closingonly. (reserved for #Closing; does not support images)
Section / divider / framing layouts (use sparingly, between topics or for emphasis)
emblema-section— big section number on full-bleed teal. Use as a divider before a major part of the deck (e.g. after the cover, before each major topic group when you have ≥6 topics). (does not support images)emblema-statement— centered pull quote with em-highlight in teal. Use for key claims, mission statements, principles. (does not support images)emblema-question— single full-screen question. Use for rhetorical or framing questions. (does not support images)emblema-definition— term + IPA + part of speech + meaning + italic example. Use when the slide content is defining a concept. (does not support images)
Text-and-bullet workhorses (default for ordinary content)
emblema-bullets— up to 4 numbered bullets in a 2×2 grid with circular markers. Default workhorse for slides with 2-4 takeaway points. (does not support images)emblema-two-col— two-column with text left, visual panel right. Use when the slide has a primary explanation and a single supporting visual/diagram. (supports images)emblema-agenda— numbered 2-column agenda grid with topics + durations. Use ONLY when the user explicitly wants an agenda slide (NOT for every topic). (does not support images)
Photography-driven layouts (pick when content is visual / photo-rich)
Note: not every layout in this section accepts an <img> input — see the _(supports images)_ / _(does not support images)_ suffix on each entry.
emblema-image— full-bleed image (or teal gradient) with bottom overlay text. Use for dramatic single-image slides. (supports images)emblema-magazine— photo-left magazine spread with story body and metadata. Use for narrative case studies or features. (supports images)emblema-two-img— two side-by-side images with separate titles, descriptions, credits. Use for paired examples (before/after at a visual level, two products, two case studies). (supports images)emblema-grid-img— 2×2 image grid on full-bleed teal with header copy above. Use for portfolios, product lineups, gallery moments. (does not support images)emblema-hero-centered— centered hero image with header above, italic caption below. Use for product reveals, brand moments. (supports images)emblema-hero-card— full-bleed hero with white floating card containing eyebrow + title + body. Use for emotive, lifestyle-style slides. (does not support images)emblema-collage— 3-image rotated photo collage with copy on the left. Use for editorial / lookbook moments. (does not support images)
Data layouts (numbers / metrics / tables / status)
emblema-stat— single huge metric in teal with eyebrow + headline + supporting copy. Use when ONE number is the headline. (does not support images)emblema-kpis— 3-9 KPI cards (multiples of 3, 1-3 rows) with large numbers, optional deltas, label, detail. Use for dashboards: 3 for headline metrics, 6 for paired-metric dashboards, 9 for full quarterly scorecards. (does not support images)emblema-compare— Before/After comparison with teal arrow; "after" column highlighted. Use for impact slides showing transformation. (does not support images)emblema-chart— chart wrapper with header, legend, source caption. Use when content describes a chart (the LLM can output an inline SVG chart). (does not support images)emblema-table— financial/metrics table with right-aligned numerics and totals row. Use for spreadsheet-shaped data. (does not support images)emblema-matrix— comparison matrix (features × options) with one featured "OUR PICK" column. Use for vendor/option comparisons. (does not support images)emblema-spec— grouped key:value spec sheet in 2-column layout. Use for product specs, technical reference data. (does not support images)emblema-status— RAG portfolio table with green/amber/red/gray status dots. Use for project status, portfolio reviews. (does not support images)
Process / flow / time layouts
emblema-process— 4 or 8 step cards (1 or 2 rows of 4) with numbered markers, icons, titles, descriptions. Use for methodologies, "how it works" explanations. (does not support images)emblema-timeline— 5-point horizontal timeline with done/now/upcoming dots. Use for milestones, history, schedule. (does not support images)emblema-roadmap— Now/Next/Later 3-phase roadmap with bulleted deliverables. Use for product roadmaps, project plans. (does not support images)
People / social proof
emblema-team— 4-up team grid with avatar tiles, name, role, bio. Use for team intros. (supports images)emblema-testimonial— round-photo testimonial with large pull quote and attribution. Use for customer quotes, endorsements. (supports images)emblema-logos— 5-column logo grid for customer/partner wordmarks. Use for "trusted by" / partner showcases. (supports images)
Specialized
emblema-ui— feature list left, browser-chrome screenshot mockup right. Use for SaaS / product feature slides. (supports images)emblema-features— Feature grid (6, 9, or 12 cards in a 3-col layout) with teal icon tiles, titles, descriptions. Use 6 for sparse, 9 for medium, 12 for very dense feature lists. (does not support images)emblema-pricing— 3 tiers with one featured (lifted, teal CTA). Use for pricing pages. (does not support images)emblema-code— code slide: copy left, dark IBM Plex Mono code block right with syntax-color spans. Use when content is technical and includes a code snippet. (does not support images)
Layout picking rule (apply in Phase 3 step B when renaming each cloned node)
Apply this priority order. The first matching rule wins.
-
If the slide content is a single defining term/concept ("definition of X", "what is X") →
emblema-definition -
If the slide content is a single rhetorical / framing question ("why does this matter?", "what if…?") →
emblema-question -
If the slide content is a single bold claim / pull quote / mission statement →
emblema-statement -
If the slide is a section divider introducing a new major part of the deck (numbered "Section N", "Part N", "Capitolo N") →
emblema-section -
If the slide content is a code listing (mentions a programming language, API call, snippet, "```") →
emblema-code -
If the slide content is chronological (timeline, milestones, history, evolution, phases, dates) →
emblema-timeline -
If the slide content is a roadmap (now/next/later, quarters, planning horizons) →
emblema-roadmap -
If the slide content is a process / methodology / how-it-works with discrete steps →
emblema-process -
If the slide is a single hero metric / one-number-tells-the-story →
emblema-stat -
If the slide is a dashboard with 3-4 paired KPIs →
emblema-kpis -
If the slide is a before vs after / transformation / impact comparison →
emblema-compare -
If the slide is a chart description / data visualization →
emblema-chart -
If the slide is a financial / metrics table with rows and columns →
emblema-table -
If the slide is a feature/option comparison matrix ("vs", "competitors", "OUR PICK") →
emblema-matrix -
If the slide is a technical spec sheet (key:value pairs, dimensions, technical attributes) →
emblema-spec -
If the slide is a portfolio/project status with RAG indicators →
emblema-status -
If the slide is a team intro (people, names, roles) →
emblema-team -
If the slide is a customer quote / endorsement →
emblema-testimonial -
If the slide is a logo cloud / "trusted by" / partner list →
emblema-logos -
If the slide is a product UI showcase / app screenshot →
emblema-ui -
If the slide enumerates ≥5-6 product features →
emblema-features -
If the slide is pricing tiers →
emblema-pricing -
If the slide is a two-column with one supporting visual →
emblema-two-col -
If the user provided imagery and the slide content is photo-driven:
- Single dramatic image →
emblema-image(supports images) - Magazine-style story with one photo →
emblema-magazine(supports images) - Two paired images →
emblema-two-img(supports images) - Centered hero / product reveal →
emblema-hero-centered(supports images) - Text + visual panel →
emblema-two-col(supports images)
The following layouts are visually photo-rich but currently render with CSS-painted placeholders (no upstream image is injected) — pick them only when the visual placeholder is acceptable, NOT when you need a generated image to appear:
- 2×2 image grid →
emblema-grid-img(does not support images) - Hero with floating card →
emblema-hero-card(does not support images) - 3-photo collage →
emblema-collage(does not support images)
- Single dramatic image →
-
Else (ordinary content with 2-4 bullet takeaways) →
emblema-bullets(the safe default)
The agent MUST NOT use emblema-cover or emblema-closing for topic slides — those are reserved for the #Cover and #Closing nodes.
Variety target: across total_slides topic slides, aim for at least 4 different layouts. If your mapping table has every slide on emblema-bullets, re-read the slide names from "Selezione dei Topic Principali" and apply the picking rule again — you almost certainly missed signals (numbers, dates, comparisons, processes).
Theme consistency: Emblema is the active theme throughout this template (Presentation Composer + PPTX Converter + all Slide Generator prototypes). All slide generators, the Presentation Composer, and the PPTX Converter must use emblema theme and emblema-* layouts.
Important: Node IDs and Field Names
- After
applyCanvasData(used only for initial template commit), always usenodeFinderto locate nodes by label name —nodesPreviewIDs are no longer valid. - Use atomic tools (
upsertNode,upsertEdge,deleteNode,deleteEdge,cloneNodes) for ALL canvas modifications after the initial template apply. These execute instantly — noapplyCanvasDataneeded. - The Drive Folder Reader input field is
driveItemId(NOTfolderId). - The Drive Document Reader input field is
driveItemId. - Always verify that a
nodeIdis a valid UUID before passing it toupsertEdge. IfnodeFinderreturns no match, do NOT proceed with a missing ID — investigate the label mismatch first.
Execution Strategy
You MUST complete all 4 phases in order: Phase 1 → Phase 2 → Phase 3 → Phase 4. Do NOT skip any phase. Do NOT jump to Phase 4 early.
After calling nodeExecutor, the system notifies automatically when execution completes. Do NOT poll getNodeOutput in a loop.
Do NOT call followUp anywhere in this template. Phase 4 ends with the verification audit at step 20 — that is the terminal action for this flow. The two user-decision pauses inside Phase 4 (steps 17 fallback and 18) use requestUserDecision, not followUp.
If a tool call returns an error, retry up to 3 times with the same or corrected parameters before giving up. If all 3 retries fail, ask the user for help — do NOT skip the step and continue with default values.
If interrupted mid-step and the user sends "continua", resume from where you left off. Use nodeFinder to check which nodes already have the correct label/connections before re-processing them. Nodes that already show the correct label or connections do not need to be updated again.
NEVER execute either prototype node ("Topic #x - Slide #1" or "Topic #x - Slide #1 - [With Image Input]" or its paired Image Creation prototype) directly. Only execute cloned and renamed nodes. The prototypes are templates for cloning — executing them produces incorrect output.
Phase 1: Setup
Phase 1 has 7 required steps: 1, 2, 3 (canvas + drive setup) + 3a, 3b, 3c, 3d (user-decision gates). All seven are MANDATORY before Phase 2. The four gates (3a–3d) are NOT optional and NOT skippable — they collect inputs that Phase 2 needs to configure node prompts correctly. Skipping them causes Phase 2 to run with defaults, then re-run with gate answers, doubling LLM cost.
applyCanvasData→ IMMEDIATELY call this to commit the template to workspace. You MUST callapplyCanvasDatahere before any other tool call.searchDriveItems+selectDriveItem→ get the folder/file ID the user mentioned. If no results, try shorter/broader queries (e.g., "product" instead of "product launch strategy"). If still nothing after 2 attempts, ask the user to specify the drive item. Do NOT retry the same query.- Configure the data source:
- If folder:
nodeFinder→ find "Seleziona Materiale Sorgente".upsertNode({ nodeId: "<found ID>", input: { driveItemId: "<selected ID>" } }). - If single file:
nodeFinder→ find "Seleziona Materiale Sorgente".deleteNode({ nodeId: "<found ID>" }). ThennodeFinder→ find "Leggi i Documenti disponibili".upsertNode({ nodeId: "<found ID>", input: { driveItemId: "<selected ID>" } }).
- If folder:
3a. Gate 1 — Number of topics. Call requestUserDecision:
requestUserDecision({ question: "Quanti topic principali vuoi nella presentazione?", context: "Ogni topic produce uno o più slide. Il totale slide finale = Cover + (topics × slides per topic) + Closing.", options: [ { id: "3", label: "3 topic" }, { id: "5", label: "5 topic" }, { id: "10", label: "10 topic" }, { id: "auto", label: "Automatico", description: "Lascia decidere all'agente in base alla rilevanza e all'estensione dei documenti" }, ], allowOther: true, // user may type a specific number, e.g. "7" })
Store the answer as topicCountChoice. If cancelled, default to auto. If otherText and it parses as a positive integer ≤ 50, use that integer; otherwise fall back to auto.
3b. Gate 2 — Number of slides per topic. Call requestUserDecision:
requestUserDecision({ question: "Quante slide per ogni topic?", context: "1 slide per topic = deck conciso. 2-3 slide per topic = deck dettagliato. Il numero finale di slide-content = topic × slide-per-topic.", options: [ { id: "1", label: "1 slide per topic" }, { id: "2", label: "2 slide per topic" }, { id: "3", label: "3 slide per topic" }, { id: "auto", label: "Automatico", description: "Lascia decidere all'agente per ogni topic in base alla complessità del contenuto" }, ], allowOther: true, })
Store the answer as slidesPerTopicChoice. If cancelled, default to auto. If otherText and it parses as a positive integer ≤ 10, use that integer; otherwise fall back to auto.
3c. Gate 3 — Images on slides? Call requestUserDecision:
requestUserDecision({ question: "Vuoi includere immagini generate automaticamente nelle slide?", context: "Se sì, ogni slide riceve un nodo di generazione immagine collegato. Se no, il deck è solo testo e grafica vettoriale Emblema.", options: [ { id: "yes", label: "Sì, genera immagini per le slide" }, { id: "no", label: "No, solo testo e grafica vettoriale" }, ], })
Store the answer as useImagesChoice. If cancelled, default to no.
3d. Gate 4 — Image style (only when useImagesChoice === "yes" from step 3c — skip this gate entirely if the user picked "no" or cancelled Gate 3). Call requestUserDecision:
requestUserDecision({ question: "Che stile vuoi per le immagini delle slide?", context: "La scelta guida tutti i prompt di generazione immagine. Diagrammatico = visualizzazioni con icone, testo etichette e schemi vettoriali. Fotografico = immagini realistiche di persone, ambienti e atmosfere, senza grafica vettoriale.", options: [ { id: "diagram", label: "Diagrammatico", description: "Diagrammi, icone, testo etichette, schemi vettoriali. Ottimo per contenuti tecnici, dati, processi." }, { id: "photo", label: "Fotografico", description: "Persone, ambienti, mood realistici. Niente icone, testo o grafica vettoriale. Ottimo per contenuti editoriali e narrativi." }, ], })
Store as imageStyleChoice. If cancelled, default to photo. The chosen value is used at Phase 3 step 11 to build the per-slide image-creation prompts (and at Phase 4 step 16 if any prompt needs to be patched).
Phase 1 complete. Now proceed to Phase 2. Do NOT skip to Phase 4.
CRITICAL — Phase 2 entry precondition: Before executing ANY step in Phase 2, verify that ALL FOUR Phase 1 gate variables are set: topicCountChoice (from gate 3a), slidesPerTopicChoice (from gate 3b), useImagesChoice (from gate 3c), and imageStyleChoice (from gate 3d, only if useImagesChoice === "yes"). If any required variable is missing, you SKIPPED a Phase 1 gate. STOP. Go back to Phase 1 and run the missing gates with requestUserDecision. Do NOT proceed to step 4 with default values — running Phase 2 nodes with defaults and then re-running them after the gates wastes both LLM calls AND user time.
Phase 2: Topic Selection & Content Generation
- Precondition check: confirm
topicCountChoiceandslidesPerTopicChoiceare both set. If either isundefined, gates 3a/3b were skipped — STOP and run them now. Then:nodeFinder→ find "Selezione dei Topic Principali" by label. Configure its prompt based ontopicCountChoice(Gate 1) andslidesPerTopicChoice(Gate 2):- Both
auto: leave the default prompt unchanged. - At least one explicit:
upsertNode({ nodeId: <found ID>, input: { prompt: "Seleziona <N> topic principali dei documenti e per ciascun topic seleziona <M> temi (slides) rilevanti" } }). Replace<N>with the numeric Gate 1 answer (or "i topic" ifauto); replace<M>with the numeric Gate 2 answer (or "i temi" ifauto). - Focus area mentioned by user in original message: append the focus area to whichever prompt was chosen above.
- Both
nodeExecutor→ execute "Selezione dei Topic Principali". Wait for completion. The system will execute "Leggi i Documenti disponibili" as a dependency of this node.getNodeOutput→ read the output. Extract the structured list of topics AND their slides. For each topic, extract the topic name and the list of slide names (one or more). Store as:topics = [topic1_name, topic2_name, ..., topicN_name]slides[X] = [slide1_name, slide2_name, ..., slideM_name]for each topic X (often M=1 in default mode)N = number of topics,M_x = number of slides for topic X- If the user specified a maximum topic count, use that count. If the user did NOT specify a maximum, use ALL topics from the output — do NOT cap or reduce them.
- If the user specified a maximum slides count per topic, use that count. If the user did NOT specify a maximum, use ALL slides per topic from the output — do NOT cap or reduce them. When the prompt was the default (single-slide),
M_xwill be 1 for every topic. - If
topicCountChoicewas a specific integer, cap N to that integer. IfslidesPerTopicChoicewas a specific integer, cap M_x to that integer for every topic. The user already chose at Gates 1 and 2 — do NOT re-ask. - Safety limits: if
N(number of topics) exceeds 20, warn the user and ask whether to proceed or reduce. Iftotal_slides(sum of M_x across all topics) exceeds 50, warn the user and ask whether to proceed or reduce. Do NOT silently reduce. (Apply the warning AFTER the gate caps above — the user has already chosen at Gates 1 and 2, so do not re-ask Gate 1 / Gate 2 questions; only ask whether to proceed or reduce at this safety threshold.)
CRITICAL: ALL topic names and slide names used in subsequent phases MUST come from the "Selezione dei Topic Principali" output (this step). Do NOT invent topic or slide names — always use the exact names from this output.
nodeFinder→ find "Contenuto Presentazione" by label. Update its prompt to list ALL topics and slides using the exact names from step 6:upsertNode({ nodeId: <found ID>, input: { prompt: "Prepara il contenuto della presentazione espandendo ogni topic e slide selezionati dal nodo \"Selezione dei Topic Principali\" creando il testo per ciascuna sezione.\n\nNel prompt elenca tutti i topic e le slide da espandere includendo il loro nome esteso nel seguente formato:\n\n#Topic 1: <topic1_name>\n##Slide 1: <slides[1][1]>\n##Slide 2: <slides[1][2]>\n...\n#Topic 2: <topic2_name>\n##Slide 1: <slides[2][1]>\n...\n\nPer ogni topic principale, sviluppa una sezione dettagliata con tutte le slide associate, fornendo descrizioni complete e coerenti per la presentazione finale." } })— replace ALL placeholder names with actual names from step 6. WhenM_x = 1for every topic (default mode), each topic block has exactly one##Slide 1: <name>line.
nodeExecutor→ execute "Contenuto Presentazione". IMMEDIATELY after step 7 — do NOT stop between step 7 and step 8. Wait for completion.
Phase 2 complete. Now proceed to Phase 3. Do NOT skip to Phase 4.
CRITICAL — Phase 3 partition precondition (Yes-images branch): When useImagesChoice === "yes", step 9c is MANDATORY before step 10. Skipping 9c is the most common Phase 3 error: it causes every slide to get an Image Creation clone regardless of whether the slide's layout supports images, wasting image-generation compute and cluttering the canvas with unused image-creation nodes. If you find yourself about to call batchCloneNodes with both prototype IDs and count: total_slides, STOP — that's the symptom of having skipped 9c.
Phase 3: Slide Chain Creation
This phase has 4 sequential steps (A → B → C → D) with hard gates. You MUST complete each step before proceeding to the next step. Each batch tool must be called EXACTLY ONCE per step.
Rules:
- Use batch tools for all bulk modifications:
batchCloneNodesto duplicate all at once,batchUpsertNodesto rename/configure all at once,batchUpsertEdgesfor all connections at once. - Each batch tool is called EXACTLY ONCE per step. Do NOT call a batch tool a second time to "fix" or "add" entries. If you discover a mistake after a batch call, use the single-operation tool (
upsertNode,upsertEdge) to patch the specific item. - Do NOT use
applyCanvasDatain this phase. All modifications use atomic/batch tools only (compileCanvasDatawas removed from the toolset — atomic tools are the only canvas-edit path available here). - Do NOT delete either prototype node — both must remain on the canvas for future reference.
Step A — Build mapping table, find the prototype(s), and clone ALL slide nodes:
- Find prototypes and key nodes (branch on
useImagesChoicefrom Gate 3):- If
useImagesChoice === "no"(or default):nodeFinder→ find"Topic #x - Slide #1"(standalone Slide Generator prototype). Store itsnodeIdassimplePrototypeId.
- If
useImagesChoice === "yes":nodeFinder→ find"Topic #x - Slide #1 - [With Image Input]"(paired Slide Generator prototype). Store itsnodeIdasimageSlidePrototypeId.nodeFinder→ find"Image Creation - for Topic #x - Slide #1 - [With Image Input]"(paired Image Creation prototype). Store itsnodeIdasimageCreationPrototypeId.
- In both branches:
nodeFinder→ find "Contenuto Presentazione" and "Make Array". Store theirnodeIdvalues for all subsequent steps.
- If
9b. Build the COMPLETE MAPPING TABLE before cloning. This mapping is IMMUTABLE — once built, do NOT change it. All subsequent steps (B, C, D) MUST use this exact mapping.
9c. MANDATORY (Yes-images branch) — Partition the mapping table by layout image-capability. Skipping this step is the most common error in this template: it causes every slide to get an Image Creation clone regardless of layout. Only when useImagesChoice === "yes" — skip in the No branch.
-
Clone ALL slide nodes in one or two calls depending on branch:
No-images branch (
useImagesChoice === "no"or default):batchCloneNodes({ nodeIds: ["<simplePrototypeId>"], count: <total_slides> }). Eachclones[i].nodeIdMap[simplePrototypeId]= cloned Slide Generator ID. Theclonesarray is indexed byclone_indexfrom the mapping table;clones[0]= first row,clones[1]= second row, etc.
Yes-images branch (
useImagesChoice === "yes"):Precondition: did you build
text_rowsandimage_rowsat step 9c? If they are not both defined as concrete arrays, you skipped 9c — STOP, go back, partition first. Do NOT callbatchCloneNodeswithcount: total_slidesand both prototype IDs at once — that bypasses the partition and clones the image-paired prototype for every slide, including those whose layout doesn't support images.- Text slides (using the partition from 9c):
batchCloneNodes({ nodeIds: ["<simplePrototypeId>"], count: text_rows.length }). Skip this call entirely iftext_rows.length === 0. Result:textClones[t].nodeIdMap[simplePrototypeId]= cloned Slide Generator ID for thet-th text row (wheret = 0...text_rows.length - 1). - Image slides:
batchCloneNodes({ nodeIds: ["<imageCreationPrototypeId>", "<imageSlidePrototypeId>"], count: image_rows.length }). Skip this call entirely ifimage_rows.length === 0. The pair's internalimage_creation.image → slide_gen.imageedge IS preserved per clone (because both prototypes are passed together innodeIds). Result:imageClones[g].nodeIdMap[imageSlidePrototypeId]= cloned Slide Generator ID,imageClones[g].nodeIdMap[imageCreationPrototypeId]= cloned Image Creation ID, paired by partition-indexg(whereg = 0...image_rows.length - 1).
The mapping table from 9b/9c remains the source of truth for naming, instruction text, layout, and pin assignments. Each row's
clone_index(from 9b) determines its Make Array pin (item_<clone_index + 1>); use the row's partition (text_rowsorimage_rows) to look up its slide-generator clone in the rightclonesarray (textClones[t]orimageClones[g]).Verify after cloning (Yes branch, image_rows partition only): inspect any one cloned pair from
imageClonesvianodeFinderto confirm the internal image edge from the cloned Image Creation[image]→ cloned Slide Generator[image]survived. If missing on any pair, patch withupsertEdge({ sourceNodeId: imageClones[g].nodeIdMap[<imageCreationPrototypeId>], sourcePort: "image", targetNodeId: imageClones[g].nodeIdMap[<imageSlidePrototypeId>], targetPort: "image" })per pair before continuing.
Do NOT proceed to Step B until Step A completes. You should have exactly total_slides slide-generator clones across the partitions (No branch: clones.length === total_slides; Yes branch: textClones.length + imageClones.length === total_slides). In the Yes branch, each imageClones[g].nodeIdMap contains BOTH the Slide Generator clone ID and the Image Creation clone ID for that pair.
Step B — Rename ALL cloned nodes, set their instructions, AND set their emblema layouts in one call:
CRITICAL: Cloned nodes keep the original label "Topic #x - Slide #1", the original instruction, and the original template_name: "emblema-bullets". You MUST update EVERY cloned node to set label, input.instruction, AND input.template_name (to the layout picked in the mapping table).
-
Build the operations array using the MAPPING TABLE from step 9b plus the partitions from step 9c, and call
batchUpsertNodesexactly once.No-images branch (
useImagesChoice === "no"or default):For each row in the mapping table at
clone_index = 0...total_slides - 1, construct one slide-generator op:{ nodeId: clones[clone_index].nodeIdMap["<simplePrototypeId>"], label: "Topic #X - Slide #Y", input: { instruction: "Inserisci il contenuto del Topic #X '<topicX_name>' - Slide #Y '<slides[X][Y]>'", template_name: "<layout from mapping table>" } }Total ops in No branch:
total_slides.Yes-images branch (
useImagesChoice === "yes"):Build the operations array from BOTH partitions (use the row's
topic,slide,layoutfields verbatim from the mapping table):For each text row at partition-index
t(wheret = 0...text_rows.length - 1) — contributes 1 op:- Slide Generator op:
{ nodeId: textClones[t].nodeIdMap["<simplePrototypeId>"], label: "Topic #X - Slide #Y", input: { instruction: "Inserisci il contenuto del Topic #X '<topicX_name>' - Slide #Y '<slides[X][Y]>'", template_name: "<layout from row — guaranteed NOT in IMAGE_LAYOUTS>" } }
For each image row at partition-index
g(whereg = 0...image_rows.length - 1) — contributes 2 ops (paired):- Slide Generator op:
{ nodeId: imageClones[g].nodeIdMap["<imageSlidePrototypeId>"], label: "Topic #X - Slide #Y", input: { instruction: "Inserisci il contenuto del Topic #X '<topicX_name>' - Slide #Y '<slides[X][Y]>'", template_name: "<layout from row — guaranteed IN IMAGE_LAYOUTS>" } } - Image Creation op:
{ nodeId: imageClones[g].nodeIdMap["<imageCreationPrototypeId>"], label: "Image - Topic #X - Slide #Y", input: { prompt: <prompt-for-imageStyleChoice> } }
Where
<prompt-for-imageStyleChoice>is one of (selected fromimageStyleChoiceset at step 3d):imageStyleChoice === "diagram":"Genera un'immagine diagrammatica per il topic '<topicX_name>', slide '<slides[X][Y]>'. Stile: diagramma editoriale con icone, testo etichette, schemi visivi vettoriali. Composizione chiara e leggibile, coerente con il brand Emblema (colori teal/bianco, tipografia pulita)."imageStyleChoice === "photo":"Genera un'immagine fotografica per il topic '<topicX_name>', slide '<slides[X][Y]>'. Stile: fotografia editoriale realistica di persone, ambienti, atmosfere coerenti con il contenuto. NON usare icone, testo, grafica vettoriale, schemi o diagrammi. Mood evocativo, palette teal/bianco quando possibile, coerente con il brand Emblema."
Total ops in Yes branch:
text_rows.length + 2 × image_rows.length. Example: withtext_rows.length = 4andimage_rows.length = 6(sototal_slides = 10), the operations array contains exactly4 + 2 × 6 = 16entries.- The instruction MUST include the exact topic and slide names. Example:
"Inserisci il contenuto del Topic #1 'Lancio del prodotto Q3' - Slide #2 'KPI di adozione'"— NOT"Inserisci il contenuto del Topic #1 - Slide #2"alone. - The
template_nameMUST be one of the 32 emblema content layouts (everything in "Emblema Layout Selection" exceptemblema-coverandemblema-closing, which are reserved for#Coverand#Closing). Do NOT usemarina-*layouts. Do NOT use other layout names. - In the Yes branch, every Image Creation clone MUST have a non-empty
promptset in this same call — referencing both the topic name and the slide name. Do NOT leave any Image Creation clone with the placeholder default prompt. Image-creation ops only exist forimage_rows; text rows never get an image-creation op.
- Slide Generator op:
ANTI-PATTERN: Do NOT call batchUpsertNodes a second time to "fix" or "add" entries. If you discover a mistake, use the single upsertNode tool to patch that specific node.
Do NOT proceed to Step C until Step B completes with all nodes renamed and their emblema layouts set.
Step C — Wire content input on ALL cloned nodes in one call:
CRITICAL: Cloned nodes do NOT inherit external edges. Each cloned Slide Generator starts with NO content input. You MUST create an edge from "Contenuto Presentazione" to EVERY cloned Slide Generator. Without this edge, slides will be EMPTY.
-
Build the edges array using the MAPPING TABLE from step 9b plus the partitions from step 9c, and call
batchUpsertEdgesexactly once. This step targets the Slide Generator clones only across BOTH partitions (not the Image Creation clones — those already have their image edge preserved internally and do NOT need a content edge — theirpromptwas set inline in step 11).No-images branch: for each row at
clone_index = 0...total_slides - 1:{ sourceNodeId: "<Contenuto Presentazione ID>", sourcePort: "summary", targetNodeId: clones[clone_index].nodeIdMap["<simplePrototypeId>"], targetPort: "content" }Yes-images branch: build the edges array from BOTH partitions:
- For each text row at partition-index
t(wheret = 0...text_rows.length - 1):{ sourceNodeId: "<Contenuto Presentazione ID>", sourcePort: "summary", targetNodeId: textClones[t].nodeIdMap["<simplePrototypeId>"], targetPort: "content" } - For each image row at partition-index
g(whereg = 0...image_rows.length - 1):{ sourceNodeId: "<Contenuto Presentazione ID>", sourcePort: "summary", targetNodeId: imageClones[g].nodeIdMap["<imageSlidePrototypeId>"], targetPort: "content" }
Image-creation clones do NOT receive a content edge.
Total edges:
total_slidesin both branches (one content edge per slide-generator clone). - For each text row at partition-index
ANTI-PATTERN: Do NOT call batchUpsertEdges a second time. If a single edge is missing, use upsertEdge to add it.
Do NOT proceed to Step D until Step C completes with all content inputs wired.
Step D — Wire ALL cloned nodes to Make Array and connect #Closing in one call:
CRITICAL: item_0 is RESERVED for #Cover (already pre-connected). The first topic slide MUST go to item_1, NOT item_0. If you connect a topic slide to item_0, you will overwrite the Cover slide.
-
nodeFinder→ find "#Closing". Store itsnodeIdfor the last connection. -
Build the edges array using the MAPPING TABLE from step 9b plus the partitions from step 9c, and call
batchUpsertEdgesexactly once. This step also targets the Slide Generator clones only across BOTH partitions — Image Creation clones do NOT connect to Make Array.Each row in the mapping table keeps its
clone_indexand pin (pin = item_<clone_index + 1>). Look up the row's slide-generator clone in the right partition:- If row ∈
text_rowsat partition-indext: source =textClones[t].nodeIdMap["<simplePrototypeId>"] - If row ∈
image_rowsat partition-indexg: source =imageClones[g].nodeIdMap["<imageSlidePrototypeId>"] - In the No branch, source =
clones[clone_index].nodeIdMap["<simplePrototypeId>"]for every row.
The edge is then:
{ sourceNodeId: <source>, sourcePort: "slide_html", targetNodeId: "<Make Array ID>", targetPort: "item_<clone_index + 1>" }.LAST entry: #Closing on
item_<total_slides + 1>:{ sourceNodeId: "<#Closing ID>", sourcePort: "slide_html", targetNodeId: "<Make Array ID>", targetPort: "item_<total_slides + 1>" } - If row ∈
ANTI-PATTERN: Do NOT call batchUpsertEdges a second time. If a single edge is missing, use upsertEdge to add it.
Do NOT proceed to Phase 4 until Step D completes with #Closing connected to Make Array.
Pin order example (3 topics, multi-slide: M_1=2, M_2=3, M_3=2):
item_0= #Cover (pre-connected — do NOT touch)item_1= Topic #1 - Slide #1 (clone_index=0),item_2= Topic #1 - Slide #2 (clone_index=1)item_3= Topic #2 - Slide #1 (clone_index=2),item_4= Topic #2 - Slide #2 (clone_index=3),item_5= Topic #2 - Slide #3 (clone_index=4)item_6= Topic #3 - Slide #1 (clone_index=5),item_7= Topic #3 - Slide #2 (clone_index=6)item_8= #Closing
Do NOT delete either prototype node. Both must remain on the canvas for future reference.
Step E — Move all cloned slide nodes into a clean "Topic Slides" section and lay out (REQUIRED, do NOT skip):
Skipping this step leaves the cloned nodes overlapping at the prototype's coordinates — the canvas appears broken. This is the canonical post-batch layout workflow, mandatory after any batchCloneNodes / batchUpsertNodes that creates ≥3 nodes. In the Yes branch the cluster is twice as large for image-paired rows (one Slide Generator clone PLUS one Image Creation clone per image_rows entry, plus one Slide Generator clone per text_rows entry) — pass ALL clone IDs (both partitions and both clone families) to every sub-step so the whole cluster is laid out and grouped together.
CRITICAL — Step 15a is NOT optional. Even though 15a appears as a letter-suffix sub-step, it is mandatory. Calling 15b before 15a is the single most common cause of Phase 3 tool-call inflation in real testing.
-
Run the four-step layout workflow on the cloned cluster. The cloned nodes likely arrive already inside an inherited parent section (the JSON template ships its prototype pair inside a section literally named
"Section") becausebatchCloneNodesplaces clones in the prototype's parent. The first sub-step lifts them out so the rest can group cleanly.Define the full clone-ID set = every cloned slide-generator clone (across both partitions in the Yes branch —
textClones[t].nodeIdMap["<simplePrototypeId>"]for everyt, plusimageClones[g].nodeIdMap["<imageSlidePrototypeId>"]for everyg) PLUS every cloned image-creation ID (imageClones[g].nodeIdMap["<imageCreationPrototypeId>"]for everyg— Yes branch only). In the No branch, this is simplyclones[i].nodeIdMap["<simplePrototypeId>"]for everyi.a. MANDATORY — Ungroup inherited parents. Skipping this sub-step causes step 15b (
organizeNodes) to fail with"requires all selected nodes to share the same parent"and step 15c (sectionManager(group)) to fail with"Cannot group — already in a section". Both errors are recoverable but consume tool calls and slow the response. Without 15a, you typically end up retryingorganizeNodes10+ times before falling through toautoLayout. ALWAYS run 15a, even when you think it's not needed. For each cloned node ID:sectionManager({ action: "ungroup", nodeIds: [<full clone-ID set>] })once. This lifts every cloned node back to the canvas root before we re-group them under the desired label. If the call returns a "not in any section" error for nodes that were already standalone (No-images branch with the simple prototype), that error is benign — proceed. b.organizeNodes({ nodeIds: [<full clone-ID set>] })— arranges the cluster as a clean 16:9 grid. After 15a all clones share the same (root) parent so this call no longer fails with"requires all selected nodes to share the same parent. Got nodes from multiple parents". c.sectionManager({ action: "group", nodeIds: [<full clone-ID set>], label: "Topic Slides" })— wraps the freshly organized grid in a single labeled section. Use color if appropriate (optional). Do NOT use the literal label"Section"— that's the JSON's reserved internal name that the prototype pair already inhabits. d.autoLayout()— spaces the new "Topic Slides" section against the rest of the canvas along the edge flow. Defaults (direction: "LR") match the rest of this template.
ANTI-PATTERN: Do NOT skip 15a (ungroup). Without it, 15b fails with "requires all selected nodes to share the same parent. Got nodes from multiple parents", and 15c fails with "Cannot group — already in a section: 'Topic #1 - Slide #1' → 'Section', 'Topic #1 - Slide #2' → 'Section', ...". Both errors are recoverable but log noise; the ungroup step prevents them.
ANTI-PATTERN: Do NOT call autoLayout before organizeNodes + sectionManager. The order is fixed: ungroup → organize → group → autoLayout. Calling autoLayout alone on unsectioned clones at the same coordinate will not separate them.
Phase 3 complete. All slide nodes must be cloned, renamed, assigned an emblema layout, wired for content, connected to Make Array starting at item_1, #Closing connected as the last pin, and the cloned cluster organized + grouped + auto-laid-out. Now proceed to Phase 4.
Phase 4: Verification & Final Execution
Phase 4 ordering (CRITICAL): step 17 → step 18 → step 19 → step 20. Do NOT call verification (step 20) before nodeExecutor (step 19). Do NOT call followUp anywhere in this template — Phase 4 ends with verification, which is the canonical terminal action for this flow.
CRITICAL: Do NOT skip this phase. You MUST verify connections before executing. Missing edges cause empty slides in the final presentation. Phase 4 ends with a final audit via the verification tool.
nodeFinder→ find "Make Array" and ALL Slide Generator nodes (matching "Topic #" in label, plus "#Cover" and "#Closing"). Computetotal_slides= sum of M_x across all topics. Check that Make Array has exactlytotal_slides + 2inputs (1 #Cover + total_slides + 1 #Closing). Verify:- #Cover on item_0: "#Cover" output
slide_html→ Make Arrayitem_0. If a topic slide is onitem_0instead, it has overwritten #Cover — fix by reconnecting #Cover toitem_0and shifting topic slides to start fromitem_1. - Content connection: For EACH Slide Generator node labeled
"Topic #X - Slide #Y", verify it hascontentinput connected. If a Slide Generator showsinputs: []or no input from "Contenuto Presentazione", it will produce an EMPTY slide. Fix withupsertEdge({ sourceNodeId: "<Contenuto Presentazione>", sourcePort: "summary", targetNodeId: <Slide Gen ID>, targetPort: "content" }). - Emblema layout: For EACH Slide Generator node labeled
"Topic #X - Slide #Y", verifytemplate_namestarts withemblema-and is NOTemblema-coveroremblema-closing(those are reserved). If any showstemplate_name: "emblema-bullets"uniformly across all slides, step B may have skipped the per-slide layout picking — re-check and patch withupsertNodeif needed for visual variety. - Make Array connection: Each
"Topic #X - Slide #Y"→ "Make Array"item_<pin>. Topic slides start atitem_1. #Closing is always on the last pin. - #Closing connected: "#Closing" output
slide_html→ Make Array last pin. If #Closing is NOT connected, the presentation will have no closing slide. Fix immediately. - Theme consistency: Presentation Composer
theme == "emblema"AND PPTX Convertertheme == "emblema". If either drifted to another theme (e.g. default"white"or"marina"), fix withupsertNode. - Image mode (only when
useImagesChoice === "yes"): For EACH Slide Generator clone labeled"Topic #X - Slide #Y", verify it has BOTH acontentinput (from "Contenuto Presentazione") AND animageinput (from its paired Image Creation clone labeled"Image - Topic #X - Slide #Y"). Ifimageinput is missing, patch withupsertEdge({ sourceNodeId: <paired Image Creation clone ID>, sourcePort: "image", targetNodeId: <Slide Gen clone ID>, targetPort: "image" }). Then verify EACH Image Creation clone has a non-emptypromptset in itsinput; if any prompt is empty or still equals the prototype default, patch withupsertNode({ nodeId: <Image Creation clone ID>, input: { prompt: <prompt-for-imageStyleChoice> } })referencing that clone's topic and slide names, where<prompt-for-imageStyleChoice>is the same branched template defined at step 11 (selected fromimageStyleChoiceset at step 3d):diagram→"Genera un'immagine diagrammatica per il topic '<topicX_name>', slide '<slides[X][Y]>'. Stile: diagramma editoriale con icone, testo etichette, schemi visivi vettoriali. Composizione chiara e leggibile, coerente con il brand Emblema (colori teal/bianco, tipografia pulita).";photo→"Genera un'immagine fotografica per il topic '<topicX_name>', slide '<slides[X][Y]>'. Stile: fotografia editoriale realistica di persone, ambienti, atmosfere coerenti con il contenuto. NON usare icone, testo, grafica vettoriale, schemi o diagrammi. Mood evocativo, palette teal/bianco quando possibile, coerente con il brand Emblema.". If ANY Slide Generator node showsoutputs: [], it is missing its Make Array edge. Fix ALL missing connections withupsertEdgebefore proceeding.
- #Cover on item_0: "#Cover" output
Step — Pick the canonical execution target (prefer Public Share when present):
The canonical "execute me" target for this presentation flow is the Public Share node (template apps/workspace-service/app/templates/public_share.py, ID a3f1d8c2-6e4b-5a9d-b7c3-2f8e1a4d6b9c, category output). It returns a /s/{slug} URL the user can share, and triggers the upstream chain (Presentation Composer + PPTX Converter etc.) via dirty-cache. PPTX Converter is the optional download branch — execute it directly only when the user explicitly opts to skip publishing.
nodeFinder({ query: "Public Share" }). Two outcomes:- Public Share node found → store its
nodeIdas<finalTargetId>. The chosen target is "Public Share". Proceed to step 18. - No Public Share node found → ask the user how to proceed using
requestUserDecision: Ifcancelledor no answer, default toadd_share. Branch:add_share: create the node withupsertNode({ templateId: "a3f1d8c2-6e4b-5a9d-b7c3-2f8e1a4d6b9c" }), store the returnednodeIdas<publicShareId>, then connect Presentation Composer'spresentationoutput to it:upsertEdge({ sourceNodeId: "<Presentation Composer ID>", sourcePort: "presentation", targetNodeId: "<publicShareId>", targetPort: "content" }). Set<finalTargetId> = <publicShareId>. The chosen target is "Public Share". Proceed to step 18.execute_pptx:nodeFinder({ query: "PPTX Converter" })→ store the returnednodeIdas<finalTargetId>. The chosen target is "PPTX Converter". Skip step 18 (PPTX target doesn't need a confirmation gate). Proceed to step 19.
- Public Share node found → store its
Step — Ask the user for explicit confirmation before publishing (REQUIRED for Public Share):
This is the only nodeExecutor call in this template that requires a confirmation pause. All upstream nodeExecutor calls in Phases 2–3 stay autonomous. The pause exists so the user can review the deck before a public URL is minted.
-
Only when
<finalTargetId>resolves to "Public Share": present a one-sentence summary in the user's language stating what is about to run and what the upstream chain will trigger. If the Public Share node haspin_protectedorexpiry_hoursalready configured, mention those (read viagetNodeOutput({ nodeId: "<finalTargetId>" })if needed). Then callrequestUserDecision:publish: proceed immediately to step 19 (nodeExecutor). Do NOT callfollowUpbetween this answer and step 19.cancelorcancelled: skip step 19 and proceed directly to the verification audit at step 20. Mention in step 20's summary that publishing was deferred.
If the chosen target is "PPTX Converter" (user opted out of Public Share in step 17), this step is already skipped per step 17's branch — proceed directly to step 19.
Step — Execute the confirmed target:
nodeExecutor({ nodeId: "<finalTargetId>" })→ execute the chosen final node. The system auto-runs all upstream nodes (#Cover + all topic slides + #Closing → Make Array → Presentation Composer → PPTX Converter / Public Share). Wait for completion. The result card will show the/s/{slug}URL (Public Share) or the downloadable .pptx file (PPTX Converter).
Step — Final audit:
-
verification→ run a final audit and present the results to the user as a checklist before summarizing. Items array (useinfoseverity ✓ for passing checks,warning/errorfor real issues found):Make Arrayhas exactlytotal_slides + 2inputs#Coveris onitem_0#Closingis on the lastMake Arraypin- Every
"Topic #X - Slide #Y"Slide Generator has acontentedge from "Contenuto Presentazione" - Every
"Topic #X - Slide #Y"Slide Generator has a valid emblema content layout (anyemblema-*value EXCEPTemblema-cover/emblema-closing) - Layout variety check: at least 4 distinct emblema layouts are used across topic slides (when
total_slides >= 4) - Presentation Composer
theme == "emblema"and PPTX Convertertheme == "emblema"(only relevant when PPTX Converter is in the chain) - Only when
useImagesChoice === "yes": every"Topic #X - Slide #Y"Slide Generator has BOTH acontentedge AND animageedge wired; every"Image - Topic #X - Slide #Y"Image Creation clone has a non-empty per-slidepromptset - The confirmed final node (Public Share if present, else PPTX Converter) completed successfully and produced its output (a
/s/{slug}URL for Public Share, a downloadable .pptx for PPTX Converter)
Summary:
"Verifica finale della presentazione Emblema: <N> controlli, <E> errori, <W> warning."Use the user's language. If anyerror-severity items are reported, the user will select which to fix — wait for the user's fix selection and then re-run the relevant Phase 4 steps. If all checks pass (items are allinfoseverity or empty), include in the sameverificationsummary a one-line wrap-up of what was done: N topics, M_x slides per topic, total slides =total_slides + 2(cover + closing), theme = Emblema, the chosen final target ("Public Share" or "PPTX Converter"), and the artifact (the/s/{slug}URL or the downloadable PPTX file). If publishing was deferred (step 18cancelbranch), state that explicitly so the user knows the flow ended in draft.verificationis the terminal action for this template — do NOT callfollowUpafter it.
Common User Requests → Configuration Tips
Error Handling
- MANIFEST_NOT_FOUND: Documents not processed. User needs to upload them to a KnowledgeBase first.
- Connection refused: Check that www-emblema is running at the correct EMBLEMA_API_BASE_URL.
- Empty slides: Cloned Slide Generator nodes do NOT inherit external edges. Verify that "Contenuto Presentazione"
[summary]→ each Slide Generator[content]edge exists. Also check the "Contenuto Presentazione" prompt includes all topics and slides with their full names. - No topics or slides in output: If "Selezione dei Topic Principali" returns no topics, or fewer slides per topic than expected, the document may not have enough content. Try providing more source material or adjusting the prompt to be less restrictive.
- Missing "Contenuto Presentazione" content: If the prompt was not updated with all topics and slides, or if topic/slide names are wrong, the Slide Generators will produce incomplete or mismatched slides. Always verify the prompt matches step 6 output exactly.
- Connected slide_html → undefined: The
targetNodeIdortargetPortwas invalid. Verify Make Array nodeId is a valid UUID fromnodeFinderand theitem_Xpin number is correct. Always validate nodeIds before passing toupsertEdge. - Nodes still named
"Topic #x - Slide #1"after rename step:cloneNodeswith a singlenodeIdsdoes NOT rename the cloned node. You MUST callupsertNodewithlabelto rename each clone (Step B). - All slides are
emblema-bulletsuniformly: Step B's per-slide layout picking was skipped. Go back and patch each cloned node withupsertNodeto set the right layout based on the slide's content shape using the Layout Picking Rule. If even half the deck is onemblema-bullets, you've definitely missed signals — re-read the slide names and apply the rule top-to-bottom. - Theme drifted from emblema: If Presentation Composer or PPTX Converter
themefield shows anything other than"emblema"(e.g. default"white"or"marina"), fix withupsertNode({ nodeId: <found ID>, input: { theme: "emblema" } }). Both nodes must match. - Wrong layout family used (
marina-*instead ofemblema-*): The agent picked a Marina layout name for an Emblema deck. Marina layouts will NOT render with the Emblema theme — they expect Marina-specific CSS classes (.card-grid,.timeline > .event,data-header-title) that the Emblema theme does not provide. Fix withupsertNodeto swap eachmarina-*value for the equivalentemblema-*(e.g.marina-metrics→emblema-kpis,marina-timeline→emblema-timeline,marina-comparison→emblema-matrixoremblema-table,marina-content→emblema-bulletsoremblema-two-col). - PPTX colors look off: The PPTX converter does NOT ship a dedicated
emblemapalette inTHEME_PALETTES— it falls back toDEFAULT_THEMEfor theme-aware color mapping. The Emblema CSS is still inlined in the HTML, so the slide visuals are correct, but PPTX-converter-driven accents (e.g. notes pages) may use neutral defaults. This is a known gap; the fix is to add anemblemaentry toTHEME_PALETTESinapps/workspace-service/app/templates/pptx_converter.py(separate task — do NOT block on this).
Cross-references
apps/workspace-service/app/templates/themes/emblema/— staging folder for the emblema theme adaptation work (source HTML + extracted CSS / base64 artifacts).apps/workspace-service/app/templates/slide_generator.py— slide generator with the 34emblema-*entries inTEMPLATE_INSTRUCTIONS(lines 313-onward) and the layout enum inINPUT_SCHEMA(lines 59-67).apps/workspace-service/app/templates/presentation_composer.py— composer source with_EMBLEMA_CSS,CUSTOM_THEMES["emblema"], andTHEME_DIMENSIONS["emblema"] = (1920, 1080).apps/workspace-service/app/templates/pptx_converter.py— PPTX converter; note thatTHEME_PALETTESdoes NOT yet includeemblema(falls back to default — see Error Handling above).apps/workspace-service/app/templates/ADDING_PRESENTATION_THEMES.md— procedure for adding or extending themes (use this as a reference if extending the emblema layout set).apps/workspace-service/app/templates/reference/pptx-from-drive.md— sibling instruction file for the Marina Militare theme (same workflow shape, different theme + 6 layouts instead of 34).apps/workspace-service/app/templates/reference/INSTRUCTION_GUIDELINES.md— master playbook for writing workspace template agent instructions._
// Dependencies
1from input import DriveFolderReader2from input import DriveDocumentReader3from process import TextSummarizer4from process import SlideGenerator5from process import ImageCreation6from process import MakeArray7from output import PresentationComposer8from output import PublicShare9from output import PPTXConverter