feat(ai-studio): public AI workflow demo - Visualize node, agent web search, richer templates#48
Draft
librowski wants to merge 54 commits into
Draft
feat(ai-studio): public AI workflow demo - Visualize node, agent web search, richer templates#48librowski wants to merge 54 commits into
librowski wants to merge 54 commits into
Conversation
…laimer Replace the internal Synergy sales-inquiry template with a generic, relatable Customer Support Triage flagship (classify -> route by type -> specialist draft -> QA), built on the existing trigger/agent/decision nodes. Auto-load it on first visit via initialNodes/initialEdges on WorkflowBuilder.Root so visitors land on a runnable workflow instead of a blank canvas; a returning visitor's saved diagram still wins. Add a dismissible first-visit disclaimer modal stating the workflows run real OpenRouter calls, that the demo is not a model benchmark, and that its purpose is to showcase Workflow Builder.
Cheaper, fast default for the public demo (roughly an order of magnitude cheaper than claude-3.5-haiku). Quality-per-cost is what matters here - the model is the engine, not the product. Override with the AI_MODEL env var.
POST /api/workflows/:id/execute is the only endpoint that spends real LLM budget, so it gets a per-IP fixed-window rate limit plus an optional Cloudflare Turnstile check, applied right after authorization via guardExecution(). Both controls are no-ops when unconfigured (TURNSTILE_SECRET_KEY empty), so local dev runs unprotected; the rate limit is active out of the box as a budget backstop. Turnstile verification fails closed on a verifier error.
Before the execute request, fetch a Turnstile token from an invisible widget and send it as the cf-turnstile-token header. Degrades gracefully: with no VITE_TURNSTILE_SITE_KEY the token is undefined, no header is sent, and the backend skips verification - so local dev needs no keys.
…templates Grow the picker into a small gallery of relatable, runnable examples beyond the flagship: AI Debate (parallel personas that fan out then converge on a verdict), Content Repurposer (one post fanned out to three channel variants), and Meeting Notes to Action Items (a linear summarize -> extract -> format chain). All use the existing trigger/agent/decision nodes.
Generalize the on-canvas preview node from markdown-only to a future generic Visualizer: type ai-studio/markdown-preview -> ai-studio/visualize, palette label Visualize (Eye icon), worker domain VisualizeNode + executeVisualize + registry, flagship node preview-1 -> visualize-1. Rendering is still markdown for now; renderer set, format detection, charts, diagrams, export and expand land in the following commits. Includes the visualize plan and long-work backlog.
Heuristic detectFormat(text) -> {renderer, data?, chartable?} mapping an output
string to a renderer for the Visualize node's auto mode: mermaid->diagram,
JSON->{chart for {label,value}/{type,data}, table for object arrays, stat-cards
for flat scalar objects, json for nested}, CSV->table, else markdown. Conservative
(chart/diagram only on clear signals), prose falls back to markdown. 11 unit tests.
Adds a 'Render as' select to the Visualize node: VISUALIZE_MODES = auto + markdown/text/json/table/stat-cards/chart/diagram, default auto. Auto detects the format; the rest force a specific renderer.
Add a renderer registry (getRenderer) with markdown, text, JSON tree (collapsible, hand-rolled), table (JSON array / CSV rows), and stat-cards (flat object) renderers. The visualize card now runs detectFormat (or the node's mode), picks the renderer, shows an 'Auto > X' badge, and renders into a framed body. chart and diagram fall back to table/text for now (real renderers land next).
The visualize card now renders for the node at all times with a fixed minimum
size: an empty-state placeholder ('The visualization appears here after you run
the workflow') before a run, a generating indicator while running, and the
rendered output on completion. The reveal animation moved from the card frame to
the content so it plays when the result arrives, not on every render.
Add a lazy-loaded recharts renderer (bar/line/area/pie) for the chart mode and
chart-spec envelopes ({type, data}) or {label,value}/{x,y} arrays. recharts only
loads when a chart is shown (React.lazy + Suspense). The card shows a 'Try as
chart' chip when auto-detected data is chartable but rendered as a table.
Add a lazy-loaded mermaid renderer for the diagram mode: it renders a mermaid source string to SVG in the browser (securityLevel strict), and falls back to the raw text on a syntax error. mermaid (~150KB) only loads when a diagram is shown (React.lazy).
Add export-visualization util (html-to-image): download PNG, copy image to clipboard (with a download fallback on browsers like Firefox that cannot write image blobs), copy source text, and download SVG (native serialization fast-path for chart/diagram). The card header now offers Copy image / Download PNG / Copy source actions over the rendered content.
Add an Expand action that opens the visualization full-size in a modal (rendered through a portal to document.body so its fixed overlay escapes the React Flow viewport transform). The modal renders the same renderer larger and offers PNG / SVG / copy-image / copy-source export.
…omment All visualize work items complete and verified (test/typecheck/lint/build). chart/diagram visual smoke deferred (needs a structured upstream); covered by unit tests + build (lazy chunks confirmed).
Move the visualization from a detached floating panel into the node itself: the content renders in-flow inside the node body (via OptionalNodeContent), so the node grows vertically to contain it and reads as one cohesive card. Drops the redundant card title (the node header already shows it) in favor of a slim badge + export/expand toolbar; the body scrolls when the output is long. Empty state and the expand modal are unchanged.
Two causes of the giant 'Syntax error in text / mermaid version' graphics:
- The diagram renderer called mermaid.render on invalid input, and mermaid
injects its error graphic into the DOM on failure. Now it validates with
mermaid.parse({ suppressErrors: true }) first and falls back to raw text
(with a note) without ever calling render, so nothing is injected.
- Auto-detection matched a bare leading word (graph/pie/timeline/journey), so
prose starting with those was mis-detected as a diagram. Detection is now
strict: a fenced ```mermaid block, or flowchart/graph WITH a direction, or a
distinctive declaration keyword. Prose stays markdown. (+3 tests, 13 total)
Adapt mixed output to the right format without an LLM: the markdown renderer now renders a fenced ```mermaid block as a real diagram and a ```json block via the detected renderer (chart/table/...), so a markdown response with an embedded diagram renders it inline instead of as raw code. The diagram and chart renderers also extract a fenced block from a larger response when their mode is forced.
Adds an opt-in 'AI adapt' action on the Visualize node: it sends the upstream output to a new backend endpoint (POST /api/visualize/adapt) that uses an LLM to convert it into the active format (clean Mermaid / chart JSON / table / ...), then renders the result. The endpoint reuses the execution abuse gate (per-IP rate limit + optional Turnstile) and is disabled (501) when the backend has no OPENROUTER_API_KEY. Verified end-to-end: action-items prose -> clean mermaid.
Adds an 'AI: adapt output to this format' Switch to the Visualize node's properties panel. When on, the node automatically LLM-converts the upstream output into the active render format on each run (no need to click the on-card button, which is hidden while the toggle is on). Off keeps the manual on-card adapt button for structured formats.
The format-adapt prompts were terse and gave the model little guidance, so conversions were mediocre on a small model. Each format now has detailed, role-framed instructions (pick the fitting diagram type and quote labels safely; find a category + numeric measure and aggregate for charts; extract 2-8 KPIs for stat-cards; use only facts, no invention) plus temperature 0.2 and output trim. JSON renderers also strip a fenced block in case the model wraps the output.
LLM-produced field values often contain markdown (bold, lists, links, inline code). A new RichText helper renders such strings as markdown in table cells and stat-card values, while leaving plain strings (e.g. 'user_id') untouched so they are not mangled. Raw HTML stays escaped (no rehype-raw) to avoid XSS from untrusted model output.
…sualize label - Set explicit text color (theme token) on the Execution Log and node-detail panels so their text stays readable in dark mode instead of inheriting a dark color. - Align the welcome modal's icon and title on one horizontal row. - Shorten the Visualize node's palette description so its subtitle fits.
Hide the SDK app-bar logo (no prop to replace or link it yet) and render the Workflow Builder CDN logo over the slot, linked to workflowbuilder.io. Swap the transparent dark-text / white-text variants by the SDK theme so the logo reads on both light and dark app bars (the -solid asset bakes in a white background). Remove once the SDK exposes a logo/logoHref prop.
End the flow in a Visualize node and shape the recap as markdown with an action-items table (owner/task/due) so it renders as a structured artifact.
Add a Content Pack agent that merges the three channel drafts into one sectioned markdown document, then a Visualize node to render it. Keeps the fan-out and gives the flow a single final artifact.
Remove the unused isTurnstileEnabled helper and de-export symbols that are only used within their own module (renderers, VISUALIZE_MODES/VisualizeMode, DetectResult, VisualizeNode). knip-clean across ai-studio, backend, worker.
Remove the long-work backlog and crystallize plan; agent-process scratch, not product docs for the public repo. Net diff vs main no longer includes them.
The ai-agent palette item had no outputSchema, so {{ nodes.<id>.response }}
references (e.g. in decision conditions) fell through to the SDK's unresolved
'missing mention' label. Declaring the response output renders a clean
'Classify Ticket · Response' pill instead.
Add an opt-in 'Web search' toggle to the AI Agent node. When enabled and TAVILY_API_KEY is set, the executor gives the agent a Tavily-backed web_search tool via the AI SDK tool-calling loop (capped at a few steps). The loop runs inside the activity, so it needs no graph cycles and stays DAG-compatible. Without the key the node runs unchanged (tool simply not exposed).
A Trigger -> Research Agent (web search enabled) -> Visualize flow that showcases the AI Agent web-search tool: the agent searches and writes a sourced markdown brief, rendered in the Visualize node. Needs TAVILY_API_KEY to actually search; runs without it, just answers from the model.
Remove the aiAdapt switch from the Visualize node. Structured formats (chart/diagram/table/json/stat-cards) are now always LLM-adapted to fit the selected format; markdown/text stay passthrough. The manual adapt button is gone too since adaptation is automatic.
Drop the per-node Error Policy control (schema/uischema/defaults across all nodes + errorPolicy from every template) and the now-dead ErrorHandle decorator/component. Nodes rely on the engine default (fail on error). Simplifies the demo UI - the feature was underdeveloped and not worth keeping.
The SDK i18n auto-detects the browser locale, so the public demo came up in Polish on PL machines. An inline script in index.html pins 'en' before the module (and the SDK i18n it loads) boots, unless the visitor made a clean en/pl choice via the selector. Placed in HTML because a bundler import can't be guaranteed to run before the SDK's i18n init. Pairs with the SDK selector fix (regional locales) tracked separately.
…nimum Remove comments that restate code or justify changes across ai-studio, the execution worker and the backend; keep only terse notes for genuine traps and non-obvious constraints (single-instance rate limit, fail-closed verification, mermaid pre-validation, script ordering, browser quirks). Rationale lives in commit messages, not inline. No code changes.
Widen decision-branch spacing, hide the decision node's unused outer output port (branches carry their own), and give prompt inputs a monospace font and a capped height. Consumer-side overrides on SDK tokens/classes, no SDK change.
Port the undo-redo plugin from demo (app-bar buttons + Ctrl+Z / Ctrl+Shift+Z), register it on WorkflowBuilder.Root, and add its react-i18next dependency.
Running/completed/failed rings mixed opacities (40/90/50%), so some looked half-transparent and others solid. Drop the transparency so every status ring is a consistent solid color.
Move the execution log to the bottom center. Clicking a node's flag marker now reveals and flashes that node's entry in the log instead of opening a separate detail panel (that panel is removed).
The flag marker now explicitly opens the log (store-owned collapsed state) so it works even when the log is already showing that node. Selecting a node on the canvas highlights its log entry without opening the log. Drops the select-driven auto-expand effect that could fight a manual collapse.
Rate limiting now comes solely from the shared execute rate-limit middleware (per-minute/per-day, proxy-aware, tested), which also covers the Visualize adapt endpoint via a shared instance so both LLM entry points draw from one budget. guardExecution keeps only the Turnstile check.
With the SDK selector fix the displayed language matches what actually renders, so starting in the browser's language is correct behavior.
Clicking a node (its flag marker included) already selects it on the canvas, so the execution store's selectedNodeId, its mirror effect and the extra store write were redundant. The log derives the highlight from the SDK's single-selection hook - it now also clears on deselect - and the flag click only expands the log.
Move the AI-adapt call into an effect with cleanup so an in-flight response can't overwrite a newer output (and no LLM call is wasted on it). Remove the 'Try as chart' chip and the chartable detection it hung on - always-on adapt covers it and the numbers-array path rendered as plain text anyway. VALID_MODES now derives from the schema's VISUALIZE_MODES instead of a hand copy.
The SDK useKeyPress toggles on OS key-repeat, so holding Ctrl+Z undid many steps, and skipTarget hijacked Ctrl+Z inside text fields. Replace with a plain keydown listener guarded by event.repeat and text-target checks; adds Ctrl+Shift+Z as redo.
Drop redundant errors:[] from template data (the canonical demo templates omit it), serialize Turnstile token requests (the single widget can't handle overlapping calls), use useId for mermaid render ids (the module counter could collide across concurrent diagrams), and shorten the textarea font stack to ui-monospace/monospace.
…K fix The SDK gap applies correctly but scales to about 2px at the demo zoom-to-fit, reading as no spacing; widening it is this app's presentation choice, so the SDK default stays untouched and the change was dropped from the SDK polish PR.
The decision node's dead outer port and the branch-card panel spacing are fixed in the SDK, and prompt inputs cap their growth via the new maxRows uischema option instead of a CSS max-height.
The spacing complaint was about the properties panel, fixed in the SDK; the canvas override was a misdiagnosis artifact.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI Studio public demo: Visualize node, agent web search, richer templates, demo hardening
What & why
Turns
apps/ai-studio(the SDK reference app) into a polished, runnable public demo of Workflow Builder. Visitors land on a real, executable workflow, can run it against live LLM calls, and see results rendered in-canvas.Scope:
apps/**only - zeropackages/sdkchanges, so no changeset needed. The published SDK is untouched; everything here is reference-app / backend / worker code.Highlights
Demo bootstrap
google/gemini-2.5-flash-lite).Visualize node (generic output renderer)
modeparam.POST /api/visualize/adapt(markdown/text pass through; endpoint optional, 501 when no key).```mermaid/```jsonblocks render inline.Templates
Agent web search (tool calling)
TAVILY_API_KEY→ tool simply not exposed, node runs unchanged.Fixes & polish
outputSchemaso{{ nodes.<id>.response }}mentions resolve to a clean pill instead of an unresolved label.UX fix batch (post-review round)
maxRowsuischema option.Hygiene
main(through fix(sdk): decision-node ports and panel spacing, TextArea maxRows #60); still zeropackages/sdkchanges here - the SDK-side fixes this demo surfaced (dead decision output port, glued branch cards,maxRows, regional-locale language selector) landed separately in fix(sdk): language selector reflects resolved language for regional locales #58 and fix(sdk): decision-node ports and panel spacing, TextArea maxRows #60.Configuration (all optional, features degrade gracefully)
OPENROUTER_API_KEY- worker (run workflows) + backend (Visualize AI adapt).TAVILY_API_KEY- worker (agent web search). Free tier ~1000/mo.Verification
Known issues / follow-ups (not in this PR)
apps/docshas 4 pre-existingastro checktype errors, unrelated to this branch (tracked: WB-326).🤖 Generated with Claude Code