TABLE / CHART Advanced
Beyond their basic rendering, the table and chart templates each carry a set of advanced
capabilities: TABLE can show the SQL that produced it and let the user take the data away,
CHART can offer several renderings of one dataset and let the user switch. Neither needs
any props — fill the fields in the template and the UI appears.
Pick an Example
Two tabs sit above the table — Table and SQL — and the Table tab's title row carries a download button (CSV / JSON Lines).
TABLE: the SQL Tab
The table object has two optional fields:
| Field | Type | Purpose |
|---|---|---|
sql | string | The query behind the table, shown in the SQL tab as a syntax-highlighted code block |
sqlExplanation | string | An explanation, rendered as Markdown below the SQL |
Either one being present grows a Table / SQL tab pair above the table; with neither, no
tab row renders at all and it is just an ordinary table. The two fields are independent —
supplying only sqlExplanation is valid, and the SQL tab then holds just the prose.
TABLE: Download
The download button sits at the right of the title row, on the condition that you are on the Table tab and there is data (it disappears on the SQL tab). It opens two choices:
| Format | Output |
|---|---|
| CSV | Comma-separated; a value containing ,, " or a newline is quoted and its " escaped to "". The file is prefixed with a BOM so Excel reads non-ASCII text correctly |
| JSON Lines | One JSON object per line, keyed by each column's key |
The filename is <title>_<YYYY-MM-DD>.<ext>, with non-alphanumeric characters in the title
replaced by underscores.
pagination only governs how many rows are on screen; the download always exports the whole of
data. The entire file is generated in the browser from the current data — the backend is never
asked again.
Both formats export the formatted display values, not the raw ones — a CURRENCY column comes
out as "$135,000", not 135000. If you need the underlying numbers for further computation,
this is not the right source.
A snackbar appears afterwards offering to open the file you just downloaded, and dismisses itself after 5 seconds.
TABLE: Columns and Formats
interface TableData {
rowType: "OBJECT" | "ARRAY";
columns: { header: string; key?: string; format?: "DATE" | "DATE_TIME" | "CURRENCY" }[];
pagination: { size: number } | null;
data: Record<string, unknown>[] | unknown[][];
sql?: string;
sqlExplanation?: string;
}
rowType decides how a cell is read out of a row, and what the header looks like:
OBJECT— each row is an object, read viacolumn.key. The header shows two lines:headerabove,keybelow.ARRAY— each row is an array, read positionally (keyis unused). The header is the singleheaderline.
format governs the cell's presentation: DATE goes through toLocaleDateString(),
DATE_TIME through toLocaleString(), and CURRENCY formats as TWD with no decimals. Without
a format the value is stringified as-is; null / undefined always render as an empty
string.
CHART: Switching Between Renderings
chartOptions is an array, one entry per rendering:
| Field | Type | Purpose |
|---|---|---|
chartOptions[].type | string | The identifier defaultChart matches against |
chartOptions[].title | string | The name shown in the dropdown |
chartOptions[].spec | object | A Vega or Vega-Lite spec |
defaultChart | string | The type selected initially |
With a single option the chart still renders, but there is nothing to switch to, so no dropdown is drawn.
A defaultChart that matches no type silently falls back to the first option's spec
rather than erroring — so a typo just quietly renders the first chart.
{
type: MessageTemplateType.CHART,
title: "Sales by Region",
text: "The same data in two renderings.",
defaultChart: "bar",
chartOptions: [
{ type: "bar", title: "Bar", spec: barSpec },
{ type: "line", title: "Line", spec: lineSpec },
],
}
CHART: the SDK Rewrites Your Spec
The spec you supply is not drawn verbatim. The SDK clones it and changes two things:
- The background is forced to white. Deliberately — Vega's default axis and legend text is dark, and a transparent background would make it nearly invisible under a dark theme.
- The width tracks the container. A
ResizeObservermeasures the wrapper and writes it intospec.width, addingautosize: { type: "fit", resize: true, contains: "padding" }. So awidthof your own is overwritten;heightis left alone.
Vega's built-in actions menu (the save / view-source affordance) is always disabled.
See Also
- Message Templates — the full template catalog
- Theme — chat surface colors (the chart background is exempt, see above)