Skip to content

Commit a2a337d

Browse files
wyf7107copybara-github
authored andcommitted
feat: Remove pending event dialog, replace with inline
chip for human in the loop responses PiperOrigin-RevId: 875781819
1 parent ebc9cbc commit a2a337d

20 files changed

Lines changed: 337 additions & 442 deletions

‎src/app/components/a2ui-canvas/a2ui-canvas.component.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class A2uiCanvasComponent implements OnChanges {
5252

5353
readonly surfaceId = signal<string | null>(null);
5454

55-
readonly activeSurface = signal<Types.Surface | null>(null);
55+
readonly activeSurface = signal<any>(null);
5656
readonly surface = computed(() => this.activeSurface());
5757

5858
constructor() {}

‎src/app/components/chat-panel/chat-panel.component.html‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,16 @@
275275
{{ functionCall.name }}
276276
</button>
277277
}
278+
279+
@if (functionCall.needsResponse) {
280+
<app-long-running-response
281+
[functionCall]="functionCall"
282+
[appName]="appName"
283+
[userId]="userId"
284+
[sessionId]="sessionId"
285+
(responseComplete)="longRunningResponseComplete.emit($event)"
286+
></app-long-running-response>
287+
}
278288
}
279289
}
280290
@if (message.functionResponses && message.functionResponses.length > 0) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {A2uiCanvasComponent} from '../a2ui-canvas/a2ui-canvas.component';
4848
import {MediaType,} from '../artifact-tab/artifact-tab.component';
4949
import {AudioPlayerComponent} from '../audio-player/audio-player.component';
5050
import {ComputerActionComponent} from '../computer-action/computer-action.component';
51+
import {LongRunningResponseComponent} from '../long-running-response/long-running-response';
5152
import {MARKDOWN_COMPONENT, MarkdownComponentInterface} from '../markdown/markdown.component.interface';
5253
import {MessageFeedbackComponent} from '../message-feedback/message-feedback.component';
5354

@@ -80,6 +81,7 @@ const ROOT_AGENT = 'root_agent';
8081
NgClass,
8182
JsonTooltipDirective,
8283
ComputerActionComponent,
84+
LongRunningResponseComponent,
8385
],
8486
})
8587
export class ChatPanelComponent implements OnChanges, AfterViewInit {
@@ -100,6 +102,8 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit {
100102
@Input() isAudioRecording: boolean = false;
101103
@Input() isVideoRecording: boolean = false;
102104
@Input() hoveredEventMessageIndices: number[] = [];
105+
@Input() userId: string = '';
106+
@Input() sessionId: string = '';
103107

104108
@Output() readonly userInputChange = new EventEmitter<string>();
105109
@Output() readonly userEditEvalCaseMessageChange = new EventEmitter<string>();
@@ -125,6 +129,7 @@ export class ChatPanelComponent implements OnChanges, AfterViewInit {
125129
@Output() readonly updateState = new EventEmitter<void>();
126130
@Output() readonly toggleAudioRecording = new EventEmitter<void>();
127131
@Output() readonly toggleVideoRecording = new EventEmitter<void>();
132+
@Output() readonly longRunningResponseComplete = new EventEmitter<any[]>();
128133

129134
@ViewChild('videoContainer', {read: ElementRef}) videoContainer!: ElementRef;
130135
@ViewChild('autoScroll') scrollContainer!: ElementRef;

‎src/app/components/chat/chat.component.html‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,7 @@
278278
</div>
279279
}
280280
</div>
281-
} } @if (longRunningEvents.length > 0) {
282-
<button mat-fab color="primary" class="fab-button" (click)="openDialog()">
283-
<mat-icon>priority_high</mat-icon>
284-
</button>
285-
} @if (appName != "") {
281+
} } @if (appName != "") {
286282
<app-chat-panel
287283
[appName]="appName"
288284
[messages]="messages()"
@@ -300,6 +296,8 @@
300296
[isAudioRecording]="isAudioRecording"
301297
[isVideoRecording]="isVideoRecording"
302298
[hoveredEventMessageIndices]="hoveredEventMessageIndices"
299+
[userId]="userId"
300+
[sessionId]="sessionId"
303301
(clickEvent)="clickEvent($event)"
304302
(handleKeydown)="handleKeydown($event.event, $event.message)"
305303
(cancelEditMessage)="cancelEditMessage($event)"
@@ -316,6 +314,7 @@
316314
(updateState)="updateState()"
317315
(toggleAudioRecording)="toggleAudioRecording()"
318316
(toggleVideoRecording)="toggleVideoRecording()"
317+
(longRunningResponseComplete)="processRunSseResponse($event)"
319318
[sessionName]="sessionId"
320319
></app-chat-panel>
321320
}

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

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @license
3-
* Copyright 2025 Google LLC
3+
* Copyright 2026 Google LLC
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -67,7 +67,6 @@ import {CanvasComponent} from '../canvas/canvas.component';
6767
import {ChatPanelComponent} from '../chat-panel/chat-panel.component';
6868
import {EditJsonDialogComponent} from '../edit-json-dialog/edit-json-dialog.component';
6969
import {EvalTabComponent} from '../eval-tab/eval-tab.component';
70-
import {PendingEventDialogComponent} from '../pending-event-dialog/pending-event-dialog.component';
7170
import {DeleteSessionDialogComponent, DeleteSessionDialogData,} from '../session-tab/delete-session-dialog/delete-session-dialog.component';
7271
import {SidePanelComponent} from '../side-panel/side-panel.component';
7372
import {TraceEventComponent} from '../trace-tab/trace-event/trace-event.component';
@@ -929,29 +928,35 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
929928
}
930929

931930
if (e?.longRunningToolIds && e.longRunningToolIds.length > 0) {
931+
const startIndex = this.longRunningEvents.length;
932932
this.getAsyncFunctionsFromParts(
933933
e.longRunningToolIds, e.content.parts, e.invocationId);
934-
const func = this.longRunningEvents[0].function;
935-
if (func.args.authConfig &&
936-
func.args.authConfig.exchangedAuthCredential &&
937-
func.args.authConfig.exchangedAuthCredential.oauth2) {
938-
// for OAuth
939-
const authUri =
940-
func.args.authConfig.exchangedAuthCredential.oauth2.authUri;
941-
const updatedAuthUri = this.updateRedirectUri(
942-
authUri,
943-
this.redirectUri,
944-
);
945-
this.openOAuthPopup(updatedAuthUri)
946-
.then((authResponseUrl) => {
947-
this.functionCallEventId = e.id;
948-
this.sendOAuthResponse(func, authResponseUrl, this.redirectUri);
949-
})
950-
.catch((error) => {
951-
console.error('OAuth Error:', error);
952-
});
953-
} else {
954-
this.functionCallEventId = e.id;
934+
935+
// Store event ID for later reference
936+
this.functionCallEventId = e.id;
937+
938+
// Check all newly added events for OAuth requirements
939+
for (let i = startIndex; i < this.longRunningEvents.length; i++) {
940+
const func = this.longRunningEvents[i].function;
941+
if (func.args.authConfig &&
942+
func.args.authConfig.exchangedAuthCredential &&
943+
func.args.authConfig.exchangedAuthCredential.oauth2) {
944+
// for OAuth
945+
const authUri =
946+
func.args.authConfig.exchangedAuthCredential.oauth2.authUri;
947+
const updatedAuthUri = this.updateRedirectUri(
948+
authUri,
949+
this.redirectUri,
950+
);
951+
this.openOAuthPopup(updatedAuthUri)
952+
.then((authResponseUrl) => {
953+
this.sendOAuthResponse(func, authResponseUrl, this.redirectUri);
954+
})
955+
.catch((error) => {
956+
console.error('OAuth Error:', error);
957+
});
958+
break; // Handle one OAuth at a time
959+
}
955960
}
956961
}
957962
if (e?.actions && e.actions.artifactDelta) {
@@ -1009,7 +1014,21 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
10091014
}
10101015
message.eventId = e?.id;
10111016
} else if (part.functionCall) {
1012-
message.functionCalls = [part.functionCall];
1017+
// Enrich function call with long-running metadata if applicable
1018+
const isLongRunning =
1019+
e?.longRunningToolIds?.includes(part.functionCall.id);
1020+
const enrichedFunctionCall = {
1021+
...part.functionCall,
1022+
...(isLongRunning && {
1023+
isLongRunning: true,
1024+
invocationId: e.invocationId,
1025+
functionCallEventId: e.id,
1026+
needsResponse: true,
1027+
responseStatus: 'pending',
1028+
userResponse: '',
1029+
}),
1030+
};
1031+
message.functionCalls = [enrichedFunctionCall];
10131032
message.eventId = e?.id;
10141033
} else if (part.functionResponse) {
10151034
message.functionResponses = [part.functionResponse];
@@ -1297,7 +1316,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
12971316
});
12981317
}
12991318

1300-
private processRunSseResponse(response: any) {
1319+
protected processRunSseResponse(response: any) {
13011320
for (const e of response) {
13021321
if (e.content) {
13031322
for (let part of e.content.parts) {
@@ -1307,33 +1326,6 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy {
13071326
}
13081327
}
13091328

1310-
openDialog(): void {
1311-
const dialogRef = this.dialog.open(PendingEventDialogComponent, {
1312-
width: '600px',
1313-
data: {
1314-
event: this.longRunningEvents[0].function,
1315-
appName: this.appName,
1316-
userId: this.userId,
1317-
sessionId: this.sessionId,
1318-
functionCallEventId: this.functionCallEventId,
1319-
invocationId: this.longRunningEvents[0].invocationId
1320-
},
1321-
});
1322-
1323-
dialogRef.afterClosed().subscribe((t) => {
1324-
if (t) {
1325-
this.removeFinishedLongRunningEvents(t.events);
1326-
this.processRunSseResponse(t.response);
1327-
this.changeDetectorRef.detectChanges();
1328-
}
1329-
});
1330-
}
1331-
1332-
removeFinishedLongRunningEvents(finishedEvents: any[]) {
1333-
const idsToExclude = new Set(finishedEvents.map((obj: any) => obj.id));
1334-
this.longRunningEvents =
1335-
this.longRunningEvents.filter(obj => !idsToExclude.has(obj.id));
1336-
}
13371329

13381330
createAgentIconColorClass(agentName: string) {
13391331
const agentIconColor = this.stringToColorService.stc(agentName);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
Copyright 2026 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
<div class="response-chip-container" (click)="$event.stopPropagation()">
18+
@if (functionCall.responseStatus === 'sent') {
19+
<div class="status-chip sent">
20+
<mat-icon>check_circle</mat-icon>
21+
<span>Response sent</span>
22+
</div>
23+
} @else if (functionCall.responseStatus === 'sending') {
24+
<div class="status-chip sending">
25+
<mat-icon>hourglass_empty</mat-icon>
26+
<span>Sending...</span>
27+
</div>
28+
} @else {
29+
<div class="input-chip">
30+
<mat-icon class="input-icon">rate_review</mat-icon>
31+
<input
32+
class="response-input"
33+
[(ngModel)]="functionCall.userResponse"
34+
(keydown.enter)="onSend()"
35+
placeholder="Enter response..."
36+
/>
37+
<button
38+
mat-icon-button
39+
(click)="onSend()"
40+
[disabled]="!functionCall.userResponse"
41+
class="send-icon-btn"
42+
>
43+
<mat-icon>send</mat-icon>
44+
</button>
45+
</div>
46+
}
47+
</div>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
.response-chip-container {
18+
display: inline-block;
19+
margin: 5px;
20+
}
21+
22+
.input-chip {
23+
display: inline-flex;
24+
align-items: center;
25+
gap: 4px;
26+
border: 1px solid var(--mat-standard-button-toggle-divider-color);
27+
border-radius: 16px;
28+
padding: 0 4px 0 12px;
29+
background-color: var(--chat-panel-function-event-button-background-color);
30+
height: 30px;
31+
box-sizing: border-box;
32+
max-width: 300px;
33+
34+
.input-icon {
35+
color: var(--long-running-response-icon-color);
36+
font-size: 18px;
37+
width: 18px;
38+
height: 18px;
39+
}
40+
41+
.response-input {
42+
border: none;
43+
outline: none;
44+
background: transparent;
45+
font-size: 13px;
46+
flex: 1;
47+
min-width: 120px;
48+
padding: 0;
49+
color: var(--long-running-response-input-text-color);
50+
caret-color: var(--long-running-response-input-caret-color);
51+
52+
&::placeholder {
53+
color: var(--long-running-response-input-placeholder-color);
54+
}
55+
}
56+
57+
.send-icon-btn {
58+
width: 24px;
59+
height: 24px;
60+
padding: 0;
61+
min-width: unset;
62+
color: var(--long-running-response-send-button-color);
63+
64+
mat-icon {
65+
font-size: 18px;
66+
width: 18px;
67+
height: 18px;
68+
}
69+
}
70+
}
71+
72+
.status-chip {
73+
display: inline-flex;
74+
align-items: center;
75+
gap: 4px;
76+
border-radius: 16px;
77+
padding: 0 12px;
78+
font-size: 13px;
79+
font-weight: 500;
80+
height: 32px;
81+
box-sizing: border-box;
82+
border: 1px solid;
83+
line-height: 32px;
84+
85+
mat-icon {
86+
font-size: 18px;
87+
width: 18px;
88+
height: 18px;
89+
}
90+
91+
&.sending {
92+
background-color: rgba(33, 150, 243, 0.1);
93+
border-color: rgba(33, 150, 243, 0.3);
94+
color: #2196f3;
95+
}
96+
97+
&.sent {
98+
background-color: rgba(76, 175, 80, 0.1);
99+
border-color: rgba(76, 175, 80, 0.3);
100+
color: #4caf50;
101+
}
102+
}

0 commit comments

Comments
 (0)