Skip to content

Add AI-powered chart generation to panel create/edit UX#1623

Merged
lyubov-voloshko merged 2 commits into
mainfrom
frontend_ai_chart_generation
Feb 23, 2026
Merged

Add AI-powered chart generation to panel create/edit UX#1623
lyubov-voloshko merged 2 commits into
mainfrom
frontend_ai_chart_generation

Conversation

@gugu

@gugu gugu commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

Integrate the backend POST /dashboard/:dashboardId/widget/generate/:connectionId endpoint into the chart editor. Users can now describe a chart in natural language, select a table, and have AI generate the SQL query, chart type, and all configuration. The chart preview is always visible outside the collapsible manual config section.

Integrate the backend POST /dashboard/:dashboardId/widget/generate/:connectionId
endpoint into the chart editor. Users can now describe a chart in natural language,
select a table, and have AI generate the SQL query, chart type, and all configuration.
The chart preview is always visible outside the collapsible manual config section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Integrates the backend AI widget generation endpoint into the chart create/edit experience, adding a “Generate with AI” flow and restructuring the editor so chart preview is displayed outside the manual configuration section.

Changes:

  • Added DashboardsService.generateWidgetWithAi() to call /dashboard/:dashboardId/widget/generate/:connectionId with tableName query param.
  • Introduced GeneratedPanelWithPosition model and applied AI response into ChartEditComponent state.
  • Updated chart editor UI to include an AI generation section, collapsible manual configuration section, and an always-visible preview area when results exist.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
frontend/src/app/services/dashboards.service.ts Adds an API wrapper method for AI widget generation.
frontend/src/app/models/saved-query.ts Adds a new interface describing the AI-generated panel payload.
frontend/src/app/components/charts/chart-edit/chart-edit.component.ts Loads AI prerequisites, calls AI generation, and maps AI output into editor state.
frontend/src/app/components/charts/chart-edit/chart-edit.component.html Restructures the chart editor layout to add AI section + collapsible manual config + preview placement changes.
frontend/src/app/components/charts/chart-edit/chart-edit.component.css Adds styling for the new AI/manual collapsible sections and updated layout.
Comments suppressed due to low confidence (1)

frontend/src/app/components/charts/chart-edit/chart-edit.component.html:628

  • The template appears to have mismatched <div> nesting/closures near the bottom: chart-edit-layout/chart-edit-main/chart-edit-page open three wrapper <div>s, but only two are closed at the end of the file, and there is an extra closing </div> right after the @if (!loading()) block. This will break template compilation and/or layout. Please re-check the wrapper structure so all opened elements are closed exactly once and the actions block is nested as intended.
        </div>
    }
        </div>

        @if (!loading()) {
            <div class="actions">
                <a mat-stroked-button [routerLink]="['/panels', connectionId()]" data-testid="cancel-button">Back</a>
                <button mat-flat-button color="primary"
                    (click)="saveQuery()"
                    [disabled]="!canSave()"
                    data-testid="save-query-button">
                    @if (saving()) {
                        <mat-spinner diameter="18"></mat-spinner>
                    }
                    {{ saving() ? 'Saving...' : (isEditMode() ? 'Update query' : 'Save query') }}
                </button>
            </div>
        }
    </div>
</div>

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +688 to +722
if (result.widget_options) {
const opts = result.widget_options as Partial<ChartWidgetOptions>;

if (opts.label_column) this.labelColumn.set(opts.label_column);
if (opts.label_type) this.labelType.set(opts.label_type);

// Series
if (opts.series_column) {
this.seriesMode.set('column');
this.seriesColumn.set(opts.series_column);
if (opts.value_column) this.seriesValueColumn.set(opts.value_column);
} else if (opts.series?.length) {
this.seriesMode.set('manual');
this.seriesList.set([...opts.series]);
} else if (opts.value_column) {
this.seriesMode.set('manual');
this.seriesList.set([{ value_column: opts.value_column }]);
}

// Display options
if (opts.stacked) this.stacked.set(true);
if (opts.horizontal) this.horizontal.set(true);
if (opts.show_data_labels) this.showDataLabels.set(true);

// Legend
if (opts.legend) {
if (opts.legend.show !== undefined) this.legendShow.set(opts.legend.show);
if (opts.legend.position) this.legendPosition.set(opts.legend.position);
}

// Color palette
if (opts.color_palette?.length) {
this.colorPalette.set([...opts.color_palette]);
}
}

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says AI should generate “all configuration”, but _applyAiResponse currently ignores several supported ChartWidgetOptions fields (e.g. units, number_format, y_axis, x_axis, sort_by, limit). If the backend returns these, they will be dropped and the manual UI will show defaults instead of the generated config. Consider applying the full set of widget options here (or update the description/contract if partial application is intentional).

Copilot uses AI. Check for mistakes.
Comment on lines +25 to 31
<div class="ai-section-header" (click)="aiExpanded.set(!aiExpanded())">
<div class="ai-section-title">
<mat-icon class="ai-icon">auto_awesome</mat-icon>
<span>Generate with AI</span>
</div>
<mat-icon class="ai-toggle-icon">{{ aiExpanded() ? 'expand_less' : 'expand_more' }}</mat-icon>
</div>

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI section header is a clickable <div> without button semantics, so it’s not keyboard-accessible and won’t announce expanded/collapsed state to assistive tech. Use a <button> (or mat-expansion-panel) for the header, or add role="button", tabindex="0", aria-expanded, and keyboard handlers (Enter/Space).

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +127
<div class="manual-section-header" (click)="manualExpanded.set(!manualExpanded())">
<div class="manual-section-title">
<mat-icon>tune</mat-icon>
<span>Manual configuration</span>
</div>
<mat-icon class="manual-toggle-icon">{{ manualExpanded() ? 'expand_less' : 'expand_more' }}</mat-icon>
</div>

@if (manualExpanded()) {
<div class="manual-section-body">

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Manual configuration” collapse header is also implemented as a clickable <div> without button semantics, which prevents keyboard toggling and doesn’t expose expanded state for screen readers. Prefer a real <button>/mat-expansion-panel, or add role="button", tabindex="0", aria-expanded, and Enter/Space key handling.

Suggested change
<div class="manual-section-header" (click)="manualExpanded.set(!manualExpanded())">
<div class="manual-section-title">
<mat-icon>tune</mat-icon>
<span>Manual configuration</span>
</div>
<mat-icon class="manual-toggle-icon">{{ manualExpanded() ? 'expand_less' : 'expand_more' }}</mat-icon>
</div>
@if (manualExpanded()) {
<div class="manual-section-body">
<button
type="button"
class="manual-section-header"
(click)="manualExpanded.set(!manualExpanded())"
aria-expanded="{{ manualExpanded() ? 'true' : 'false' }}"
aria-controls="manual-section-body">
<div class="manual-section-title">
<mat-icon>tune</mat-icon>
<span>Manual configuration</span>
</div>
<mat-icon class="manual-toggle-icon">{{ manualExpanded() ? 'expand_less' : 'expand_more' }}</mat-icon>
</button>
@if (manualExpanded()) {
<div class="manual-section-body" id="manual-section-body">

Copilot uses AI. Check for mistakes.
Comment on lines +143 to +144
dashboardId: string,
connectionId: string,

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateWidgetWithAi uses a different parameter ordering than the rest of DashboardsService (dashboardId first vs connectionId first for create/update/delete). This makes it easy to call with swapped args and is inconsistent with the existing public API. Consider reordering the signature to connectionId, dashboardId, ... (while keeping URL construction the same) to match the established pattern in this service.

Suggested change
dashboardId: string,
connectionId: string,
connectionId: string,
dashboardId: string,

Copilot uses AI. Check for mistakes.
name: string;
description: string | null;
widget_type: DashboardWidgetType;
chart_type: string | null;

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GeneratedPanelWithPosition.chart_type is typed as string | null, even though this file already defines ChartType and the UI code expects/validates against that union. Typing this as ChartType | null would improve type-safety and avoid repeated casting in consumers.

Suggested change
chart_type: string | null;
chart_type: ChartType | null;

Copilot uses AI. Check for mistakes.
Comment on lines +662 to +670
}
} finally {
this.aiGenerating.set(false);
}

this.angulartics2.eventTrack.next({
action: 'Charts: AI generation used',
});
posthog.capture('Charts: AI generation used');

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Telemetry is recorded even when AI generation fails or returns null (the tracking calls run after the try/finally unconditionally). This will inflate usage metrics and make it hard to distinguish success vs failure. Move the Angulartics/PostHog tracking into the if (result) block (or add a separate failure event).

Suggested change
}
} finally {
this.aiGenerating.set(false);
}
this.angulartics2.eventTrack.next({
action: 'Charts: AI generation used',
});
posthog.capture('Charts: AI generation used');
this.angulartics2.eventTrack.next({
action: 'Charts: AI generation used',
});
posthog.capture('Charts: AI generation used');
}
} finally {
this.aiGenerating.set(false);
}

Copilot uses AI. Check for mistakes.
Comment on lines +708 to +710
if (opts.stacked) this.stacked.set(true);
if (opts.horizontal) this.horizontal.set(true);
if (opts.show_data_labels) this.showDataLabels.set(true);

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_applyAiResponse only sets stacked, horizontal, and showDataLabels to true when the AI response value is truthy. If a user previously enabled one of these and AI returns false, the UI state will not be reset, leading to stale config after regeneration. Use !== undefined checks and set the signal to the actual boolean value from opts instead of only setting true.

Suggested change
if (opts.stacked) this.stacked.set(true);
if (opts.horizontal) this.horizontal.set(true);
if (opts.show_data_labels) this.showDataLabels.set(true);
if (opts.stacked !== undefined) this.stacked.set(opts.stacked);
if (opts.horizontal !== undefined) this.horizontal.set(opts.horizontal);
if (opts.show_data_labels !== undefined) this.showDataLabels.set(opts.show_data_labels);

Copilot uses AI. Check for mistakes.
@lyubov-voloshko lyubov-voloshko merged commit 8a7b1e1 into main Feb 23, 2026
15 of 17 checks passed
@lyubov-voloshko lyubov-voloshko deleted the frontend_ai_chart_generation branch February 23, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants