/* Minimal styles for the natural language grid state example */
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.input-group {
display: flex;
margin-bottom: 15px;
align-items: center;
}
.examples {
background: #e7f3ff;
padding: 15px;
border-radius: 4px;
margin: 15px 0;
border-left: 4px solid #007bff;
}
.examples ul {
margin: 10px 0 0 20px;
}
.examples li {
margin: 5px 0;
}
#processingStatus {
margin-top: 15px;
}
#aiResponse,
#currentState {
margin-top: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
border: 1px solid #dee2e6;
}
#currentState {
max-height: 300px;
overflow-y: auto;
}
#aiResponse pre,
#currentState pre {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 12px;
border: 1px solid #e9ecef;
}
#aiResponse h4,
#currentState h4 {
margin: 0 0 10px 0;
color: #495057;
}
@media (max-width: 768px) {
.input-group {
flex-direction: column;
}
.input-group input {
margin-bottom: 10px;
margin-right: 0 !important;
}
}
import type { GridApi } from 'ag-grid-community';
import { generateChatGPTSchema } from './chatgptSchema';
import type { ChatGPTGridStateResponse } from './gridStateSchema';
// OpenAI API configuration
const OPENAI_API_URL = 'https://a2zblo9oti.execute-api.us-west-1.amazonaws.com';
const MODEL = 'gpt-4.1-mini';
/**
* Calls ChatGPT API to process natural language requests for grid state changes
* Returns a Promise that resolves to the ChatGPT response
*/
export function callChatGPT(
userRequest: string,
currentState: any,
gridApi: GridApi
): Promise<ChatGPTGridStateResponse> {
return new Promise((resolve, reject) => {
const columnDefs = gridApi.getColumnDefs();
if (!columnDefs) {
reject(new Error('Column definitions not available'));
return;
}
const schema = generateChatGPTSchema(columnDefs);
const systemPrompt = `You are an AG-Grid state management assistant. You help users modify grid configuration using natural language commands.
The schema defines which columns can be used in different contexts based on their configuration:
- Only sortable columns can be used in sort operations
- Only groupable columns can be used for row grouping
- Only pivotable columns can be used for pivoting
- Only aggregatable columns can be used for aggregation
- Only resizable columns can have their width/flex changed
- Only pinnable columns can be pinned
Current grid state: ${JSON.stringify(currentState)}
Respond with only the necessary state changes, not the complete state. Provide a clear explanation of what you changed.
Any unchanged properties that are present in the current state must be included in \`propertiesToIgnore\`. Otherwise they will be removed from the state.
Important: Only modify the properties that the user specifically requested. If they ask to "hide the age column", only include columnVisibility in your response, not other unrelated properties.`;
const requestBody = {
model: MODEL,
input: [
{
role: 'system',
content: systemPrompt,
},
{
role: 'user',
content: userRequest,
},
],
max_output_tokens: 2048,
text: {
format: {
type: 'json_schema',
name: 'grid_state_response',
strict: true,
schema,
},
},
};
fetch(OPENAI_API_URL, {
method: 'POST',
body: JSON.stringify(requestBody),
})
.then((response) => {
if (!response.ok) {
return response.json().then((error) => {
throw new Error(`OpenAI API error: ${error.error?.message || 'Unknown error'}`);
});
}
return response.json();
})
.then((data) => {
console.log(data);
if (!data.output[0]?.content[0]) {
throw new Error('Unknown Error');
}
const content = data.output[0].content[0];
if (!content) {
throw new Error('No response content received');
}
try {
const parsedResponse = JSON.parse(content.text);
resolve(parsedResponse);
} catch (e) {
throw new Error('Invalid JSON response from OpenAI');
}
})
.catch((error) => {
reject(error);
});
});
}
import type { ColDef, ColGroupDef } from 'ag-grid-community';
import { type JSONSchema, createEnumSchema, createObjectSchema } from './schema';
/**
* Column analysis result for schema generation
*/
interface ColumnAnalysis {
allColumnIds: string[];
sortableColumnIds: string[];
filterableColumnIds: string[];
groupableColumnIds: string[];
pivotableColumnIds: string[];
aggregatableColumnIds: string[];
resizableColumnIds: string[];
pinnableColumnIds: string[];
}
/**
* Analyzes column definitions to determine which columns can be used in different contexts
*/
function analyzeColumns(columnDefs: (ColDef | ColGroupDef)[]): ColumnAnalysis {
const analysis: ColumnAnalysis = {
allColumnIds: [],
sortableColumnIds: [],
filterableColumnIds: [],
groupableColumnIds: [],
pivotableColumnIds: [],
aggregatableColumnIds: [],
resizableColumnIds: [],
pinnableColumnIds: [],
};
function processColumn(colDef: ColDef | ColGroupDef) {
if ('children' in colDef && colDef.children) {
// Process column group children
colDef.children.forEach(processColumn);
} else {
const col = colDef as ColDef;
const colId = col.field || col.colId;
if (!colId) return;
analysis.allColumnIds.push(colId);
// Check if sortable (default true unless explicitly false)
if (col.sortable !== false) {
analysis.sortableColumnIds.push(colId);
}
// Check if filterable (default true unless explicitly false)
if (col.filter !== false && col.filter !== null) {
analysis.filterableColumnIds.push(colId);
}
// Check if can be used for row grouping
if (col.enableRowGroup !== false && col.rowGroup !== false) {
analysis.groupableColumnIds.push(colId);
}
// Check if can be used for pivoting
if (col.enablePivot !== false && col.pivot !== false) {
analysis.pivotableColumnIds.push(colId);
}
// Check if can be aggregated (typically numeric columns)
if (col.enableValue !== false && (col.aggFunc || col.type === 'number')) {
analysis.aggregatableColumnIds.push(colId);
}
// Check if resizable (default true unless explicitly false)
if (col.resizable !== false) {
analysis.resizableColumnIds.push(colId);
}
// Check if pinnable (usually all columns can be pinned)
analysis.pinnableColumnIds.push(colId);
}
}
columnDefs.forEach(processColumn);
return analysis;
}
/**
* Generates a dynamic ChatGPT schema based on column definitions
*/
export function generateChatGPTSchema(columnDefs: (ColDef | ColGroupDef)[]): JSONSchema {
const analysis = analyzeColumns(columnDefs);
return createObjectSchema({
description: 'Response for modifying AG-Grid state using natural language commands',
$defs: {
allColumnId: createEnumSchema({
enum: analysis.allColumnIds,
description: 'Valid column ID from the grid',
}),
sortableColumnId: createEnumSchema({
enum: analysis.sortableColumnIds,
description: 'Column ID that supports sorting',
}),
filterableColumnId: createEnumSchema({
enum: analysis.filterableColumnIds,
description: 'Column ID that supports filtering',
}),
groupableColumnId: createEnumSchema({
enum: analysis.groupableColumnIds,
description: 'Column ID that supports row grouping',
}),
pivotableColumnId: createEnumSchema({
enum: analysis.pivotableColumnIds,
description: 'Column ID that supports pivoting',
}),
aggregatableColumnId: createEnumSchema({
enum: analysis.aggregatableColumnIds,
description: 'Column ID that supports aggregation',
}),
resizableColumnId: createEnumSchema({
enum: analysis.resizableColumnIds,
description: 'Column ID that supports resizing',
}),
pinnableColumnId: createEnumSchema({
enum: analysis.pinnableColumnIds,
description: 'Column ID that can be pinned',
}),
sortDirection: createEnumSchema({
enum: ['asc', 'desc'],
description: 'Sort direction: ascending or descending',
}),
aggregationFunction: createEnumSchema({
enum: ['sum', 'avg', 'min', 'max', 'count', 'first', 'last'],
description: 'Aggregation function to apply to numeric columns',
}),
sideBarPosition: createEnumSchema({ enum: ['left', 'right'], description: 'Position of the sidebar' }),
toolPanelId: createEnumSchema({ enum: ['columns', 'filters'], description: 'Tool panel identifier' }),
gridStateProperty: createEnumSchema({
enum: [
'aggregation',
'columnGroup',
'columnOrder',
'columnPinning',
'columnSizing',
'columnVisibility',
'filter',
'focusedCell',
'pagination',
'rowPinning',
'pivot',
'cellSelection',
'rowGroup',
'rowGroupExpansion',
'rowSelection',
'scroll',
'sideBar',
'sort',
],
description: 'Grid state property that can be ignored when setting state',
}),
filterModel: createObjectSchema({
description: 'Column filter configurations where each key is a column ID',
properties: analysis.filterableColumnIds.reduce(
(prev, current) => ({ ...prev, [current]: { $ref: '#/$defs/columnFilter' } }),
{}
),
}),
columnFilter: {
anyOf: [
{ $ref: '#/$defs/textFilter' },
{ $ref: '#/$defs/numberFilter' },
{ $ref: '#/$defs/dateFilter' },
{ $ref: '#/$defs/setFilter' },
{ $ref: '#/$defs/multiFilter' },
],
},
textFilter: createObjectSchema({
properties: {
filterType: { type: 'string', enum: ['text'] },
type: {
type: 'string',
enum: [
'equals',
'notEqual',
'contains',
'notContains',
'startsWith',
'endsWith',
'blank',
'notBlank',
],
},
filter: { type: 'string' },
},
}),
numberFilter: createObjectSchema({
properties: {
filterType: { type: 'string', enum: ['number'] },
type: {
type: 'string',
enum: [
'equals',
'notEqual',
'lessThan',
'lessThanOrEqual',
'greaterThan',
'greaterThanOrEqual',
'inRange',
'blank',
'notBlank',
],
},
filter: { type: 'number' },
filterTo: { type: 'number' },
},
}),
dateFilter: createObjectSchema({
properties: {
filterType: { type: 'string', enum: ['date'] },
type: {
type: 'string',
enum: ['equals', 'notEqual', 'lessThan', 'greaterThan', 'inRange', 'blank', 'notBlank'],
},
dateFrom: { type: 'string' },
dateTo: { type: 'string' },
},
}),
setFilter: createObjectSchema({
properties: {
filterType: { type: 'string', enum: ['set'] },
values: {
type: 'array',
items: { type: 'string' },
},
},
}),
multiFilter: createObjectSchema({
properties: {
filterType: { type: 'string', enum: ['multi'] },
operator: { type: 'string', enum: ['AND', 'OR'] },
conditions: {
type: 'array',
items: { $ref: '#/$defs/columnFilter' },
},
},
}),
advancedFilterModel: {
anyOf: [{ $ref: '#/$defs/advancedFilterCondition' }, { $ref: '#/$defs/advancedFilterJoin' }],
},
advancedFilterCondition: createObjectSchema({
properties: {
filterType: { type: 'string' },
colId: { $ref: '#/$defs/filterableColumnId' },
type: { type: 'string' },
filter: { anyOf: [{ type: 'string' }, { type: 'number' }] },
filterTo: { anyOf: [{ type: 'string' }, { type: 'number' }] },
},
}),
advancedFilterJoin: createObjectSchema({
properties: {
filterType: { type: 'string', enum: ['join'] },
type: { type: 'string', enum: ['AND', 'OR'] },
conditions: {
type: 'array',
items: { $ref: '#/$defs/advancedFilterModel' },
},
},
}),
rowPosition: createObjectSchema({
properties: {
rowIndex: { type: 'number', minimum: 0 },
rowPinned: { anyOf: [{ type: 'string', enum: ['top', 'bottom'] }, { type: 'null' }] },
},
}),
toolPanelConfig: createObjectSchema({
properties: {
id: { type: 'string' },
labelDefault: { type: 'string' },
labelKey: { type: 'string' },
iconKey: { type: 'string' },
toolPanel: { type: 'string' },
},
}),
serverSideRowSelection: createObjectSchema({
properties: {
selectAll: { type: 'boolean' },
toggledNodes: {
type: 'array',
items: { type: 'string' },
},
},
}),
},
properties: {
gridState: createObjectSchema({
description: 'The grid state changes to apply. Only include properties that need to be modified.',
properties: {
version: {
type: 'string',
description: 'Grid state version',
},
columnOrder: {
type: 'object',
description: 'Change the order of columns in the grid',
properties: {
orderedColIds: {
type: 'array',
description: 'Array of column IDs in the desired order',
items: { $ref: '#/$defs/allColumnId' },
},
},
required: ['orderedColIds'],
additionalProperties: false,
},
columnPinning: {
type: 'object',
description: 'Pin or unpin columns to the left or right side of the grid',
properties: {
leftColIds: {
type: 'array',
description: 'Column IDs to pin to the left side',
items: { $ref: '#/$defs/pinnableColumnId' },
},
rightColIds: {
type: 'array',
description: 'Column IDs to pin to the right side',
items: { $ref: '#/$defs/pinnableColumnId' },
},
},
required: ['leftColIds', 'rightColIds'],
additionalProperties: false,
},
columnSizing: {
type: 'object',
description: 'Resize columns by setting width or flex values',
properties: {
columnSizingModel: {
type: 'array',
description: 'Array of column sizing configurations',
items: {
anyOf: [
{
type: 'object',
properties: {
colId: {
$ref: '#/$defs/resizableColumnId',
},
width: {
type: 'number',
description: 'Fixed width in pixels',
minimum: 20,
},
},
required: ['colId', 'width'],
additionalProperties: false,
},
{
type: 'object',
properties: {
colId: {
$ref: '#/$defs/resizableColumnId',
},
flex: {
type: 'number',
description: 'Flexible sizing ratio',
minimum: 0,
},
},
required: ['colId', 'flex'],
additionalProperties: false,
},
],
},
},
},
required: ['columnSizingModel'],
additionalProperties: false,
},
columnVisibility: {
type: 'object',
description: 'Show or hide columns in the grid',
properties: {
hiddenColIds: {
type: 'array',
description: 'Array of column IDs to hide',
items: { $ref: '#/$defs/allColumnId' },
},
},
required: ['hiddenColIds'],
additionalProperties: false,
},
filter: createObjectSchema({
description: 'Apply filters to the grid data',
properties: {
filterModel: {
$ref: '#/$defs/filterModel',
},
columnFilterState: createObjectSchema({
description: 'Column filter state information (rarely used)',
properties: {},
}),
advancedFilterModel: {
$ref: '#/$defs/advancedFilterModel',
},
},
}),
sort: {
type: 'object',
description: 'Sort the grid data by one or more columns',
properties: {
sortModel: {
type: 'array',
description: 'Array of sort configurations',
items: {
type: 'object',
properties: {
colId: {
$ref: '#/$defs/sortableColumnId',
},
sort: {
$ref: '#/$defs/sortDirection',
},
},
required: ['colId', 'sort'],
additionalProperties: false,
},
},
},
required: ['sortModel'],
additionalProperties: false,
},
rowGroup: {
type: 'object',
description: 'Group rows by one or more columns',
properties: {
groupColIds: {
type: 'array',
description: 'Array of column IDs to group by',
items: { $ref: '#/$defs/groupableColumnId' },
},
},
required: ['groupColIds'],
additionalProperties: false,
},
pivot: {
type: 'object',
description: 'Enable pivot mode and set pivot columns',
properties: {
pivotMode: {
type: 'boolean',
description: 'Whether pivot mode is enabled',
},
pivotColIds: {
type: 'array',
description: 'Array of column IDs to use as pivot columns',
items: { $ref: '#/$defs/pivotableColumnId' },
},
},
required: ['pivotMode', 'pivotColIds'],
additionalProperties: false,
},
aggregation: {
type: 'object',
description: 'Apply aggregation functions to columns',
properties: {
aggregationModel: {
type: 'array',
description: 'Array of aggregation configurations',
items: {
type: 'object',
properties: {
colId: {
$ref: '#/$defs/aggregatableColumnId',
},
aggFunc: {
$ref: '#/$defs/aggregationFunction',
},
},
required: ['colId', 'aggFunc'],
additionalProperties: false,
},
},
},
required: ['aggregationModel'],
additionalProperties: false,
},
rowSelection: {
anyOf: [
{
type: 'array',
description: 'Array of row IDs to select',
items: { type: 'string' },
},
{
$ref: '#/$defs/serverSideRowSelection',
},
],
},
pagination: {
type: 'object',
description: 'Control pagination settings',
properties: {
page: {
type: 'number',
description: 'Current page number (0-based)',
minimum: 0,
},
pageSize: {
type: 'number',
description: 'Number of rows per page',
minimum: 1,
},
},
required: ['page', 'pageSize'],
additionalProperties: false,
},
sideBar: {
type: 'object',
description: 'Control sidebar visibility and tool panels',
properties: {
visible: {
type: 'boolean',
description: 'Whether the sidebar is visible',
},
position: {
$ref: '#/$defs/sideBarPosition',
},
openToolPanel: {
anyOf: [{ type: 'string' }, { type: 'null' }],
},
toolPanels: createObjectSchema({
description: 'Tool panel configurations',
properties: {},
}),
},
required: ['visible', 'position', 'openToolPanel', 'toolPanels'],
additionalProperties: false,
},
rowGroupExpansion: {
type: 'object',
description: 'Control which row groups are expanded',
properties: {
expandedRowGroupIds: {
type: 'array',
description: 'Array of row group IDs that should be expanded',
items: { type: 'string' },
},
},
required: ['expandedRowGroupIds'],
additionalProperties: false,
},
rowPinning: {
type: 'object',
description: 'Pin specific rows to top or bottom of the grid',
properties: {
top: {
type: 'array',
description: 'Array of row IDs to pin to the top',
items: { type: 'string' },
},
bottom: {
type: 'array',
description: 'Array of row IDs to pin to the bottom',
items: { type: 'string' },
},
},
required: ['top', 'bottom'],
additionalProperties: false,
},
cellSelection: {
type: 'object',
description: 'Select specific cell ranges',
properties: {
cellRanges: {
type: 'array',
description: 'Array of cell range selections',
items: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'Unique identifier for the cell range',
},
type: {
type: 'string',
description: 'Type of cell range',
},
startRow: {
$ref: '#/$defs/rowPosition',
},
endRow: {
$ref: '#/$defs/rowPosition',
},
colIds: {
type: 'array',
description: 'Array of column IDs in the range',
items: { $ref: '#/$defs/allColumnId' },
},
startColId: {
$ref: '#/$defs/allColumnId',
},
},
required: ['id', 'type', 'startRow', 'endRow', 'colIds', 'startColId'],
additionalProperties: false,
},
},
},
required: ['cellRanges'],
additionalProperties: false,
},
focusedCell: {
type: 'object',
description: 'Set the currently focused cell',
properties: {
rowIndex: {
type: 'number',
description: 'Row index of the focused cell',
minimum: 0,
},
rowPinned: {
anyOf: [{ type: 'string' }, { type: 'null' }],
},
colId: {
$ref: '#/$defs/allColumnId',
},
},
required: ['rowIndex', 'rowPinned', 'colId'],
additionalProperties: false,
},
scroll: {
type: 'object',
description: 'Set the scroll position of the grid',
properties: {
top: {
type: 'number',
description: 'Vertical scroll position in pixels',
minimum: 0,
},
left: {
type: 'number',
description: 'Horizontal scroll position in pixels',
minimum: 0,
},
},
required: ['top', 'left'],
additionalProperties: false,
},
columnGroup: {
type: 'object',
description: 'Control which column groups are open',
properties: {
openColumnGroupIds: {
type: 'array',
description: 'Array of column group IDs that should be open',
items: { type: 'string' },
},
},
required: ['openColumnGroupIds'],
additionalProperties: false,
},
},
}),
propertiesToIgnore: {
type: 'array',
description: 'Grid state properties to ignore when applying the state (optional)',
items: { $ref: '#/$defs/gridStateProperty' },
},
explanation: {
type: 'string',
description: 'Human-readable explanation of what changes were made to the grid state',
},
},
});
}
import type { GridState, GridStateKey } from 'ag-grid-community';
/**
* Response format for ChatGPT when modifying AG-Grid state
* Uses the official AG-Grid GridState interface
*/
export interface ChatGPTGridStateResponse {
/**
* The grid state object that will be passed to gridApi.setState()
* Uses the official AG-Grid GridState interface
*/
gridState: GridState;
/**
* Properties to ignore when setting state (optional)
*/
propertiesToIgnore?: GridStateKey[];
/**
* Human-readable explanation of what changes were made
*/
explanation: string;
}
export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null' | 'anyOf';
export interface SchemaProperty {
type: JSONSchemaType | JSONSchemaType[];
description?: string;
$defs?: Record<string, JSONSchema>;
}
export interface ReferencedProperty {
$ref: string;
description?: never;
}
export interface AnyOfSchema {
anyOf: JSONSchema[];
}
export type StringFormat = 'date-time' | 'date' | 'time' | 'duration' | 'email' | 'hostname' | 'ipv4' | 'ipv6' | 'uuid';
export interface StringSchema extends SchemaProperty {
type: 'string';
pattern?: string;
format?: StringFormat;
}
export const createStringSchema = (schema: Omit<StringSchema, 'type'>): StringSchema => ({
type: 'string',
...schema,
});
export interface EnumSchema extends SchemaProperty {
type: 'string';
enum: (string | number | boolean)[];
}
export const createEnumSchema = (schema: Omit<EnumSchema, 'type'>): EnumSchema => ({
type: 'string',
...schema,
});
export interface NumberSchema extends SchemaProperty {
type: 'number' | 'integer';
minimum?: number;
maximum?: number;
exclusiveMinimum?: number;
exclusiveMaximum?: number;
multipleOf?: number;
}
export const createNumberSchema = (schema: Omit<NumberSchema, 'type'>): NumberSchema => ({
type: 'number',
...schema,
});
export interface BooleanSchema extends SchemaProperty {
type: 'boolean';
}
export const createBooleanSchema = (schema: Omit<BooleanSchema, 'type'>): BooleanSchema => ({
type: 'boolean',
...schema,
});
export interface ArraySchema extends SchemaProperty {
type: 'array';
items: JSONSchema;
minItems?: number;
maxItems?: number;
}
export const createArraySchema = (schema: Omit<ArraySchema, 'type'>): ArraySchema => ({
type: 'array',
...schema,
});
export interface ObjectSchema extends SchemaProperty {
type: 'object';
properties: Record<string, JSONSchema>;
required: string[];
additionalProperties: false;
minProperties?: number;
maxProperties?: number;
}
export const createObjectSchema = (
schema: Omit<ObjectSchema, 'type' | 'additionalProperties' | 'required'>
): ObjectSchema => ({
type: 'object',
required: Object.keys(schema.properties),
additionalProperties: false,
...schema,
});
export interface NullSchema extends SchemaProperty {
type: 'null';
}
export const createNullSchema = (schema: Omit<NullSchema, 'type'>): NullSchema => ({
type: 'null',
...schema,
});
export type JSONSchema =
| StringSchema
| EnumSchema
| NumberSchema
| BooleanSchema
| ObjectSchema
| ArraySchema
| NullSchema
| ReferencedProperty
| AnyOfSchema;
export interface ChatGPTJSONSchema {
name: string;
description?: string;
strict: true;
schema: JSONSchema;
}
'use client';
import React, { useCallback, useMemo, useRef, useState, StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { AgGridReact } from 'ag-grid-react';
import './styles.css';
import { AllCommunityModule, ColDef, ColGroupDef, GridApi, GridOptions, ModuleRegistry, SideBarDef } from 'ag-grid-community';
import { AllEnterpriseModule } from 'ag-grid-enterprise';
import { callChatGPT } from './chatgptApi';
ModuleRegistry.registerModules([AllCommunityModule, AllEnterpriseModule]);
import { useFetchJson } from './useFetchJson';
interface IOlympicData {
athlete: string;
age: number;
country: string;
year: number;
sport: string;
gold: number;
silver: number;
bronze: number;
total: number;
}
const GridExample = () => {
const gridRef = useRef<AgGridReact<IOlympicData>>(null);
const containerStyle = useMemo(() => ({ width: '100%', height: '100%' }), []);
const gridStyle = useMemo(() => ({height: '100%', width: '100%'}), []);
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{
field: 'athlete',
headerName: 'Athlete',
minWidth: 200,
enableRowGroup: true,
enablePivot: false,
},
{
field: 'age',
headerName: 'Age',
width: 90,
type: 'number',
enableValue: true,
enableRowGroup: false,
},
{
field: 'country',
headerName: 'Country',
minWidth: 150,
enableRowGroup: true,
enablePivot: true,
},
{
field: 'year',
headerName: 'Year',
width: 90,
type: 'number',
enableRowGroup: true,
enableValue: false,
},
{
field: 'sport',
headerName: 'Sport',
minWidth: 150,
enableRowGroup: true,
enablePivot: true,
},
{
field: 'gold',
headerName: 'Gold',
width: 100,
type: 'number',
enableValue: true,
aggFunc: 'sum',
},
{
field: 'silver',
headerName: 'Silver',
width: 100,
type: 'number',
enableValue: true,
aggFunc: 'sum',
},
{
field: 'bronze',
headerName: 'Bronze',
width: 100,
type: 'number',
enableValue: true,
aggFunc: 'sum',
},
{
field: 'total',
headerName: 'Total',
width: 100,
type: 'number',
enableValue: true,
aggFunc: 'sum',
},
]);
const defaultColDef = useMemo<ColDef>(() => { return {
flex: 1,
minWidth: 100,
filter: true,
sortable: true,
resizable: true,
} }, []);
const sideBar = useMemo<SideBarDef | string | string[] | boolean | null>(() => { return {
toolPanels: [
{
id: 'columns',
labelDefault: 'Columns',
labelKey: 'columns',
iconKey: 'columns',
toolPanel: 'agColumnsToolPanel',
},
{
id: 'filters',
labelDefault: 'Filters',
labelKey: 'filters',
iconKey: 'filter',
toolPanel: 'agFiltersToolPanel',
},
],
defaultToolPanel: 'columns',
} }, []);
const paginationPageSizeSelector = useMemo<number[] | boolean>(() => { return [10, 20, 50, 100] }, []);
const { data, loading } = useFetchJson<IOlympicData>(
'https://www.ag-grid.com/example-assets/olympic-winners.json'
);
const processNaturalLanguageRequest = useCallback(() =>{
const inputElement = document.getElementById('naturalLanguageInput') as HTMLInputElement;
const outputElement = document.getElementById('aiResponse') as HTMLDivElement;
const statusElement = document.getElementById('processingStatus') as HTMLDivElement;
const userRequest = inputElement.value.trim();
if (!userRequest) {
outputElement.innerHTML = '<p style="color: red;">Please enter a request</p>';
return;
}
statusElement.innerHTML = '<p>Processing request with ChatGPT...</p>';
outputElement.innerHTML = '';
const currentState = gridRef.current!.api.getState();
callChatGPT(userRequest, currentState, gridRef.current!.api)
.then(function (response) {
// Apply the state changes
if (Object.keys(response.gridState).length > 0) {
gridRef.current!.api.setState(response.gridState, response.propertiesToIgnore);
}
// Display the response
statusElement.innerHTML = '<p style="color: green;">✓ Request processed successfully!</p>';
outputElement.innerHTML = `
<h4>AI Response:</h4>
<p>${response.explanation}</p>
`;
outputElement.style.display = 'block';
// Clear the input
inputElement.value = '';
})
.catch(function (error) {
statusElement.innerHTML = '<p style="color: red;">✗ Error processing request</p>';
outputElement.innerHTML = `<p style="color: red;">Error: ${error instanceof Error ? error.message : String(error)}</p>`;
outputElement.style.display = 'block';
});
}, [callChatGPT])
const getCurrentState = useCallback(() =>{
const state = gridRef.current!.api.getState();
const outputElement = document.getElementById('currentState') as HTMLDivElement;
outputElement.innerHTML = `<h4>Current Grid State:</h4><pre>${JSON.stringify(state, null, 2)}</pre>`;
outputElement.style.display = 'block';
}, [])
const resetGrid = useCallback(() =>{
gridRef.current!.api.setState({
columnVisibility: { hiddenColIds: [] },
columnPinning: { leftColIds: [], rightColIds: [] },
sort: { sortModel: [] },
filter: { filterModel: {} },
rowGroup: { groupColIds: [] },
pagination: { page: 0, pageSize: 20 },
});
const aiResponse = document.getElementById('aiResponse')!;
const processingStatus = document.getElementById('processingStatus')!;
const currentState = document.getElementById('currentState')!;
aiResponse.innerHTML = '';
aiResponse.style.display = 'none';
processingStatus.innerHTML = '';
currentState.innerHTML = '';
currentState.style.display = 'none';
}, [])
return (
<div style={containerStyle}>
<div className="example-wrapper">
<div>
<div className="input-group">
<input type="text" id="naturalLanguageInput" placeholder="Enter your request in natural language (e.g., 'hide age column', 'sort by gold medals')" autocomplete="off" style={{"flex":"1","padding":"8px","border":"1px solid #ddd","borderRadius":"4px","marginRight":"10px"}} />
<button id="processRequest" onClick={processNaturalLanguageRequest} style={{"padding":"8px 16px","background":"#007bff","color":"white","border":"none","borderRadius":"4px","cursor":"pointer"}}>
Process Request
</button>
</div>
<div style={{"background":"#fff3cd","border":"1px solid #ffeaa7","color":"#856404","padding":"15px","borderRadius":"4px","margin":"15px 0"}}>
<strong>Setup Required:</strong> This example requires an OpenAI API key to be configured. Set the <code>OPENAI_API_KEY</code> environment variable or update the API key in the source code.
</div>
<div className="examples" style={{"background":"#e7f3ff","padding":"15px","borderRadius":"4px","margin":"15px 0","borderLeft":"4px solid #007bff"}}>
<strong>Try these example commands:</strong>
<ul style={{"margin":"10px 0 0 20px"}}>
<li><code>hide the age column</code> - Hide the age column</li>
<li><code>sort by gold medals descending</code> - Sort by gold medals</li>
<li><code>group rows by country</code> - Group by country (only groupable columns)</li>
<li><code>pin athlete column to the left</code> - Pin the athlete column</li>
<li><code>aggregate gold medals using sum</code> - Apply aggregation (only numeric columns)</li>
<li><code>create pivot table with sport as columns</code> - Enable pivot mode</li>
<li><code>resize age column to 120 pixels</code> - Set column width</li>
</ul>
</div>
<div className="button-group">
<button onClick={getCurrentState}>Get Current State</button>
<button onClick={resetGrid}>Reset Grid</button>
</div>
<div id="processingStatus" style={{"marginTop":"15px"}}></div>
<div id="aiResponse" style={{"marginTop":"15px","padding":"15px","background":"#f8f9fa","borderRadius":"4px","display":"none"}}></div>
<div id="currentState" style={{"marginTop":"15px","padding":"15px","background":"#f8f9fa","borderRadius":"4px","display":"none","maxHeight":"300px","overflowY":"auto"}}></div>
</div>
<div style={gridStyle}>
<AgGridReact<IOlympicData>
ref={gridRef}
rowData={data}
loading={loading}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
enableRangeSelection={true}
rowGroupPanelShow={'always'}
sideBar={sideBar}
pagination={true}
paginationPageSize={20}
paginationPageSizeSelector={paginationPageSizeSelector}
/>
</div>
</div>
</div>
);
}
const root = createRoot(document.getElementById('root')!);
root.render(<StrictMode><GridExample /></StrictMode>);
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
}
import { useState, useEffect } from 'react';
/**
* Fetch example Json data
* Not recommended for production use!
*/
export const useFetchJson = <T,>(url:string, limit?: number) => {
const [data, setData] = useState<T[]>();
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
// Note error handling is omitted here for brevity
const response = await fetch(url);
const json = await response.json();
const data = limit ? json.slice(0, limit) : json;
setData(data);
setLoading(false);
};
fetchData();
}, [url, limit]);
return { data, loading };
};
if (typeof window !== 'undefined') {
var waitSeconds = 100;
var head = document.getElementsByTagName('head')[0];
var isWebkit = !!window.navigator.userAgent.match(/AppleWebKit\/([^ ;]*)/);
var webkitLoadCheck = function (link, callback) {
setTimeout(function () {
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href == link.href) return callback();
}
webkitLoadCheck(link, callback);
}, 10);
};
var cssIsReloadable = function cssIsReloadable(links) {
// Css loaded on the page initially should be skipped by the first
// systemjs load, and marked for reload
var reloadable = true;
forEach(links, function (link) {
if (!link.hasAttribute('data-systemjs-css')) {
reloadable = false;
link.setAttribute('data-systemjs-css', '');
}
});
return reloadable;
};
var findExistingCSS = function findExistingCSS(url) {
// Search for existing link to reload
var links = head.getElementsByTagName('link');
return filter(links, function (link) {
return link.href === url;
});
};
var noop = function () {};
var loadCSS = function (url, existingLinks) {
const stylesUrl = url.includes('styles.css') || url.includes('style.css');
return new Promise((outerResolve, outerReject) => {
setTimeout(
() => {
new Promise(function (resolve, reject) {
var timeout = setTimeout(function () {
reject('Unable to load CSS');
}, waitSeconds * 1000);
var _callback = function (error) {
clearTimeout(timeout);
link.onload = link.onerror = noop;
setTimeout(function () {
if (error) {
reject(error);
outerReject(error);
} else {
resolve('');
outerResolve('');
}
}, 7);
};
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
link.setAttribute('data-systemjs-css', '');
if (!isWebkit) {
link.onload = function () {
_callback();
};
} else {
webkitLoadCheck(link, _callback);
}
link.onerror = function (event) {
_callback(event.error || new Error('Error loading CSS file.'));
};
if (existingLinks.length) head.insertBefore(link, existingLinks[0]);
else head.appendChild(link);
})
// Remove the old link regardless of loading outcome
.then(
function (result) {
forEach(existingLinks, function (link) {
link.parentElement.removeChild(link);
});
return result;
},
function (err) {
forEach(existingLinks, function (link) {
link.parentElement.removeChild(link);
});
throw err;
}
);
},
stylesUrl ? 5 : 0
);
});
};
exports.fetch = function (load) {
// dont reload styles loaded in the head
var links = findExistingCSS(load.address);
if (!cssIsReloadable(links)) return '';
return loadCSS(load.address, links);
};
} else {
var builderPromise;
function getBuilder(loader) {
if (builderPromise) return builderPromise;
return (builderPromise = System['import']('./css-plugin-base.js', module.id).then(function (CSSPluginBase) {
return new CSSPluginBase(function compile(source, address) {
return {
css: source,
map: null,
moduleSource: null,
moduleFormat: null,
};
});
}));
}
exports.cssPlugin = true;
exports.fetch = function (load, fetch) {
if (!this.builder) return '';
return fetch(load);
};
exports.translate = function (load, opts) {
if (!this.builder) return '';
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.translate.call(loader, load, opts);
});
};
exports.instantiate = function (load, opts) {
if (!this.builder) return;
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.instantiate.call(loader, load, opts);
});
};
exports.bundle = function (loads, compileOpts, outputOpts) {
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.bundle.call(loader, loads, compileOpts, outputOpts);
});
};
exports.listAssets = function (loads, opts) {
var loader = this;
return getBuilder(loader).then(function (builder) {
return builder.listAssets.call(loader, loads, opts);
});
};
}
// Because IE8?
function filter(arrayLike, func) {
var arr = [];
forEach(arrayLike, function (item) {
if (func(item)) arr.push(item);
});
return arr;
}
// Because IE8?
function forEach(arrayLike, func) {
for (var i = 0; i < arrayLike.length; i++) {
func(arrayLike[i]);
}
}
(function (global) {
process = { env: { NODE_ENV: 'development' } };
// Valid versions (18/19)
const config = { version: 19, isProd: false };
const filePart = config.isProd ? 'production.min' : 'development';
const reactConfig =
config.version == 18
? {
map: {
react: 'npm:react@18.2.0',
'react-dom': 'npm:react-dom@18.2.0',
'react-dom/client': 'npm:react-dom@18.2.0',
},
packages: {
react: {
main: `./umd/react.${filePart}.js`,
},
'react-dom': {
main: `./umd/react-dom.${filePart}.js`,
},
},
}
: {
map: {
react: `npm:react@19.1.0/cjs/react.${filePart}.js`,
'react-dom': `npm:react-dom@19.1.0/cjs/react-dom.${filePart}.js`,
'react-dom/client': `npm:react-dom@19.1.0/cjs/react-dom-client.${filePart}.js`,
scheduler: `npm:scheduler@0.26.0/cjs/scheduler.${filePart}.js`,
},
packages: {
react: {
format: 'cjs',
},
'react-dom': {
format: 'cjs',
},
scheduler: {
format: 'cjs',
},
},
};
var sjsPaths = {};
if (typeof systemJsPaths !== 'undefined') {
sjsPaths = systemJsPaths;
}
System.config({
transpiler: 'ts',
typescriptOptions: {
target: 'es2020',
jsx: 'react',
},
paths: {
// paths serve as alias
'npm:': 'https://cdn.jsdelivr.net/npm/',
...sjsPaths,
},
map: {
// css: boilerplatePath + "css.js",
css: 'npm:systemjs-plugin-css@0.1.37/css.js',
...reactConfig.map,
ts: 'npm:plugin-typescript@8.0.0/lib/plugin.js',
typescript: 'npm:typescript@5.4.5/lib/typescript.min.js',
app: appLocation,
// systemJsMap comes from index.html
...systemJsMap,
},
packages: {
css: {},
...reactConfig.packages,
app: {
main: './index.tsx',
defaultExtension: 'tsx',
},
'ag-grid-community': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-grid-enterprise': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-grid-react': {
main: './dist/package/index.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-types': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-core': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-community': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-enterprise': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'@ag-grid-community/locale': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
},
meta: {
typescript: {
exports: 'ts',
},
'*.css': { loader: 'css' },
},
});
window.addEventListener('error', (e) => {
console.error('ERROR', e.message, e.filename);
});
System.import(startFile).catch(function (err) {
document.body.innerHTML =
'<div class="example-error" style="background:#fdb022;padding:1rem;">' + 'Example Error: ' + err + '</div>';
console.error(err);
});
})(this);
(function (global) {
process = { env: { NODE_ENV: 'development' } };
// Valid versions (18/19)
const config = { version: 19, isProd: true };
const filePart = config.isProd ? 'production.min' : 'development';
const reactConfig =
config.version == 18
? {
map: {
react: 'npm:react@18.2.0',
'react-dom': 'npm:react-dom@18.2.0',
'react-dom/client': 'npm:react-dom@18.2.0',
},
packages: {
react: {
main: `./umd/react.${filePart}.js`,
},
'react-dom': {
main: `./umd/react-dom.${filePart}.js`,
},
},
}
: {
map: {
react: `npm:react@19.1.0/cjs/react.${filePart}.js`,
'react-dom': `npm:react-dom@19.1.0/cjs/react-dom.${filePart}.js`,
'react-dom/client': `npm:react-dom@19.1.0/cjs/react-dom-client.${filePart}.js`,
scheduler: `npm:scheduler@0.26.0/cjs/scheduler.${filePart}.js`,
},
packages: {
react: {
format: 'cjs',
},
'react-dom': {
format: 'cjs',
},
scheduler: {
format: 'cjs',
},
},
};
System.config({
transpiler: 'ts',
typescriptOptions: {
target: 'es2020',
jsx: 'react',
},
paths: {
// paths serve as alias
'npm:': 'https://cdn.jsdelivr.net/npm/',
...systemJsPaths,
},
map: {
css: (boilerplatePath.length === 0 ? `./` : `${boilerplatePath}/`) + 'css.js',
...reactConfig.map,
ts: 'npm:plugin-typescript@8.0.0/lib/plugin.js',
typescript: 'npm:typescript@5.4.5/lib/typescript.min.js',
app: appLocation,
// systemJsMap comes from index.html
...systemJsMap,
},
packages: {
css: {},
...reactConfig.packages,
app: {
main: './index.tsx',
defaultExtension: 'tsx',
},
'ag-grid-community': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-grid-enterprise': {
main: './dist/package/main.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-grid-react': {
main: './dist/package/index.cjs.js',
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-types': {
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-core': {
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-community': {
defaultExtension: 'js',
format: 'cjs',
},
'ag-charts-enterprise': {
defaultExtension: 'js',
format: 'cjs',
},
'@ag-grid-community/locale': {
format: 'cjs',
},
},
meta: {
typescript: {
exports: 'ts',
},
'*.css': { loader: 'css' },
},
});
window.addEventListener('error', (e) => {
console.error('ERROR', e.message, e.filename);
});
System.import(startFile).catch(function (err) {
document.body.innerHTML =
'<div class="example-error" style="background:#fdb022;padding:1rem;">' + 'Example Error: ' + err + '</div>';
console.error(err);
});
})(this);
{
"name": "ag-grid-example",
"dependencies": {
"react": "18",
"react-dom": "18",
"@types/react": "18",
"@types/react-dom": "18",
"ag-grid-react": "34.0.0",
"ag-grid-community": "34.0.0",
"ag-grid-enterprise": "34.0.0"
},
"devDependencies": {
"@types/node": "^22"
}
}
<!DOCTYPE html><html lang="en"> <head><title>React Example - Ai Examples - Natural Language Grid State</title><meta charSet="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex"><meta http-equiv="last-modified" content="Wed Jul 09 2025 15:32:39 GMT+0100 (British Summer Time)"><link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;700&display=swap" rel="stylesheet"><style media="only screen">
:root,
body, #root {
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
html {
position: absolute;
top: 0;
left: 0;
padding: 0;
overflow: auto;
font-family: -apple-system, 'system-ui', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}
body {
padding: 16px;
overflow: auto;
background-color: transparent;
}
</style></head> <body> <div id="root"></div> <script>
var appLocation = '';
var boilerplatePath = '';
var startFile = 'index.tsx';
var systemJsMap = {
"@ag-grid-community/locale": "https://localhost:4610/files/@ag-grid-community/locale",
"ag-charts-community": "https://localhost:4610/files/ag-charts-community",
"ag-charts-core": "https://localhost:4610/files/ag-charts-core",
"ag-charts-enterprise": "https://localhost:4610/files/ag-charts-enterprise",
"ag-charts-types": "https://localhost:4610/files/ag-charts-types",
"ag-grid-community": "https://localhost:4610/files/ag-grid-community",
"ag-grid-enterprise": "https://localhost:4610/files/ag-grid-enterprise",
"ag-grid-react": "https://localhost:4610/files/ag-grid-react"
};
var systemJsPaths = {
"@ag-grid-community/locale": "https://localhost:4610/files/@ag-grid-community/locale/dist/package/main.cjs.js",
"ag-charts-community": "https://localhost:4610/files/ag-charts-community/dist/package/main.cjs.js",
"ag-charts-core": "https://localhost:4610/files/ag-charts-core/dist/package/main.cjs.js",
"ag-charts-enterprise": "https://localhost:4610/files/ag-charts-enterprise/dist/package/main.cjs.js",
"ag-charts-types": "https://localhost:4610/files/ag-charts-types/dist/package/main.cjs.js"
};
</script><script src="https://cdn.jsdelivr.net/npm/systemjs@0.19.47/dist/system.js"></script><script src="systemjs.config.dev.js"></script> </body></html>