From 7c05b6f27999b6f4c8fd1c3f316a0d2b89e01d38 Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Wed, 13 Nov 2024 11:32:33 +0100 Subject: [PATCH] fix: AI history view crashing on first use The AI history view sometimes crashed on first use when the user tried to select an agent. This was caused by an empty agent list bound in the initial UI. Refactors the history view by making sure that the history view is only instantiated once it is shown instead of early in application start. This avoids binding a still empty list of agent contributions to the UI. Also adds fallback content in case there are no registered agents as the used 'SelectComponent' crashes with empty options. Also moves the history sort change tracking outside of the widget. Previously this was broken as only the initial widget was tracked. --- .../src/browser/ai-history-contribution.ts | 22 +++++++------------ .../src/browser/ai-history-widget.tsx | 16 +++++++++----- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/ai-history/src/browser/ai-history-contribution.ts b/packages/ai-history/src/browser/ai-history-contribution.ts index 38d87ce4aa425..2b5bba583b61e 100644 --- a/packages/ai-history/src/browser/ai-history-contribution.ts +++ b/packages/ai-history/src/browser/ai-history-contribution.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { FrontendApplication, codicon } from '@theia/core/lib/browser'; import { AIViewContribution } from '@theia/ai-core/lib/browser'; -import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; +import { inject, injectable } from '@theia/core/shared/inversify'; import { AIHistoryView } from './ai-history-widget'; import { Command, CommandRegistry, Emitter } from '@theia/core'; import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; @@ -49,6 +49,9 @@ export const AI_HISTORY_VIEW_CLEAR = Command.toLocalizedCommand({ export class AIHistoryViewContribution extends AIViewContribution implements TabBarToolbarContribution { @inject(CommunicationRecordingService) private recordingService: CommunicationRecordingService; + protected readonly chronologicalChangedEmitter = new Emitter(); + protected readonly chronologicalStateChanged = this.chronologicalChangedEmitter.event; + constructor() { super({ widgetId: AIHistoryView.ID, @@ -75,6 +78,7 @@ export class AIHistoryViewContribution extends AIViewContribution isVisible: widget => this.withHistoryWidget(widget, historyView => !historyView.isChronological), execute: widget => this.withHistoryWidget(widget, historyView => { historyView.sortHistory(true); + this.chronologicalChangedEmitter.fire(); return true; }) }); @@ -83,6 +87,7 @@ export class AIHistoryViewContribution extends AIViewContribution isVisible: widget => this.withHistoryWidget(widget, historyView => historyView.isChronological), execute: widget => this.withHistoryWidget(widget, historyView => { historyView.sortHistory(false); + this.chronologicalChangedEmitter.fire(); return true; }) }); @@ -106,31 +111,20 @@ export class AIHistoryViewContribution extends AIViewContribution return widget instanceof AIHistoryView ? predicate(widget) : false; } - protected readonly onAIHistoryWidgetStateChangedEmitter = new Emitter(); - protected readonly onAIHistoryWidgettStateChanged = this.onAIHistoryWidgetStateChangedEmitter.event; - - @postConstruct() - protected override init(): void { - super.init(); - this.widget.then(widget => { - widget.onStateChanged(() => this.onAIHistoryWidgetStateChangedEmitter.fire()); - }); - } - registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: AI_HISTORY_VIEW_SORT_CHRONOLOGICALLY.id, command: AI_HISTORY_VIEW_SORT_CHRONOLOGICALLY.id, tooltip: 'Sort chronologically', isVisible: widget => this.withHistoryWidget(widget), - onDidChange: this.onAIHistoryWidgettStateChanged + onDidChange: this.chronologicalStateChanged }); registry.registerItem({ id: AI_HISTORY_VIEW_SORT_REVERSE_CHRONOLOGICALLY.id, command: AI_HISTORY_VIEW_SORT_REVERSE_CHRONOLOGICALLY.id, tooltip: 'Sort reverse chronologically', isVisible: widget => this.withHistoryWidget(widget), - onDidChange: this.onAIHistoryWidgettStateChanged + onDidChange: this.chronologicalStateChanged }); registry.registerItem({ id: AI_HISTORY_VIEW_CLEAR.id, diff --git a/packages/ai-history/src/browser/ai-history-widget.tsx b/packages/ai-history/src/browser/ai-history-widget.tsx index d106323f121c5..fab5d6c1f5061 100644 --- a/packages/ai-history/src/browser/ai-history-widget.tsx +++ b/packages/ai-history/src/browser/ai-history-widget.tsx @@ -19,7 +19,7 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify' import * as React from '@theia/core/shared/react'; import { CommunicationCard } from './ai-history-communication-card'; import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component'; -import { deepClone, Emitter } from '@theia/core'; +import { deepClone } from '@theia/core'; namespace AIHistoryView { export interface State { @@ -40,8 +40,6 @@ export class AIHistoryView extends ReactWidget implements StatefulWidget { protected selectedAgent?: Agent; protected _state: AIHistoryView.State = { chronological: false }; - protected readonly onStateChangedEmitter = new Emitter(); - readonly onStateChanged = this.onStateChangedEmitter.event; constructor() { super(); @@ -58,7 +56,7 @@ export class AIHistoryView extends ReactWidget implements StatefulWidget { protected set state(state: AIHistoryView.State) { this._state = state; - this.onStateChangedEmitter.fire(this._state); + this.update(); } storeState(): object { @@ -79,7 +77,6 @@ export class AIHistoryView extends ReactWidget implements StatefulWidget { this.toDispose.push(this.recordingService.onDidRecordRequest(entry => this.historyContentUpdated(entry))); this.toDispose.push(this.recordingService.onDidRecordResponse(entry => this.historyContentUpdated(entry))); this.toDispose.push(this.recordingService.onStructuralChange(() => this.update())); - this.toDispose.push(this.onStateChanged(newState => this.update())); this.selectAgent(this.agentService.getAllAgents()[0]); } @@ -99,10 +96,17 @@ export class AIHistoryView extends ReactWidget implements StatefulWidget { this.selectedAgent = this.agentService.getAllAgents().find(agent => agent.id === value.value); this.update(); }; + const agents = this.agentService.getAllAgents(); + if (agents.length === 0) { + return ( +
+
No agent available.
+
); + } return (
({ + options={agents.map(agent => ({ value: agent.id, label: agent.name, description: agent.description || ''