Skip to content

Commit 3714657

Browse files
committed
add agent search
1 parent 1c8aa1c commit 3714657

File tree

5 files changed

+103
-3
lines changed

5 files changed

+103
-3
lines changed

src/app/components/side-panel/side-panel.component.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,22 @@
3333
class="app-select"
3434
panelClass="wide-agent-dropdown-panel"
3535
(selectionChange)="appSelectionChange.emit($event)"
36+
(openedChange)="agentSearchControl.setValue('')"
3637
[placeholder]="isLoadingApps()() ? 'Loading...' : 'Select an agent'"
3738
[formControl]="selectedAppControl()"
3839
>
39-
@if (apps$() | async; as availableApps) {
40+
<mat-option class="search-option" (click)="$event.stopPropagation()" [value]="null">
41+
<mat-form-field class="agent-search-field" subscriptSizing="dynamic" (click)="$event.stopPropagation()">
42+
<input
43+
matInput
44+
placeholder="Search agents..."
45+
[formControl]="agentSearchControl"
46+
(click)="$event.stopPropagation()"
47+
(keydown)="$event.stopPropagation()"
48+
/>
49+
</mat-form-field>
50+
</mat-option>
51+
@if (filteredApps$ | async; as availableApps) {
4052
@for (appName of availableApps; track appName) {
4153
<mat-option class="app-name-option" [value]="appName">{{ appName }}</mat-option>
4254
}

src/app/components/side-panel/side-panel.component.scss

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@
233233
:host ::ng-deep .wide-agent-dropdown-panel {
234234
min-width: 300px;
235235
max-width: 600px;
236+
max-height: 400px;
236237

237238
.mat-mdc-option {
238239
white-space: normal;
@@ -241,4 +242,34 @@
241242
min-height: 48px;
242243
padding: 8px 16px;
243244
}
245+
246+
.search-option {
247+
position: sticky !important;
248+
top: 0 !important;
249+
z-index: 1000 !important;
250+
background-color: var(--mat-select-panel-background-color, white) !important;
251+
padding: 8px 16px !important;
252+
border-bottom: 1px solid var(--mat-divider-color, rgba(0, 0, 0, 0.12));
253+
min-height: auto !important;
254+
height: auto !important;
255+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
256+
opacity: 1 !important;
257+
258+
&:hover {
259+
background-color: var(--mat-select-panel-background-color, white) !important;
260+
}
261+
262+
&.mat-mdc-option.mat-mdc-option-active {
263+
background-color: var(--mat-select-panel-background-color, white) !important;
264+
}
265+
}
266+
}
267+
268+
269+
.agent-search-field {
270+
width: 100%;
271+
272+
.mat-mdc-form-field-subscript-wrapper {
273+
display: none;
274+
}
244275
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,9 @@ describe('SidePanelComponent', () => {
253253
it('shows all apps in selector', () => {
254254
const appSelect = fixture.debugElement.query(APP_SELECT_SELECTOR);
255255
const options = appSelect.componentInstance.options;
256-
expect(options.map((option: MatOption) => option.value)).toEqual([
256+
// Filter out the search option (which has value=null)
257+
const appOptions = options.filter((option: MatOption) => option.value !== null);
258+
expect(appOptions.map((option: MatOption) => option.value)).toEqual([
257259
'app1',
258260
'app2',
259261
]);

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717

1818
import {AsyncPipe, NgComponentOutlet, NgTemplateOutlet} from '@angular/common';
1919
import {AfterViewInit, Component, DestroyRef, effect, EnvironmentInjector, inject, input, output, runInInjectionContext, signal, Type, viewChild, ViewContainerRef, type WritableSignal} from '@angular/core';
20+
import {toObservable} from '@angular/core/rxjs-interop';
2021
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
2122
import {MatMiniFabButton} from '@angular/material/button';
2223
import {MatOption} from '@angular/material/core';
24+
import {MatFormField} from '@angular/material/form-field';
25+
import {MatInput} from '@angular/material/input';
2326
import {MatIcon} from '@angular/material/icon';
2427
import {MatPaginator, PageEvent} from '@angular/material/paginator';
2528
import {MatProgressSpinner} from '@angular/material/progress-spinner';
@@ -29,7 +32,7 @@ import {MatTooltip} from '@angular/material/tooltip';
2932
import {type SafeHtml} from '@angular/platform-browser';
3033
import {NgxJsonViewerModule} from 'ngx-json-viewer';
3134
import {combineLatest, Observable, of} from 'rxjs';
32-
import {first, map} from 'rxjs/operators';
35+
import {first, map, startWith, switchMap} from 'rxjs/operators';
3336

3437
import {EvalCase} from '../../core/models/Eval';
3538
import {Session} from '../../core/models/Session';
@@ -77,6 +80,8 @@ import {SidePanelMessagesInjectionToken} from './side-panel.component.i18n';
7780
MatSelect,
7881
ReactiveFormsModule,
7982
MatProgressSpinner,
83+
MatFormField,
84+
MatInput,
8085
],
8186
})
8287
export class SidePanelComponent implements AfterViewInit {
@@ -154,6 +159,27 @@ export class SidePanelComponent implements AfterViewInit {
154159
protected readonly isSessionsTabReorderingEnabledObs =
155160
this.featureFlagService.isSessionsTabReorderingEnabled();
156161

162+
// Agent search
163+
readonly agentSearchControl = new FormControl('', { nonNullable: true });
164+
readonly filteredApps$: Observable<string[] | undefined> = toObservable(this.apps$).pipe(
165+
switchMap(appsObservable =>
166+
combineLatest([
167+
appsObservable,
168+
this.agentSearchControl.valueChanges.pipe(startWith(''))
169+
])
170+
),
171+
map(([apps, searchTerm]) => {
172+
if (!apps) {
173+
return apps;
174+
}
175+
if (!searchTerm || searchTerm.trim() === '') {
176+
return apps;
177+
}
178+
const lowerSearch = searchTerm.toLowerCase().trim();
179+
return apps.filter(app => app.toLowerCase().startsWith(lowerSearch));
180+
})
181+
);
182+
157183
ngAfterViewInit() {
158184
// Wait one tick until the eval tab container is ready.
159185
setTimeout(() => {

src/styles.scss

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,3 +885,32 @@ html.dark-theme {
885885
border-bottom: none !important;
886886
}
887887
}
888+
889+
.wide-agent-dropdown-panel {
890+
padding: 0 !important;
891+
892+
.search-option {
893+
position: sticky !important;
894+
top: 0 !important;
895+
z-index: 1000 !important;
896+
opacity: 1 !important;
897+
padding-top: 8px;
898+
padding-bottom: 8px;
899+
}
900+
901+
span {
902+
width: 100%;
903+
}
904+
}
905+
906+
html.dark-theme .wide-agent-dropdown-panel .search-option {
907+
background-color: #2b2b2f !important;
908+
909+
input {
910+
caret-color: white !important;
911+
}
912+
}
913+
914+
html.light-theme .wide-agent-dropdown-panel .search-option {
915+
background-color: #ffffff !important;
916+
}

0 commit comments

Comments
 (0)