Skip to content

Commit 8b09856

Browse files
ergunshDevtools-frontend LUCI CQ
authored andcommitted
[AiAssistance] Remove viewProps from presenter and construct it
Previously, we were keeping `viewProps` separately and that generated a source of confusion where the `state` of the presenter is held. This CL puts it to an end by: * Removing `#viewProps` object and constructing it while calling the `view()` function. * Puts necessary state properties as properties of the presenter class. Bug: 394005781 Change-Id: I2be3a008f1abed96bcadb28b00be43661cb9d545 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6254337 Commit-Queue: Ergün Erdoğmuş <[email protected]> Reviewed-by: Alex Rudenko <[email protected]>
1 parent 9169666 commit 8b09856

File tree

1 file changed

+83
-92
lines changed

1 file changed

+83
-92
lines changed

front_end/panels/ai_assistance/AiAssistancePanel.ts

Lines changed: 83 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ import aiAssistancePanelStyles from './aiAssistancePanel.css.js';
4343
import {AiHistoryStorage, Conversation, ConversationType} from './AiHistoryStorage.js';
4444
import {ChangeManager} from './ChangeManager.js';
4545
import {
46+
type ChatMessage,
4647
ChatMessageEntity,
4748
ChatView,
4849
type ModelChatMessage,
4950
type Props as ChatViewProps,
5051
State as ChatViewState,
51-
type Step,
52+
type Step
5253
} from './components/ChatView.js';
5354

5455
const {html} = Lit;
@@ -203,7 +204,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
203204
#toggleSearchElementAction: UI.ActionRegistration.Action;
204205
#contentContainer: HTMLElement;
205206
#aidaClient: Host.AidaClient.AidaClient;
206-
#viewProps: ChatViewProps;
207207
#viewOutput: ViewOutput = {};
208208
#serverSideLoggingEnabled = isAiAssistanceServerSideLoggingEnabled();
209209
#aiAssistanceEnabledSetting: Common.Settings.Setting<boolean>|undefined;
@@ -228,6 +228,28 @@ export class AiAssistancePanel extends UI.Panel.Panel {
228228
#selectedInsight: InsightContext|null = null;
229229
#selectedRequest: RequestContext|null = null;
230230

231+
// Messages displayed in the `ChatView` component.
232+
#messages: ChatMessage[] = [];
233+
// Indicates whether the new conversation context is blocked due to cross-origin restrictions.
234+
// This happens when the conversation's context has a different
235+
// origin than the selected context.
236+
#blockedByCrossOrigin: boolean = false;
237+
// Whether the UI should show loading or not.
238+
#isLoading: boolean = false;
239+
// Selected conversation context. The reason we keep this as a
240+
// state field rather than using `#getConversationContext` is that,
241+
// there is a case where the context differs from the selectedElement (or other selected context type).
242+
// Specifically, it allows restoring the previous context when a new selection is cross-origin.
243+
// See `#onContextSelectionChanged` for details.
244+
#selectedContext: ConversationContext<unknown>|null = null;
245+
// Stores the availability status of the `AidaClient` and the reason for unavailability, if any.
246+
#aidaAvailability: Host.AidaClient.AidaAccessPreconditions;
247+
// Info of the currently logged in user.
248+
#userInfo: {
249+
accountImage?: string,
250+
accountFullName?: string,
251+
};
252+
231253
constructor(private view: View = defaultView, {aidaClient, aidaAvailability, syncInfo}: {
232254
aidaClient: Host.AidaClient.AidaClient,
233255
aidaAvailability: Host.AidaClient.AidaAccessPreconditions,
@@ -242,31 +264,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
242264
UI.ActionRegistry.ActionRegistry.instance().getAction('elements.toggle-element-search');
243265
this.#aidaClient = aidaClient;
244266
this.#contentContainer = this.contentElement.createChild('div', 'chat-container');
245-
246-
this.#viewProps = {
247-
state: this.#getChatUiState(),
248-
aidaAvailability,
249-
messages: [],
250-
inspectElementToggled: this.#toggleSearchElementAction.toggled(),
251-
isLoading: false,
252-
onTextSubmit: (text: string) => {
253-
void this.#startConversation(text);
254-
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceQuerySubmitted);
255-
},
256-
onInspectElementClick: this.#handleSelectElementClick.bind(this),
257-
onFeedbackSubmit: this.#handleFeedbackSubmit.bind(this),
258-
onCancelClick: this.#cancel.bind(this),
259-
onContextClick: this.#handleContextClick.bind(this),
260-
onNewConversation: this.#handleNewChatRequest.bind(this),
261-
canShowFeedbackForm: this.#serverSideLoggingEnabled,
262-
userInfo: {
263-
accountImage: syncInfo.accountImage,
264-
accountFullName: syncInfo.accountFullName,
265-
},
266-
selectedContext: null,
267-
blockedByCrossOrigin: false,
268-
stripLinks: false,
269-
isReadOnly: false,
267+
this.#aidaAvailability = aidaAvailability;
268+
this.#userInfo = {
269+
accountImage: syncInfo.accountImage,
270+
accountFullName: syncInfo.accountFullName,
270271
};
271272

272273
this.#conversations = AiHistoryStorage.instance().getHistory().map(item => Conversation.fromSerialized(item));
@@ -437,16 +438,13 @@ export class AiAssistancePanel extends UI.Panel.Panel {
437438
#updateAgentState(agent?: AiAgent<unknown>): void {
438439
if (this.#currentAgent !== agent) {
439440
this.#cancel();
441+
this.#messages = [];
442+
this.#isLoading = false;
440443
this.#currentAgent = agent;
441-
this.#viewProps.agentType = this.#currentAgent?.type;
442-
this.#viewProps.messages = [];
443-
this.#viewProps.changeSummary = undefined;
444-
this.#viewProps.isLoading = false;
445444
if (this.#currentAgent?.type) {
446445
this.#currentConversation =
447446
new Conversation(agentTypeToConversationType(this.#currentAgent.type), [], agent?.id, false);
448447
this.#conversations.push(this.#currentConversation);
449-
this.#viewProps.isReadOnly = false;
450448
}
451449
}
452450

@@ -460,8 +458,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
460458
this.#viewOutput.chatView?.focusTextInput();
461459
this.#selectDefaultAgentIfNeeded();
462460
void this.#handleAidaAvailabilityChange();
463-
void this
464-
.#handleAiAssistanceEnabledSettingChanged(); // If the setting was switched on/off while the AiAssistancePanel was not shown.
465461
this.#selectedElement =
466462
createNodeContext(selectedElementFilter(UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode))),
467463
this.#selectedRequest =
@@ -471,19 +467,12 @@ export class AiAssistancePanel extends UI.Panel.Panel {
471467
this.#selectedInsight =
472468
createPerfInsightContext(UI.Context.Context.instance().flavor(TimelineUtils.InsightAIContext.InsightAIContext));
473469
this.#selectedFile = createFileContext(UI.Context.Context.instance().flavor(Workspace.UISourceCode.UISourceCode)),
474-
this.#viewProps = {
475-
...this.#viewProps,
476-
agentType: this.#currentAgent?.type,
477-
inspectElementToggled: this.#toggleSearchElementAction.toggled(),
478-
selectedContext: this.#getConversationContext(),
479-
};
480470
void this.doUpdate();
481471

482-
this.#aiAssistanceEnabledSetting?.addChangeListener(this.#handleAiAssistanceEnabledSettingChanged, this);
472+
this.#aiAssistanceEnabledSetting?.addChangeListener(this.doUpdate, this);
483473
Host.AidaClient.HostConfigTracker.instance().addEventListener(
484474
Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.#handleAidaAvailabilityChange);
485-
this.#toggleSearchElementAction.addEventListener(
486-
UI.ActionRegistration.Events.TOGGLED, this.#handleSearchElementActionToggled);
475+
this.#toggleSearchElementAction.addEventListener(UI.ActionRegistration.Events.TOGGLED, this.doUpdate, this);
487476
UI.Context.Context.instance().addFlavorChangeListener(SDK.DOMModel.DOMNode, this.#handleDOMNodeFlavorChange);
488477
UI.Context.Context.instance().addFlavorChangeListener(
489478
SDK.NetworkRequest.NetworkRequest, this.#handleNetworkRequestFlavorChange);
@@ -507,11 +496,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
507496
}
508497

509498
override willHide(): void {
510-
this.#aiAssistanceEnabledSetting?.removeChangeListener(this.#handleAiAssistanceEnabledSettingChanged, this);
499+
this.#aiAssistanceEnabledSetting?.removeChangeListener(this.doUpdate, this);
511500
Host.AidaClient.HostConfigTracker.instance().removeEventListener(
512501
Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, this.#handleAidaAvailabilityChange);
513-
this.#toggleSearchElementAction.removeEventListener(
514-
UI.ActionRegistration.Events.TOGGLED, this.#handleSearchElementActionToggled);
502+
this.#toggleSearchElementAction.removeEventListener(UI.ActionRegistration.Events.TOGGLED, this.doUpdate, this);
515503
UI.Context.Context.instance().removeFlavorChangeListener(SDK.DOMModel.DOMNode, this.#handleDOMNodeFlavorChange);
516504
UI.Context.Context.instance().removeFlavorChangeListener(
517505
SDK.NetworkRequest.NetworkRequest, this.#handleNetworkRequestFlavorChange);
@@ -543,28 +531,18 @@ export class AiAssistancePanel extends UI.Panel.Panel {
543531

544532
#handleAidaAvailabilityChange = async(): Promise<void> => {
545533
const currentAidaAvailability = await Host.AidaClient.AidaClient.checkAccessPreconditions();
546-
if (currentAidaAvailability !== this.#viewProps.aidaAvailability) {
547-
this.#viewProps.aidaAvailability = currentAidaAvailability;
534+
if (currentAidaAvailability !== this.#aidaAvailability) {
535+
this.#aidaAvailability = currentAidaAvailability;
548536
const syncInfo = await new Promise<Host.InspectorFrontendHostAPI.SyncInformation>(
549537
resolve => Host.InspectorFrontendHost.InspectorFrontendHostInstance.getSyncInformation(resolve));
550-
this.#viewProps.userInfo = {
538+
this.#userInfo = {
551539
accountImage: syncInfo.accountImage,
552540
accountFullName: syncInfo.accountFullName,
553541
};
554-
this.#viewProps.state = this.#getChatUiState();
555542
void this.doUpdate();
556543
}
557544
};
558545

559-
#handleSearchElementActionToggled = (ev: Common.EventTarget.EventTargetEvent<boolean>): void => {
560-
if (this.#viewProps.inspectElementToggled === ev.data) {
561-
return;
562-
}
563-
564-
this.#viewProps.inspectElementToggled = ev.data;
565-
void this.doUpdate();
566-
};
567-
568546
#handleDOMNodeFlavorChange = (ev: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void => {
569547
if (this.#selectedElement?.getItem() === ev.data) {
570548
return;
@@ -616,19 +594,40 @@ export class AiAssistancePanel extends UI.Panel.Panel {
616594
this.#updateAgentState(this.#currentAgent);
617595
};
618596

619-
#handleAiAssistanceEnabledSettingChanged = (): void => {
620-
const nextChatUiState = this.#getChatUiState();
621-
if (this.#viewProps.state === nextChatUiState) {
622-
return;
623-
}
624-
625-
this.#viewProps.state = nextChatUiState;
626-
void this.doUpdate();
627-
};
628-
629597
async doUpdate(): Promise<void> {
630598
this.#updateToolbarState();
631-
this.view(this.#viewProps, this.#viewOutput, this.#contentContainer);
599+
this.view(
600+
{
601+
state: this.#getChatUiState(),
602+
blockedByCrossOrigin: this.#blockedByCrossOrigin,
603+
aidaAvailability: this.#aidaAvailability,
604+
isLoading: this.#isLoading,
605+
messages: this.#messages,
606+
selectedContext: this.#selectedContext,
607+
agentType: this.#currentAgent?.type,
608+
isReadOnly: this.#currentConversation?.isReadOnly ?? false,
609+
changeSummary:
610+
(isAiAssistancePatchingEnabled() && this.#currentAgent && !this.#currentConversation?.isReadOnly) ?
611+
this.#changeManager.formatChanges(this.#currentAgent.id) :
612+
undefined,
613+
stripLinks: this.#currentAgent?.type === AgentType.PERFORMANCE,
614+
inspectElementToggled: this.#toggleSearchElementAction.toggled(),
615+
userInfo: this.#userInfo,
616+
canShowFeedbackForm: this.#serverSideLoggingEnabled,
617+
onTextSubmit: (text: string) => {
618+
void this.#startConversation(text);
619+
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceQuerySubmitted);
620+
},
621+
onInspectElementClick: this.#handleSelectElementClick.bind(this),
622+
onFeedbackSubmit: this.#handleFeedbackSubmit.bind(this),
623+
onCancelClick: this.#cancel.bind(this),
624+
onContextClick: this.#handleContextClick.bind(this),
625+
onNewConversation: this.#handleNewChatRequest.bind(this),
626+
onCancelCrossOriginChat: this.#blockedByCrossOrigin && this.#previousSameOriginContext ?
627+
this.#handleCrossOriginChatCancellation.bind(this) :
628+
undefined,
629+
},
630+
this.#viewOutput, this.#contentContainer);
632631
}
633632

634633
#handleSelectElementClick(): void {
@@ -651,7 +650,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
651650
}
652651

653652
#handleContextClick(): void|Promise<void> {
654-
const context = this.#viewProps.selectedContext;
653+
const context = this.#selectedContext;
655654
if (context instanceof RequestContext) {
656655
const requestLocation = NetworkForward.UIRequestLocation.UIRequestLocation.tab(
657656
context.getItem(), NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT);
@@ -668,7 +667,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
668667
}
669668

670669
handleAction(actionId: string): void {
671-
if (this.#viewProps.isLoading) {
670+
if (this.#isLoading) {
672671
// If running some queries already, focus the input with the abort
673672
// button and do nothing.
674673
this.#viewOutput.chatView?.focusTextInput();
@@ -792,8 +791,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
792791
return;
793792
}
794793
this.#currentConversation = conversation;
795-
this.#viewProps.messages = [];
796-
this.#viewProps.isReadOnly = this.#currentConversation?.isReadOnly ?? false;
794+
this.#messages = [];
797795
await this.#doConversation(conversation.history);
798796
}
799797

@@ -813,29 +811,25 @@ export class AiAssistancePanel extends UI.Panel.Panel {
813811
#runAbortController = new AbortController();
814812
#cancel(): void {
815813
this.#runAbortController.abort();
816-
this.#viewProps.isLoading = false;
814+
this.#isLoading = false;
817815
void this.doUpdate();
818816
}
819817

820818
#onContextSelectionChanged(contextToRestore?: ConversationContext<unknown>): void {
821819
if (!this.#currentAgent) {
822-
this.#viewProps.blockedByCrossOrigin = false;
820+
this.#blockedByCrossOrigin = false;
823821
return;
824822
}
825823
const currentContext = contextToRestore ?? this.#getConversationContext();
826-
this.#viewProps.selectedContext = currentContext;
824+
this.#selectedContext = currentContext;
827825
if (!currentContext) {
828-
this.#viewProps.blockedByCrossOrigin = false;
826+
this.#blockedByCrossOrigin = false;
829827
return;
830828
}
831-
this.#viewProps.blockedByCrossOrigin = !currentContext.isOriginAllowed(this.#currentAgent.origin);
832-
if (!this.#viewProps.blockedByCrossOrigin) {
829+
this.#blockedByCrossOrigin = !currentContext.isOriginAllowed(this.#currentAgent.origin);
830+
if (!this.#blockedByCrossOrigin) {
833831
this.#previousSameOriginContext = currentContext;
834832
}
835-
if (this.#viewProps.blockedByCrossOrigin && this.#previousSameOriginContext) {
836-
this.#viewProps.onCancelCrossOriginChat = this.#handleCrossOriginChatCancellation.bind(this);
837-
}
838-
this.#viewProps.stripLinks = this.#viewProps.agentType === AgentType.PERFORMANCE;
839833
}
840834

841835
#getConversationContext(): ConversationContext<unknown>|null {
@@ -917,20 +911,20 @@ export class AiAssistancePanel extends UI.Panel.Panel {
917911
}
918912
}
919913

920-
this.#viewProps.isLoading = true;
914+
this.#isLoading = true;
921915
for await (const data of items) {
922916
step.sideEffect = undefined;
923917
switch (data.type) {
924918
case ResponseType.USER_QUERY: {
925-
this.#viewProps.messages.push({
919+
this.#messages.push({
926920
entity: ChatMessageEntity.USER,
927921
text: data.query,
928922
});
929923
systemMessage = {
930924
entity: ChatMessageEntity.MODEL,
931925
steps: [],
932926
};
933-
this.#viewProps.messages.push(systemMessage);
927+
this.#messages.push(systemMessage);
934928
break;
935929
}
936930
case ResponseType.QUERYING: {
@@ -981,9 +975,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
981975
step.code ??= data.code;
982976
step.output ??= data.output;
983977
step.canceled = data.canceled;
984-
if (isAiAssistancePatchingEnabled() && this.#currentAgent && !this.#currentConversation?.isReadOnly) {
985-
this.#viewProps.changeSummary = this.#changeManager.formatChanges(this.#currentAgent.id);
986-
}
987978
commitStep();
988979
break;
989980
}
@@ -1019,7 +1010,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
10191010

10201011
// Commit update intermediated step when not
10211012
// in read only mode.
1022-
if (!this.#viewProps.isReadOnly) {
1013+
if (!this.#currentConversation?.isReadOnly) {
10231014
void this.doUpdate();
10241015

10251016
// This handles scrolling to the bottom for live conversations when:
@@ -1031,7 +1022,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
10311022
}
10321023
}
10331024

1034-
this.#viewProps.isLoading = false;
1025+
this.#isLoading = false;
10351026
this.#viewOutput.chatView?.finishTextAnimations();
10361027
void this.doUpdate();
10371028
} finally {

0 commit comments

Comments
 (0)