VRYO
FeaturesGetting StartedPricingFAQDocs
GitHubLog InSign Up

Introduction

  • Getting Started
  • Concepts

Workspace

  • Workspace Guide

Reference

  • Widget Reference
  • API Reference

Examples

  • Recipes

Resources

  • Templates
Recipes

Recipes

Common patterns and real-world examples.

Persist canvas to localStorage

Use the usePersistence hook to save and restore the canvas layout automatically.

app/canvas/page.tsx
"use client";
import { useRef, useEffect } from "react";
import { VryoProvider, VryoProviderRef, usePersistence } from "@vryo/client";

export default function CanvasPage() {
  const ref = useRef<VryoProviderRef>(null);
  const { restoredState, saveState } = usePersistence("my-canvas");

  return (
    <VryoProvider
      ref={ref}
      initialState={restoredState ?? undefined}
      onStateChange={saveState}
      style={{ width: "100%", height: "100vh" }}
    />
  );
}

Export canvas as JSON

Add a toolbar button that downloads the current canvas state.

components/CanvasToolbar.tsx
"use client";
import { useExportCanvas } from "@vryo/client";

export function CanvasToolbar() {
  const { exportJSON, exportTableCSV } = useExportCanvas();

  return (
    <div>
      <button onClick={() => exportJSON("my-canvas")}>
        Download JSON
      </button>
      <button onClick={() => exportTableCSV("widget-id-here")}>
        Export Table CSV
      </button>
    </div>
  );
}

Bind a chart to live Arrow data

Wire a chart widget to your server-side Arrow endpoint so it auto-refreshes.

app/api/vryo/data/route.ts
import { jsonToArrowIPC } from "@vryo/server";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const dataset = searchParams.get("dataset");

  // Fetch from your DB / data source
  const rows = await getRows(dataset);
  const buffer = jsonToArrowIPC(rows);

  return new Response(buffer, {
    headers: { "Content-Type": "application/vnd.apache.arrow.stream" },
  });
}

Then reference the endpoint in your widget definition:

const patch: AgentPatch = {
  agentId: "my-app",
  baseRevision: 0,
  upsert: [{
    id: "revenue",
    type: "chart",
    title: "Revenue",
    x: 100, y: 100, width: 480, height: 320,
    chartType: "bar",
    dataBinding: { endpoint: "/api/vryo/data?dataset=revenue" },
  }],
};
ref.current?.applyPatch(patch);

Stream agent responses

Use the streaming API to show a real-time 'agent typing' indicator while the patch arrives.

app/api/vryo/agent/route.ts
import {
  streamAgentWithCanvas,
  asyncGeneratorToReadableStream,
  collectStreamingPatch,
} from "@vryo/server";

// Option A: stream raw text to client
export async function POST(request: Request) {
  const { canvasState, message } = await request.json();
  const gen = streamAgentWithCanvas(canvasState, message);
  const stream = asyncGeneratorToReadableStream(gen);
  return new Response(stream, {
    headers: { "Content-Type": "text/event-stream" },
  });
}

// Option B: collect the full patch server-side, return JSON
export async function POST(request: Request) {
  const { canvasState, message } = await request.json();
  const gen = streamAgentWithCanvas(canvasState, message);
  const patch = await collectStreamingPatch(gen);
  return Response.json(patch);
}

Track canvas selection

React to widget selection changes using the useWidgetSelection hook.

"use client";
import { useWidgetSelection } from "@vryo/client";

export function SelectionPanel() {
  const { activeWidget, hasSingleSelection } = useWidgetSelection();

  if (!hasSingleSelection || !activeWidget) {
    return <p>Select a widget</p>;
  }

  return (
    <div>
      <h3>{activeWidget.title}</h3>
      <p>Type: {activeWidget.type}</p>
    </div>
  );
}

Use OpenAI / Groq instead of Claude

The OpenAI integration is compatible with any OpenAI-spec API.

app/api/vryo/agent/route.ts
import { callOpenAIAgentWithCanvas } from "@vryo/server";

export async function POST(request: Request) {
  const { canvasState, message } = await request.json();

  // Use Groq β€” or any OpenAI-compatible endpoint
  const patch = await callOpenAIAgentWithCanvas(canvasState, message, {
    baseURL: "https://api.groq.com/openai/v1",
    model: "llama-3.3-70b-versatile",
    apiKey: process.env.GROQ_API_KEY,
  });

  return Response.json(patch);
}

Create KPI cards programmatically

Build executive dashboard KPI cards with trend indicators, targets, and sparklines.

import { generateWidgetId } from "@vryo/core";

const kpiCard = {
  id: generateWidgetId("kpi"),
  type: "kpi" as const,
  x: 60, y: 60, width: 280, height: 180,
  title: "Monthly Revenue",
  value: 570000,
  format: "$,.0f",
  trend: "up" as const,
  trendValue: "+8.2%",
  trendPositive: true,
  comparisonLabel: "vs last month",
  target: 600000,
  sparkline: [380000, 410000, 430000, 460000, 490000, 520000, 570000],
  icon: "πŸ’°",
  accent: "indigo" as const,
  dataBinding: null,
};

// Apply via agent patch
vryoRef.current?.applyPatch({
  agentId: "dashboard-builder",
  baseRevision: currentRevision,
  upsert: [kpiCard],
  rationale: "Added revenue KPI card",
  timestamp: Date.now(),
});

Add a gauge for SLA monitoring

Create semicircular gauges with color zones for real-time metric monitoring.

const slaGauge = {
  id: generateWidgetId("gauge"),
  type: "gauge" as const,
  x: 400, y: 60, width: 300, height: 240,
  title: "API Uptime SLA",
  value: 99.7, min: 95, max: 100,
  target: 99.9,
  unit: "%",
  zones: [
    { limit: 97, color: "#f43f5e", label: "Critical" },
    { limit: 99, color: "#f59e0b", label: "Warning" },
    { limit: 100, color: "#10b981", label: "Healthy" },
  ],
  variant: "semicircle" as const,
  accent: "emerald" as const,
  dataBinding: null,
};

Create cross-filtering slicers

Add Power BI-style slicers that filter other widgets by region, date, or category.

const regionFilter = {
  id: generateWidgetId("filter"),
  type: "filter" as const,
  x: 60, y: 300, width: 240, height: 320,
  title: "Region Filter",
  filterType: "checkbox" as const,
  field: "region",
  label: "Filter by Region",
  options: ["North America", "Europe", "APAC", "LATAM", "MEA"],
  selectedValues: ["North America", "Europe"],
  multiSelect: true,
  // Link to other widget IDs for cross-filtering
  targetWidgetIds: [revenueChart.id, customerTable.id],
  accent: "indigo" as const,
  dataBinding: null,
};

Fetch live data with useDataSource

Wire any chart to a REST endpoint with auto-refresh. The hook handles fetching, parsing, column inference, and error states.

"use client";
import { useDataSource, VryoProvider, VryoProviderRef } from "@vryo/client";
import { useRef, useEffect } from "react";

export default function LiveDashboard() {
  const ref = useRef<VryoProviderRef>(null);
  const { data, columns, loading } = useDataSource({
    url: "/api/sales/monthly",
    type: "json",
    refreshInterval: 30_000,  // re-fetch every 30s
  });

  useEffect(() => {
    if (data.length === 0 || !ref.current) return;
    ref.current.applyPatch({
      agentId: "live-data",
      baseRevision: 0,
      timestamp: Date.now(),
      upsert: [{
        id: "revenue-chart",
        type: "chart",
        title: "Live Revenue",
        x: 60, y: 60, width: 600, height: 380,
        chartType: "area",
        dataBinding: { type: "json", records: data },
        xAxis: { field: columns.find(c => c.type === "date")?.name ?? "date" },
        series: columns
          .filter(c => c.type === "number")
          .map(c => ({ field: c.name, label: c.name })),
        lastModifiedBy: "live-data",
      }],
    });
  }, [data, columns]);

  return <VryoProvider ref={ref} style={{ width: "100%", height: "100vh" }} />;
}

Build a toolbar with useDashboardActions

Use the useDashboardActions hook to add, remove, duplicate, and export widgets from a custom toolbar.

"use client";
import { useDashboardActions } from "@vryo/client";

export function DashboardToolbar() {
  const {
    widgetCount,
    addWidget,
    duplicateWidget,
    clearAll,
    exportJSON,
  } = useDashboardActions();

  return (
    <div style={{ display: "flex", gap: 8, padding: 12 }}>
      <span>{widgetCount} widgets</span>
      <button onClick={() => addWidget({
        id: `chart-${Date.now()}`,
        type: "chart",
        title: "New Chart",
        x: 60, y: 60, width: 480, height: 320,
        chartType: "bar",
        dataBinding: { type: "json", records: [] },
        xAxis: { field: "name" },
        series: [{ field: "value", label: "Value" }],
        lastModifiedBy: "user",
      })}>
        Add Chart
      </button>
      <button onClick={() => {
        const json = exportJSON();
        navigator.clipboard.writeText(json);
      }}>
        Copy JSON
      </button>
      <button onClick={clearAll}>Clear All</button>
    </div>
  );
}

Subscribe to a widget with useWidget

React to changes on a specific widget β€” great for building detail panels or property editors.

"use client";
import { useWidget } from "@vryo/client";

export function WidgetInspector({ widgetId }: { widgetId: string }) {
  const { widget, exists, update, remove } = useWidget(widgetId);

  if (!exists) return <p>Widget not found</p>;

  return (
    <div>
      <h3>{widget!.title}</h3>
      <p>Type: {widget!.type} | Position: ({widget!.x}, {widget!.y})</p>
      <input
        value={widget!.title}
        onChange={e => update({ title: e.target.value })}
      />
      <button onClick={remove}>Delete</button>
    </div>
  );
}

Load a dashboard template

Import a pre-built template and apply it to an empty canvas. Great for onboarding flows.

"use client";
import { VryoProvider, VryoProviderRef } from "@vryo/client";
import { useRef } from "react";

// Template widgets β€” could be loaded from an API or imported from a file
const salesTemplate = [
  { id: "kpi-revenue", type: "kpi", title: "Revenue", x: 60, y: 60, ... },
  { id: "chart-trend", type: "chart", title: "Trend", x: 60, y: 240, ... },
  { id: "table-deals", type: "table", title: "Deals", x: 60, y: 640, ... },
];

export default function TemplatePage() {
  const ref = useRef<VryoProviderRef>(null);

  function loadTemplate() {
    ref.current?.applyPatch({
      agentId: "template-loader",
      baseRevision: 0,
      timestamp: Date.now(),
      upsert: salesTemplate,
      rationale: "Loaded Sales Dashboard template",
    });
  }

  return (
    <>
      <button onClick={loadTemplate}>Load Sales Template</button>
      <VryoProvider ref={ref} style={{ width: "100%", height: "100vh" }} />
    </>
  );
}

Let the agent build a complete dashboard

Prompt the agent to analyze data and automatically create the right mix of widget types.

// The agent prompt includes all 6 widget types and 13 chart types.
// It picks the right visualization based on your request:
// import { callVryoAgent } from "@/actions/vryo/actions"
const result = await callVryoAgent(canvasState, `
  Build an executive dashboard from the sales data:
  - KPI cards for revenue, users, NPS, and churn
  - Revenue vs cost combo chart with margin overlay
  - Regional sales radar chart
  - Lead conversion funnel
  - Customer table with sparklines and conditional formatting
  - Gauge for SLA uptime
  - Region filter slicer
  - AI analysis note with key findings
`);

// The action returns { data: AgentPatch } with all widgets positioned
if (!("error" in result)) vryoRef.current?.applyPatch(result.data);