DuckVizBeta
Packages

@duckviz/report

Report editor and deck presenter with PDF, DOCX, and PPTX export.

@duckviz/report turns a dashboard config into a full written report (Notion-like block editor, PDF/DOCX export) or a presenter deck (slideshow + PPTX export). The same SSE pipeline that powers the hosted DuckViz product is bundled here — just pass your widgets and go.

Installation

npm install @duckviz/report @duckviz/db @duckviz/ui @duckviz/widgets
npm install react react-dom

The package also has several optional peer dependencies (@yoopta/editor + plugins, docx, jspdf, pptxgenjs). Install them when the bundler complains — all are required for full functionality.

Quick start

import { DuckvizDBProvider } from "@duckviz/db";
import { ReportBuilder } from "@duckviz/report";
import type { BuilderDashboardConfig } from "@duckviz/report";
import type { DuckvizDataset } from "@duckviz/db";

const config: BuilderDashboardConfig = {
  name: "Q4 Sales",
  widgets: [
    {
      id: "revenue",
      type: "bar",
      title: "Revenue by Category",
      description: "Top 10 categories",
      dataKey: "SELECT category, SUM(amount) AS value FROM t_sales GROUP BY category ORDER BY value DESC LIMIT 10",
    },
  ],
};

const datasets: DuckvizDataset[] = [
  { name: "Sales", data: salesRows, tableName: "t_sales" },
];

function App({ onBack }: { onBack: () => void }) {
  return (
    <DuckvizDBProvider persistence>
      <ReportBuilder config={config} datasets={datasets} onBack={onBack} />
    </DuckvizDBProvider>
  );
}

<ReportBuilder> props

Prop

Type

The builder handles:

  • Instant scaffold — H1 title + H2 section headings + chart placeholders render immediately, no LLM wait.
  • Streaming generation — parallel SSE calls per section (3 concurrent) fill in written content as each completes.
  • In-place editing — Slate.js block editor (headings, lists, quotes, callouts, dividers, code, tables, images).
  • Chat modifications — right-panel chat re-writes sections via /api/modify-report-section.
  • Pre-export dialog — A4 preview, editable report + dashboard names, optional cover page, optional clickable table of contents (1.2.0).
  • Export — programmatic PDF (jsPDF) and DOCX (docx.js) that walk StructuredReport block by block. Only chart SVGs are rasterized; text and tables stay vector/native. Data-table widgets export as real tables (first 30 rows, 1.2.3); TOC bookmarks are clickable in both formats (1.2.2).

<DeckBuilder> props

Same shape as ReportBuilder, plus:

Prop

Type

import { DeckBuilder } from "@duckviz/report";

<DeckBuilder
  config={config}
  datasets={datasets}
  onBack={handleBack}
  onPresenting={(p) => setFullscreen(p)}
/>

The deck builder generates a StructuredDeck (title + slides), renders a presenter UI, and exports to PowerPoint via pptxgenjs.

Pre-fetch async data — don't pass a Promise or loader

<ReportBuilder> and <DeckBuilder> accept a resolved array of DuckvizDataset. The async loader mode is Explorer-only — Report and Deck silently render empty if you pass a Promise, an async function, or a loader that returns datasets without an activeId. Await your fetch first, then pass the array.

Templates

import { REPORT_TEMPLATES, getRelevantTemplateIds } from "@duckviz/report";

// Find templates relevant to the data domains in your widgets
const domains = ["Sales & Revenue", "Customer Analytics"];
const relevant = getRelevantTemplateIds(domains);
// → ["executive-summary", "sales-analysis", ...]

const template = REPORT_TEMPLATES.find((t) => t.id === relevant[0]);

Each template declares its sections (SectionTemplate[]) and a systemPromptHint that steers the LLM's tone.

Power-user composition

If the high-level builders don't fit (custom toolbars, resizable panels, Mantine chrome), drop to the lower-level components:

import {
  ReportProvider,
  ReportPageContent,
  createReportCallbacks,
  DeckView,
  DeckRightPanel,
  DeckPresenter,
  DeckChartCapture,
  useDeckGeneration,
  exportToPptx,
} from "@duckviz/report";

createReportCallbacks({ customFetch, runQuery }) returns the SSE callback bundle that ReportProvider expects. You own the layout; the package owns the state machine.

exportToPptx(deck, options)

Standalone PPTX export — call from a button click, not only from <DeckBuilder>:

import { exportToPptx } from "@duckviz/report";

await exportToPptx(structuredDeck, {
  fileName: "q4-review.pptx",
  branding: { companyName: "Acme", logoDataUrl: "..." },
});

useDeckGeneration()

Low-level SSE hook for decks. Returns { phase, slides, start, modify, reset } — use when you render your own presenter chrome but still want the package's state machine driving the stream.

Server entry

API route code should import from the server subpath so React/DOM don't leak into server bundles:

import type {
  StructuredReport,
  ReportSection,
  StructuredDeck,
  Slide,
  DeckVariableSpec,
  ValidatedWidget,
} from "@duckviz/report/server";

import {
  // Template engine (pure, deterministic, no React)
  parse,
  evaluate,
  processTemplate,
  processTemplateRich,
  formatCurrency,
  formatPercent,
  formatDate,
  formatDuration,
  abbreviateNumber,
  TEMPLATE_SYNTAX_REFERENCE,

  // Prompt builders
  REPORT_SYSTEM_PROMPT,
  buildSectionPrompt,
  DECK_SYSTEM_PROMPT,
  buildChartSlidePrompt,
  buildSummarySlidePrompt,

  // Server-side spec validator (runs after LLM output)
  validateSpecs,
} from "@duckviz/report/server";

Template engine

Slides can declare variables ({{metric}}, {{delta | percent}}) that are evaluated server-side against the widget's SQL result. parseevaluateprocessTemplate is the full pipeline; the format* helpers drive number / currency / date rendering. TEMPLATE_SYNTAX_REFERENCE is a string constant you can paste into a system prompt so the LLM emits syntactically correct templates. See /docs/guides/deck-integration for the end-to-end flow.

Export types

The @duckviz/report/server entry is the canonical place for all non-React report/deck types — use it from any Next.js API route, Node proxy, or Cloudflare Worker.

Exported types

// Client (default entry)
import type {
  ReportBuilderProps,
  DeckBuilderProps,
  BuilderDashboardConfig,
  BuilderWidget,
  CompanyBranding,
  ReportSection,
  StructuredReport,
  StructuredDeck,
  BuildPhase,
  DeckBuildPhase,
  WidgetInput,
  FetchFn,
} from "@duckviz/report";

// Server (API routes only)
import type { StructuredReport, ReportSection } from "@duckviz/report/server";

Styles

The package auto-injects its stylesheet via "sideEffects": ["*.css"]. If your bundler strips side-effects, import explicitly:

import "@duckviz/report/styles.css";