Grid state for AI models
"Show me the pending orders in the Aegean, biggest first, and hide the SKU." That is a filter, a sort and a hidden column, and the grid already supports all three. A language model can produce that state if it knows exactly what the state looks like for this grid. The grid gives you three calls for that:
| Call | What it does |
|---|---|
getAiSchema(options?) | A JSON Schema of the grid's state, limited to what its columns allow |
getAiState(options?) | The current state in that shape, for the model to start from |
applyAiState(answer, options?) | Checks the model's answer and applies the valid part in one step |
There is no model inside the grid, and the grid sends nothing anywhere. You use
your own model (any model that supports structured output, or any that can
return JSON) and call it from your own code, usually your server. The calls are
on the controller, so they work the same in React (onReady) and in plain
JavaScript (grid.controller). Enterprise only: in Community they throw an
error that names the feature.
The PivotGrid has the same for its layout of fields, measures and totals: see Pivot layout for AI models.
A round trip
// In the browser: describe the grid and its current state.
const schema = controller.getAiSchema();
const current = controller.getAiState();
// On your server: ask your own model, with the schema as its output format.
const answer = await askModel({
instructions:
'You change the state of a data grid. Start from the current state and ' +
'change only what the user asks for.',
input: `Current state: ${JSON.stringify(current)}\nRequest: ${userText}`,
outputSchema: schema,
});
// Back in the browser: apply what came back.
const { applied, dropped } = controller.applyAiState(answer);
if (dropped.length > 0) showNotice(`Could not apply: ${dropped.join('; ')}`);
askModel stands for your own call to your model's API. Most APIs that support
structured output accept the schema as it is. answer can be the parsed object
or the JSON text the API returns.
What the schema covers
| Part | Property | Notes |
|---|---|---|
filter | filter | One condition or a tree of and / or groups. null clears it |
sort | sort | Outermost first; [] for unsorted |
grouping | groupBy | Outermost first; [] for none. Not offered on a tree grid |
columnVisibility | columns[].visible | Only the columns listed change |
columnSizing | columns[].width | Pixels, or null to leave as is |
columnPinning | columns[].pinned | 'left', 'right' or null |
search | searchText | The toolbar's free-text search |
The schema only offers what the grid would accept. A column with
sortable: false is not in the sort list, a column with groupable: false is
not in the grouping list, and each data type gets the operators the
filter builder offers for it. Text columns get
contains / starts with / ends with. Numbers and dates get comparisons and
between. Dates also get period, which keeps "this month" meaning this month
tomorrow. Yes/no columns get =, and every column gets is blank / is not blank.
The schema's description lists every column with its id, header, type and meaning, so a model can map "biggest" to the right number column.
Strict structured output
Every object in the schema lists all of its properties as required and allows
no others. "Leave this alone" is written as an explicit null or []. That is
the subset the strictest structured-output modes accept, and any other JSON
Schema consumer accepts it too. The schema is draft 2020-12. The filter is
recursive through $defs.
Telling the model more
const options = {
// Parts the model may not change. They are also left out of the schema.
exclude: ['columnSizing', 'columnPinning'],
columns: {
revenue: { description: 'Order total in USD, VAT included' },
// List this column's values, so "orders from Izmir" matches what is stored.
city: { includeValues: true },
// Never mention this column to the model.
customerEmail: { exclude: true },
},
maxValues: 50, // most values listed per column (default 100)
};
controller.getAiSchema(options);
controller.applyAiState(answer, options); // same options on the way back
Data values are listed only when you ask. includeValues sends the
column's distinct values to whatever model you use, so turn it on only for
columns whose values you are allowed to share. A column with a static lookup
list is the exception: that list is your own code → label table (say izm
shown as "Izmir"), and a model cannot match a coded value without it. It is
listed by default. Set includeValues: false to keep it out.
What applyAiState does with an answer
- Keeps the valid part. A condition on a column that does not exist, an operator that does not suit the column's type, a sort on a column that cannot be sorted: each one is dropped, and the rest is applied.
- Says what it dropped.
droppedlists each problem in words, such asfilter on country (=): no such column. Show it to the user, log it, or send it back to the model for a second try. WithdebugModethe console prints it as well. - Never throws on a bad answer. Text that is not JSON, or JSON that is not
an object, returns
applied: []with the reason. - Applies everything in one step. The filter, sort, grouping and columns change in one view change, so the grid recomputes once.
- Puts single-column conditions in that column's own filter. There the
filter row and the header popup show them, and the user can clear them the
usual way. Only conditions that span columns (an
oracross two of them) go to the filter builder's tree. - Will not hide every column. An answer that would leave no visible column keeps the visibility as it was.
- Ignores parts it was told to leave alone. If
excludenames a part, that part is ignored even when the model sends it.
What it does not do
- No model, prompt or chat box. The schema and the checks are the grid's part. The conversation, the model and the API key belong to your application.
- Not saved views. A saved view (
getViewState) holds more than the model should touch: pages, open groups, chart state. Use saved views to remember a layout and this API to change one from words. - No row data in the schema. The model sees columns and, only when you allow it, the distinct values of a column. It never sees rows. Questions about the data ("which region sold most?") need the data, which is a different feature.