Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .run/BigInt support.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BigInt support" type="JavascriptDebugType" engineId="98ca6316-2f89-46d9-a9e5-fa9e2b0625b3" uri="https://plnkr.co/edit/PRzRdjaX4KUkiGPm?open=main.js">
<method v="2" />
</configuration>
</component>
1 change: 1 addition & 0 deletions documentation/ag-grid-docs/.env.build
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ PUBLIC_GTM_AUTH=K7m5Yf0NyqFbLXvp-gPogw
PUBLIC_GTM_PREVIEW=env-55

# Use production sitemap/robots disallow
LIVE_SITEMAP_URL=https://grid-staging.ag-grid.com/sitemap-0.xml
CHARTS_SITEMAP_INDEX_URL=https://www.ag-grid.com/charts/sitemap-index.xml
CHARTS_ROBOTS_DISALLOW_JSON_URL=https://www.ag-grid.com/charts/robots-disallow.json
2 changes: 2 additions & 0 deletions documentation/ag-grid-docs/.env.build.archive
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ PUBLIC_ALGOLIA_SEARCH_KEY=01142fe24ea5d2e36f4eb66b0b2ff872
PUBLIC_ALGOLIA_INDEX_PREFIX=ag-grid-dev
PUBLIC_TRIAL_LICENCE_FORM_URL=https://us-central1-aggrid-ecommerce.cloudfunctions.net/CreateLeadForTrialLK

LIVE_SITEMAP_URL=https://www.ag-grid.com/sitemap-0.xml

# Google Tag Manager (same as production)
PUBLIC_GTM_ID=GTM-T7JG534
PUBLIC_GTM_AUTH=BZRbizt3P5eacrM4bCJO5g
Expand Down
2 changes: 2 additions & 0 deletions documentation/ag-grid-docs/.env.build.production
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ PUBLIC_ALGOLIA_SEARCH_KEY=01142fe24ea5d2e36f4eb66b0b2ff872
PUBLIC_ALGOLIA_INDEX_PREFIX=ag-grid
PUBLIC_TRIAL_LICENCE_FORM_URL=https://us-central1-aggrid-ecommerce.cloudfunctions.net/CreateLeadForTrialLK

LIVE_SITEMAP_URL=https://www.ag-grid.com/sitemap-0.xml

# Google Tag Manager
PUBLIC_GTM_ID=GTM-T7JG534
PUBLIC_GTM_AUTH=BZRbizt3P5eacrM4bCJO5g
Expand Down
2 changes: 2 additions & 0 deletions documentation/ag-grid-docs/.env.build.staging
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ PUBLIC_TRIAL_LICENCE_FORM_URL=https://us-central1-stripe-testing-19784.cloudfunc

# Use staging sitemap
CHARTS_SITEMAP_INDEX_URL=https://charts-staging.ag-grid.com/sitemap-index.xml
LIVE_SITEMAP_URL=https://grid-staging.ag-grid.com/sitemap-0.xml

# No robots disallow required, as staging is disallow all
3 changes: 2 additions & 1 deletion documentation/ag-grid-docs/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ PUBLIC_ALGOLIA_SEARCH_KEY=01142fe24ea5d2e36f4eb66b0b2ff872
PUBLIC_ALGOLIA_INDEX_PREFIX=ag-grid-dev
PUBLIC_TRIAL_LICENCE_FORM_URL=https://us-central1-stripe-testing-19784.cloudfunctions.net/CreateLeadForTrialLK

# No sitemap, as dev server doesn't generate a sitemap
LIVE_SITEMAP_URL=https://grid-staging.ag-grid.com/sitemap-0.xml

# Use production robots disallow
CHARTS_ROBOTS_DISALLOW_JSON_URL=https://www.ag-grid.com/charts/robots-disallow.json

Expand Down
2 changes: 2 additions & 0 deletions documentation/ag-grid-docs/.env.preview
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ PUBLIC_SITE_URL=http://localhost:${PORT}
PUBLIC_ALGOLIA_APP_ID=O1K1ESGB5K
PUBLIC_ALGOLIA_SEARCH_KEY=01142fe24ea5d2e36f4eb66b0b2ff872
PUBLIC_ALGOLIA_INDEX_PREFIX=ag-grid-dev

LIVE_SITEMAP_URL=https://grid-staging.ag-grid.com/sitemap-0.xml
2 changes: 2 additions & 0 deletions documentation/ag-grid-docs/.env.preview.production
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ PUBLIC_USE_PUBLISHED_PACKAGES=true
PUBLIC_ALGOLIA_APP_ID=O1K1ESGB5K
PUBLIC_ALGOLIA_SEARCH_KEY=01142fe24ea5d2e36f4eb66b0b2ff872
PUBLIC_ALGOLIA_INDEX_PREFIX=ag-grid

LIVE_SITEMAP_URL=https://www.ag-grid.com/sitemap-0.xml
2 changes: 2 additions & 0 deletions documentation/ag-grid-docs/.env.preview.staging
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ PUBLIC_SITE_URL=https://grid-staging.ag-grid.com
PUBLIC_ALGOLIA_APP_ID=O1K1ESGB5K
PUBLIC_ALGOLIA_SEARCH_KEY=01142fe24ea5d2e36f4eb66b0b2ff872
PUBLIC_ALGOLIA_INDEX_PREFIX=ag-grid-dev

LIVE_SITEMAP_URL=https://grid-staging.ag-grid.com/sitemap-0.xml
18 changes: 6 additions & 12 deletions documentation/ag-grid-docs/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,20 @@
{ "env": "PUBLIC_PACKAGE_VERSION" }
],
"cache": true,
"command": "tsx ../../external/ag-website-shared/scripts/buildWithSitemapCache.ts",
"command": "tsx ../../external/ag-website-shared/scripts/buildWithSitemapCache.ts --silent",
"options": {
"cwd": "{projectRoot}",
"silent": true
"cwd": "{projectRoot}"
},
"configurations": {
"staging": {
"silent": true,
"clean-cache": true
},
"staging": {},
"archive": {
"silent": true,
"clean-cache": true
"args": "--clean-cache=true --run-second-build=true"
},
"production": {
"silent": true,
"clean-cache": true
"args": "--clean-cache=true --run-second-build=true"
},
"verbose": {
"silent": null
"args": "--silent=false"
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions documentation/ag-grid-docs/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ function calculateGridUrl() {

export const GRID_URL = calculateGridUrl();

export const LIVE_SITEMAP_URL = import.meta.env?.LIVE_SITEMAP_URL;

export const EXAMPLE_RANDOM_SEED = 'AG Grid Random Seed';

export const TRIAL_LICENCE_FORM_URL = import.meta.env?.PUBLIC_TRIAL_LICENCE_FORM_URL;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import type { GridApi, IToolPanel, IToolPanelParams } from 'ag-grid-community';

import { callChatGPT } from './chatgptApi';
import type { ChatMessage } from './types';

export interface ChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}

// Store conversation history outside the component to persist across grid state changes
let conversationHistory: ChatMessage[] = [];

export class ChatToolPanel implements IToolPanel {
private eGui!: HTMLElement;
private gridApi!: GridApi;
private chatMessagesContainer!: HTMLElement;
private inputElement!: HTMLTextAreaElement;
private submitButton!: HTMLButtonElement;
private formElement!: HTMLFormElement;
private resetButton!: HTMLButtonElement;

// Event handler references for cleanup
private handleSubmitBound = (e: Event) => this.handleSubmit(e);
private handleKeydownBound = (e: KeyboardEvent) => this.handleKeydown(e);
private resetBound = () => this.reset();

init(params: IToolPanelParams): void {
this.gridApi = params.api;
this.eGui = this.createGui();
// Re-render existing messages when tool panel is re-created
this.renderExistingMessages();
}

getGui(): HTMLElement {
return this.eGui;
}

refresh(_params: IToolPanelParams): boolean {
return false;
}

private createGui(): HTMLElement {
const container = document.createElement('div');
container.className = 'chat-tool-panel';
container.innerHTML = `
<div class="chat-header">
<div class="chat-title-row">
<h3 class="chat-title">AI Assistant</h3>
<div class="chat-actions">
<button class="icon-btn reset-btn" title="Reset" aria-label="Reset">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg>
</button>
</div>
</div>
<p class="chat-subtitle">This example demonstrates the AI Toolkit with conversation history, embedded in a custom tool panel.</p>
</div>
<div class="chat-messages"></div>
<form class="chat-input-form">
<textarea
id="chat-input"
rows="4"
class="chat-input"
placeholder='Ask me anything, e.g. "show only failed transactions"...'
autocomplete="off"
></textarea>
<button type="submit" class="chat-submit">→</button>
</form>
`;

this.chatMessagesContainer = container.querySelector('.chat-messages')!;
this.inputElement = container.querySelector('.chat-input')!;
this.submitButton = container.querySelector('.chat-submit')!;
this.formElement = container.querySelector('.chat-input-form')!;
this.resetButton = container.querySelector('.reset-btn')!;

// Add event listeners using bound handlers for cleanup
this.formElement.addEventListener('submit', this.handleSubmitBound);
this.inputElement.addEventListener('keydown', this.handleKeydownBound);
this.resetButton.addEventListener('click', this.resetBound);

return container;
}

private handleKeydown(event: KeyboardEvent): void {
// Enter sends message, Shift+Enter adds newline
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
this.formElement.dispatchEvent(new Event('submit', { cancelable: true }));
}
}

private async handleSubmit(event: Event): Promise<void> {
event.preventDefault();

const userMessage = this.inputElement.value.trim();
if (!userMessage) return;

// Render user message
this.renderMessage('user', userMessage);

// Clear input and disable form
this.inputElement.value = '';
this.inputElement.disabled = true;
this.submitButton.disabled = true;

// Show loading indicator
const loadingId = this.showLoadingMessage();

try {
const response = await callChatGPT(userMessage, this.gridApi, conversationHistory);

// Log the LLM response
console.log('Explanation:', response.explanation);
if (response.gridState && Object.keys(response.gridState).length > 0) {
console.log('New Grid State: ', response.gridState);
}
if (response.propertiesToIgnore?.length > 0) {
console.log('Properties Ignored:', response.propertiesToIgnore);
}

// Remove loading indicator
this.removeLoadingMessage(loadingId);

// Add both messages to history after successful response
conversationHistory.push(
{ role: 'user', content: userMessage },
{ role: 'assistant', content: response.explanation }
);

// Apply grid state changes if any (this will destroy and recreate the tool panel)
// Messages will be automatically added when the tool panel reloads
if (response.gridState && Object.keys(response.gridState).length > 0) {
this.gridApi.setState(response.gridState, response.propertiesToIgnore);
} else {
// If no state change, manually render the response
this.renderMessage('assistant', response.explanation);
}
} catch (error) {
this.removeLoadingMessage(loadingId);
const errorMessage = `Error: ${error instanceof Error ? error.message : String(error)}`;
this.renderMessage('assistant', errorMessage, true);
} finally {
// Re-enable form
this.inputElement.disabled = false;
this.submitButton.disabled = false;
this.inputElement.focus();
}
}

private renderMessage(role: 'user' | 'assistant', content: string, isError = false): void {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${role}-message${isError ? ' error-message' : ''}`;

const bubble = document.createElement('div');
bubble.className = 'message-bubble';
bubble.textContent = content;

messageDiv.appendChild(bubble);
this.chatMessagesContainer.appendChild(messageDiv);

this.scrollToBottomOfChat();
}

private showLoadingMessage(): string {
const loadingId = `loading-${Date.now()}`;
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message assistant-message loading-message';
messageDiv.id = loadingId;

const bubble = document.createElement('div');
bubble.className = 'message-bubble';
bubble.innerHTML = '<span class="loading-dots">Thinking<span>.</span><span>.</span><span>.</span></span>';

const disclaimer = document.createElement('div');
disclaimer.className = 'loading-disclaimer';
disclaimer.innerHTML =
'<span class="info-icon">ⓘ</span> This demo uses a proxy, so responses may take up to 30 seconds';

messageDiv.appendChild(bubble);
messageDiv.appendChild(disclaimer);
this.chatMessagesContainer.appendChild(messageDiv);

this.scrollToBottomOfChat();

return loadingId;
}

private removeLoadingMessage(loadingId: string): void {
const loadingElement = document.getElementById(loadingId);
if (loadingElement) {
loadingElement.remove();
}
}

private renderExistingMessages(): void {
// Re-render all messages from conversation history when tool panel is recreated
for (const message of conversationHistory) {
this.renderMessage(message.role, message.content);
}
this.scrollToBottomOfChat();
}

// Scroll to bottom
private scrollToBottomOfChat = () => {
this.chatMessagesContainer.scrollTop = this.chatMessagesContainer.scrollHeight;
};

private reset(): void {
// Reset conversation
conversationHistory = [];
this.chatMessagesContainer.innerHTML = '';
this.inputElement.value = '';
this.inputElement.focus();

// Reset grid state
this.gridApi.setState({
columnVisibility: {
hiddenColIds: [
'ag-Grid-HierarchyColumn-transactionDate-year',
'ag-Grid-HierarchyColumn-transactionDate-year',
'ag-Grid-HierarchyColumn-transactionDate-formattedMonth',
'ag-Grid-HierarchyColumn-transactionDate-formattedMonth',
'currency',
],
},
columnPinning: { leftColIds: [], rightColIds: [] },
sort: { sortModel: [] },
filter: { filterModel: {} },
rowGroup: { groupColIds: [] },
pagination: { page: 0, pageSize: 100 },
});
}

destroy(): void {
// Clean up event listeners
this.formElement.removeEventListener('submit', this.handleSubmitBound);
this.inputElement.removeEventListener('keydown', this.handleKeydownBound);
this.resetButton.removeEventListener('click', this.resetBound);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { ICellRendererComp, ICellRendererParams } from 'ag-grid-community';

export class CountryFlagCellRenderer implements ICellRendererComp {
eGui!: HTMLDivElement;

init(params: ICellRendererParams) {
this.eGui = document.createElement('div');
this.eGui.style.display = 'flex';
this.eGui.style.alignItems = 'center';
this.eGui.style.gap = '6px';

if (!params.value) {
this.eGui.textContent = '';
return;
}

const countryCode = params.value.toLowerCase();
const flagUrl = `https://flags.fmcdn.net/data/flags/mini/${countryCode}.png`;
const flagImage = document.createElement('img');
flagImage.src = flagUrl;
flagImage.width = 15;
flagImage.height = 10;
flagImage.style.border = '0';

const countryText = document.createElement('span');
countryText.textContent = params.value;

this.eGui.appendChild(flagImage);
this.eGui.appendChild(countryText);
}

getGui() {
return this.eGui;
}

refresh() {
return false;
}
}
Loading
Loading