Skip to content

Commit 105bcea

Browse files
google-genai-botcopybara-github
authored andcommitted
ADK changes
PiperOrigin-RevId: 859053463
1 parent a4e45f4 commit 105bcea

File tree

5 files changed

+86
-24
lines changed

5 files changed

+86
-24
lines changed

src/app/components/chat-panel/chat-panel.component.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,13 +458,17 @@ describe('ChatPanelComponent', () => {
458458
}));
459459

460460
it('should restore scroll position', fakeAsync(() => {
461+
component['onScroll'].next(
462+
{target: scrollContainer} as unknown as Event);
463+
tick();
464+
465+
Object.defineProperty(
466+
scrollContainer, 'scrollHeight',
467+
{value: 1500, configurable: true});
461468
mockUiStateService.newMessagesLoadedResponse.next({
462469
items: [{role: 'bot', text: 'message 1'}],
463470
nextPageToken: nextToken
464471
});
465-
Object.defineProperty(
466-
scrollContainer, 'scrollHeight',
467-
{value: 1500, configurable: true});
468472

469473
tick(50);
470474

src/app/components/chat-panel/chat-panel.component.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit {
124124
@ViewChild('autoScroll') scrollContainer!: ElementRef;
125125
@ViewChild('messageTextarea') public textarea: ElementRef|undefined;
126126
scrollInterrupted = false;
127+
private scrollHeight = 0;
127128
private lastMessageRef: any = null;
128129
private nextPageToken = '';
129130
protected readonly i18n = inject(ChatPanelMessagesInjectionToken);
@@ -176,9 +177,12 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit {
176177
switchMap(
177178
() => merge(
178179
this.uiStateService.onNewMessagesLoaded().pipe(
179-
tap((response: ListResponse<any>) => {
180+
tap((response: ListResponse<any>&
181+
{isBackground?: boolean}) => {
180182
this.nextPageToken = response.nextPageToken ?? '';
181-
this.restoreScrollPosition();
183+
if (!response.isBackground) {
184+
this.restoreScrollPosition();
185+
}
182186
})),
183187
this.onScroll.pipe(switchMap((event: Event) => {
184188
const element = event.target as HTMLElement;
@@ -190,6 +194,7 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit {
190194
return EMPTY;
191195
}
192196

197+
this.scrollHeight = element.scrollHeight;
193198
return this.uiStateService
194199
.lazyLoadMessages(this.sessionName(), {
195200
pageSize: 100,
@@ -264,14 +269,16 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit {
264269
}
265270

266271
private restoreScrollPosition() {
267-
// Scroll to the last unseen message after the new messages are loaded.
268-
if (this.scrollContainer?.nativeElement) {
269-
const oldScrollHeight = this.scrollContainer.nativeElement.scrollHeight;
270-
setTimeout(() => {
271-
const newScrollHeight = this.scrollContainer.nativeElement.scrollHeight;
272-
this.scrollContainer.nativeElement.scrollTop =
273-
newScrollHeight - oldScrollHeight;
274-
});
272+
if (!this.scrollHeight) {
273+
this.scrollInterrupted = false;
274+
this.scrollToBottom();
275+
return;
276+
}
277+
const scrollContainer = this.scrollContainer?.nativeElement;
278+
if (scrollContainer) {
279+
scrollContainer.scrollTop =
280+
scrollContainer.scrollHeight - this.scrollHeight;
281+
this.scrollHeight = 0;
275282
}
276283
}
277284

src/app/components/chat/chat.component.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ describe('ChatComponent', () => {
377377
];
378378

379379
beforeEach(async () => {
380+
component.messages.set([]);
381+
component.eventData = new Map();
380382
mockUiStateService.newMessagesLoadedResponse.next({
381383
items: events,
382384
nextPageToken: '',
@@ -390,6 +392,42 @@ describe('ChatComponent', () => {
390392
expect(messages[1].text).toBe('bot response');
391393
});
392394

395+
it(
396+
'should not clear existing messages when new messages are loaded',
397+
fakeAsync(() => {
398+
component.messages.set([
399+
{role: 'user', text: 'existing message'},
400+
]);
401+
mockUiStateService.newMessagesLoadedResponse.next({
402+
items: events,
403+
nextPageToken: '',
404+
});
405+
tick();
406+
const messages = component.messages();
407+
expect(messages.length).toBe(3);
408+
expect(messages[0].text).toBe('user message');
409+
expect(messages[1].text).toBe('bot response');
410+
expect(messages[2].text).toBe('existing message');
411+
}));
412+
413+
it(
414+
'should clear existing messages when new messages are loaded for a different session',
415+
fakeAsync(() => {
416+
component.messages.set([
417+
{role: 'user', text: 'existing message'},
418+
]);
419+
component.sessionId = 'session-2'; // change session
420+
mockUiStateService.newMessagesLoadedResponse.next({
421+
items: events,
422+
nextPageToken: '',
423+
});
424+
tick();
425+
const messages = component.messages();
426+
expect(messages.length).toBe(2);
427+
expect(messages[0].text).toBe('user message');
428+
expect(messages[1].text).toBe('bot response');
429+
}));
430+
393431
it('should store events', () => {
394432
expect(component.eventData.has('event-1')).toBeFalse();
395433
expect(component.eventData.has('event-2')).toBeTrue();

src/app/components/chat/chat.component.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
194194
userId = 'user';
195195
appName = '';
196196
sessionId = ``;
197+
sessionIdOfLoadedMessages = '';
197198
evalCase: EvalCase|null = null;
198199
updatedEvalCase: EvalCase|null = null;
199200
evalSetId = '';
@@ -388,8 +389,9 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
388389
.subscribe((enabled) => {
389390
if (enabled) {
390391
this.uiStateService.onNewMessagesLoaded().subscribe(
391-
(response: ListResponse<any>) => {
392-
this.populateMessages(response.items, true);
392+
(response: ListResponse<any> & {isBackground?: boolean}) => {
393+
this.populateMessages(
394+
response.items, true, !response.isBackground);
393395
this.loadTraceData();
394396
});
395397

@@ -1237,9 +1239,12 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
12371239
}
12381240
}
12391241

1240-
private resetEventsAndMessages() {
1242+
private resetEventsAndMessages({keepMessages}: {keepMessages?:
1243+
boolean} = {}) {
12411244
this.eventData.clear();
1242-
this.messages.set([]);
1245+
if (!keepMessages) {
1246+
this.messages.set([]);
1247+
}
12431248
this.artifacts = [];
12441249
}
12451250

@@ -1255,21 +1260,28 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
12551260
this.changeDetectorRef.detectChanges();
12561261
}
12571262

1258-
private populateMessages(events: any[], reverse: boolean = false) {
1259-
this.resetEventsAndMessages();
1263+
private populateMessages(
1264+
events: any[], reverseOrder: boolean = false,
1265+
keepOldMessages: boolean = false) {
1266+
this.resetEventsAndMessages({
1267+
keepMessages:
1268+
keepOldMessages && this.sessionIdOfLoadedMessages === this.sessionId
1269+
});
12601270

12611271
events.forEach((event: any) => {
12621272
const parts = event.content?.parts || [];
1263-
const partsToProcess = reverse ? [...parts].reverse() : parts;
1273+
const partsToProcess = reverseOrder ? [...parts].reverse() : parts;
12641274
partsToProcess.forEach((part: any) => {
12651275
this.storeMessage(
12661276
part, event, event.author === 'user' ? 'user' : 'bot', undefined,
1267-
undefined, reverse);
1277+
undefined, reverseOrder);
12681278
if (event.author && event.author !== 'user') {
12691279
this.storeEvents(part, event);
12701280
}
12711281
});
12721282
});
1283+
1284+
this.sessionIdOfLoadedMessages = this.sessionId;
12731285
}
12741286

12751287
protected updateWithSelectedSession(session: Session) {

src/app/core/services/ui-state.service.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class UiStateService implements UiStateServiceInterface {
3939
new BehaviorSubject<boolean>(false);
4040
private readonly _isMessagesLoading = new BehaviorSubject<boolean>(false);
4141
protected readonly _newMessagesLoadedResponse =
42-
new Subject<ListResponse<any>>();
42+
new Subject<ListResponse<any>&{isBackground?: boolean}>();
4343
protected readonly _newMessagesLoadingFailedResponse =
4444
new Subject<{message: string}>();
4545
private readonly featureFlagService = inject(FEATURE_FLAG_SERVICE);
@@ -96,8 +96,9 @@ export class UiStateService implements UiStateServiceInterface {
9696
);
9797
}
9898

99-
lazyLoadMessages(sessionName: string, listParams?: ListParams, isBackground?: boolean):
100-
Observable<void> {
99+
lazyLoadMessages(
100+
sessionName: string, listParams?: ListParams,
101+
isBackground?: boolean): Observable<void> {
101102
throw new Error('Not implemented');
102103
}
103104

0 commit comments

Comments
 (0)