diff --git a/karma.conf.js b/karma.conf.js index 8a78074c..12c33f40 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,3 +1,20 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + module.exports = function (config) { config.set({ basePath: '', diff --git a/package-lock.json b/package-lock.json index 9561cd87..fefe3622 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "safevalues": "^1.2.0", "string-to-color": "2.2.2", "tslib": "^2.3.0", - "uuid": "^11.1.0", + "uuidv7": "^1.0.2", "vanilla-jsoneditor": "^3.6.0", "zone.js": "~0.15.0" }, @@ -34,7 +34,6 @@ "@angular/cli": "^19.1.7", "@angular/compiler-cli": "^19.1.0", "@types/jasmine": "~5.1.0", - "@types/uuid": "10.0.0", "jasmine-core": "~5.5.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -4623,13 +4622,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -14370,10 +14362,20 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", + "optional": true, "bin": { "uuid": "dist/esm/bin/uuid" } }, + "node_modules/uuidv7": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uuidv7/-/uuidv7-1.0.2.tgz", + "integrity": "sha512-8JQkH4ooXnm1JCIhqTMbtmdnYEn6oKukBxHn1Ic9878jMkL7daTI7anTExfY18VRCX7tcdn5quzvCb6EWrR8PA==", + "license": "Apache-2.0", + "bin": { + "uuidv7": "cli.js" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/src/app/components/chat/chat.component.ts b/src/app/components/chat/chat.component.ts index 396702d9..120c354d 100644 --- a/src/app/components/chat/chat.component.ts +++ b/src/app/components/chat/chat.component.ts @@ -44,7 +44,7 @@ import {URLUtil} from '../../../utils/url-util'; import {AgentRunRequest} from '../../core/models/AgentRunRequest'; import {EvalCase} from '../../core/models/Eval'; import {Session, SessionState} from '../../core/models/Session'; -import {LlmResponse} from '../../core/models/types'; +import {Event as AdkEvent} from '../../core/models/types'; import {AGENT_SERVICE, AgentService} from '../../core/services/agent.service'; import {ARTIFACT_SERVICE, ArtifactService} from '../../core/services/artifact.service'; import {AUDIO_SERVICE, AudioService} from '../../core/services/audio.service'; @@ -66,7 +66,6 @@ import {AudioPlayerComponent} from '../audio-player/audio-player.component'; import {ChatPanelComponent} from '../chat-panel/chat-panel.component'; import {EditJsonDialogComponent} from '../edit-json-dialog/edit-json-dialog.component'; import {EvalTabComponent} from '../eval-tab/eval-tab.component'; -import {EventTabComponent} from '../event-tab/event-tab.component'; import {PendingEventDialogComponent} from '../pending-event-dialog/pending-event-dialog.component'; import {DeleteSessionDialogComponent, DeleteSessionDialogData,} from '../session-tab/delete-session-dialog/delete-session-dialog.component'; import {SessionTabComponent} from '../session-tab/session-tab.component'; @@ -147,7 +146,6 @@ const BIDI_STREAMING_RESTART_WARNING = export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { chatPanel = viewChild.required(ChatPanelComponent); sideDrawer = viewChild.required('sideDrawer'); - eventTabComponent = viewChild.required(EventTabComponent); sessionTab = viewChild(SessionTabComponent); evalTab = viewChild(EvalTabComponent); private scrollContainer = viewChild.required('autoScroll'); @@ -459,7 +457,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { let index = this.eventMessageIndexArray.length - 1; this.streamingTextMessage = null; this.agentService.runSse(req).subscribe({ - next: async (chunkJson: LlmResponse) => { + next: async (chunkJson: AdkEvent) => { if (chunkJson.error) { this.openSnackBar(chunkJson.error, 'OK'); return; @@ -1155,6 +1153,7 @@ export class ChatComponent implements OnInit, AfterViewInit, OnDestroy { }); this.bottomPanelVisible = false; + this.changeDetectorRef.detectChanges(); } protected updateWithSelectedEvalCase(evalCase: EvalCase) { diff --git a/src/app/components/event-tab/event-tab.component.html b/src/app/components/event-tab/event-tab.component.html index 351825ac..b45f9634 100644 --- a/src/app/components/event-tab/event-tab.component.html +++ b/src/app/components/event-tab/event-tab.component.html @@ -15,7 +15,7 @@ -->
- @if (eventsMap.size>0) { + @if (eventsMap().size>0) {
@if (!isTraceView()) { @@ -24,7 +24,7 @@ @if (isTraceView()) {

Trace

} - @if (traceData) { + @if (traceData()) { Events Trace @@ -33,7 +33,7 @@
@if (!isTraceView()) { - @for (jsonData of eventsMap | keyvalue: mapOrderPreservingSort; track jsonData; let i = $index) { + @for (jsonData of eventsMap() | keyvalue: mapOrderPreservingSort; track jsonData; let i = $index) { {{i}} {{jsonData.value.title}} @@ -43,17 +43,17 @@ } @if (isTraceView()) { - @for (invoc of invocTraces | keyvalue: mapOrderPreservingSort; track invoc; let i = $index) { + @for (invoc of spansByTraceId() | keyvalue: mapOrderPreservingSort; track invoc; let i = $index) { {{i}} - Invocation {{findInvocIdFromTraceId(invoc.key)}} + Invocation {{invoc.value | invocId}} } }
} - @if (eventsMap.size==0) { + @if (eventsMap().size==0) {

No conversations

diff --git a/src/app/components/event-tab/event-tab.component.ts b/src/app/components/event-tab/event-tab.component.ts index 06b08722..9ddaa7c9 100644 --- a/src/app/components/event-tab/event-tab.component.ts +++ b/src/app/components/event-tab/event-tab.component.ts @@ -15,14 +15,16 @@ * limitations under the License. */ -import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; +import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, computed, inject, input, signal} from '@angular/core'; import {MatDialog} from '@angular/material/dialog'; +import {Span} from '../../core/models/Trace'; import {TraceChartComponent} from './trace-chart/trace-chart.component'; import { MatButtonToggleGroup, MatButtonToggle } from '@angular/material/button-toggle'; import { FormsModule } from '@angular/forms'; import { MatList, MatListItem } from '@angular/material/list'; import { KeyValuePipe } from '@angular/common'; +import {InvocIdPipe} from './invoc-id.pipe'; @Component({ selector: 'app-event-tab', @@ -35,29 +37,36 @@ import { KeyValuePipe } from '@angular/common'; MatList, MatListItem, KeyValuePipe, + InvocIdPipe, ], }) -export class EventTabComponent implements OnChanges { - @Input() eventsMap = new Map(); +export class EventTabComponent { + readonly eventsMap = input>(new Map()); + readonly traceData = input([]); @Output() selectedEvent = new EventEmitter(); - @Input() traceData: any[] = []; - llmRequest: any = undefined; - llmResponse: any = undefined; - llmRequestKey = 'gcp.vertex.agent.llm_request'; - llmResponseKey = 'gcp.vertex.agent.llm_response'; - isDetailsPanelOpen = false; - view = 'events'; - invocTraces = new Map(); + private readonly dialog = inject(MatDialog); - constructor(private dialog: MatDialog) {} - - ngOnChanges(changes: SimpleChanges): void { - if ('traceData' in changes) { - this.prcessTraceDataToInvocTrace(); + readonly view = signal('events'); + readonly isTraceView = computed(() => this.view() === 'trace'); + readonly spansByTraceId = computed(() => { + if (!this.traceData || this.traceData.length == 0) { + return new Map(); } - } + return this.traceData().reduce((map, span) => { + const key = span.trace_id; + const group = map.get(key); + if (group) { + span.invoc_id = span.attributes?.['gcp.vertex.agent.invocation_id']; + group.push(span); + group.sort((a: Span, b: Span) => a.start_time - b.start_time); + } else { + map.set(key, [span]); + } + return map; + }, new Map()); + }); - showJson: boolean[] = Array(this.eventsMap.size).fill(false); + showJson: boolean[] = Array(this.eventsMap().size).fill(false); toggleJson(index: number) { this.showJson[index] = !this.showJson[index]; @@ -67,46 +76,24 @@ export class EventTabComponent implements OnChanges { this.selectedEvent.emit(key); } - isTraceView() { - return this.view == 'trace'; - } - mapOrderPreservingSort = (a: any, b: any): number => 0; - prcessTraceDataToInvocTrace() { - if (!this.traceData || this.traceData.length == 0) { - return; - } - this.invocTraces = this.traceData.reduce((map, item) => { - const key = item.trace_id; - const group = map.get(key); - if (group) { - group.push(item); - group.sort((a: any, b: any) => a.start_time - b.start_time); - } else { - map.set(key, [item]); - } - return map; - }, new Map()); - } - - findInvocIdFromTraceId(traceId: string) { - const group = this.invocTraces.get(traceId); - return group - ?.find( + findInvocId(spans: Span[]) { + return spans + .find( item => item.attributes !== undefined && 'gcp.vertex.agent.invocation_id' in item.attributes) - .attributes['gcp.vertex.agent.invocation_id'] + ?.attributes['gcp.vertex.agent.invocation_id'] } openDialog(traceId: string): void { + const spans = this.spansByTraceId().get(traceId); + if (!spans) return; + const dialogRef = this.dialog.open(TraceChartComponent, { width: 'auto', maxWidth: '90vw', - data: { - spans: this.invocTraces.get(traceId), - invocId: this.findInvocIdFromTraceId(traceId) - }, + data: {spans, invocId: this.findInvocId(spans)}, }); } } diff --git a/src/app/components/event-tab/invoc-id.pipe.ts b/src/app/components/event-tab/invoc-id.pipe.ts new file mode 100644 index 00000000..237f7818 --- /dev/null +++ b/src/app/components/event-tab/invoc-id.pipe.ts @@ -0,0 +1,36 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Pipe, PipeTransform} from '@angular/core'; +import {Span} from '../../core/models/Trace'; + +@Pipe({ + name: 'invocId', + standalone: true, +}) +export class InvocIdPipe implements PipeTransform { + transform(spans: Span[] | undefined | null): string | undefined { + if (!spans) { + return undefined; + } + return spans.find( + (item) => + item.attributes !== undefined && + 'gcp.vertex.agent.invocation_id' in item.attributes, + )?.attributes['gcp.vertex.agent.invocation_id']; + } +} diff --git a/src/app/core/models/Trace.ts b/src/app/core/models/Trace.ts index 84fc9eff..7e4f4c9e 100644 --- a/src/app/core/models/Trace.ts +++ b/src/app/core/models/Trace.ts @@ -23,6 +23,7 @@ export interface Span { trace_id: string; attributes?: any; children?: Span[]; + invoc_id?: string; // For backward compatibility. 'gcp.vertex.agent.llm_request'?: string; 'gcp.vertex.agent.llm_response'?: string; @@ -38,4 +39,4 @@ export interface SpanNode extends Span { export interface TimeTick { position: number; label: string; -} \ No newline at end of file +} diff --git a/src/app/core/models/types.ts b/src/app/core/models/types.ts index 56a67f0d..396a06e9 100644 --- a/src/app/core/models/types.ts +++ b/src/app/core/models/types.ts @@ -55,7 +55,6 @@ export interface LlmRequest { } export interface LlmResponse { - id?: number; content: GenAiContent; error?: string; errorMessage?: string; @@ -66,9 +65,11 @@ export interface EventActions { message?: string; functionCall?: FunctionCall; functionResponse?: FunctionResponse; + finishReason?: string; } export interface Event extends LlmResponse { + id?: string; author?: string invocationId?: string; actions?: EventActions;