Conversation
…nd UI improvements - Add grayed character limit counter styling - Add adaptive textarea rows based on window height (HostListener) - Add responsive layout for screen widths <= 800px - Rename suggestions to templates with icons and descriptions - Add dynamic suggestions based on table column names (date, status, price patterns) - Pass tableColumns input from dashboard to AI panel component Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add aiPanelExpandedSubject and aiPanelExpandedCast to TableStateService - Add toggleAIPanelExpanded() and restoreAIPanelExpandedState() methods - Persist expanded state in sessionStorage - Add expand/collapse button with open_in_full/close_fullscreen icons - Add CSS transitions for smooth expand/collapse animation - Center content with max-width 800px when expanded - Panel expands to full width minus 260px for sidebar Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add *ngIf to ai-panel-sidebar-content to completely remove it from DOM when panel is closed, preventing it from overlaying table actions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move AI panel outside mat-sidenav-container to fix z-index issues - Use 100vw instead of 100% for fixed positioning - Increase z-index to 100/1000 to stay above sidebar - Set panel top: 44px to not overlap header - Simplify HTML by removing redundant *ngIf checks - Change aiPanelSubject type from any to boolean Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move AI panel back inside mat-sidenav-content for proper table shifting - Add expanded styles for templates (horizontal layout, nowrap description) - Reduce gap between section titles and content - Center header in expanded mode with max-width 800px - Adjust z-index for sidebar to appear over expanded panel Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add sidebarExpanded input to AI panel component - Pass shownTableTitles from dashboard to AI panel - Adjust expanded panel position: 240px when sidebar expanded, 65px when collapsed - Content adapts dynamically to available space Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Position action buttons (close/collapse) fixed to top-right
- Center header content with max-width 800px aligned with input
- Show "AI insights for {tableName}" in expanded mode
- Show only "AI insights" in collapsed mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use Material Design cubic-bezier easing for smoother feel - Fix sidebar collapse/expand showing table rows by using padding-left instead of left position - Add transitions to content elements (header, welcome, message form, footer) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move AI insights button to rightmost position using flexbox order - Make expand/collapse animation smoother with 550ms duration - Use gentler easing curve for more pleasant user experience Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Disable sidebar width transition when expanded to prevent table flashing - Table area is instantly covered when expanding AI chat Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use padding-left approach to hide table during sidebar transitions - Panel stays at left: 65px, content shifts via padding - Smooth 400ms ease-out animations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
- Set width transition to 0ms for sidebar expand/collapse - Keep transform and opacity animations for initial panel open - Add toggle animation trigger for Angular animations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix z-index so expanded AI panel covers table alerts - Add smooth transitions for collapse animation (left, width, padding-left) - Make expanded panel overlay table instead of pushing it - Add request cancellation support for cleanup on panel close Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AI panel always opens in collapsed view (no state persistence) - Make tables more compact: smaller font (12px), reduced padding - Add horizontal scroll for tables that don't fit - Reduce AI message background opacity for lighter appearance - Fix suggestion chip text color in dark mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add dedicated click handler for suggestions with analytics tracking. Rename formatColumnName to _formatColumnName following private method convention. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Center input field with rounded corners on welcome screen - Add three suggestion categories: Explore, Data Quality, Insights - Show dropdown with completions when clicking suggestion chips - Update AI message background to softer blue tint - Increase padding in message bubbles - Remove templates section - Hide bottom form when showing welcome screen Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add animated rocket icon on welcome screen with floating and sparkle effects - Add dynamic loading text that cycles through AI processing steps - Fix input focus border styling (remove extra vertical line) - Add click outside handler to close suggestions dropdown - Position dropdown directly below input field - Make rocket icon visually lighter with semi-transparent fill Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Show suggestion text in input when clicking chip - Preview completion text on hover in dropdown - Update completions to start with suggestion prompt - Adjust category spacing (4px to chips, 12px between categories) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates the Dashboard’s “AI insights” chat panel UI/UX and state handling to support an expandable panel layout, richer suggestion templates, and improved loading/cancellation behavior.
Changes:
- Added expanded/collapsed state for the AI panel in
TableStateServiceand wired it into the AI panel component. - Reworked AI panel welcome experience: categorized suggestion chips + completions dropdown + revised loading indicator.
- Added styling updates for panel layout/expansion and the AI insights button positioning.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/app/services/table-state.service.ts | Adds expanded-state observable and ensures it resets when closing the AI panel. |
| frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css | Adjusts AI insights button positioning within the actions row. |
| frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.ts | Adds expansion handling, suggestion categories, request cancellation, and loading-step cycling logic. |
| frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.spec.ts | Updates tests for new suggestion category structure and expanded-cast mock. |
| frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.html | Reworks AI panel markup to support expansion controls and new welcome/suggestion UX. |
| frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css | Major styling changes for expanded layout, welcome screen, suggestions/completions, and loading indicator. |
| frontend/src/app/components/dashboard/dashboard.component.html | Passes additional inputs (columns, sidebar expanded state) into the AI panel component. |
| frontend/src/app/animations/toggle.animation.ts | Introduces a new toggle animation export (currently unused). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| this._tableState.aiPanelCast.subscribe((isAIpanelOpened) => { | ||
| this.isAIpanelOpened = isAIpanelOpened; | ||
| }); | ||
|
|
||
| this._tableState.aiPanelExpandedCast.subscribe((isExpanded) => { | ||
| this.isExpanded = isExpanded; | ||
| }); |
There was a problem hiding this comment.
aiPanelCast and aiPanelExpandedCast are subscribed to in ngOnInit() without any teardown. Since these are BehaviorSubject-backed observables that never complete, this will leak subscriptions each time the panel is opened/closed. Please tie these subscriptions to the component lifecycle (e.g., takeUntilDestroyed() / DestroyRef, or store them in a Subscription composite and unsubscribe in ngOnDestroy()).
| ngOnChanges(): void { | ||
| this.generateSuggestionCategories(); | ||
| } |
There was a problem hiding this comment.
ngOnChanges() is declared but the component does not implement OnChanges (and OnChanges is not imported). The repo’s TSLint config enables use-lifecycle-interface, which expects lifecycle hooks to have their corresponding interfaces implemented. Please add OnChanges to the implements list (and import it).
| createThread(suggestedMessage?: string) { | ||
| if (suggestedMessage) { | ||
| this.message = suggestedMessage; | ||
| } | ||
| this.submitting = true; | ||
| this.startLoadingSteps(); | ||
| this.messagesChain.push({ | ||
| type: 'user', | ||
| text: this.message | ||
| }); | ||
| const messageCopy = this.message; | ||
| this.message = ''; | ||
|
|
There was a problem hiding this comment.
createThread() will submit even when message is empty (welcome form has no required and the method has no guard). This results in sending an empty prompt to the backend and adding an empty user message to the chain. Please add an early return (and ideally keep submitting false) when the trimmed message is empty.
| sendMessage(suggestedMessage?: string): void { | ||
| if (suggestedMessage) { | ||
| this.message = suggestedMessage; | ||
| } | ||
| this.submitting = true; | ||
| this.startLoadingSteps(); | ||
| this.messagesChain.push({ | ||
| type: 'user', | ||
| text: this.message | ||
| }); | ||
| const messageCopy = this.message; | ||
| this.message = ''; | ||
| this.charactrsNumber = 0; |
There was a problem hiding this comment.
sendMessage() similarly doesn’t guard against an empty/whitespace-only message (and will start loading steps + push an empty user message). Add the same trimmed-message validation used for thread creation, and consider ignoring calls while submitting is already true to avoid overlapping requests.
|
|
||
| <form (ngSubmit)="threadID ? sendMessage() : createThread()" class="ai-message-form"> | ||
| <form *ngIf="messagesChain.length" (ngSubmit)="threadID ? sendMessage() : createThread()" class="ai-message-form"> | ||
| <mat-form-field class="form-field" appearance="outline" class="ai-message-form__textarea"> |
There was a problem hiding this comment.
This <mat-form-field> has two class attributes; in HTML the latter overwrites the former, so form-field will be dropped. Merge these into a single class attribute so both classes apply.
| <mat-form-field class="form-field" appearance="outline" class="ai-message-form__textarea"> | |
| <mat-form-field class="form-field ai-message-form__textarea" appearance="outline"> |
| startLoadingSteps(): void { | ||
| this._currentStepIndex = 0; | ||
| this.currentLoadingStep = this._loadingSteps[0]; | ||
| this._loadingStepsInterval = setInterval(() => { | ||
| this._currentStepIndex = (this._currentStepIndex + 1) % this._loadingSteps.length; | ||
| this.currentLoadingStep = this._loadingSteps[this._currentStepIndex]; | ||
| }, 2000); | ||
| } |
There was a problem hiding this comment.
startLoadingSteps() starts a new setInterval each time it’s called but doesn’t clear any existing interval first. If the user triggers createThread() / sendMessage() multiple times quickly, this can leave multiple intervals running and cause rapid step changes + leaks. Consider calling stopLoadingSteps() at the start of startLoadingSteps() and/or guarding against re-entrancy when submitting is already true.
| const target = event.target as HTMLElement; | ||
| const clickedOnChip = target.closest('.suggestion-chip'); | ||
| const clickedOnDropdown = target.closest('.ai-completions'); | ||
| const clickedOnInput = target.closest('.ai-welcome-form__field'); | ||
|
|
There was a problem hiding this comment.
onDocumentClick() casts event.target to HTMLElement and unconditionally calls .closest(...). In practice event.target can be a Text node (or other non-HTMLElement), which would throw at runtime. Please guard with event.target instanceof Element (or cast to Element after checking) before calling .closest.
| <button mat-icon-button (click)="toggleExpand()"> | ||
| <mat-icon>{{isExpanded ? 'close_fullscreen' : 'open_in_full'}}</mat-icon> | ||
| </button> | ||
| <button mat-icon-button (click)="handleClose()"> |
There was a problem hiding this comment.
The expand and close icon-only buttons don’t have an accessible name (no aria-label / aria-labelledby). Please add an aria-label that reflects the action (e.g., “Expand AI insights panel” / “Collapse…” and “Close AI insights panel”) so screen readers can announce them.
| <button mat-icon-button (click)="toggleExpand()"> | |
| <mat-icon>{{isExpanded ? 'close_fullscreen' : 'open_in_full'}}</mat-icon> | |
| </button> | |
| <button mat-icon-button (click)="handleClose()"> | |
| <button mat-icon-button | |
| (click)="toggleExpand()" | |
| [attr.aria-label]="isExpanded ? 'Collapse AI insights panel' : 'Expand AI insights panel'"> | |
| <mat-icon>{{isExpanded ? 'close_fullscreen' : 'open_in_full'}}</mat-icon> | |
| </button> | |
| <button mat-icon-button | |
| (click)="handleClose()" | |
| aria-label="Close AI insights panel"> |
| import { | ||
| trigger, | ||
| transition, | ||
| style, | ||
| animate, | ||
| query, | ||
| group, | ||
| } from '@angular/animations'; | ||
|
|
||
| const easing = 'cubic-bezier(0.4, 0.0, 0.2, 1)'; | ||
| const duration = '600ms'; | ||
|
|
||
| export const toggleAnimation = trigger('toggle', [ | ||
| transition(':enter', [ | ||
| style({ height: 0, opacity: 0 }), | ||
| query('.details', [ | ||
| style({ transform: 'translateY(-100%)' }) | ||
| ], { optional: true }), | ||
| group([ | ||
| animate(`${duration} ${easing}`, style({ height: '*', opacity: 1 })), | ||
| query('.details', [ | ||
| animate(`${duration} ${easing}`, style({ transform: 'translateY(0)' })) | ||
| ], { optional: true }) | ||
| ]) | ||
| ]), | ||
| transition(':leave', [ | ||
| style({ height: '*', opacity: 1 }), | ||
| query('.details', [ | ||
| style({ transform: 'translateY(0)' }) | ||
| ], { optional: true }), | ||
| group([ | ||
| animate(`${duration} ${easing}`, style({ height: 0, opacity: 0 })), | ||
| query('.details', [ | ||
| animate(`${duration} ${easing}`, style({ transform: 'translateY(-100%)' })) | ||
| ], { optional: true }) | ||
| ]) | ||
| ]) | ||
| ]); |
There was a problem hiding this comment.
toggleAnimation is introduced as a new exported animation, but there are no references to it anywhere else in the frontend codebase. If it’s not intended for immediate use, it should be removed; otherwise, please wire it into the component(s) that need it to avoid carrying dead code (and to prevent unused-export tooling from flagging it).
| import { | |
| trigger, | |
| transition, | |
| style, | |
| animate, | |
| query, | |
| group, | |
| } from '@angular/animations'; | |
| const easing = 'cubic-bezier(0.4, 0.0, 0.2, 1)'; | |
| const duration = '600ms'; | |
| export const toggleAnimation = trigger('toggle', [ | |
| transition(':enter', [ | |
| style({ height: 0, opacity: 0 }), | |
| query('.details', [ | |
| style({ transform: 'translateY(-100%)' }) | |
| ], { optional: true }), | |
| group([ | |
| animate(`${duration} ${easing}`, style({ height: '*', opacity: 1 })), | |
| query('.details', [ | |
| animate(`${duration} ${easing}`, style({ transform: 'translateY(0)' })) | |
| ], { optional: true }) | |
| ]) | |
| ]), | |
| transition(':leave', [ | |
| style({ height: '*', opacity: 1 }), | |
| query('.details', [ | |
| style({ transform: 'translateY(0)' }) | |
| ], { optional: true }), | |
| group([ | |
| animate(`${duration} ${easing}`, style({ height: 0, opacity: 0 })), | |
| query('.details', [ | |
| animate(`${duration} ${easing}`, style({ transform: 'translateY(-100%)' })) | |
| ], { optional: true }) | |
| ]) | |
| ]) | |
| ]); | |
| export {}; |
| cancelRequest(): void { | ||
| if (this._currentRequest) { | ||
| this._currentRequest.unsubscribe(); | ||
| this._currentRequest = null; | ||
| this.submitting = false; | ||
| this.stopLoadingSteps(); | ||
|
|
||
| this.messagesChain.push({ | ||
| type: 'ai-error', | ||
| text: 'Request cancelled' | ||
| }); | ||
|
|
||
| this.angulartics2.eventTrack.next({ | ||
| action: 'AI panel: request cancelled', | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| startLoadingSteps(): void { | ||
| this._currentStepIndex = 0; | ||
| this.currentLoadingStep = this._loadingSteps[0]; | ||
| this._loadingStepsInterval = setInterval(() => { | ||
| this._currentStepIndex = (this._currentStepIndex + 1) % this._loadingSteps.length; | ||
| this.currentLoadingStep = this._loadingSteps[this._currentStepIndex]; | ||
| }, 2000); | ||
| } | ||
|
|
||
| stopLoadingSteps(): void { | ||
| if (this._loadingStepsInterval) { | ||
| clearInterval(this._loadingStepsInterval); | ||
| this._loadingStepsInterval = null; | ||
| } | ||
| } |
There was a problem hiding this comment.
New behaviors were added (request cancellation + loading-steps timer management), but the spec file doesn’t cover them. Adding unit tests for cancelRequest() (unsubscribes, sets submitting false, stops the interval) and startLoadingSteps()/stopLoadingSteps() (interval is created and cleared) would help prevent regressions.
No description provided.