…search, richer templates (#48)
* feat(ai-studio): auto-load flagship template and add first-visit disclaimer
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.
* chore(execution-worker): default to gemini-2.5-flash-lite for the demo
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.
* feat(backend): gate workflow execution with rate limit and Turnstile
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.
* feat(ai-studio): attach Turnstile token to workflow execution
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.
* feat(ai-studio): add AI Debate, Content Repurposer and Meeting Notes 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.
* refactor(ai-studio): rename markdown-preview node to visualize
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.
* feat(ai-studio): detect output format for visualize
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.
* feat(ai-studio): add mode param to visualize node
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.
* feat(ai-studio): json/table/text/stat-card renderers for visualize
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).
* feat(ai-studio): always-on visualize card with empty state
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.
* feat(ai-studio): chart renderer (recharts) for visualize
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.
* feat(ai-studio): mermaid diagram renderer for visualize
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).
* feat(ai-studio): export visualize output (png/svg/copy)
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.
* feat(ai-studio): expand visualize to fullscreen modal
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.
* docs(ai-studio): finalize visualize backlog and refresh getRenderer comment
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).
* feat(ai-studio): render visualize output inside the node body
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.
* fix(ai-studio): stop mermaid error graphics on non-diagram input
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)
* feat(ai-studio): render embedded mermaid/json blocks in visualize
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.
* feat(ai-studio): AI adapt - LLM-convert output to the chosen format
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.
* feat(ai-studio): add aiAdapt toggle to visualize node config
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.
* feat(backend): stronger visualize adapt prompts for better conversion
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.
* feat(ai-studio): render markdown in table cells and stat-card values
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.
* fix(ai-studio): execution-panel dark-mode contrast, modal heading, visualize 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.
* feat(ai-studio): linked CDN logo with theme-aware variants
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.
* fix(ai-studio): keep app-bar Save button clear of the logo overlay
display:none on the SDK logo collapsed its flex box, sliding the Save
button left under the fixed logo overlay. Hide the logo with visibility
and reserve its width instead so the nav-segment keeps its place.
* feat(ai-studio): add Visualize node to AI Debate template
End the debate flow in a Visualize node and shape the verdict as markdown
(verdict line + For/Against table + reasoning) so it renders richly.
* feat(ai-studio): add Visualize node to Meeting Notes template
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.
* feat(ai-studio): converge Content Repurposer into a Visualize node
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.
* refactor(ai-studio): drop dead code and unused exports
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.
* chore(ai-studio): drop internal planning docs from the PR
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.
* fix(ai-studio): declare ai-agent output so node mentions resolve
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.
* feat(ai-studio): web search tool for AI Agent nodes (Tavily)
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).
* feat(ai-studio): add Market Research template using web search
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.
* feat(ai-studio): always AI-adapt visualize output, drop the toggle
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.
* refactor(ai-studio): remove the Error Policy feature entirely
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.
* feat(ai-studio): default the demo to English
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.
* refactor(ai-studio): trim narration comments to a self-explanatory minimum
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.
* feat(ai-studio): tidy node + form UI via SDK CSS overrides
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.
* feat(ai-studio): add undo/redo
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.
* fix(ai-studio): make execution node highlight solid across all statuses
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.
* feat(ai-studio): center execution log, drop the node-detail popup
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).
* fix(ai-studio): flag opens the log; node selection highlights its entry
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.
* fix(ai-studio): center the icon inside node execution markers
* refactor(backend): drop the in-house execute rate limit
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.
* fix(ai-studio): drop the English pin, let the locale reflect reality
With the SDK selector fix the displayed language matches what actually
renders, so starting in the browser's language is correct behavior.
* refactor(ai-studio): derive log highlight from canvas selection
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.
* fix(ai-studio): cancel stale visualize adaptations, drop the chart chip
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.
* fix(ai-studio): one undo per keypress, keep native text-field undo
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.
* refactor(ai-studio): bundle the brand logo locally instead of the CDN
* refactor(ai-studio): trim template noise and close latent races
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.
* docs(ai-studio): branch-gap override is app styling, not a pending SDK 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.
* refactor(ai-studio): drop overrides superseded by SDK fixes
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.
* docs(ai-studio): drop a narration comment from the app bootstrap
* refactor(ai-studio): drop the branch-gap override
The spacing complaint was about the properties panel, fixed in the SDK; the
canvas override was a misdiagnosis artifact.
* refactor(ai-studio): pass the brand logo through the SDK props
The logo/logoHref props landed in the SDK, so the fixed-position overlay,
the hash-prefix CSS hide and the theme-swap component all go away.
---------
Co-authored-by: Jan Librowski <jan.librowski@synergycodes.com>
No description provided.