Skip to content

Commit 5deef73

Browse files
reniowoodcopybara-github
authored andcommitted
ADK changes
PiperOrigin-RevId: 855414184
1 parent 4644c74 commit 5deef73

File tree

2 files changed

+81
-5
lines changed

2 files changed

+81
-5
lines changed

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

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import {MARKDOWN_COMPONENT} from '../markdown/markdown.component.interface';
7070
import {MockMarkdownComponent} from '../markdown/testing/mock-markdown.component';
7171
import {SidePanelComponent} from '../side-panel/side-panel.component';
7272

73-
import {ChatComponent} from './chat.component';
73+
import {ChatComponent, INITIAL_USER_INPUT_QUERY_PARAM} from './chat.component';
7474

7575
// Mock EvalTabComponent to satisfy the required viewChild in ChatComponent
7676
@Component({
@@ -179,10 +179,13 @@ describe('ChatComponent', () => {
179179

180180
mockDialog = jasmine.createSpyObj('MatDialog', ['open']);
181181
mockSnackBar = jasmine.createSpyObj('MatSnackBar', ['open']);
182-
mockRouter = jasmine.createSpyObj('Router', ['navigate', 'createUrlTree'], {
183-
events: of(new NavigationEnd(1, '', '')),
184-
});
185-
mockLocation = jasmine.createSpyObj('Location', ['replaceState']);
182+
mockRouter = jasmine.createSpyObj(
183+
'Router',
184+
['navigate', 'createUrlTree', 'parseUrl', 'navigateByUrl'],
185+
{events: of(new NavigationEnd(1, '', ''))},
186+
);
187+
mockRouter.parseUrl.and.returnValue({queryParams: {}} as any);
188+
mockLocation = jasmine.createSpyObj('Location', ['replaceState', 'path']);
186189
mockAgentBuilderService = jasmine.createSpyObj(
187190
'AgentBuilderService', ['clear', 'setLoadedAgentData']);
188191

@@ -267,6 +270,25 @@ describe('ChatComponent', () => {
267270
expect(component).toBeTruthy();
268271
});
269272

273+
it(
274+
'should pre-fill user input from "q" query param only when app is selected',
275+
fakeAsync(() => {
276+
mockAgentService.setApp(''); // Initially no app
277+
mockActivatedRoute.snapshot!
278+
.queryParams = {[INITIAL_USER_INPUT_QUERY_PARAM]: 'hello'};
279+
mockActivatedRoute.queryParams =
280+
of({[INITIAL_USER_INPUT_QUERY_PARAM]: 'hello'});
281+
fixture = TestBed.createComponent(ChatComponent);
282+
component = fixture.componentInstance;
283+
fixture.detectChanges();
284+
expect(component.userInput).toBe(''); // Should be empty initially
285+
286+
mockAgentService.setApp(TEST_APP_1_NAME);
287+
tick();
288+
289+
expect(component.userInput).toBe('hello'); // Should be set now
290+
}));
291+
270292
it(
271293
'should project content into adk-web-chat-container-top', () => {
272294
const hostFixture = TestBed.createComponent(TestHostComponent);
@@ -912,6 +934,35 @@ describe('ChatComponent', () => {
912934
.toContain(TEST_MESSAGE);
913935
});
914936

937+
it(
938+
'should clear "q" query param when message is sent',
939+
fakeAsync(() => {
940+
mockAgentService.setApp(''); // Initially no app
941+
mockActivatedRoute.snapshot!
942+
.queryParams = {[INITIAL_USER_INPUT_QUERY_PARAM]: 'hello'};
943+
mockActivatedRoute.queryParams =
944+
of({[INITIAL_USER_INPUT_QUERY_PARAM]: 'hello'});
945+
const urlTree = {
946+
queryParams: {[INITIAL_USER_INPUT_QUERY_PARAM]: 'hello'},
947+
};
948+
mockRouter.parseUrl.and.returnValue(urlTree as any);
949+
mockLocation.path.and.returnValue('/?q=hello');
950+
fixture = TestBed.createComponent(ChatComponent);
951+
component = fixture.componentInstance;
952+
fixture.detectChanges();
953+
mockAgentService.setApp(TEST_APP_1_NAME);
954+
tick();
955+
956+
component.sendMessage(new KeyboardEvent('keydown', {key: 'Enter'}));
957+
tick();
958+
959+
expect(mockLocation.path).toHaveBeenCalled();
960+
expect(mockRouter.parseUrl).toHaveBeenCalledWith('/?q=hello');
961+
expect(urlTree.queryParams).toEqual({} as any); // q param should be
962+
// deleted.
963+
expect(mockRouter.navigateByUrl).toHaveBeenCalledWith(urlTree as any);
964+
}));
965+
915966
describe('when event contains multiple text parts', () => {
916967
it(
917968
'should combine consecutive text parts into a single message',

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ import {ViewImageDialogComponent} from '../view-image-dialog/view-image-dialog.c
7676
import {ChatMessagesInjectionToken} from './chat.component.i18n';
7777

7878
const ROOT_AGENT = 'root_agent';
79+
/** Query parameter for pre-filling user input. */
80+
export const INITIAL_USER_INPUT_QUERY_PARAM = 'q';
7981

8082
function fixBase64String(base64: string): string {
8183
// Replace URL-safe characters if they exist
@@ -306,6 +308,25 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
306308
this.syncSelectedAppFromUrl();
307309
this.updateSelectedAppUrl();
308310

311+
combineLatest([
312+
this.agentService.getApp(),
313+
this.activatedRoute.queryParams,
314+
])
315+
.pipe(
316+
filter(
317+
([app, params]) =>
318+
!!app && !!params[INITIAL_USER_INPUT_QUERY_PARAM],
319+
),
320+
first(),
321+
map(([, params]) => params[INITIAL_USER_INPUT_QUERY_PARAM]))
322+
.subscribe((initialUserInput) => {
323+
// Use `setTimeout` to ensure the userInput is set after the current
324+
// change detection cycle is complete.
325+
setTimeout(() => {
326+
this.userInput = initialUserInput;
327+
});
328+
});
329+
309330
this.streamChatService.onStreamClose().subscribe((closeReason) => {
310331
const error =
311332
'Please check server log for full details: \n' + closeReason;
@@ -570,6 +591,10 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
570591
});
571592
// Clear input
572593
this.userInput = '';
594+
// Clear the query param for the initial user input once it is sent.
595+
const updatedUrl = this.router.parseUrl(this.location.path());
596+
delete updatedUrl.queryParams[INITIAL_USER_INPUT_QUERY_PARAM];
597+
await this.router.navigateByUrl(updatedUrl);
573598
this.changeDetectorRef.detectChanges();
574599
}
575600

0 commit comments

Comments
 (0)