From 3e710845da34164a5e0e542127fcf1000137ace4 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Thu, 2 Jul 2026 18:35:59 -0700 Subject: [PATCH 01/20] improvement(academy): set 2, pages for the new video wave (workflow embeds, 2K players, full sequencing) (#5382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * academy: Chat section (intro, building) + Agents tool-calling and skills pages, sequenced in meta.json — pages for the four approved videos, blob src pattern, chapters offset by each recorded intro * academy pages: Related documentation links point at real docs routes (mothership/agents/logs-debugging/deployment), not other academy videos * academy: Tables — Workflow Columns page (combined-cut chapters, receipts pedagogy), sequenced after tables/intro * academy set 2: five new pages (agents/block, agents/memory, knowledge-bases/connectors, tables/operations, files/object) + retimed chapters on the redone tables/files intros (recomposed/working-day cuts) + full meta.json sequencing — Chat · Agents(5) · Tables(3) · Files(2) · KB(2) * academy pages: every page shows the VIDEO's workflow (new academy-video-workflows.ts registry — invoice intake, Qualify, memory, table ops, tools/skills agents, support-desk, content-agent) + plain pedagogical headings throughout (no poetry: 'The warm and cold split' → 'The same agent, with and without memory', 'From one pass to a loop' → 'What tools change', etc.); files/intro's embed swapped to the video's machine * academy: all video players point at the 2K blob set (academy/.mp4) — 20 pages rewritten from academy-preview/*-light-with-intro, files/intro plays the new files-intro cut, workflows/logs gains its src (video now exists); use-case placeholders stay src-less (no videos yet) * biome: format academy-video-workflows.ts * fix (cursor/greptile): support-desk condition block uses branches + rows:[] (the renderer's real fields — conditions: was never read, so Urgent? rendered without if/else handles and branch edges dangled); bgColor aligned to the product condition example * biome: format meta.json (CI lint:check) * fix (cursor): Start exposes , not / — support-desk Triage and content-agent Writer message rows now match the product's Start output (same pattern as the file's other workflows) * academy lesson material (CTO ask): FAQ on every lesson page + the index (grounded in the audited codebase facts: memory modes, the ten table ops, coerce/refuse, deploy surfaces + draft-vs-deployed, file parser formats; the FAQ component emits FAQPage JSON-LD for SEO); em-dashes 202 → 0 with hand-fixed splices; added copy where sparse (agent block placement, when-to-use-memory); SEO phrasing sprinkled naturally into FAQ answers ('visual platform for building AI workflows and agents', 'no-code', model names), not over-indexed * fix: quote frontmatter descriptions that gained colons in the em-dash pass (unquoted YAML scalars with a second colon broke every page) --- .../academy-video-workflows.ts | 393 ++++++++++++++++++ .../content/docs/en/academy/agents/block.mdx | 85 ++++ .../content/docs/en/academy/agents/intro.mdx | 34 +- .../content/docs/en/academy/agents/memory.mdx | 84 ++++ .../content/docs/en/academy/agents/skills.mdx | 84 ++++ .../docs/en/academy/agents/tool-calling.mdx | 84 ++++ .../content/docs/en/academy/chat/building.mdx | 89 ++++ .../content/docs/en/academy/chat/intro.mdx | 88 ++++ .../content/docs/en/academy/files/intro.mdx | 44 +- .../content/docs/en/academy/files/object.mdx | 81 ++++ apps/docs/content/docs/en/academy/index.mdx | 21 +- .../en/academy/knowledge-bases/connectors.mdx | 74 ++++ .../docs/en/academy/knowledge-bases/intro.mdx | 28 +- apps/docs/content/docs/en/academy/meta.json | 11 + .../content/docs/en/academy/tables/intro.mdx | 44 +- .../docs/en/academy/tables/operations.mdx | 82 ++++ .../en/academy/tables/workflow-columns.mdx | 84 ++++ .../academy/use-cases/document-extraction.mdx | 2 +- .../academy/use-cases/monitoring-research.mdx | 6 +- .../use-cases/sales-data-enrichment.mdx | 2 +- .../en/academy/use-cases/slack-it-triage.mdx | 4 +- .../docs/en/academy/workflows/branching.mdx | 23 +- .../docs/en/academy/workflows/deployment.mdx | 29 +- .../docs/en/academy/workflows/intro.mdx | 28 +- .../docs/en/academy/workflows/logs.mdx | 30 +- .../docs/en/academy/workflows/loops.mdx | 25 +- .../en/academy/workflows/subworkflows.mdx | 23 +- 27 files changed, 1456 insertions(+), 126 deletions(-) create mode 100644 apps/docs/components/workflow-preview/academy-video-workflows.ts create mode 100644 apps/docs/content/docs/en/academy/agents/block.mdx create mode 100644 apps/docs/content/docs/en/academy/agents/memory.mdx create mode 100644 apps/docs/content/docs/en/academy/agents/skills.mdx create mode 100644 apps/docs/content/docs/en/academy/agents/tool-calling.mdx create mode 100644 apps/docs/content/docs/en/academy/chat/building.mdx create mode 100644 apps/docs/content/docs/en/academy/chat/intro.mdx create mode 100644 apps/docs/content/docs/en/academy/files/object.mdx create mode 100644 apps/docs/content/docs/en/academy/knowledge-bases/connectors.mdx create mode 100644 apps/docs/content/docs/en/academy/tables/operations.mdx create mode 100644 apps/docs/content/docs/en/academy/tables/workflow-columns.mdx diff --git a/apps/docs/components/workflow-preview/academy-video-workflows.ts b/apps/docs/components/workflow-preview/academy-video-workflows.ts new file mode 100644 index 00000000000..290d47f8b65 --- /dev/null +++ b/apps/docs/components/workflow-preview/academy-video-workflows.ts @@ -0,0 +1,393 @@ +import type { PreviewWorkflow } from './workflow-data' + +/** + * Workflows shown in the academy videos, reproduced block-for-block so each + * page's written supplement shows the same machine the video builds. Rows and + * operation labels match the videos (which match the block registry). + */ + +/** files/intro + files/object — the invoice intake machine. */ +export const AV_INVOICE_INTAKE_WORKFLOW: PreviewWorkflow = { + id: 'av-invoice-intake', + name: 'Invoice intake', + blocks: [ + { + id: 'gmailtrigger', + name: 'Gmail Email Trigger', + type: 'gmail', + bgColor: '#E0E0E0', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [{ title: 'Include Attachments', value: 'Enabled' }], + }, + { + id: 'file', + name: 'File', + type: 'file', + bgColor: '#40916C', + position: { x: 340, y: 0 }, + rows: [ + { title: 'Operation', value: 'Read' }, + { title: 'Files', value: '' }, + ], + }, + { + id: 'agent', + name: 'Agent', + type: 'agent', + bgColor: '#33C482', + position: { x: 680, y: 0 }, + rows: [ + { title: 'Messages', value: 'Extract the invoice fields' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + { title: 'Files', value: '' }, + ], + }, + { + id: 'supabase', + name: 'Supabase', + type: 'supabase', + bgColor: '#1C1C1C', + position: { x: 1020, y: 0 }, + rows: [ + { title: 'Operation', value: 'Create a Row' }, + { title: 'Table', value: 'invoices' }, + { title: 'Data', value: '' }, + ], + }, + ], + edges: [ + { id: 'trigger-file', source: 'gmailtrigger', target: 'file' }, + { id: 'file-agent', source: 'file', target: 'agent' }, + { id: 'agent-supabase', source: 'agent', target: 'supabase' }, + ], +} + +/** agents/block — the Qualify agent the camera rides through. */ +export const AV_QUALIFY_WORKFLOW: PreviewWorkflow = { + id: 'av-qualify', + name: 'Qualify a lead', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Lead' }], + }, + { + id: 'qualify', + name: 'Qualify', + type: 'agent', + bgColor: '#33C482', + position: { x: 340, y: 0 }, + rows: [ + { title: 'Messages', value: 'Qualify this lead: ' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + ], + }, + { + id: 'response', + name: 'Response', + type: 'response', + bgColor: '#2F55FF', + position: { x: 680, y: 0 }, + rows: [{ title: 'Data', value: '' }], + }, + ], + edges: [ + { id: 'start-qualify', source: 'start', target: 'qualify' }, + { id: 'qualify-response', source: 'qualify', target: 'response' }, + ], +} + +/** agents/memory — the Support agent with Memory set to Conversation. */ +export const AV_MEMORY_WORKFLOW: PreviewWorkflow = { + id: 'av-memory', + name: 'Support agent with memory', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Customer message' }], + }, + { + id: 'support', + name: 'Support', + type: 'agent', + bgColor: '#33C482', + position: { x: 340, y: 0 }, + rows: [ + { title: 'Messages', value: '' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + { title: 'Memory', value: 'Conversation · user-123' }, + ], + }, + ], + edges: [{ id: 'start-support', source: 'start', target: 'support' }], +} + +/** tables/operations — the Table block with a filtered query. */ +export const AV_TABLE_OPS_WORKFLOW: PreviewWorkflow = { + id: 'av-table-ops', + name: 'Query the tickets table', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Run' }], + }, + { + id: 'table', + name: 'Table 1', + type: 'table', + bgColor: '#10B981', + position: { x: 340, y: 0 }, + rows: [ + { title: 'Operation', value: 'Query Rows' }, + { title: 'Table', value: 'tickets' }, + { title: 'Filter Conditions', value: 'priority equals high' }, + ], + }, + ], + edges: [{ id: 'start-table', source: 'start', target: 'table' }], +} + +/** agents/tool-calling — the Qualify agent with research tools attached. */ +export const AV_TOOLS_WORKFLOW: PreviewWorkflow = { + id: 'av-tools', + name: 'Qualify with tools', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Lead' }], + }, + { + id: 'qualify', + name: 'Qualify', + type: 'agent', + bgColor: '#33C482', + position: { x: 340, y: 0 }, + rows: [ + { title: 'Messages', value: 'Qualify this lead: ' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + ], + tools: [ + { type: 'exa', name: 'Search', bgColor: '#1F40ED' }, + { type: 'github', name: 'GitHub', bgColor: '#181717' }, + { type: 'hubspot', name: 'CRM', bgColor: '#FF7A59' }, + ], + }, + { + id: 'response', + name: 'Response', + type: 'response', + bgColor: '#2F55FF', + position: { x: 680, y: 0 }, + rows: [{ title: 'Data', value: '' }], + }, + ], + edges: [ + { id: 'start-qualify', source: 'start', target: 'qualify' }, + { id: 'qualify-response', source: 'qualify', target: 'response' }, + ], +} + +/** agents/skills — the same agent with a skill attached. */ +export const AV_SKILLS_WORKFLOW: PreviewWorkflow = { + id: 'av-skills', + name: 'Qualify with a skill', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 0 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Lead' }], + }, + { + id: 'qualify', + name: 'Qualify', + type: 'agent', + bgColor: '#33C482', + position: { x: 340, y: 0 }, + rows: [ + { title: 'Messages', value: 'Qualify this lead: ' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + { title: 'Skills', value: 'answer-with-citations' }, + ], + tools: [{ type: 'exa', name: 'Search', bgColor: '#1F40ED' }], + }, + ], + edges: [{ id: 'start-qualify', source: 'start', target: 'qualify' }], +} + +/** chat/intro — the support-desk workflow the chat operates. */ +export const AV_SUPPORT_DESK_WORKFLOW: PreviewWorkflow = { + id: 'av-support-desk', + name: 'support-desk', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 60 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Ticket' }], + }, + { + id: 'triage', + name: 'Triage', + type: 'agent', + bgColor: '#33C482', + position: { x: 320, y: 60 }, + rows: [ + { title: 'Messages', value: '' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + ], + tools: [{ type: 'knowledge', name: 'Help Center', bgColor: '#00B0B0' }], + }, + { + id: 'condition', + name: 'Urgent?', + type: 'condition', + bgColor: '#FF752F', + position: { x: 660, y: 60 }, + rows: [], + branches: [ + { id: 'condition-if', label: 'If', value: '' }, + { id: 'condition-else', label: 'else' }, + ], + }, + { + id: 'escalate', + name: 'Escalate', + type: 'slack', + bgColor: '#611F69', + position: { x: 1000, y: -40 }, + rows: [{ title: 'Channel', value: '#support-urgent' }], + }, + { + id: 'reply', + name: 'Reply', + type: 'agent', + bgColor: '#33C482', + position: { x: 1000, y: 160 }, + rows: [{ title: 'Messages', value: 'Draft the reply' }], + }, + { + id: 'log', + name: 'Log', + type: 'table', + bgColor: '#10B981', + position: { x: 1340, y: 60 }, + rows: [ + { title: 'Operation', value: 'Insert Row' }, + { title: 'Table', value: 'tickets' }, + ], + }, + ], + edges: [ + { id: 'start-triage', source: 'start', target: 'triage' }, + { id: 'triage-condition', source: 'triage', target: 'condition' }, + { + id: 'condition-escalate', + source: 'condition', + target: 'escalate', + sourceHandle: 'condition-if', + }, + { id: 'condition-reply', source: 'condition', target: 'reply', sourceHandle: 'condition-else' }, + { id: 'escalate-log', source: 'escalate', target: 'log' }, + { id: 'reply-log', source: 'reply', target: 'log' }, + ], +} + +/** chat/building — the content-agent the chat builds: candidates drafted in + * parallel, media generated, an evaluator scoring, results kept. */ +export const AV_CONTENT_AGENT_WORKFLOW: PreviewWorkflow = { + id: 'av-content-agent', + name: 'content-agent', + blocks: [ + { + id: 'start', + name: 'Start', + type: 'start_trigger', + bgColor: '#2FB3FF', + position: { x: 0, y: 95 }, + hideTargetHandle: true, + rows: [{ title: 'Input', value: 'Idea' }], + }, + { + id: 'candidates', + name: 'Candidates', + type: 'parallel', + bgColor: '#1D1C1A', + position: { x: 320, y: 30 }, + size: { width: 430, height: 170 }, + rows: [], + }, + { + id: 'writer', + name: 'Writer', + type: 'agent', + bgColor: '#33C482', + position: { x: 150, y: 62 }, + parentId: 'candidates', + rows: [ + { title: 'Messages', value: '' }, + { title: 'Model', value: 'claude-sonnet-4-6' }, + ], + }, + { + id: 'evaluator', + name: 'Evaluator', + type: 'evaluator', + bgColor: '#8B5CF6', + position: { x: 840, y: 95 }, + rows: [ + { title: 'Metrics', value: 'Voice · Hook' }, + { title: 'Content', value: '' }, + ], + }, + { + id: 'scores', + name: 'Scores', + type: 'table', + bgColor: '#10B981', + position: { x: 1180, y: 95 }, + rows: [ + { title: 'Operation', value: 'Insert Row' }, + { title: 'Table', value: 'scores' }, + ], + }, + ], + edges: [ + { id: 'start-candidates', source: 'start', target: 'candidates' }, + { + id: 'candidates-writer', + source: 'candidates', + target: 'writer', + sourceHandle: 'parallel-start-source', + }, + { id: 'candidates-evaluator', source: 'candidates', target: 'evaluator' }, + { id: 'evaluator-scores', source: 'evaluator', target: 'scores' }, + ], +} diff --git a/apps/docs/content/docs/en/academy/agents/block.mdx b/apps/docs/content/docs/en/academy/agents/block.mdx new file mode 100644 index 00000000000..221038fe253 --- /dev/null +++ b/apps/docs/content/docs/en/academy/agents/block.mdx @@ -0,0 +1,85 @@ +--- +title: The Agent Block +description: "Inside the Agent block, a run becomes a conversation: a message stack the model reads, a swappable brain, and an output bundle every later block can use." +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_QUALIFY_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +On the canvas, an Agent block looks like any other step: a run arrives at its input handle, and a result leaves on the other side. Inside, it's a conversation machine. This video follows the data in, station by station. + + + +
+ + + +
+ +Here is the workflow the video rides through: the Qualify agent between its input and its result: + + + +## What's inside the block + +The first station is the message stack: your **system message** (the standing instructions), any **history** you preload, worked examples or an earlier conversation, and the newest **user message**, which usually arrives from an earlier block through a connection tag. The second station is the **model**: the brain that reads the whole stack. Sim supports the frontier providers, and because the block stays the same while the model changes, swapping brains never means rebuilding the step. + +## What a run costs and returns + +When the run reaches the model, every message on the stack ships as input tokens; the reply streams back as output tokens. If tools are attached, the model can call them mid-run. Everything the run did, the content it wrote, the model that ran, the token bill, the tool calls, lands in the block's outputs, where any later block can read it. + +## Where it fits + +The Agent block is the reasoning step of an AI workflow. Everything else in Sim's workflow builder is deterministic: triggers fire, blocks transform data, tables store it. The Agent block is where a language model reads the conversation you configured and decides what to write. Most AI agents you build in Sim are ordinary workflows with one or more Agent blocks doing the thinking. + +." }, +]} /> + +## Related documentation + +- [Agent block](/blocks/agent) +- [Custom tools](/agents/custom-tools) diff --git a/apps/docs/content/docs/en/academy/agents/intro.mdx b/apps/docs/content/docs/en/academy/agents/intro.mdx index a9fb034c3c2..e911f7a9593 100644 --- a/apps/docs/content/docs/en/academy/agents/intro.mdx +++ b/apps/docs/content/docs/en/academy/agents/intro.mdx @@ -1,25 +1,26 @@ --- title: Agents -description: An AI agent is a Sim workflow that uses Agent blocks — the reasoning engine you shape, parameterize, and compose into intelligent processes. +description: "An AI agent is a Sim workflow that uses Agent blocks: the reasoning engine you shape, parameterize, and compose into intelligent processes." --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, CLASSIFY_WORKFLOW } from '@/components/workflow-preview' - +
-In Sim, you build AI agents as **workflows that use the Agent block**. There's no separate "agent" object to learn — an AI agent *is* a workflow, and the Agent block is where it thinks. +In Sim, you build AI agents as **workflows that use the Agent block**. There's no separate "agent" object to learn: an AI agent *is* a workflow, and the Agent block is where it thinks. ## Inside the Agent block -The Agent block is **a chat with a model, spawned programmatically**. Picture a conversation in ChatGPT or Claude: the block's **Messages** parameter is exactly those messages, except you set them — you decide what instructions, context, and earlier outputs go in. The model reasons over that and returns a result the rest of the workflow can read. +The Agent block is **a chat with a model, spawned programmatically**. Picture a conversation in ChatGPT or Claude: the block's **Messages** parameter is exactly those messages, except you set them, you decide what instructions, context, and earlier outputs go in. The model reasons over that and returns a result the rest of the workflow can read. -The Agent block is where it *thinks* — the reasoning engine of your agent. You design the blocks *around* it to shape how the agent processes data: what it reads before it reasons, what it does with the result after. +The Agent block is where it *thinks*, the reasoning engine of your agent. You design the blocks *around* it to shape how the agent processes data: what it reads before it reasons, what it does with the result after. ## Tools and skills Two parameters extend the Agent block, and both fit the chat-box picture: -- **Tools** are the functions and blocks the agent can decide to call while answering — search the web, send an email, run another workflow. They're the tool calls the model makes mid-response. -- **Skills** are prompt modules the model loads on demand — like a manual it looks up only when a task calls for it, instead of stuffing everything into every prompt. +- **Tools** are the functions and blocks the agent can decide to call while answering, search the web, send an email, run another workflow. They're the tool calls the model makes mid-response. +- **Skills** are prompt modules the model loads on demand, like a manual it looks up only when a task calls for it, instead of stuffing everything into every prompt. ## Make a workflow agentic -The clearest way to see an agent isn't to build one from nothing — it's to take a **deterministic workflow and let an agent into it**. Replace a fixed rule with an agent that *decides* the path (routing), or *augment* a step with real reasoning — or both. Same workflow, now with judgment in it. +The clearest way to see an agent isn't to build one from nothing, it's to take a **deterministic workflow and let an agent into it**. Replace a fixed rule with an agent that *decides* the path (routing), or *augment* a step with real reasoning, or both. Same workflow, now with judgment in it. ## Composing intelligence -Your ability to compose workflows that make **multiple agent calls** is central to designing intelligent processes. And every agentic workflow you build becomes a building block — your workspace's library of agents composes into larger AI systems. +Your ability to compose workflows that make **multiple agent calls** is central to designing intelligent processes. And every agentic workflow you build becomes a building block: your workspace's library of agents composes into larger AI systems. -Start with one Agent block; the platform scales as your ambition does. The most sophisticated systems are just more of these, composed — more agent blocks swapping in, more reasoning, more reach, all from the same model you learned here. +Start with one Agent block; the platform scales as your ambition does. The most sophisticated systems are just more of these, composed, more agent blocks swapping in, more reasoning, more reach, all from the same model you learned here. + + ## Related documentation diff --git a/apps/docs/content/docs/en/academy/agents/memory.mdx b/apps/docs/content/docs/en/academy/agents/memory.mdx new file mode 100644 index 00000000000..8fa17c56d83 --- /dev/null +++ b/apps/docs/content/docs/en/academy/agents/memory.mdx @@ -0,0 +1,84 @@ +--- +title: Memory +description: "One setting gives an agent memory: a conversation ID under which every turn is kept, so the next run starts from the conversation so far." +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_MEMORY_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +By default, an agent keeps nothing between runs: every conversation starts completely fresh. The Memory setting changes that: choose Conversation, give it a conversation ID, and everything said under that key is kept and loaded back before the model runs. + + + +
+ + + +
+ +Here is the agent from the video with Memory set on the block: + + + +## The same agent, with and without memory + +The video runs the same agent twice, side by side: once with no memory and once with the conversation ID. The same follow-up question arrives in both. Without the key, the agent starts from zero and has to ask for everything again; with it, everything stored under the key was loaded back before the model saw the new message, and the answer picks up exactly where yesterday stopped. + +## When conversations grow + +Memory can also be a sliding window, keeping the most recent messages, or the most recent tokens, while the oldest quietly fall away. The stored transcript keeps every turn; the window controls how much of it rides into the model on each run. + +## When to reach for memory + +Any agent that talks to the same person or process more than once needs memory: support desks, sales assistants, scheduled check-ins. Stateless agents are the right default for one-shot tasks like classification or extraction, where history would only add cost. + + + +## Related documentation + +- [Agent block](/blocks/agent) diff --git a/apps/docs/content/docs/en/academy/agents/skills.mdx b/apps/docs/content/docs/en/academy/agents/skills.mdx new file mode 100644 index 00000000000..1a6ccdd9168 --- /dev/null +++ b/apps/docs/content/docs/en/academy/agents/skills.mdx @@ -0,0 +1,84 @@ +--- +title: Skills +description: A skill is a written method the agent reads when it decides it's relevant, turning a vague guess into a sourced, repeatable procedure without crowding the prompt. +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_SKILLS_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +An agent with tools but no guidance does the minimum: one quick search, then a hedged guess. A **skill** fixes that: a written method the agent can read, like a manual you look up when the situation calls for it. Attach one, and the same run follows the procedure: every claim sourced, every gap flagged. + + + +
+ + + +
+ +Here is the agent from the video with the skill attached: + + + +## The same run, with and without the skill + +The video runs the same lead-qualification task twice. Without a skill, the agent searches once and guesses: "probably Series B, maybe a hundred people." With Exa's `answer-with-citations` skill attached, it loads the method mid-run, turns each claim into a specific factual question, pulls a sourced answer for every one, and returns a verdict where each figure carries its citation. + +## Why not just a longer prompt? + +Skills keep procedure out of the prompt. Only the name and one-line description stay in view; the full method loads only when the agent judges it relevant: so you can attach many skills without bloating context, and the agent picks the right manual for the moment. + +## Ready to use + +Adding a skill is the same motion as adding a tool: pick it in the block's Skills section and it shows on the block. Integrations ship curated skills out of the box, and skills you write are shared across your workspace. + + + +## Related documentation + +- [Skills](/agents/skills) +- [Custom tools](/agents/custom-tools) diff --git a/apps/docs/content/docs/en/academy/agents/tool-calling.mdx b/apps/docs/content/docs/en/academy/agents/tool-calling.mdx new file mode 100644 index 00000000000..d93f5ee9856 --- /dev/null +++ b/apps/docs/content/docs/en/academy/agents/tool-calling.mdx @@ -0,0 +1,84 @@ +--- +title: Tool Calling +description: Give an agent tools and it works in a loop, deciding, calling, reading results, and converging on an answer you can audit call-by-call in the logs. +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_TOOLS_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +A plain Agent block is text in, one model pass, text out, it can't look anything up. Give it tools, and it starts working in a **loop**: deciding which tool it needs, calling it (several at once when it can), reading what comes back, and calling again with better arguments until it has enough to answer. + + + +
+ + + +
+ +Here is the agent from the video with its tools attached: + + + +## What tools change + +Without tools, an agent has only its prompt and its training, asked to qualify a lead, it can only say it has no way to look anything up. With tools attached, the same block plans: research the company, check the funding, read the careers page. It calls tools in parallel when the calls don't depend on each other, revises its plan as results land, and answers only when its context holds enough. + +## Tools are blocks + +Anything you'd use as a workflow step can be handed to an agent as a tool. In the Tools section you pin the parameters that should never change and leave the rest empty: the model fills those per task. And when no block does what you need, you write a custom tool: a schema and a description, where the description is how the model knows when to reach for it. Whole external toolsets connect the same way over MCP. + +## The audit trail + +Every agent run records its tool calls in order. Open the log and walk them: the name of each tool, the exact arguments the model chose, the result that returned into context. Debugging an agent is reading that sequence, not guessing at what the model was thinking. + + + +## Related documentation + +- [Custom tools](/agents/custom-tools) +- [MCP](/agents/mcp) +- [Logs & debugging](/logs-debugging) diff --git a/apps/docs/content/docs/en/academy/chat/building.mdx b/apps/docs/content/docs/en/academy/chat/building.mdx new file mode 100644 index 00000000000..ca2fd54dbdd --- /dev/null +++ b/apps/docs/content/docs/en/academy/chat/building.mdx @@ -0,0 +1,89 @@ +--- +title: Building with Chat +description: Describe the system you want in plain language, and Sim builds it, then tests it, debugs it, shows you the evidence, and deploys it, all inside one conversation. +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_CONTENT_AGENT_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +You can build a whole workflow without dragging a single block. Describe the system you want: an agent that turns an idea into a finished post, with a draft, a hero image, a slide deck, and a voiceover, and Sim lays out every block, wired and parameterized, in front of you. + + + +
+ + + +
+ +## From description to blocks + +The chat turns your description into real structure: a parallel container drafting candidate posts, generation blocks for the image, the deck, and the voiceover, an evaluator scoring the results, and a table keeping every score. Nothing about the result is special, it's a normal workflow you could have built by hand, which means you can open it, read it, and change it. + +Here is the shape of the workflow the chat builds in the video, candidates drafted in parallel, an evaluator scoring them, and the scores kept in a table: + + + +## It tests and fixes itself + +Sim doesn't hand you an untested guess. It runs the workflow immediately, and when a step fails (here, the image block missing an aspect ratio), it reads the error, patches the parameter, and runs again. You watch the failure and the fix happen on the canvas, in the log, and in the conversation at once. + +## Inspecting the run + +The run record shows every step with its timing and output: the draft, the image, the deck, the audio. When you test it on a real idea, the candidates fan out in parallel, the evaluator scores each one, and the table records what was promoted and what was pruned. + +## Deploying + +Deploying is one more message in the conversation. The workflow deploys behind a live endpoint, ready for whatever you send it, and it remains a workflow in your workspace: inspectable, schedulable, and callable from everything else you build. + + + +## Related documentation + +- [Building workflows from chat](/mothership/workflows) +- [Deployment](/workflows/deployment) diff --git a/apps/docs/content/docs/en/academy/chat/intro.mdx b/apps/docs/content/docs/en/academy/chat/intro.mdx new file mode 100644 index 00000000000..c39951319f7 --- /dev/null +++ b/apps/docs/content/docs/en/academy/chat/intro.mdx @@ -0,0 +1,88 @@ +--- +title: Chat +description: "The chat in Sim is your workspace control plane: one conversation that queries your tables, searches your docs, runs and schedules workflows, researches the outside world, and composes it all into new systems." +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_SUPPORT_DESK_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +The chat lives inside your workspace, and everything in it, your workflows, tables, knowledge bases, and files, is within its reach. You ask from one place, and Sim works across all of it: answering from your data, operating your workflows, and shaping new systems around the task. + + + +
+ + + +
+ +## Everything within reach + +The chat isn't a help box beside your workspace, it's a control plane over it. Every resource you've built is addressable in plain language: the tickets table, the help-center knowledge base, the support-desk workflow. When you ask a question, Sim decides which resources the answer needs, touches them in parallel, and composes the reply. + +Here is the workflow the chat operates in the video: the support desk that triages a ticket, escalates or replies, and logs the result: + + + +## Asking it to act + +Telling the chat to *do* something is the same motion as asking it something. Run the backlog through the support desk, ship the weekly digest, put it on a schedule for Friday mornings: each lands as a real operation, confirmed with a toast, with the run's log available the moment it finishes. + +## Research beyond the workspace + +The chat can research beyond your workspace: search X and Reddit for this week's conversations, pull the rising themes, and file them as a new document. What it learns becomes a resource like any other, ready for the next ask. + +## Composing a new workflow + +One sentence can compose what the chat just touched into a new workflow. A watcher that reads the trends file on a schedule, matches them against your tickets, and calls your support-desk workflow when something hits. You described it, and the chat assembled it from the resources you already have. + + + +## Related documentation + +- [Chat overview](/mothership) +- [Operating workflows from chat](/mothership/workflows) +- [Research from chat](/mothership/research) diff --git a/apps/docs/content/docs/en/academy/files/intro.mdx b/apps/docs/content/docs/en/academy/files/intro.mdx index ea9900e38ca..83fd2730a60 100644 --- a/apps/docs/content/docs/en/academy/files/intro.mdx +++ b/apps/docs/content/docs/en/academy/files/intro.mdx @@ -1,25 +1,27 @@ --- title: Files -description: Sim natively supports files — store, reference, and pass around documents and media so workflows can read and produce real assets like PDFs, images, audio, and video. +description: Sim natively supports files, store, reference, and pass around documents and media so workflows can read and produce real assets like PDFs, images, audio, and video. --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' -import { WorkflowPreview, FILE_SUMMARY_WORKFLOW } from '@/components/workflow-preview' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_INVOICE_INTAKE_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' - +
-Sim has native support for **files** — a way to store, reference, and pass around documents, media, and generated outputs, so your workflows can read and produce real assets: PDFs, images, audio, video. +Sim has native support for **files**, a way to store, reference, and pass around documents, media, and generated outputs, so your workflows can read and produce real assets: PDFs, images, audio, video. @@ -46,11 +51,11 @@ Sim has native support for **files** — a way to store, reference, and pass aro ## Why you need files -Most of the work you'd want to automate touches files somewhere. You receive a PDF and need to extract from it. An email needs an image attached. Audio comes in and has to be transcribed. A user uploads a document to process. These aren't edge cases — workflows reference file types as inputs and outputs all the time, so files are a first-class thing in Sim rather than something you work around. +Most of the work you'd want to automate touches files somewhere. You receive a PDF and need to extract from it. An email needs an image attached. Audio comes in and has to be transcribed. A user uploads a document to process. These aren't edge cases, workflows reference file types as inputs and outputs all the time, so files are a first-class thing in Sim rather than something you work around. -Here's the shape of it — a workflow that takes a file in and produces a result from it: +Here is the machine the video follows: an email's attachment read as a file, an agent extracting its fields, and the result written to a table: - + ## Files in and out @@ -58,7 +63,7 @@ Workflows **consume** files and **produce** them, and hand them between blocks l ## What you can build -The point isn't a new concept to learn — it's recognizing that the file-based operations you already picture are supported here: +The point isn't a new concept to learn, it's recognizing that the file-based operations you already picture are supported here: - Attach a generated report to an email. - Transcribe an audio clip that came in through a trigger. @@ -66,7 +71,14 @@ The point isn't a new concept to learn — it's recognizing that the file-based - Extract structured fields from a batch of PDFs. - Produce a rendered document, chart, or audio file as a workflow's output. -If you're thinking "could I automate the thing that involves *that* document?" — the answer is almost always yes. Files are how Sim handles the real assets your processes already run on. +If you're thinking "could I automate the thing that involves *that* document?", the answer is almost always yes. Files are how Sim handles the real assets your processes already run on. + + ## Related documentation diff --git a/apps/docs/content/docs/en/academy/files/object.mdx b/apps/docs/content/docs/en/academy/files/object.mdx new file mode 100644 index 00000000000..86ece899e1d --- /dev/null +++ b/apps/docs/content/docs/en/academy/files/object.mdx @@ -0,0 +1,81 @@ +--- +title: The File Object +description: Every file in Sim, whatever made it, travels as the same object, so producers and consumers compose without special handling. +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_INVOICE_INTAKE_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +Your workflows make files from very different places: an email attachment, a generated image, synthesized speech, a parsed document. Inside the workflow, every one of them travels as the same thing: a file object. + + + +
+ + + +
+ +## The common shape + +Catch a file mid-run and look inside: an I.D., a name, a URL where the bytes live, a size, a type, and the content itself. A couple more fields exist under the hood, but the shape never changes: which is exactly what lets a Gmail attachment, a generated image, and a text-to-speech clip all feed the same agent without special handling. + +The video closes on this machine: one file object riding the whole chain: + + + +## Why one shape matters + +Swap which producer feeds the agent, and nothing on the agent side changes. Point the same object at a different consumer, an email, cloud storage, another agent, and it just rides. In the closing machine, an invoice lands in Gmail, gets parsed, an agent extracts the fields, and the result settles in the database: one object carried the whole way through. + +. The agent reads the document content directly." }, + { question: "Do generated files work the same as uploaded ones?", answer: "Yes. An image from a generator, an audio clip from text-to-speech, and an email attachment all travel as the same object, so downstream blocks treat them identically." }, +]} /> + +## Related documentation + +- [Files overview](/files) diff --git a/apps/docs/content/docs/en/academy/index.mdx b/apps/docs/content/docs/en/academy/index.mdx index 930824142f7..d1d8668eafc 100644 --- a/apps/docs/content/docs/en/academy/index.mdx +++ b/apps/docs/content/docs/en/academy/index.mdx @@ -1,12 +1,13 @@ --- title: What is Sim? -description: Sim is a unified workspace for building and operating AI systems — data, workflows, and the integrations that connect them to the outside world. +description: Sim is a unified workspace for building and operating AI systems, data, workflows, and the integrations that connect them to the outside world. --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { FAQ } from '@/components/ui/faq' - + Sim is a unified workspace for **building and operating AI systems**. Everything you make lives in one place, and everything connects. @@ -14,7 +15,7 @@ Sim is a unified workspace for **building and operating AI systems**. Everything items={[ { title: 'What a workspace is', - body: 'A workspace holds two things: resources — your data — and workflows — your processes. Both live together and reference each other.', + body: 'A workspace holds two things: resources, your data, and workflows: your processes. Both live together and reference each other.', }, { title: 'How the pieces relate', @@ -28,13 +29,13 @@ Sim is a unified workspace for **building and operating AI systems**. Everything A **workspace** is a collection of shared resources and the workflows that use them. - **Resources** are your data: [tables](/academy/tables/intro) (structured records), [knowledge bases](/academy/knowledge-bases/intro) (searchable memory), and [files](/academy/files/intro) (documents and media). -- **Workflows** are your processes — the agents and automations that read those resources, act, and write results back. +- **Workflows** are your processes: the agents and automations that read those resources, act, and write results back. -Everything in a workspace can see everything else in it. A workflow reads a table, searches a knowledge base, produces a file — and the next workflow picks up where it left off. +Everything in a workspace can see everything else in it. A workflow reads a table, searches a knowledge base, produces a file, and the next workflow picks up where it left off. ## Integrations connect you to the outside world -**Integrations** are plugins. They let your resources and workflows reach services beyond Sim — send a Slack message, read a Gmail inbox, write a row to a CRM, call any API. You connect an account once, and any workflow can use it. +**Integrations** are plugins. They let your resources and workflows reach services beyond Sim, send a Slack message, read a Gmail inbox, write a row to a CRM, call any API. You connect an account once, and any workflow can use it. So the whole picture is small: a workspace is **data + processes**, integrations plug it into **everything else**, and the rest of this course is just learning each piece and watching them compose. @@ -42,3 +43,11 @@ So the whole picture is small: a workspace is **data + processes**, integrations - [Introduction](/introduction) - [Getting started](/getting-started) + + + diff --git a/apps/docs/content/docs/en/academy/knowledge-bases/connectors.mdx b/apps/docs/content/docs/en/academy/knowledge-bases/connectors.mdx new file mode 100644 index 00000000000..7eb7d137fdf --- /dev/null +++ b/apps/docs/content/docs/en/academy/knowledge-bases/connectors.mdx @@ -0,0 +1,74 @@ +--- +title: Connectors +description: A connector keeps a knowledge base in sync with the tool where your documents actually live, first import, steady re-sync, changes and removals included. +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' + + + +
+
+ +You can upload documents to a knowledge base by hand, but your documents usually live somewhere that keeps changing. A connector links the knowledge base to that source, imports everything in scope, and then keeps it current on a schedule. + + + +
+ + + +
+ +## The lifecycle + +The video follows one connector through its whole life: picking Notion as the source, authorizing the account, scoping which pages sync, and choosing the frequency. The first import brings everything in scope into the knowledge base as chunks. From then on the connector re-syncs on schedule: a page edited at the source is re-imported, and a page deleted at the source leaves the knowledge base. + +## Why it matters + +Knowledge that drifts out of date is worse than no knowledge: an agent will confidently answer from a stale document. A connector makes the knowledge base track the source, so the answers your agents give reflect what your documents say now. + + + +## Related documentation + +- [Knowledge bases](/knowledgebase) diff --git a/apps/docs/content/docs/en/academy/knowledge-bases/intro.mdx b/apps/docs/content/docs/en/academy/knowledge-bases/intro.mdx index 850dc6d8c2a..1f19ccd1e47 100644 --- a/apps/docs/content/docs/en/academy/knowledge-bases/intro.mdx +++ b/apps/docs/content/docs/en/academy/knowledge-bases/intro.mdx @@ -1,19 +1,20 @@ --- title: Knowledge Bases -description: Knowledge bases give your workflows searchable memory — large volumes of text an agent can search by meaning and use to ground its answers. +description: Knowledge bases give your workflows searchable memory, large volumes of text an agent can search by meaning and use to ground its answers. --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, SUPPORT_KB_WORKFLOW } from '@/components/workflow-preview' - +
-A **knowledge base** gives your workflows searchable memory. You put in large volumes of text, and an agent retrieves just the parts that matter — by meaning, not keywords. +A **knowledge base** gives your workflows searchable memory. You put in large volumes of text, and an agent retrieves just the parts that matter, by meaning, not keywords. ## Inside a knowledge base -A knowledge base is a resource that lives in your workspace — you can have several. Open one and it holds **documents**, plus **connectors** that bring in and keep documents in sync from outside sources. +A knowledge base is a resource that lives in your workspace, you can have several. Open one and it holds **documents**, plus **connectors** that bring in and keep documents in sync from outside sources. -Sim automatically **indexes and embeds** every document into searchable **chunks**, so the contents become queryable by meaning — you don't manage any of that yourself. +Sim automatically **indexes and embeds** every document into searchable **chunks**, so the contents become queryable by meaning, you don't manage any of that yourself. ## Searching by meaning -Use a Knowledge block with a query and it returns the most relevant chunks, each with a **similarity score** — how closely it matches the *meaning* of the query, not its words. So "refund timelines" can match a passage that says "we process returns within 14 days," even with no shared words. +Use a Knowledge block with a query and it returns the most relevant chunks, each with a **similarity score**, how closely it matches the *meaning* of the query, not its words. So "refund timelines" can match a passage that says "we process returns within 14 days," even with no shared words. ## Grounding an answer -On its own, that's retrieval. The power comes one step later: feed those chunks into an [Agent](/academy/agents/intro) block's context, and the model answers from your actual documents — accurate, up to date, and able to **cite** where each fact came from. That's grounding plus retrieval. +On its own, that's retrieval. The power comes one step later: feed those chunks into an [Agent](/academy/agents/intro) block's context, and the model answers from your actual documents, accurate, up to date, and able to **cite** where each fact came from. That's grounding plus retrieval. -Knowledge bases power systems that need to give agents accurate, current context — and they're where you store what your systems learn over time. +Knowledge bases power systems that need to give agents accurate, current context, and they're where you store what your systems learn over time. + + ## Related documentation diff --git a/apps/docs/content/docs/en/academy/meta.json b/apps/docs/content/docs/en/academy/meta.json index 7cda92f55ff..e395214b76c 100644 --- a/apps/docs/content/docs/en/academy/meta.json +++ b/apps/docs/content/docs/en/academy/meta.json @@ -11,14 +11,25 @@ "workflows/loops", "workflows/subworkflows", "workflows/deployment", + "---Chat---", + "chat/intro", + "chat/building", "---Agents---", "agents/intro", + "agents/block", + "agents/tool-calling", + "agents/skills", + "agents/memory", "---Tables---", "tables/intro", + "tables/operations", + "tables/workflow-columns", "---Files---", "files/intro", + "files/object", "---Knowledge Bases---", "knowledge-bases/intro", + "knowledge-bases/connectors", "---Use Cases---", "use-cases/slack-it-triage", "use-cases/document-extraction", diff --git a/apps/docs/content/docs/en/academy/tables/intro.mdx b/apps/docs/content/docs/en/academy/tables/intro.mdx index de736769903..c700d582cf6 100644 --- a/apps/docs/content/docs/en/academy/tables/intro.mdx +++ b/apps/docs/content/docs/en/academy/tables/intro.mdx @@ -1,25 +1,26 @@ --- title: Tables -description: Tables store structured data — columns as types, rows as entries — a spreadsheet or lightweight database your workflows read, write, and operate on at scale. +description: Tables store structured data, columns as types, rows as entries, a spreadsheet or lightweight database your workflows read, write, and operate on at scale. --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, TABLE_ENRICH_WORKFLOW } from '@/components/workflow-preview' - +
-A **table** stores structured data: columns are the types, rows are the entries. Think of it as giving your workflows a spreadsheet — or a lightweight database — for tracking and maintaining records. +A **table** stores structured data: columns are the types, rows are the entries. Think of it as giving your workflows a spreadsheet, or a lightweight database, for tracking and maintaining records. @@ -53,7 +56,7 @@ A **table** stores structured data: columns are the types, rows are the entries. If you've used a spreadsheet, you already understand a table: columns with types, rows of entries. Your workflows read rows to work on, write rows they produce, and update rows in place. -Here's the shape of it — a workflow that reads a table, operates on each record, and writes the result back: +Here's the shape of it: a workflow that reads a table, operates on each record, and writes the result back: @@ -61,17 +64,24 @@ Here's the shape of it — a workflow that reads a table, operates on each recor A surprising amount of real business work decomposes into a few operations over structured records: -- **A query** — "which leads are still unprocessed?" -- **An update** — "mark these rows handled." -- **A combination with logic** — "for each new signup, score it, then write the score back." +- **A query**, "which leads are still unprocessed?" +- **An update**, "mark these rows handled." +- **A combination with logic**, "for each new signup, score it, then write the score back." Once you see a use case that way, building it in Sim is mostly wiring those operations together. ## A surface for scale -Tables aren't only storage — they're a working surface for your AI systems. **Workflow columns** let you run an operation across every row at once, in parallel, so a table becomes the place you launch and track work in bulk. +Tables aren't only storage, they're a working surface for your AI systems. **Workflow columns** let you run an operation across every row at once, in parallel, so a table becomes the place you launch and track work in bulk. -That makes a table a powerful interface for automation — the place you manage and operate agentic processes at scale. +That makes a table a powerful interface for automation: the place you manage and operate agentic processes at scale. + + ## Related documentation diff --git a/apps/docs/content/docs/en/academy/tables/operations.mdx b/apps/docs/content/docs/en/academy/tables/operations.mdx new file mode 100644 index 00000000000..0a405368c70 --- /dev/null +++ b/apps/docs/content/docs/en/academy/tables/operations.mdx @@ -0,0 +1,82 @@ +--- +title: Table Operations +description: The Table block works across two surfaces, a panel where you describe the operation, and the live table that answers it, with one shape shared by query, insert, update, and delete. +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' +import { WorkflowPreview } from '@/components/workflow-preview' +import { AV_TABLE_OPS_WORKFLOW } from '@/components/workflow-preview/academy-video-workflows' + + + +
+
+ +Workflows work with tables through the Table block. Everything the block can do starts from its Operation dropdown, and every operation follows the same shape: you describe it in the panel, run the block, and read the result in the table. + + + +
+ + + +
+ +Here is the block from the video, configured for the first query: + + + +## One shape for every operation + +The panel describes what you mean: an operation, a table, and, depending on the operation, a filter, row data, or both. A filter is conditions stacked together (priority equals high, and status equals open), each one narrowing the match. Queries read without touching the table; Delete Rows by Filter removes what matches; Insert Row lands a payload as a new row; Update Rows by Filter picks rows and changes them in place. + +## Reading the result + +The live table answers every run: matched rows highlight, deleted rows leave, inserted rows land, and updated cells flip where they sit. The block's output carries the same result back into the workflow for the next block to use. + + + +## Related documentation + +- [Tables overview](/tables) +- [Using tables in workflows](/tables/using-in-workflows) diff --git a/apps/docs/content/docs/en/academy/tables/workflow-columns.mdx b/apps/docs/content/docs/en/academy/tables/workflow-columns.mdx new file mode 100644 index 00000000000..9ffc44bf709 --- /dev/null +++ b/apps/docs/content/docs/en/academy/tables/workflow-columns.mdx @@ -0,0 +1,84 @@ +--- +title: Workflow Columns +description: Attach a workflow to a table column and the table fills itself, and every value it writes carries the full trace of the run that produced it. +--- + +import { VideoPlaceholder } from '@/components/ui/video-placeholder' +import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' +import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' + + + +
+
+ +A workflow column doesn't hold typed-in values, it holds the **output of a workflow**, run once per row. Attach the workflow, bind its input to a column and its output to the column it fills, and the table starts doing the work itself: one run for every row, cascading through stages, updating as new rows arrive. + + + +
+ + + +
+ +## How a workflow column is defined + +Everything that defines a workflow column lives in one panel: the workflow that runs, the column bound to its input, and the column that receives its output. On every row, the workflow reads that row's values and writes one cell back, right beside its source. Attach a second stage that reads what the first one filled, and rows flow through the pipeline in order. + +## Watching it run + +Run a single row and the cell reports the run as it moves, queued, then running, then the value. Turn on auto-run and the whole column fills in a cascade; type a new company into a blank row and the pipeline runs it end to end, the domain first, then the score, without a single click. + +## Every cell records its run + +Take one value: a lead score of 75. The cell remembers the run that wrote it: open its menu and view the execution, a real trace where every block is a span, with its timing and cost. That's true all the way down the column: every cell is its own run. When a row fails, the cell says so, its trace shows which block failed and why, and re-running the cell re-runs the workflow for just that row. + + + +## Related documentation + +- [Tables overview](/tables) +- [Workflow columns](/tables/workflow-columns) +- [Logs & debugging](/logs-debugging) diff --git a/apps/docs/content/docs/en/academy/use-cases/document-extraction.mdx b/apps/docs/content/docs/en/academy/use-cases/document-extraction.mdx index 445237a5b18..d4aa2203a20 100644 --- a/apps/docs/content/docs/en/academy/use-cases/document-extraction.mdx +++ b/apps/docs/content/docs/en/academy/use-cases/document-extraction.mdx @@ -12,7 +12,7 @@ import { VideoChapters } from '@/components/ui/video-chapters'
-Turn a pile of unstructured documents — invoices, contracts, forms — into structured data. The workflow reads each file, extracts the fields that matter, and writes them as rows you can query and process. +Turn a pile of unstructured documents, invoices, contracts, forms, into structured data. The workflow reads each file, extracts the fields that matter, and writes them as rows you can query and process.
-Build a system that runs on a schedule, checks the sources you care about, researches what's new, and delivers a synthesized report — competitor moves, market signals, anything worth watching — without you asking each time. +Build a system that runs on a schedule, checks the sources you care about, researches what's new, and delivers a synthesized report, competitor moves, market signals, anything worth watching, without you asking each time.
-Run your sales pipeline as data. Start with thin lead records in a table, enrich each one — company info, fit signals — score it, and write the results back, all in parallel across the whole table. +Run your sales pipeline as data. Start with thin lead records in a table, enrich each one, company info, fit signals, score it, and write the results back, all in parallel across the whole table. @@ -40,4 +40,4 @@ Build an agent that lives in a Slack channel: it reads each incoming IT request, A **Slack trigger** starts the workflow on each new message. An **Agent** block classifies the request and decides the path: a **knowledge-base search** drafts an answer for common questions, while anything it can't resolve is routed to a human or filed as a ticket. Every run is recorded in the [logs](/logs-debugging). -This pulls together [workflows](/academy/workflows/intro), [agents](/academy/agents/intro), and [knowledge bases](/academy/knowledge-bases/intro) into one system — the foundations, composed. +This pulls together [workflows](/academy/workflows/intro), [agents](/academy/agents/intro), and [knowledge bases](/academy/knowledge-bases/intro) into one system: the foundations, composed. diff --git a/apps/docs/content/docs/en/academy/workflows/branching.mdx b/apps/docs/content/docs/en/academy/workflows/branching.mdx index e721089334a..7074c76df6f 100644 --- a/apps/docs/content/docs/en/academy/workflows/branching.mdx +++ b/apps/docs/content/docs/en/academy/workflows/branching.mdx @@ -1,19 +1,20 @@ --- title: Branching -description: Split a workflow into different paths based on the result of a step — the Condition block decides with a rule, the Router lets a model decide. +description: "Split a workflow into different paths based on the result of a step: the Condition block decides with a rule, the Router lets a model decide." --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, CONDITION_ROUTE_WORKFLOW } from '@/components/workflow-preview' - +
-Branching is how a workflow **splits into different paths based on the result of a step**. Sim gives you two blocks for it — the **Condition** block, which decides with an explicit rule, and the **Router**, which lets a model decide from intent. +Branching is how a workflow **splits into different paths based on the result of a step**. Sim gives you two blocks for it: the **Condition** block, which decides with an explicit rule, and the **Router**, which lets a model decide from intent. -## The Condition block — decide with a rule +## The Condition block, decide with a rule -The **Condition** block branches on an **explicit rule** — a comparison over an earlier block's output, like priority equals "high". The matching branch runs; the others don't. It's deterministic and predictable. +The **Condition** block branches on an **explicit rule**, a comparison over an earlier block's output, like priority equals "high". The matching branch runs; the others don't. It's deterministic and predictable. -## The Router block — let a model decide +## The Router block, let a model decide -Sometimes the rule is fuzzy: "send billing questions one way, everything else another." The **Router** block hands the decision to a **model** — it reads the input, picks the branch that matches the intent, and the run continues down it. Same fork, but the decider is reasoning instead of a fixed rule. +Sometimes the rule is fuzzy: "send billing questions one way, everything else another." The **Router** block hands the decision to a **model**: it reads the input, picks the branch that matches the intent, and the run continues down it. Same fork, but the decider is reasoning instead of a fixed rule. ## Still one workflow Whichever way it forks, it's **still one workflow**, and every run is recorded. Open the logs and you can see exactly which path a given run took, and why. + + ## Related documentation - [How workflows run](/workflows/how-it-runs) diff --git a/apps/docs/content/docs/en/academy/workflows/deployment.mdx b/apps/docs/content/docs/en/academy/workflows/deployment.mdx index cac52a21ca6..daebc36f68c 100644 --- a/apps/docs/content/docs/en/academy/workflows/deployment.mdx +++ b/apps/docs/content/docs/en/academy/workflows/deployment.mdx @@ -1,19 +1,20 @@ --- title: Deployment -description: Deploying gives a workflow an address so the outside world can run it — the same Start block answers calls as an API, a chat, or an MCP tool. +description: "Deploying gives a workflow an address so the outside world can run it: the same Start block answers calls as an API, a chat, or an MCP tool." --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, RESPONSE_API_WORKFLOW } from '@/components/workflow-preview' - +
-Deploying publishes a workflow and gives it an **address**, so something other than the editor can run it. Nothing inside the workflow changes — the same blocks, the same Start block — but now an external request can reach it and start a run. +Deploying publishes a workflow and gives it an **address**, so something other than the editor can run it. Nothing inside the workflow changes, the same blocks, the same Start block, but now an external request can reach it and start a run. ## Deploy gives it an address -Deploying publishes the workflow and gives it an **address**. Nothing inside the chain changes — same blocks, same Start block, same logic. What's new is that the workflow is now **live** and reachable from outside, at a stable, versioned URL. A deployment is a snapshot, so you can promote new versions and roll back. +Deploying publishes the workflow and gives it an **address**. Nothing inside the chain changes, same blocks, same Start block, same logic. What's new is that the workflow is now **live** and reachable from outside, at a stable, versioned URL. A deployment is a snapshot, so you can promote new versions and roll back. ## A call from outside -An external request comes in, hits that address, and flows straight into the **Start block** — the same entry you tested with in the editor. From there the run is identical: the chain executes and the response goes back to whoever called it. An external call is just a run; the only difference is where it came from. +An external request comes in, hits that address, and flows straight into the **Start block**, the same entry you tested with in the editor. From there the run is identical: the chain executes and the response goes back to whoever called it. An external call is just a run; the only difference is where it came from. ## The same workflow, three surfaces The one deployed workflow can be reached three ways, and you choose which: -- **API** — other systems POST to an endpoint and get the response back. -- **Chat** — a hosted chat page anyone can open; each message becomes the workflow's input. -- **MCP** — the workflow becomes a tool that other AI agents (like Cursor or Claude) can call. +- **API**, other systems POST to an endpoint and get the response back. +- **Chat**, a hosted chat page anyone can open; each message becomes the workflow's input. +- **MCP**, the workflow becomes a tool that other AI agents (like Cursor or Claude) can call. -Same blocks underneath, same Start — just different doors in. +Same blocks underneath, same Start, just different doors in. ## Runs when they run it -Once deployed, the workflow runs on demand — whenever a caller hits it, with no one in the editor. The same process you built is now an operational system. +Once deployed, the workflow runs on demand, whenever a caller hits it, with no one in the editor. The same process you built is now an operational system. + + ## Related documentation diff --git a/apps/docs/content/docs/en/academy/workflows/intro.mdx b/apps/docs/content/docs/en/academy/workflows/intro.mdx index 633306c5d67..30e1bb89df4 100644 --- a/apps/docs/content/docs/en/academy/workflows/intro.mdx +++ b/apps/docs/content/docs/en/academy/workflows/intro.mdx @@ -1,19 +1,20 @@ --- title: Workflows -description: A workflow is a visual program made of blocks — where you compose data, operations, integrations, and AI agents into a process. +description: "A workflow is a visual program made of blocks: where you compose data, operations, integrations, and AI agents into a process." --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, CLASSIFY_REPLY_WORKFLOW } from '@/components/workflow-preview' - +
-A **workflow** is a visual program made of blocks. It's where you compose everything else in Sim — data, operations, integrations, and AI agents — into a process, and bind it to a trigger so it runs. +A **workflow** is a visual program made of blocks. It's where you compose everything else in Sim, data, operations, integrations, and AI agents, into a process, and bind it to a trigger so it runs. `. -- Everything that happened is saved in the **logs** — the trigger, every block, and each block's input and output — so you can see exactly where a value came from. +- Everything that happened is saved in the **logs**, the trigger, every block, and each block's input and output, so you can see exactly where a value came from. -Here's a small workflow you can read top to bottom — blocks wired in order, each one a step. The highlighted block reads an earlier block's output through a connection tag: +Here's a small workflow you can read top to bottom, blocks wired in order, each one a step. The highlighted block reads an earlier block's output through a connection tag: ## How execution flows -A block waits only for the blocks it depends on — nothing else. +A block waits only for the blocks it depends on, nothing else. So if `A` feeds both `B` and `C`, then `B` and `C` start at the same moment `A` finishes; they don't wait for each other. And if `B` and `C` both feed `D`, then `D` waits for both before it runs. The shape of the connections *is* the execution plan. ## Branches, loops, and parallel -From those primitives you get expressive control flow: **branch** down one path or another with a Condition or Router, **loop** over a list, run work in **parallel** across many items. These are the expressive core of what you'll build — and they're all just blocks and connections. +From those primitives you get expressive control flow: **branch** down one path or another with a Condition or Router, **loop** over a list, run work in **parallel** across many items. These are the expressive core of what you'll build, and they're all just blocks and connections. -Everything complicated — an automated software factory, an autonomous triage system that takes action on its own — is built from these same simple primitives. You start with a few blocks; the platform scales to your ambition. As you get more comfortable, the workflows you compose get richer — more branches, more agents, more reach — without ever leaving the same model you learned here. We're glad you're on this journey: Sim is built of simple primitives that compose into anything you can imagine. +Everything complicated: an automated software factory, an autonomous triage system that takes action on its own, is built from these same simple primitives. You start with a few blocks; the platform scales to your ambition. As you get more comfortable, the workflows you compose get richer, more branches, more agents, more reach, without ever leaving the same model you learned here. We're glad you're on this journey: Sim is built of simple primitives that compose into anything you can imagine. + +: a block's parameter references the output of an earlier block, and the value resolves when the run reaches it." }, + { question: "When does a block run?", answer: "When every block it depends on has finished. Independent branches run in parallel automatically, and a block waits for all of its inputs." }, +]} /> ## Related documentation diff --git a/apps/docs/content/docs/en/academy/workflows/logs.mdx b/apps/docs/content/docs/en/academy/workflows/logs.mdx index 9e8f888a991..36a4e01a0c9 100644 --- a/apps/docs/content/docs/en/academy/workflows/logs.mdx +++ b/apps/docs/content/docs/en/academy/workflows/logs.mdx @@ -1,19 +1,20 @@ --- title: Logs -description: A log is the complete record of one workflow run — and debugging is reading that record backwards, from a wrong value to the block that produced it. +description: A log is the complete record of one workflow run, and debugging is reading that record backwards, from a wrong value to the block that produced it. --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, ROUTER_TRIAGE_WORKFLOW } from '@/components/workflow-preview' - +
-Every time a workflow runs, Sim writes down exactly what happened: the trigger, every block in the order it ran, and for each block its input, its output, its timing, and any error. That record is a **log** — the ground truth for understanding and debugging a run. +Every time a workflow runs, Sim writes down exactly what happened: the trigger, every block in the order it ran, and for each block its input, its output, its timing, and any error. That record is a **log**, the ground truth for understanding and debugging a run. @@ -48,27 +49,34 @@ Every time a workflow runs, Sim writes down exactly what happened: the trigger, ## Every run leaves a record -When a workflow runs, the run isn't a black box. Sim records the **trigger** that started it, every **block** in the order it executed, and for each block its **input**, its **output**, its **timing**, and an error message if it failed. Nothing about a run is a mystery — it all gets written down. +When a workflow runs, the run isn't a black box. Sim records the **trigger** that started it, every **block** in the order it executed, and for each block its **input**, its **output**, its **timing**, and an error message if it failed. Nothing about a run is a mystery; it all gets written down. -Here's the kind of workflow whose runs you'll read — a trigger, an agent, and the branches it routes between: +Here's the kind of workflow whose runs you'll read: a trigger, an agent, and the branches it routes between: ## The record -Open a run and the log lists every block in the order it ran, each with its **duration**. So when a run is slow, you can see exactly where the time went — which block took the seconds — instead of guessing. +Open a run and the log lists every block in the order it ran, each with its **duration**. So when a run is slow, you can see exactly where the time went, which block took the seconds, instead of guessing. ## What each block did -Selecting a block shows everything it actually did: the **input** it received, the **output** it returned, and — for an agent — the **tool calls** it made and the **tokens** it used. From the outside a step can look instant; the log keeps everything that happened inside it. +Selecting a block shows everything it actually did: the **input** it received, the **output** it returned, and, for an agent, the **tool calls** it made and the **tokens** it used. From the outside a step can look instant; the log keeps everything that happened inside it. ## Read it backwards -The log also shows where every value came from. A block's input names the block that produced it — a connection tag like ``. So to debug, you start from the **wrong value** and step **backwards**: which block produced it, what that block read, and on back through the chain until you reach the source. Debugging a workflow is reading its log backward from the symptom. +The log also shows where every value came from. A block's input names the block that produced it: a connection tag like ``. So to debug, you start from the **wrong value** and step **backwards**: which block produced it, what that block read, and on back through the chain until you reach the source. Debugging a workflow is reading its log backward from the symptom. ## Every run writes one -You don't enable any of this — every run produces a log automatically. When a workflow does something you didn't expect, the log is the first place to look, because it's the one place that records what actually happened. +You don't enable any of this: every run produces a log automatically. When a workflow does something you didn't expect, the log is the first place to look, because it's the one place that records what actually happened. + + ## Related documentation diff --git a/apps/docs/content/docs/en/academy/workflows/loops.mdx b/apps/docs/content/docs/en/academy/workflows/loops.mdx index c1ea512144c..55c8353261e 100644 --- a/apps/docs/content/docs/en/academy/workflows/loops.mdx +++ b/apps/docs/content/docs/en/academy/workflows/loops.mdx @@ -1,19 +1,20 @@ --- title: Loops & Parallel -description: Repeat the same steps over a collection — the Loop block runs them one item at a time, the Parallel block runs every item at once. +description: "Repeat the same steps over a collection: the Loop block runs them one item at a time, the Parallel block runs every item at once." --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, LOOP_WORKFLOW } from '@/components/workflow-preview' - +
-Repeating the same steps across a whole collection is one of the most fundamental ideas in programming. Sim gives you two blocks for it — the **Loop** block and the **Parallel** block. They do the same job and differ only in *how* they run. +Repeating the same steps across a whole collection is one of the most fundamental ideas in programming. Sim gives you two blocks for it: the **Loop** block and the **Parallel** block. They do the same job and differ only in *how* they run. ## What each run knows -Inside the container, every iteration gets the **current item** to work on (and its index). That's how one lane of blocks becomes a run per lead, per row, or per file — the steps stay the same, the data changes each pass. +Inside the container, every iteration gets the **current item** to work on (and its index). That's how one lane of blocks becomes a run per lead, per row, or per file: the steps stay the same, the data changes each pass. ## One at a time, or all at once This is the whole difference between the two blocks: -- **Loop** runs the items **sequentially** — one finishes before the next begins. Reach for it when each step builds on the last, or when you need to respect order or rate limits. -- **Parallel** runs them **all at once** — every item concurrently. Reach for it when the items are independent; it's dramatically faster across a large collection. +- **Loop** runs the items **sequentially**, one finishes before the next begins. Reach for it when each step builds on the last, or when you need to respect order or rate limits. +- **Parallel** runs them **all at once**, every item concurrently. Reach for it when the items are independent; it's dramatically faster across a large collection. ## Swapping the container -Because the two blocks share the same shape, you can **swap a Loop for a Parallel** (or back) without rewiring anything inside. Same logic, different execution — sequential or concurrent — chosen by which container you wrap it in. +Because the two blocks share the same shape, you can **swap a Loop for a Parallel** (or back) without rewiring anything inside. Same logic, different execution, sequential or concurrent, chosen by which container you wrap it in. + + ## Related documentation diff --git a/apps/docs/content/docs/en/academy/workflows/subworkflows.mdx b/apps/docs/content/docs/en/academy/workflows/subworkflows.mdx index 39a8697e10b..4443c32b082 100644 --- a/apps/docs/content/docs/en/academy/workflows/subworkflows.mdx +++ b/apps/docs/content/docs/en/academy/workflows/subworkflows.mdx @@ -1,19 +1,20 @@ --- title: Subworkflows -description: Call one workflow from another, the way you'd call a function in code — reuse logic and compose small workflows into larger systems. +description: Call one workflow from another, the way you'd call a function in code, reuse logic and compose small workflows into larger systems. --- import { VideoPlaceholder } from '@/components/ui/video-placeholder' import { WhatYouWillLearn } from '@/components/ui/what-you-will-learn' import { VideoChapters } from '@/components/ui/video-chapters' +import { FAQ } from '@/components/ui/faq' import { WorkflowPreview, WORKFLOW_CALL_WORKFLOW } from '@/components/workflow-preview' - +
-Once you've built a workflow that does something well, you can **call it from another workflow** — the way you'd call a function in code. The Workflow block turns any workflow into a reusable step. +Once you've built a workflow that does something well, you can **call it from another workflow**, the way you'd call a function in code. The Workflow block turns any workflow into a reusable step. ## It becomes a block -Any workflow can be used as a **block** inside another. You drop a **Workflow block**, point it at the workflow you want, and pass it an input — just like calling a function. You don't rebuild its logic; you reuse it. +Any workflow can be used as a **block** inside another. You drop a **Workflow block**, point it at the workflow you want, and pass it an input, just like calling a function. You don't rebuild its logic; you reuse it. ## Call, run, return -When the parent run reaches the Workflow block, the **child workflow runs** start to finish — its own blocks, its own logic — and its **result comes back** into the parent, where the next block reads it. The parent doesn't need to know how the child works, only what it returns. +When the parent run reaches the Workflow block, the **child workflow runs** start to finish, its own blocks, its own logic, and its **result comes back** into the parent, where the next block reads it. The parent doesn't need to know how the child works, only what it returns. ## Workflows all the way up -This is how big systems stay manageable: factor shared logic into focused subworkflows and **compose** them. Each workflow you build becomes a building block for the next — small, tested pieces assembling into larger processes, without turning into a mess. +This is how big systems stay manageable: factor shared logic into focused subworkflows and **compose** them. Each workflow you build becomes a building block for the next, small, tested pieces assembling into larger processes, without turning into a mess. + + ## Related documentation From 4086750ddc0f3e7220354f3600c19fa61bf1ed51 Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 2 Jul 2026 21:25:33 -0700 Subject: [PATCH 02/20] feat(comparison): add Sim vs Competitor comparison pages (#5383) * feat(comparison): add Sim vs Competitor comparison pages - New /comparison hub + /comparison/[provider] detail pages for Sim vs 16 competitors (n8n, Zapier, Make, Gumloop, Workato, Retool, Pipedream, OpenAI AgentKit, Tines, StackAI, Power Automate, Vellum, Claude Cowork, Langflow, Flowise), each with ~60 sourced, dated facts across platform, AI capabilities, integrations, pricing, security, observability, and support - Data layer at lib/compare/data (types, per-competitor profiles) is UI-free and independently auditable - BreadcrumbList, ItemList, and FAQPage JSON-LD per page; sitemap picks up all pages automatically via ALL_COMPETITORS - Two new fact categories (parallel execution, Agent2Agent protocol) researched and sourced against every competitor's own docs * improvement(comparison): neutralize tone across competitor and Sim fact copy - Remove promotional/superlative adjectives applied to competitors (Zapier "one of the largest catalogs", Workato "notably wide", Retool "seamless"/"trusted solution", OpenAI "cutting-edge", Flowise "deep"/"mature"/"most widely adopted", Make "robust") - Reword the few places Sim's own accurate limitations read as unflattering rather than neutral fact (dynamic tool use, model fallback, durability, async execution, support channels, tracing) without changing the underlying facts - Restore the "Yes:"/"No:" value prefix on two Sim facts where it was accidentally dropped during rewording, since FactValue depends on that prefix to render the check/X status icon * fix(comparison): address review findings from Greptile and Cursor Bugbot - Fix parseFactValue regex to require a word boundary after Yes/No, so values like "Not documented"/"Not publicly documented" no longer get misread as a boolean "No" and render as neutral text again (Cursor) - Reword the self-hosting FAQ question to drop the false presupposition that the competitor doesn't self-host, which was wrong for n8n, Langflow, and Flowise (Greptile) - Rename isLastRow -> isNotLastRow in comparison-table.tsx to match what the variable actually computes (Greptile) - Move critters to devDependencies; it's a next build -time-only CSS inliner, not needed at runtime (Greptile) - Sitemap lastModified for comparison pages now matches the max(Sim, competitor) verified-date logic each page's own JSON-LD dateModified already uses, so the two never disagree (Cursor) * fix(comparison): fix doubled Yes prefix and missing period in hub FAQ - Two hub FAQ answers prepended "Yes." to fact values that already start with "Yes:", producing "Yes. Yes: ..." in visible copy and FAQPage JSON-LD. Export and reuse ensurePeriod instead of a hardcoded prefix. - The integrations-count FAQ answer ran two sentences together with no period before "Combined with...". Fixed with the same ensurePeriod helper. * improvement(comparison): remove redundant Key differences at a glance section The 5 facts it previewed (self-hosting, environment promotion, human-in-the-loop, pricing model, data residency) are the exact same rows shown again immediately below in the full comparison table, so the section read as pure repetition for a human scrolling past it. * fix(comparison): strip Yes/No prefix before lowercasing in summarizeFact summarizeFact fed the raw fact value through lowercaseFirst even when it started with "Yes: "/"No: ", producing broken mid-sentence FAQ text like "n8n: yes: many providers via dedicated Chat Model nodes." Strip the boolean prefix first, matching Greptile's suggested fix. --- .../(landing)/comparison/[provider]/page.tsx | 317 +++++ .../comparison/comparison-sections.ts | 154 +++ .../brand-icon-tile/brand-icon-tile.tsx | 83 ++ .../components/brand-icon-tile/index.ts | 2 + .../comparison-cards/comparison-cards.tsx | 47 + .../components/comparison-cards/index.ts | 2 + .../comparison-table/comparison-table.tsx | 151 ++ .../components/comparison-table/index.ts | 2 + .../components/fact-value/fact-value.tsx | 67 + .../comparison/components/fact-value/index.ts | 2 + .../components/source-info/index.ts | 2 + .../components/source-info/source-info.tsx | 40 + .../app/(landing)/comparison/fact-status.ts | 34 + .../app/(landing)/comparison/not-found.tsx | 26 + apps/sim/app/(landing)/comparison/page.tsx | 194 +++ apps/sim/app/(landing)/comparison/utils.ts | 214 +++ .../(landing)/components/footer/footer.tsx | 1 + apps/sim/app/sitemap.ts | 24 + apps/sim/components/icons.tsx | 145 ++ .../compare/data/competitors/claude-cowork.ts | 1064 ++++++++++++++ .../lib/compare/data/competitors/flowise.ts | 842 +++++++++++ .../lib/compare/data/competitors/gumloop.ts | 1093 +++++++++++++++ .../lib/compare/data/competitors/langflow.ts | 883 ++++++++++++ apps/sim/lib/compare/data/competitors/make.ts | 1232 +++++++++++++++++ apps/sim/lib/compare/data/competitors/n8n.ts | 1218 ++++++++++++++++ .../data/competitors/openai-agentkit.ts | 1163 ++++++++++++++++ .../lib/compare/data/competitors/pipedream.ts | 1069 ++++++++++++++ .../data/competitors/power-automate.ts | 1184 ++++++++++++++++ .../lib/compare/data/competitors/retool.ts | 1079 +++++++++++++++ .../lib/compare/data/competitors/stackai.ts | 940 +++++++++++++ .../sim/lib/compare/data/competitors/tines.ts | 1096 +++++++++++++++ .../lib/compare/data/competitors/vellum.ts | 991 +++++++++++++ .../lib/compare/data/competitors/workato.ts | 1220 ++++++++++++++++ .../lib/compare/data/competitors/zapier.ts | 1158 ++++++++++++++++ apps/sim/lib/compare/data/feature-catalog.ts | 925 +++++++++++++ apps/sim/lib/compare/data/index.ts | 27 + apps/sim/lib/compare/data/sim.ts | 1070 ++++++++++++++ apps/sim/lib/compare/data/types.ts | 290 ++++ apps/sim/package.json | 1 + bun.lock | 19 +- 40 files changed, 20065 insertions(+), 6 deletions(-) create mode 100644 apps/sim/app/(landing)/comparison/[provider]/page.tsx create mode 100644 apps/sim/app/(landing)/comparison/comparison-sections.ts create mode 100644 apps/sim/app/(landing)/comparison/components/brand-icon-tile/brand-icon-tile.tsx create mode 100644 apps/sim/app/(landing)/comparison/components/brand-icon-tile/index.ts create mode 100644 apps/sim/app/(landing)/comparison/components/comparison-cards/comparison-cards.tsx create mode 100644 apps/sim/app/(landing)/comparison/components/comparison-cards/index.ts create mode 100644 apps/sim/app/(landing)/comparison/components/comparison-table/comparison-table.tsx create mode 100644 apps/sim/app/(landing)/comparison/components/comparison-table/index.ts create mode 100644 apps/sim/app/(landing)/comparison/components/fact-value/fact-value.tsx create mode 100644 apps/sim/app/(landing)/comparison/components/fact-value/index.ts create mode 100644 apps/sim/app/(landing)/comparison/components/source-info/index.ts create mode 100644 apps/sim/app/(landing)/comparison/components/source-info/source-info.tsx create mode 100644 apps/sim/app/(landing)/comparison/fact-status.ts create mode 100644 apps/sim/app/(landing)/comparison/not-found.tsx create mode 100644 apps/sim/app/(landing)/comparison/page.tsx create mode 100644 apps/sim/app/(landing)/comparison/utils.ts create mode 100644 apps/sim/lib/compare/data/competitors/claude-cowork.ts create mode 100644 apps/sim/lib/compare/data/competitors/flowise.ts create mode 100644 apps/sim/lib/compare/data/competitors/gumloop.ts create mode 100644 apps/sim/lib/compare/data/competitors/langflow.ts create mode 100644 apps/sim/lib/compare/data/competitors/make.ts create mode 100644 apps/sim/lib/compare/data/competitors/n8n.ts create mode 100644 apps/sim/lib/compare/data/competitors/openai-agentkit.ts create mode 100644 apps/sim/lib/compare/data/competitors/pipedream.ts create mode 100644 apps/sim/lib/compare/data/competitors/power-automate.ts create mode 100644 apps/sim/lib/compare/data/competitors/retool.ts create mode 100644 apps/sim/lib/compare/data/competitors/stackai.ts create mode 100644 apps/sim/lib/compare/data/competitors/tines.ts create mode 100644 apps/sim/lib/compare/data/competitors/vellum.ts create mode 100644 apps/sim/lib/compare/data/competitors/workato.ts create mode 100644 apps/sim/lib/compare/data/competitors/zapier.ts create mode 100644 apps/sim/lib/compare/data/feature-catalog.ts create mode 100644 apps/sim/lib/compare/data/index.ts create mode 100644 apps/sim/lib/compare/data/sim.ts create mode 100644 apps/sim/lib/compare/data/types.ts diff --git a/apps/sim/app/(landing)/comparison/[provider]/page.tsx b/apps/sim/app/(landing)/comparison/[provider]/page.tsx new file mode 100644 index 00000000000..03ffb880ee8 --- /dev/null +++ b/apps/sim/app/(landing)/comparison/[provider]/page.tsx @@ -0,0 +1,317 @@ +import type { Metadata } from 'next' +import { notFound } from 'next/navigation' +import type { CompetitorProfile } from '@/lib/compare/data' +import { simProfile } from '@/lib/compare/data' +import { SITE_URL } from '@/lib/core/utils/urls' +import { buildLandingMetadata } from '@/lib/landing/seo' +import { COMPARISON_SECTIONS, getFactGroup } from '@/app/(landing)/comparison/comparison-sections' +import { BrandIconTile, SimIconTile } from '@/app/(landing)/comparison/components/brand-icon-tile' +import { ComparisonCards } from '@/app/(landing)/comparison/components/comparison-cards' +import { ComparisonTable } from '@/app/(landing)/comparison/components/comparison-table' +import { + ALL_COMPETITORS, + buildBottomLine, + buildComparisonFaqs, + getCompetitorBySlug, + getLatestVerifiedDate, + SIM_LATEST_VERIFIED, +} from '@/app/(landing)/comparison/utils' +import { BackLink } from '@/app/(landing)/components' +import { Cta } from '@/app/(landing)/components/cta/cta' +import { JsonLd } from '@/app/(landing)/components/json-ld' +import { LandingFAQ } from '@/app/(landing)/components/landing-faq' + +const baseUrl = SITE_URL + +export const revalidate = 3600 +export const dynamicParams = false + +export async function generateStaticParams() { + return ALL_COMPETITORS.map((competitor) => ({ provider: competitor.id })) +} + +/** Flattens a profile's facts into JSON-LD `additionalProperty` entries, in {@link COMPARISON_SECTIONS} order. */ +function factsToProperties(profile: CompetitorProfile) { + return COMPARISON_SECTIONS.flatMap((section) => { + const group = getFactGroup(profile, section.group) + return section.rows.map((row) => ({ + '@type': 'PropertyValue', + name: row.label, + value: group[row.key]?.value ?? 'Unknown', + })) + }) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ provider: string }> +}): Promise { + const { provider: providerSlug } = await params + const competitor = getCompetitorBySlug(providerSlug) + + if (!competitor) { + return {} + } + + return buildLandingMetadata({ + title: `Sim vs ${competitor.name}: AI Workspace Comparison`, + description: `Compare Sim, the open-source AI workspace, to ${competitor.name} on platform, AI, integrations, pricing, security, and support. Sourced and dated facts.`, + path: `/comparison/${competitor.id}`, + keywords: [ + `Sim vs ${competitor.name}`, + `${competitor.name} alternative`, + `${competitor.name} vs Sim`, + `open source ${competitor.name} alternative`, + `${competitor.name} comparison`, + 'AI agent workspace', + 'AI workflow automation comparison', + ].join(', '), + }) +} + +export default async function ComparisonProviderPage({ + params, +}: { + params: Promise<{ provider: string }> +}) { + const { provider: providerSlug } = await params + const competitor = getCompetitorBySlug(providerSlug) + + if (!competitor) { + notFound() + } + + const faqs = buildComparisonFaqs(competitor) + const verdict = buildBottomLine(competitor) + const CompetitorIcon = competitor.brand?.icon + + const breadcrumbJsonLd = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { '@type': 'ListItem', position: 1, name: 'Home', item: baseUrl }, + { '@type': 'ListItem', position: 2, name: 'Comparison', item: `${baseUrl}/comparison` }, + { + '@type': 'ListItem', + position: 3, + name: `Sim vs ${competitor.name}`, + item: `${baseUrl}/comparison/${competitor.id}`, + }, + ], + } + + const latestVerified = new Date( + Math.max(SIM_LATEST_VERIFIED.getTime(), getLatestVerifiedDate(competitor).getTime()) + ) + + const productComparisonJsonLd = { + '@context': 'https://schema.org', + '@type': 'ItemList', + name: `Sim vs ${competitor.name}`, + description: `Feature and pricing comparison between Sim and ${competitor.name}.`, + url: `${baseUrl}/comparison/${competitor.id}`, + dateModified: latestVerified.toISOString().slice(0, 10), + numberOfItems: 2, + itemListElement: [ + { + '@type': 'ListItem', + position: 1, + item: { + '@type': 'SoftwareApplication', + name: 'Sim', + applicationCategory: 'BusinessApplication', + url: SITE_URL, + description: simProfile.oneLiner, + additionalProperty: factsToProperties(simProfile), + }, + }, + { + '@type': 'ListItem', + position: 2, + item: { + '@type': 'SoftwareApplication', + name: competitor.name, + applicationCategory: 'BusinessApplication', + url: competitor.website, + description: competitor.oneLiner, + additionalProperty: factsToProperties(competitor), + }, + }, + ], + } + + const faqJsonLd = { + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: faqs.map((faq) => ({ + '@type': 'Question', + name: faq.question, + acceptedAnswer: { + '@type': 'Answer', + text: faq.answer, + }, + })), + } + + return ( + <> + + + + +
+
+
+ +
+ +
+

+ Sim vs {competitor.name} +

+

+ Sim is the open-source AI workspace where teams build, deploy, and manage AI agents + visually, conversationally, or with code. Here is how Sim compares to{' '} + {competitor.name} on platform architecture, AI capabilities, integrations, pricing, + security, and support. Every fact below is sourced and dated. +

+

+ Sim is an open-source AI workspace for building, deploying, and managing AI agents. + This page compares Sim to {competitor.name} across platform architecture, AI + capabilities, integrations, pricing, security and compliance, observability, and + support, using sourced, dated facts for buyers evaluating both platforms. +

+
+
+ +
+ +
+
+
+
+

+ + What is Sim? +

+

+ {simProfile.oneLiner} +

+
+
+

+ {CompetitorIcon ? ( + + ) : null} + What is {competitor.name}? +

+

+ {competitor.oneLiner} +

+
+
+ +
+ +
+

+ Sim vs {competitor.name}: feature-by-feature comparison +

+ +
+ +
+ +
+
+
+

+ Sim standout features +

+
+ +
+
+
+

+ Documented {competitor.name} limitations +

+
+ +
+
+ +
+ +
+

+ Bottom line +

+
+

+ {verdict.chooseSim} +

+

+ {verdict.chooseCompetitor} +

+
+
+ +
+ +
+

+ Frequently asked questions +

+
+ +
+
+
+
+ +
+
+ +
+ +
+ + ) +} diff --git a/apps/sim/app/(landing)/comparison/comparison-sections.ts b/apps/sim/app/(landing)/comparison/comparison-sections.ts new file mode 100644 index 00000000000..609955be3db --- /dev/null +++ b/apps/sim/app/(landing)/comparison/comparison-sections.ts @@ -0,0 +1,154 @@ +import type { ComparisonFacts, CompetitorProfile, Fact } from '@/lib/compare/data' + +/** + * The one place a {@link ComparisonFacts} group is read back out of a profile + * by group key. Every render-side consumer (the table, key-differences strip, + * JSON-LD builder) needs this same lookup; centralizing it here means there is + * exactly one cast to reason about instead of one per call site. + */ +export function getFactGroup( + profile: CompetitorProfile, + group: G +): Record { + return profile.facts[group] as Record +} + +/** + * One row in a comparison table section. Maps a human label to a fact key + * within a {@link ComparisonFacts} group. `key` is intentionally `string` + * (rather than a per-group `keyof` union) so a single array can hold rows + * for every group without TypeScript collapsing the distributed generic to + * `never`; correctness is enforced once, by construction, in + * {@link COMPARISON_SECTIONS} below, and the renderer reads through it. + */ +export interface ComparisonRowDef { + key: string + label: string +} + +/** One section of the comparison table, mirroring a {@link ComparisonFacts} group. */ +export interface ComparisonSectionDef { + group: keyof ComparisonFacts + title: string + rows: ComparisonRowDef[] +} + +/** + * Type-checks a section's rows against its own group's actual fact keys + * (via the per-call generic `G`), then widens to the plain `ComparisonRowDef` + * shape used by {@link COMPARISON_SECTIONS}. This is where row-key + * correctness is actually enforced. A typo here fails the build. + */ +function defineSection(section: { + group: G + title: string + rows: Array<{ key: keyof ComparisonFacts[G]; label: string }> +}): ComparisonSectionDef { + return section as ComparisonSectionDef +} + +/** + * Canonical section/row order for rendering a {@link ComparisonFacts} profile + * pair as a table. Single source of truth for row labels. Add a field here + * once and every comparison page picks it up. + */ +export const COMPARISON_SECTIONS: ComparisonSectionDef[] = [ + defineSection({ + group: 'platform', + title: 'Platform', + rows: [ + { key: 'builderType', label: 'Builder type' }, + { key: 'learningCurve', label: 'Learning curve' }, + { key: 'selfHostOption', label: 'Self-hosting' }, + { key: 'deploymentOptions', label: 'Deployment options' }, + { key: 'templates', label: 'Templates' }, + { key: 'license', label: 'License' }, + { key: 'environmentPromotion', label: 'Environment promotion' }, + { key: 'versionControlDepth', label: 'Version control' }, + { key: 'realtimeCollaboration', label: 'Realtime collaboration' }, + { key: 'nativeFileStorage', label: 'Native file storage' }, + ], + }), + defineSection({ + group: 'pricing', + title: 'Pricing', + rows: [ + { key: 'pricingModel', label: 'Pricing model' }, + { key: 'entryPaidPlan', label: 'Entry paid plan' }, + { key: 'freeTier', label: 'Free tier' }, + { key: 'byok', label: 'Bring your own key' }, + ], + }), + defineSection({ + group: 'security', + title: 'Security & compliance', + rows: [ + { key: 'soc2', label: 'SOC 2' }, + { key: 'dataResidency', label: 'Data residency' }, + { key: 'rbac', label: 'Role-based access control' }, + { key: 'auditLogging', label: 'Audit logging' }, + { key: 'additionalCompliance', label: 'Additional compliance' }, + { key: 'modelAndToolGovernance', label: 'Model & tool governance' }, + { key: 'credentialGovernance', label: 'Credential governance' }, + { key: 'sso', label: 'Single sign-on (SSO)' }, + { key: 'piiRedaction', label: 'PII redaction' }, + { key: 'dataRetention', label: 'Custom data retention' }, + { key: 'whiteLabeling', label: 'White-labeling' }, + ], + }), + defineSection({ + group: 'aiCapabilities', + title: 'AI capabilities', + rows: [ + { key: 'multiLlmSupport', label: 'Multi-LLM support' }, + { key: 'agentReasoningBlocks', label: 'Agent reasoning blocks' }, + { key: 'naturalLanguageBuilding', label: 'Natural-language building' }, + { key: 'knowledgeBaseRag', label: 'Knowledge base / RAG' }, + { key: 'mcpSupport', label: 'MCP support' }, + { key: 'evaluationGuardrails', label: 'Evaluation & guardrails' }, + { key: 'humanInTheLoop', label: 'Human-in-the-loop' }, + { key: 'generativeMedia', label: 'Generative media' }, + { key: 'dynamicToolUse', label: 'Dynamic tool use' }, + { key: 'modelFallback', label: 'Automatic model fallback' }, + { key: 'agentSkills', label: 'Agent skills' }, + { key: 'nativeChatDeployment', label: 'Native chat deployment' }, + { key: 'parallelExecution', label: 'Parallel execution' }, + { key: 'a2aProtocol', label: 'Agent2Agent (A2A) protocol' }, + ], + }), + defineSection({ + group: 'integrations', + title: 'Integrations', + rows: [ + { key: 'integrationCount', label: 'Integrations' }, + { key: 'triggerTypes', label: 'Trigger types' }, + { key: 'customCodeSteps', label: 'Custom code steps' }, + { key: 'apiPublishing', label: 'API publishing' }, + { key: 'extensibilitySdk', label: 'SDKs & extensibility' }, + { key: 'mcpPublishing', label: 'Publish as MCP server' }, + ], + }), + defineSection({ + group: 'observability', + title: 'Observability & durability', + rows: [ + { key: 'tracingDepth', label: 'Tracing & observability' }, + { key: 'durabilityModel', label: 'Durability & retries' }, + { key: 'failureAlerting', label: 'Failure alerting' }, + { key: 'dataDrains', label: 'Data drains' }, + { key: 'asyncExecution', label: 'Async execution' }, + { key: 'executionLimits', label: 'Execution limits' }, + { key: 'partialFailureHandling', label: 'Partial-failure handling' }, + ], + }), + defineSection({ + group: 'support', + title: 'Support', + rows: [ + { key: 'supportChannels', label: 'Support channels' }, + { key: 'sla', label: 'SLA' }, + { key: 'community', label: 'Community' }, + { key: 'academy', label: 'Academy / training' }, + ], + }), +] diff --git a/apps/sim/app/(landing)/comparison/components/brand-icon-tile/brand-icon-tile.tsx b/apps/sim/app/(landing)/comparison/components/brand-icon-tile/brand-icon-tile.tsx new file mode 100644 index 00000000000..6c8911a805d --- /dev/null +++ b/apps/sim/app/(landing)/comparison/components/brand-icon-tile/brand-icon-tile.tsx @@ -0,0 +1,83 @@ +import type { ComponentType, SVGProps } from 'react' +import { cn } from '@sim/emcn' +import type { CompetitorBrand } from '@/lib/compare/data' +import { SimWordmark } from '@/app/(landing)/components/navbar/components/sim-wordmark' + +export interface BrandIconTileProps { + icon: ComponentType> + /** + * Whether `icon` already renders a full, self-contained brand-colored + * square (e.g. a fetched app-store-style logo) rather than a bare + * transparent glyph. See {@link CompetitorBrand.selfFramed}. + */ + selfFramed?: boolean + /** Outer tile size, e.g. `size-8`. Defaults to the integrations-page card size. */ + className?: string + /** Icon glyph size inside the tile, e.g. `size-4`. Ignored when `selfFramed`. */ + iconClassName?: string +} + +/** + * A rounded, bordered icon tile matching the platform's app-icon chrome + * conventions (border radius, border token, background), so competitor brand + * logos read as the same first-class "app icon" chrome as the rest of the + * product, instead of a bare, unframed SVG floating in the layout. + * + * A self-framed logo (already a complete brand-colored square) fills the + * tile edge-to-edge, clipped to the same rounded corners. Otherwise the icon + * is a small transparent glyph centered on a plain bordered background. + */ +export function BrandIconTile({ + icon: Icon, + selfFramed = false, + className = 'size-8', + iconClassName = 'size-4', +}: BrandIconTileProps) { + if (selfFramed) { + return ( +
+
+ ) + } + return ( +
+
+ ) +} + +export interface SimIconTileProps { + /** Outer tile size, e.g. `size-8`. Defaults to the integrations-page card size. */ + className?: string +} + +/** + * The same rounded, bordered tile as {@link BrandIconTile}, but for Sim's own + * wordmark. So "Sim" gets the identical icon-chip treatment as every + * competitor it's compared against, instead of appearing as bare text. + */ +export function SimIconTile({ className = 'size-8' }: SimIconTileProps) { + return ( +
+ + + +
+ ) +} diff --git a/apps/sim/app/(landing)/comparison/components/brand-icon-tile/index.ts b/apps/sim/app/(landing)/comparison/components/brand-icon-tile/index.ts new file mode 100644 index 00000000000..fe31f6d7a24 --- /dev/null +++ b/apps/sim/app/(landing)/comparison/components/brand-icon-tile/index.ts @@ -0,0 +1,2 @@ +export type { BrandIconTileProps, SimIconTileProps } from './brand-icon-tile' +export { BrandIconTile, SimIconTile } from './brand-icon-tile' diff --git a/apps/sim/app/(landing)/comparison/components/comparison-cards/comparison-cards.tsx b/apps/sim/app/(landing)/comparison/components/comparison-cards/comparison-cards.tsx new file mode 100644 index 00000000000..8a3d62e4745 --- /dev/null +++ b/apps/sim/app/(landing)/comparison/components/comparison-cards/comparison-cards.tsx @@ -0,0 +1,47 @@ +import type { FactSource } from '@/lib/compare/data' +import { SourceLink } from '@/app/(landing)/comparison/components/source-info' + +interface ComparisonCardItem { + title: string + description: string + shortDescription?: string + source: FactSource +} + +export interface ComparisonCardsProps { + items: ComparisonCardItem[] +} + +/** + * A vertically stacked list of atomic, independently quotable fact cards, + * each self-contained title + a one-line `shortDescription` (falling back to + * `description` if a short version hasn't been authored yet). Used for both + * a competitor's standout features and its documented limitations. + * + * The full `description` is always present as `sr-only` text. Server + * rendered regardless of hover/JS state. So an LLM or crawler reading the + * page still gets the complete claim even though a human sees only the + * one-line summary. Hovering the title itself (`SourceLink`) shows a short + * "Source: X" tooltip and clicking it opens the source, rather than a + * separate info-icon affordance next to every card. + */ +export function ComparisonCards({ items }: ComparisonCardsProps) { + return ( +
+ {items.map((item, index) => ( +
0 ? 'border-[var(--border)] border-t px-6 py-4' : 'px-6 py-4'} + > +

+ {item.title} +

+

+ {item.shortDescription ?? item.description} +

+ {item.shortDescription ? {item.description} : null} +
+ ))} +
+ ) +} diff --git a/apps/sim/app/(landing)/comparison/components/comparison-cards/index.ts b/apps/sim/app/(landing)/comparison/components/comparison-cards/index.ts new file mode 100644 index 00000000000..e5fa934bb4f --- /dev/null +++ b/apps/sim/app/(landing)/comparison/components/comparison-cards/index.ts @@ -0,0 +1,2 @@ +export type { ComparisonCardsProps } from './comparison-cards' +export { ComparisonCards } from './comparison-cards' diff --git a/apps/sim/app/(landing)/comparison/components/comparison-table/comparison-table.tsx b/apps/sim/app/(landing)/comparison/components/comparison-table/comparison-table.tsx new file mode 100644 index 00000000000..70a21ae4e24 --- /dev/null +++ b/apps/sim/app/(landing)/comparison/components/comparison-table/comparison-table.tsx @@ -0,0 +1,151 @@ +import type { ReactNode } from 'react' +import { cn } from '@sim/emcn' +import type { CompetitorProfile } from '@/lib/compare/data' +import { COMPARISON_SECTIONS, getFactGroup } from '@/app/(landing)/comparison/comparison-sections' +import { BrandIconTile, SimIconTile } from '@/app/(landing)/comparison/components/brand-icon-tile' +import { FactValue } from '@/app/(landing)/comparison/components/fact-value' + +export interface ComparisonTableProps { + sim: CompetitorProfile + competitor: CompetitorProfile +} + +function ColumnHeader({ + name, + iconTile, + isSim, +}: { + name: string + iconTile: ReactNode + isSim: boolean +}) { + return ( +
+ {iconTile} + {name} +
+ ) +} + +/** + * Two-column "Sim vs {Competitor}" fact table, styled after the billing + * upgrade-page comparison table (same border/hairline rhythm and section + * headers) but data-driven off {@link CompetitorProfile.facts} instead of the + * fixed 4-tier plan schema. Data cells share one neutral surface for both + * columns. The Sim column is called out only in the header row (a bottom + * accent border), so the table reads as one clean grid rather than a + * checkerboard. Pure server component: every value is plain server-rendered + * text so crawlers and AI answer engines read the full comparison without + * any client-side hydration. + */ +export function ComparisonTable({ sim, competitor }: ComparisonTableProps) { + return ( +
+
+
+
+ Compare + + {sim.name} vs {competitor.name} + +
+ } isSim /> + + ) : null + } + isSim={false} + /> +
+ + {COMPARISON_SECTIONS.map((section, sectionIdx) => { + const simGroupFacts = getFactGroup(sim, section.group) + const competitorGroupFacts = getFactGroup(competitor, section.group) + + return ( +
+
+
0 && 'border-[var(--border-1)] border-t' + )} + > + + {section.title} + +
+
0 && 'border-[var(--border-1)] border-t' + )} + /> +
+ + {section.rows.map((row, rowIdx) => { + const simFact = simGroupFacts[row.key] + const competitorFact = competitorGroupFacts[row.key] + const isNotLastRow = rowIdx < section.rows.length - 1 + + return ( +
+
+ {row.label} +
+
+ +
+
+ +
+
+ ) + })} +
+ ) + })} +
+
+ ) +} diff --git a/apps/sim/app/(landing)/comparison/components/comparison-table/index.ts b/apps/sim/app/(landing)/comparison/components/comparison-table/index.ts new file mode 100644 index 00000000000..18ea827718e --- /dev/null +++ b/apps/sim/app/(landing)/comparison/components/comparison-table/index.ts @@ -0,0 +1,2 @@ +export type { ComparisonTableProps } from './comparison-table' +export { ComparisonTable } from './comparison-table' diff --git a/apps/sim/app/(landing)/comparison/components/fact-value/fact-value.tsx b/apps/sim/app/(landing)/comparison/components/fact-value/fact-value.tsx new file mode 100644 index 00000000000..2e95baf01dd --- /dev/null +++ b/apps/sim/app/(landing)/comparison/components/fact-value/fact-value.tsx @@ -0,0 +1,67 @@ +import { Check, X } from '@sim/emcn/icons' +import type { Fact } from '@/lib/compare/data' +import { SourceLink } from '@/app/(landing)/comparison/components/source-info' +import { parseFactValue } from '@/app/(landing)/comparison/fact-status' + +export interface FactValueProps { + fact: Fact +} + +/** + * Renders one {@link Fact} for a glancing reader while keeping the full + * granular fact server-rendered for crawlers and AI answer engines. + * + * - A true "Yes"/"No" fact renders as an icon alone (a monochrome check or + * muted cross, no colored pass/fail styling), no visible text, since the + * label column and surrounding context already say what's being asked. + * - Any other fact shows its `shortValue` (a compact, pre-authored + * restatement of `value`), never the full sentence. + * - `Tooltip` here is a cursor-following mini-bubble meant for a short + * one-line label (see its own docs/usages: "Refresh", "last updated: X") + * . It is deliberately NOT used to hold paragraph-length detail text, only + * the compact source citation, which is exactly what it's designed for. + * - When a source exists, the visible glance (icon or `shortValue` text) + * IS the hover/click target for that source, via `SourceLink`, rather + * than a separate info-icon next to every value. One affordance per + * fact keeps a 58-row table from reading as icon-cluttered. + * - A `sr-only` span always carries the complete value, detail, and source + * in the initial server-rendered HTML, independent of hover/JS state, so + * an LLM or crawler reading the page gets full granularity even though a + * human sees only the compact glance. + */ +export function FactValue({ fact }: FactValueProps) { + const { status, text } = parseFactValue(fact.value) + const isBoolean = status === 'yes' || status === 'no' + const primarySource = fact.sources[0] + + const fullText = [fact.value, fact.detail].filter(Boolean).join('. ') + + const glance = isBoolean ? ( + status === 'yes' ? ( +