@acorex-platform/extension-ai-otel-client 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,268 @@
1
+ import * as i1 from '@acorex/components/badge';
2
+ import { AXBadgeModule } from '@acorex/components/badge';
3
+ import * as i2 from '@acorex/components/loading';
4
+ import { AXLoadingModule } from '@acorex/components/loading';
5
+ import * as i4 from '@acorex/core/translation';
6
+ import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
7
+ import { AXPPageLayoutBaseComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
8
+ import { AXPQueryExecutor } from '@acorex-platform/framework-client/runtime';
9
+ import { runtimeQueryPayloadData } from '@acorex-platform/framework-shared/core';
10
+ import { AXMSystemInsightQueryKeys } from '@acorex-platform/module-system-insight-client/contracts';
11
+ import * as i3 from '@angular/common';
12
+ import { CommonModule } from '@angular/common';
13
+ import * as i0 from '@angular/core';
14
+ import { inject, input, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
15
+
16
+ //#endregion
17
+ //#region ----- Tag readers -----
18
+ function readString(tags, key) {
19
+ const value = tags?.[key];
20
+ if (typeof value === 'string' && value.trim()) {
21
+ return value.trim();
22
+ }
23
+ if (typeof value === 'number' || typeof value === 'boolean') {
24
+ return String(value);
25
+ }
26
+ return undefined;
27
+ }
28
+ function readNumber(tags, key) {
29
+ const value = tags?.[key];
30
+ if (typeof value === 'number' && Number.isFinite(value)) {
31
+ return value;
32
+ }
33
+ if (typeof value === 'string' && value.trim()) {
34
+ const parsed = Number(value);
35
+ return Number.isFinite(parsed) ? parsed : undefined;
36
+ }
37
+ return undefined;
38
+ }
39
+ function genAiOperation(span) {
40
+ return readString(span.tags, 'gen_ai.operation.name');
41
+ }
42
+ function parseMaybeJson(value) {
43
+ if (typeof value !== 'string') {
44
+ return value;
45
+ }
46
+ const trimmed = value.trim();
47
+ if (!((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
48
+ (trimmed.startsWith('[') && trimmed.endsWith(']')))) {
49
+ return value;
50
+ }
51
+ try {
52
+ return JSON.parse(trimmed);
53
+ }
54
+ catch {
55
+ return value;
56
+ }
57
+ }
58
+ function firstDefinedTag(spans, key) {
59
+ for (const span of spans) {
60
+ const value = span.tags?.[key];
61
+ if (value !== undefined && value !== null && value !== '') {
62
+ return value;
63
+ }
64
+ }
65
+ return undefined;
66
+ }
67
+ //#endregion
68
+ //#region ----- Projection -----
69
+ function projectGenAiTrace(timeline) {
70
+ const spans = timeline?.spans ?? [];
71
+ const genAiSpans = spans.filter((span) => Boolean(genAiOperation(span)));
72
+ if (!timeline || genAiSpans.length === 0) {
73
+ return null;
74
+ }
75
+ const root = genAiSpans.find((span) => genAiOperation(span) === 'invoke_agent') ?? genAiSpans[0];
76
+ const rootTags = root?.tags;
77
+ const steps = genAiSpans
78
+ .filter((span) => genAiOperation(span) === 'chat')
79
+ .map((span) => ({
80
+ spanId: span.spanId,
81
+ operationName: span.operationName,
82
+ model: readString(span.tags, 'gen_ai.request.model') ?? readString(span.tags, 'gen_ai.response.model'),
83
+ inputTokens: readNumber(span.tags, 'gen_ai.usage.input_tokens'),
84
+ outputTokens: readNumber(span.tags, 'gen_ai.usage.output_tokens'),
85
+ timeToFirstChunkSeconds: readNumber(span.tags, 'gen_ai.client.operation.time_to_first_chunk'),
86
+ finishReasons: parseMaybeJson(span.tags?.['gen_ai.response.finish_reasons']),
87
+ duration: span.duration ?? 0,
88
+ status: span.status,
89
+ }));
90
+ const tools = genAiSpans
91
+ .filter((span) => genAiOperation(span) === 'execute_tool')
92
+ .map((span) => ({
93
+ spanId: span.spanId,
94
+ toolName: readString(span.tags, 'gen_ai.tool.name'),
95
+ toolCallId: readString(span.tags, 'gen_ai.tool.call.id'),
96
+ duration: span.duration ?? 0,
97
+ status: span.status,
98
+ error: span.error,
99
+ }));
100
+ const inputMessages = parseMaybeJson(firstDefinedTag(genAiSpans, 'gen_ai.input.messages'));
101
+ const outputMessages = parseMaybeJson(firstDefinedTag([...genAiSpans].reverse(), 'gen_ai.output.messages'));
102
+ const summedInputTokens = steps.reduce((total, step) => {
103
+ if (step.inputTokens === undefined) {
104
+ return total;
105
+ }
106
+ return (total ?? 0) + step.inputTokens;
107
+ }, undefined);
108
+ const summedOutputTokens = steps.reduce((total, step) => {
109
+ if (step.outputTokens === undefined) {
110
+ return total;
111
+ }
112
+ return (total ?? 0) + step.outputTokens;
113
+ }, undefined);
114
+ return {
115
+ hasAi: true,
116
+ summary: {
117
+ model: timeline.aiModel ??
118
+ readString(rootTags, 'gen_ai.request.model') ??
119
+ readString(rootTags, 'gen_ai.response.model') ??
120
+ steps.find((step) => step.model)?.model,
121
+ provider: readString(rootTags, 'gen_ai.provider.name'),
122
+ agentName: readString(rootTags, 'gen_ai.agent.name') ??
123
+ readString(rootTags, 'ai.telemetry.functionId'),
124
+ inputTokens: timeline.aiInputTokens ??
125
+ readNumber(rootTags, 'gen_ai.usage.input_tokens') ??
126
+ summedInputTokens,
127
+ outputTokens: timeline.aiOutputTokens ??
128
+ readNumber(rootTags, 'gen_ai.usage.output_tokens') ??
129
+ summedOutputTokens,
130
+ duration: timeline.duration,
131
+ },
132
+ steps,
133
+ tools,
134
+ ...(inputMessages != null ? { inputMessages } : {}),
135
+ ...(outputMessages != null ? { outputMessages } : {}),
136
+ };
137
+ }
138
+ //#endregion
139
+
140
+ //#region ----- Imports -----
141
+ //#endregion
142
+ //#region ----- Component -----
143
+ /**
144
+ * TraceEntry detail tab — GenAI summary, steps, tools, and optional transcript.
145
+ */
146
+ class AXETraceAiPageComponent extends AXPPageLayoutBaseComponent {
147
+ constructor() {
148
+ super(...arguments);
149
+ //#region ----- Services & Dependencies -----
150
+ this.queryExecutor = inject(AXPQueryExecutor);
151
+ this.translationService = inject(AXTranslationService);
152
+ //#endregion
153
+ //#region ----- Inputs -----
154
+ /** Entity record from TraceEntry detail host. */
155
+ this.rootContext = input(/* @ts-ignore */
156
+ ...(ngDevMode ? [undefined, { debugName: "rootContext" }] : /* istanbul ignore next */ []));
157
+ this.pageConfig = input(/* @ts-ignore */
158
+ ...(ngDevMode ? [undefined, { debugName: "pageConfig" }] : /* istanbul ignore next */ []));
159
+ //#endregion
160
+ //#region ----- State Signals -----
161
+ this.loading = signal(true, /* @ts-ignore */
162
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
163
+ this.timeline = signal(null, /* @ts-ignore */
164
+ ...(ngDevMode ? [{ debugName: "timeline" }] : /* istanbul ignore next */ []));
165
+ this.view = computed(() => projectGenAiTrace(this.timeline()), /* @ts-ignore */
166
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
167
+ this.traceId = computed(() => {
168
+ const ctx = this.rootContext() ?? {};
169
+ const fromTraceId = typeof ctx['traceId'] === 'string' ? ctx['traceId'].trim() : '';
170
+ if (fromTraceId) {
171
+ return fromTraceId;
172
+ }
173
+ const fromId = typeof ctx['id'] === 'string' ? ctx['id'].trim() : '';
174
+ return fromId;
175
+ }, /* @ts-ignore */
176
+ ...(ngDevMode ? [{ debugName: "traceId" }] : /* istanbul ignore next */ []));
177
+ //#endregion
178
+ //#region ----- Lifecycle -----
179
+ this.#loadEffect = effect(() => {
180
+ const id = this.traceId();
181
+ void this.loadTimeline(id);
182
+ }, /* @ts-ignore */
183
+ ...(ngDevMode ? [{ debugName: "#loadEffect" }] : /* istanbul ignore next */ []));
184
+ }
185
+ //#endregion
186
+ //#region ----- Lifecycle -----
187
+ #loadEffect;
188
+ //#endregion
189
+ //#region ----- Page metadata -----
190
+ async getPageTitle() {
191
+ return this.translationService.translateAsync('@extensions-ai-otel:pages.trace-ai.title');
192
+ }
193
+ //#endregion
194
+ //#region ----- Display -----
195
+ hasValue(value) {
196
+ if (value === undefined || value === null) {
197
+ return false;
198
+ }
199
+ if (typeof value === 'string') {
200
+ return value.trim().length > 0;
201
+ }
202
+ if (Array.isArray(value)) {
203
+ return value.length > 0;
204
+ }
205
+ if (typeof value === 'object') {
206
+ return Object.keys(value).length > 0;
207
+ }
208
+ return true;
209
+ }
210
+ formatJson(value) {
211
+ if (typeof value === 'string') {
212
+ const trimmed = value.trim();
213
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
214
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
215
+ try {
216
+ return JSON.stringify(JSON.parse(trimmed), null, 2);
217
+ }
218
+ catch {
219
+ return value;
220
+ }
221
+ }
222
+ return value;
223
+ }
224
+ try {
225
+ return JSON.stringify(value, null, 2);
226
+ }
227
+ catch {
228
+ return String(value);
229
+ }
230
+ }
231
+ //#endregion
232
+ //#region ----- Data Loading -----
233
+ async loadTimeline(traceId) {
234
+ if (!traceId) {
235
+ this.timeline.set(null);
236
+ this.loading.set(false);
237
+ return;
238
+ }
239
+ this.loading.set(true);
240
+ try {
241
+ const result = await this.queryExecutor.fetch(AXMSystemInsightQueryKeys.TraceTimeline.Get, traceId);
242
+ this.timeline.set(runtimeQueryPayloadData(result) ?? null);
243
+ this.recompute();
244
+ }
245
+ finally {
246
+ this.loading.set(false);
247
+ }
248
+ }
249
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXETraceAiPageComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
250
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXETraceAiPageComponent, isStandalone: true, selector: "axe-trace-ai-page", inputs: { rootContext: { classPropertyName: "rootContext", publicName: "rootContext", isSignal: true, isRequired: false, transformFunction: null }, pageConfig: { classPropertyName: "pageConfig", publicName: "pageConfig", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "axe-trace-ai-page block w-full min-h-0" }, providers: [
251
+ {
252
+ provide: AXPPageLayoutBase,
253
+ useExisting: AXETraceAiPageComponent,
254
+ },
255
+ ], usesInheritance: true, ngImport: i0, template: "<!-- ----- Trace AI detail page ----- -->\n<div class=\"axe-trace-ai flex flex-col gap-4 p-4 w-full min-h-0\">\n @if (loading()) {\n <!-- ----- Loading indicator while timeline is fetched ----- -->\n <div class=\"flex justify-center py-10\">\n <ax-loading />\n </div>\n }\n <!-- ----- Empty state when timeline is missing ----- -->\n @else if (!timeline()) {\n <p class=\"typo-caption text-muted\">\n {{ '@extensions-ai-otel:pages.trace-ai.not-found' | translate | async }}\n </p>\n }\n <!-- ----- Empty state when the trace has no GenAI spans ----- -->\n @else if (!view()) {\n <p class=\"typo-caption text-muted\">\n {{ '@extensions-ai-otel:pages.trace-ai.empty' | translate | async }}\n </p>\n }\n <!-- ----- GenAI summary, steps, tools, transcript ----- -->\n @else if (view(); as data) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (data.summary.agentName; as agentName) {\n <ax-badge [text]=\"agentName\" color=\"primary\" size=\"sm\"></ax-badge>\n }\n @if (data.summary.provider; as provider) {\n <ax-badge [text]=\"provider\" color=\"secondary\" size=\"sm\"></ax-badge>\n }\n @if (data.summary.model; as model) {\n <span class=\"text-sm\">{{ model }}</span>\n }\n <span class=\"text-sm text-muted ms-auto\">\n {{ '@extensions-ai-otel:pages.trace-ai.duration' | translate | async }}:\n {{ data.summary.duration ?? 0 }} ms\n </span>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3 text-sm\">\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.model' | translate | async }}\n </div>\n <div>{{ data.summary.model || ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.provider' | translate | async }}\n </div>\n <div>{{ data.summary.provider || ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.input-tokens' | translate | async }}\n </div>\n <div>{{ data.summary.inputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.output-tokens' | translate | async }}\n </div>\n <div>{{ data.summary.outputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.steps' | translate | async }}\n </div>\n <!--->\n <!-- ----- Chat step list ----- -->\n @if (data.steps.length > 0) {\n <div class=\"flex flex-col gap-3\">\n @for (step of data.steps; track step.spanId) {\n <div class=\"border border-lightest rounded-md p-3 flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-sm\">{{ step.operationName }}</strong>\n @if (step.status) {\n <ax-badge\n [text]=\"step.status\"\n [color]=\"step.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n <span class=\"text-sm text-muted ms-auto\">{{ step.duration }} ms</span>\n </div>\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-2 text-sm\">\n @if (step.model) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.model' | translate | async }}\n </div>\n <div>{{ step.model }}</div>\n </div>\n }\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.input-tokens' | translate | async }}\n </div>\n <div>{{ step.inputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.output-tokens' | translate | async }}\n </div>\n <div>{{ step.outputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n @if (step.timeToFirstChunkSeconds != null) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.time-to-first-chunk' | translate | async }}\n </div>\n <div>{{ step.timeToFirstChunkSeconds }}</div>\n </div>\n }\n </div>\n @if (hasValue(step.finishReasons)) {\n <div>\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.finish-reason' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-32\">{{\n formatJson(step.finishReasons)\n }}</pre>\n </div>\n }\n </div>\n }\n </div>\n }\n <!-- ----- No chat steps ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-steps' | translate | async }}\n </p>\n }\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.tools' | translate | async }}\n </div>\n <!--->\n <!-- ----- Tool execution list ----- -->\n @if (data.tools.length > 0) {\n <div class=\"flex flex-col gap-3\">\n @for (tool of data.tools; track tool.spanId) {\n <div class=\"border border-lightest rounded-md p-3 flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-sm\">{{ tool.toolName || tool.spanId }}</strong>\n @if (tool.status) {\n <ax-badge\n [text]=\"tool.status\"\n [color]=\"tool.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n <span class=\"text-sm text-muted ms-auto\">{{ tool.duration }} ms</span>\n </div>\n @if (tool.toolCallId) {\n <div class=\"text-sm\">\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.tool-call-id' | translate | async }}\n </div>\n <div class=\"font-mono break-all\">{{ tool.toolCallId }}</div>\n </div>\n }\n @if (tool.error) {\n <pre class=\"text-sm whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-32 text-danger\">{{\n tool.error\n }}</pre>\n }\n </div>\n }\n </div>\n }\n <!-- ----- No tool spans ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-tools' | translate | async }}\n </p>\n }\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.transcript' | translate | async }}\n </div>\n <!--->\n <!-- ----- Recorded prompt / completion ----- -->\n @if (hasValue(data.inputMessages) || hasValue(data.outputMessages)) {\n <!--->\n <!-- ----- Input messages ----- -->\n @if (hasValue(data.inputMessages)) {\n <div class=\"mb-3\">\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.input' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(data.inputMessages)\n }}</pre>\n </div>\n }\n <!--->\n <!-- ----- Output messages ----- -->\n @if (hasValue(data.outputMessages)) {\n <div>\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.output' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(data.outputMessages)\n }}</pre>\n </div>\n }\n }\n <!-- ----- Transcript omitted when recording is off ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-transcript' | translate | async }}\n </p>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i2.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
256
+ }
257
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXETraceAiPageComponent, decorators: [{
258
+ type: Component,
259
+ args: [{ selector: 'axe-trace-ai-page', standalone: true, imports: [CommonModule, AXBadgeModule, AXLoadingModule, AXTranslationModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
260
+ {
261
+ provide: AXPPageLayoutBase,
262
+ useExisting: AXETraceAiPageComponent,
263
+ },
264
+ ], host: { class: 'axe-trace-ai-page block w-full min-h-0' }, template: "<!-- ----- Trace AI detail page ----- -->\n<div class=\"axe-trace-ai flex flex-col gap-4 p-4 w-full min-h-0\">\n @if (loading()) {\n <!-- ----- Loading indicator while timeline is fetched ----- -->\n <div class=\"flex justify-center py-10\">\n <ax-loading />\n </div>\n }\n <!-- ----- Empty state when timeline is missing ----- -->\n @else if (!timeline()) {\n <p class=\"typo-caption text-muted\">\n {{ '@extensions-ai-otel:pages.trace-ai.not-found' | translate | async }}\n </p>\n }\n <!-- ----- Empty state when the trace has no GenAI spans ----- -->\n @else if (!view()) {\n <p class=\"typo-caption text-muted\">\n {{ '@extensions-ai-otel:pages.trace-ai.empty' | translate | async }}\n </p>\n }\n <!-- ----- GenAI summary, steps, tools, transcript ----- -->\n @else if (view(); as data) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (data.summary.agentName; as agentName) {\n <ax-badge [text]=\"agentName\" color=\"primary\" size=\"sm\"></ax-badge>\n }\n @if (data.summary.provider; as provider) {\n <ax-badge [text]=\"provider\" color=\"secondary\" size=\"sm\"></ax-badge>\n }\n @if (data.summary.model; as model) {\n <span class=\"text-sm\">{{ model }}</span>\n }\n <span class=\"text-sm text-muted ms-auto\">\n {{ '@extensions-ai-otel:pages.trace-ai.duration' | translate | async }}:\n {{ data.summary.duration ?? 0 }} ms\n </span>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3 text-sm\">\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.model' | translate | async }}\n </div>\n <div>{{ data.summary.model || ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.provider' | translate | async }}\n </div>\n <div>{{ data.summary.provider || ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.input-tokens' | translate | async }}\n </div>\n <div>{{ data.summary.inputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.output-tokens' | translate | async }}\n </div>\n <div>{{ data.summary.outputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.steps' | translate | async }}\n </div>\n <!--->\n <!-- ----- Chat step list ----- -->\n @if (data.steps.length > 0) {\n <div class=\"flex flex-col gap-3\">\n @for (step of data.steps; track step.spanId) {\n <div class=\"border border-lightest rounded-md p-3 flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-sm\">{{ step.operationName }}</strong>\n @if (step.status) {\n <ax-badge\n [text]=\"step.status\"\n [color]=\"step.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n <span class=\"text-sm text-muted ms-auto\">{{ step.duration }} ms</span>\n </div>\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-2 text-sm\">\n @if (step.model) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.model' | translate | async }}\n </div>\n <div>{{ step.model }}</div>\n </div>\n }\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.input-tokens' | translate | async }}\n </div>\n <div>{{ step.inputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.output-tokens' | translate | async }}\n </div>\n <div>{{ step.outputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n @if (step.timeToFirstChunkSeconds != null) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.time-to-first-chunk' | translate | async }}\n </div>\n <div>{{ step.timeToFirstChunkSeconds }}</div>\n </div>\n }\n </div>\n @if (hasValue(step.finishReasons)) {\n <div>\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.finish-reason' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-32\">{{\n formatJson(step.finishReasons)\n }}</pre>\n </div>\n }\n </div>\n }\n </div>\n }\n <!-- ----- No chat steps ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-steps' | translate | async }}\n </p>\n }\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.tools' | translate | async }}\n </div>\n <!--->\n <!-- ----- Tool execution list ----- -->\n @if (data.tools.length > 0) {\n <div class=\"flex flex-col gap-3\">\n @for (tool of data.tools; track tool.spanId) {\n <div class=\"border border-lightest rounded-md p-3 flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-sm\">{{ tool.toolName || tool.spanId }}</strong>\n @if (tool.status) {\n <ax-badge\n [text]=\"tool.status\"\n [color]=\"tool.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n <span class=\"text-sm text-muted ms-auto\">{{ tool.duration }} ms</span>\n </div>\n @if (tool.toolCallId) {\n <div class=\"text-sm\">\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.tool-call-id' | translate | async }}\n </div>\n <div class=\"font-mono break-all\">{{ tool.toolCallId }}</div>\n </div>\n }\n @if (tool.error) {\n <pre class=\"text-sm whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-32 text-danger\">{{\n tool.error\n }}</pre>\n }\n </div>\n }\n </div>\n }\n <!-- ----- No tool spans ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-tools' | translate | async }}\n </p>\n }\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.transcript' | translate | async }}\n </div>\n <!--->\n <!-- ----- Recorded prompt / completion ----- -->\n @if (hasValue(data.inputMessages) || hasValue(data.outputMessages)) {\n <!--->\n <!-- ----- Input messages ----- -->\n @if (hasValue(data.inputMessages)) {\n <div class=\"mb-3\">\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.input' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(data.inputMessages)\n }}</pre>\n </div>\n }\n <!--->\n <!-- ----- Output messages ----- -->\n @if (hasValue(data.outputMessages)) {\n <div>\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.output' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(data.outputMessages)\n }}</pre>\n </div>\n }\n }\n <!-- ----- Transcript omitted when recording is off ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-transcript' | translate | async }}\n </p>\n }\n </div>\n }\n</div>\n" }]
265
+ }], propDecorators: { rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }] } });
266
+
267
+ export { AXETraceAiPageComponent };
268
+ //# sourceMappingURL=acorex-platform-extension-ai-otel-client-trace-ai-page.component-DJtbLNgP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acorex-platform-extension-ai-otel-client-trace-ai-page.component-DJtbLNgP.mjs","sources":["../../../../libs/extensions/ai/otel/client/src/lib/features/traces/project-gen-ai-trace.util.ts","../../../../libs/extensions/ai/otel/client/src/lib/features/traces/trace-ai-page.component.ts","../../../../libs/extensions/ai/otel/client/src/lib/features/traces/trace-ai-page.component.html"],"sourcesContent":["import type { TraceSpanDto, TraceTimelineDto } from '@acorex-platform/module-system-insight-client/contracts';\n\n//#region ----- Types -----\n\nexport interface AxpGenAiTraceSummary {\n model?: string;\n provider?: string;\n agentName?: string;\n inputTokens?: number;\n outputTokens?: number;\n duration?: number;\n}\n\nexport interface AxpGenAiTraceStep {\n spanId: string;\n operationName: string;\n model?: string;\n inputTokens?: number;\n outputTokens?: number;\n timeToFirstChunkSeconds?: number;\n finishReasons?: unknown;\n duration: number;\n status?: string;\n}\n\nexport interface AxpGenAiTraceTool {\n spanId: string;\n toolName?: string;\n toolCallId?: string;\n duration: number;\n status?: string;\n error?: string;\n}\n\nexport interface AxpGenAiTraceView {\n hasAi: true;\n summary: AxpGenAiTraceSummary;\n steps: AxpGenAiTraceStep[];\n tools: AxpGenAiTraceTool[];\n inputMessages?: unknown;\n outputMessages?: unknown;\n}\n\n//#endregion\n\n//#region ----- Tag readers -----\n\nfunction readString(tags: Record<string, unknown> | undefined, key: string): string | undefined {\n const value = tags?.[key];\n if (typeof value === 'string' && value.trim()) {\n return value.trim();\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n return undefined;\n}\n\nfunction readNumber(tags: Record<string, unknown> | undefined, key: string): number | undefined {\n const value = tags?.[key];\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n return undefined;\n}\n\nfunction genAiOperation(span: TraceSpanDto): string | undefined {\n return readString(span.tags, 'gen_ai.operation.name');\n}\n\nfunction parseMaybeJson(value: unknown): unknown {\n if (typeof value !== 'string') {\n return value;\n }\n const trimmed = value.trim();\n if (\n !(\n (trimmed.startsWith('{') && trimmed.endsWith('}')) ||\n (trimmed.startsWith('[') && trimmed.endsWith(']'))\n )\n ) {\n return value;\n }\n try {\n return JSON.parse(trimmed) as unknown;\n } catch {\n return value;\n }\n}\n\nfunction firstDefinedTag(spans: TraceSpanDto[], key: string): unknown {\n for (const span of spans) {\n const value = span.tags?.[key];\n if (value !== undefined && value !== null && value !== '') {\n return value;\n }\n }\n return undefined;\n}\n\n//#endregion\n\n//#region ----- Projection -----\n\nexport function projectGenAiTrace(\n timeline: TraceTimelineDto | null | undefined,\n): AxpGenAiTraceView | null {\n const spans = timeline?.spans ?? [];\n const genAiSpans = spans.filter((span) => Boolean(genAiOperation(span)));\n if (!timeline || genAiSpans.length === 0) {\n return null;\n }\n\n const root =\n genAiSpans.find((span) => genAiOperation(span) === 'invoke_agent') ?? genAiSpans[0];\n const rootTags = root?.tags;\n\n const steps = genAiSpans\n .filter((span) => genAiOperation(span) === 'chat')\n .map((span) => ({\n spanId: span.spanId,\n operationName: span.operationName,\n model: readString(span.tags, 'gen_ai.request.model') ?? readString(span.tags, 'gen_ai.response.model'),\n inputTokens: readNumber(span.tags, 'gen_ai.usage.input_tokens'),\n outputTokens: readNumber(span.tags, 'gen_ai.usage.output_tokens'),\n timeToFirstChunkSeconds: readNumber(span.tags, 'gen_ai.client.operation.time_to_first_chunk'),\n finishReasons: parseMaybeJson(span.tags?.['gen_ai.response.finish_reasons']),\n duration: span.duration ?? 0,\n status: span.status,\n }));\n\n const tools = genAiSpans\n .filter((span) => genAiOperation(span) === 'execute_tool')\n .map((span) => ({\n spanId: span.spanId,\n toolName: readString(span.tags, 'gen_ai.tool.name'),\n toolCallId: readString(span.tags, 'gen_ai.tool.call.id'),\n duration: span.duration ?? 0,\n status: span.status,\n error: span.error,\n }));\n\n const inputMessages = parseMaybeJson(firstDefinedTag(genAiSpans, 'gen_ai.input.messages'));\n const outputMessages = parseMaybeJson(\n firstDefinedTag([...genAiSpans].reverse(), 'gen_ai.output.messages'),\n );\n\n const summedInputTokens = steps.reduce<number | undefined>((total, step) => {\n if (step.inputTokens === undefined) {\n return total;\n }\n return (total ?? 0) + step.inputTokens;\n }, undefined);\n const summedOutputTokens = steps.reduce<number | undefined>((total, step) => {\n if (step.outputTokens === undefined) {\n return total;\n }\n return (total ?? 0) + step.outputTokens;\n }, undefined);\n\n return {\n hasAi: true,\n summary: {\n model:\n timeline.aiModel ??\n readString(rootTags, 'gen_ai.request.model') ??\n readString(rootTags, 'gen_ai.response.model') ??\n steps.find((step) => step.model)?.model,\n provider: readString(rootTags, 'gen_ai.provider.name'),\n agentName:\n readString(rootTags, 'gen_ai.agent.name') ??\n readString(rootTags, 'ai.telemetry.functionId'),\n inputTokens:\n timeline.aiInputTokens ??\n readNumber(rootTags, 'gen_ai.usage.input_tokens') ??\n summedInputTokens,\n outputTokens:\n timeline.aiOutputTokens ??\n readNumber(rootTags, 'gen_ai.usage.output_tokens') ??\n summedOutputTokens,\n duration: timeline.duration,\n },\n steps,\n tools,\n ...(inputMessages != null ? { inputMessages } : {}),\n ...(outputMessages != null ? { outputMessages } : {}),\n };\n}\n\n//#endregion\n","//#region ----- Imports -----\n\nimport { AXBadgeModule } from '@acorex/components/badge';\nimport { AXLoadingModule } from '@acorex/components/loading';\nimport { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';\nimport {\n AXPPageLayoutBase,\n AXPPageLayoutBaseComponent,\n} from '@acorex-platform/framework-client/layout/views';\nimport { AXPQueryExecutor } from '@acorex-platform/framework-client/runtime';\nimport { runtimeQueryPayloadData } from '@acorex-platform/framework-shared/core';\nimport {\n AXMSystemInsightQueryKeys,\n type TraceTimelineDto,\n} from '@acorex-platform/module-system-insight-client/contracts';\nimport { CommonModule } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { projectGenAiTrace, type AxpGenAiTraceView } from './project-gen-ai-trace.util';\n\n//#endregion\n\n//#region ----- Component -----\n\n/**\n * TraceEntry detail tab — GenAI summary, steps, tools, and optional transcript.\n */\n@Component({\n selector: 'axe-trace-ai-page',\n standalone: true,\n imports: [CommonModule, AXBadgeModule, AXLoadingModule, AXTranslationModule],\n templateUrl: './trace-ai-page.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: AXPPageLayoutBase,\n useExisting: AXETraceAiPageComponent,\n },\n ],\n host: { class: 'axe-trace-ai-page block w-full min-h-0' },\n})\nexport class AXETraceAiPageComponent extends AXPPageLayoutBaseComponent {\n //#region ----- Services & Dependencies -----\n\n private readonly queryExecutor = inject(AXPQueryExecutor);\n private readonly translationService = inject(AXTranslationService);\n\n //#endregion\n\n //#region ----- Inputs -----\n\n /** Entity record from TraceEntry detail host. */\n rootContext = input<Record<string, unknown>>();\n pageConfig = input<Record<string, unknown>>();\n\n //#endregion\n\n //#region ----- State Signals -----\n\n readonly loading = signal(true);\n readonly timeline = signal<TraceTimelineDto | null>(null);\n\n readonly view = computed<AxpGenAiTraceView | null>(() => projectGenAiTrace(this.timeline()));\n\n readonly traceId = computed(() => {\n const ctx = this.rootContext() ?? {};\n const fromTraceId = typeof ctx['traceId'] === 'string' ? ctx['traceId'].trim() : '';\n if (fromTraceId) {\n return fromTraceId;\n }\n const fromId = typeof ctx['id'] === 'string' ? ctx['id'].trim() : '';\n return fromId;\n });\n\n //#endregion\n\n //#region ----- Lifecycle -----\n\n #loadEffect = effect(() => {\n const id = this.traceId();\n void this.loadTimeline(id);\n });\n\n //#endregion\n\n //#region ----- Page metadata -----\n\n override async getPageTitle(): Promise<string> {\n return this.translationService.translateAsync('@extensions-ai-otel:pages.trace-ai.title');\n }\n\n //#endregion\n\n //#region ----- Display -----\n\n hasValue(value: unknown): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n if (typeof value === 'string') {\n return value.trim().length > 0;\n }\n if (Array.isArray(value)) {\n return value.length > 0;\n }\n if (typeof value === 'object') {\n return Object.keys(value as Record<string, unknown>).length > 0;\n }\n return true;\n }\n\n formatJson(value: unknown): string {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (\n (trimmed.startsWith('{') && trimmed.endsWith('}')) ||\n (trimmed.startsWith('[') && trimmed.endsWith(']'))\n ) {\n try {\n return JSON.stringify(JSON.parse(trimmed) as unknown, null, 2);\n } catch {\n return value;\n }\n }\n return value;\n }\n try {\n return JSON.stringify(value, null, 2);\n } catch {\n return String(value);\n }\n }\n\n //#endregion\n\n //#region ----- Data Loading -----\n\n private async loadTimeline(traceId: string): Promise<void> {\n if (!traceId) {\n this.timeline.set(null);\n this.loading.set(false);\n return;\n }\n\n this.loading.set(true);\n try {\n const result = await this.queryExecutor.fetch<string, TraceTimelineDto>(\n AXMSystemInsightQueryKeys.TraceTimeline.Get,\n traceId,\n );\n this.timeline.set(runtimeQueryPayloadData(result) ?? null);\n this.recompute();\n } finally {\n this.loading.set(false);\n }\n }\n\n //#endregion\n}\n\n//#endregion\n","<!-- ----- Trace AI detail page ----- -->\n<div class=\"axe-trace-ai flex flex-col gap-4 p-4 w-full min-h-0\">\n @if (loading()) {\n <!-- ----- Loading indicator while timeline is fetched ----- -->\n <div class=\"flex justify-center py-10\">\n <ax-loading />\n </div>\n }\n <!-- ----- Empty state when timeline is missing ----- -->\n @else if (!timeline()) {\n <p class=\"typo-caption text-muted\">\n {{ '@extensions-ai-otel:pages.trace-ai.not-found' | translate | async }}\n </p>\n }\n <!-- ----- Empty state when the trace has no GenAI spans ----- -->\n @else if (!view()) {\n <p class=\"typo-caption text-muted\">\n {{ '@extensions-ai-otel:pages.trace-ai.empty' | translate | async }}\n </p>\n }\n <!-- ----- GenAI summary, steps, tools, transcript ----- -->\n @else if (view(); as data) {\n <div class=\"flex flex-wrap items-center gap-2\">\n @if (data.summary.agentName; as agentName) {\n <ax-badge [text]=\"agentName\" color=\"primary\" size=\"sm\"></ax-badge>\n }\n @if (data.summary.provider; as provider) {\n <ax-badge [text]=\"provider\" color=\"secondary\" size=\"sm\"></ax-badge>\n }\n @if (data.summary.model; as model) {\n <span class=\"text-sm\">{{ model }}</span>\n }\n <span class=\"text-sm text-muted ms-auto\">\n {{ '@extensions-ai-otel:pages.trace-ai.duration' | translate | async }}:\n {{ data.summary.duration ?? 0 }} ms\n </span>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-3 text-sm\">\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.model' | translate | async }}\n </div>\n <div>{{ data.summary.model || ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.provider' | translate | async }}\n </div>\n <div>{{ data.summary.provider || ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.input-tokens' | translate | async }}\n </div>\n <div>{{ data.summary.inputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.output-tokens' | translate | async }}\n </div>\n <div>{{ data.summary.outputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.steps' | translate | async }}\n </div>\n <!--->\n <!-- ----- Chat step list ----- -->\n @if (data.steps.length > 0) {\n <div class=\"flex flex-col gap-3\">\n @for (step of data.steps; track step.spanId) {\n <div class=\"border border-lightest rounded-md p-3 flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-sm\">{{ step.operationName }}</strong>\n @if (step.status) {\n <ax-badge\n [text]=\"step.status\"\n [color]=\"step.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n <span class=\"text-sm text-muted ms-auto\">{{ step.duration }} ms</span>\n </div>\n <div class=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-2 text-sm\">\n @if (step.model) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.model' | translate | async }}\n </div>\n <div>{{ step.model }}</div>\n </div>\n }\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.input-tokens' | translate | async }}\n </div>\n <div>{{ step.inputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.output-tokens' | translate | async }}\n </div>\n <div>{{ step.outputTokens ?? ('@extensions-ai-otel:pages.trace-ai.unavailable' | translate | async) }}</div>\n </div>\n @if (step.timeToFirstChunkSeconds != null) {\n <div>\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.time-to-first-chunk' | translate | async }}\n </div>\n <div>{{ step.timeToFirstChunkSeconds }}</div>\n </div>\n }\n </div>\n @if (hasValue(step.finishReasons)) {\n <div>\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.finish-reason' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-32\">{{\n formatJson(step.finishReasons)\n }}</pre>\n </div>\n }\n </div>\n }\n </div>\n }\n <!-- ----- No chat steps ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-steps' | translate | async }}\n </p>\n }\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.tools' | translate | async }}\n </div>\n <!--->\n <!-- ----- Tool execution list ----- -->\n @if (data.tools.length > 0) {\n <div class=\"flex flex-col gap-3\">\n @for (tool of data.tools; track tool.spanId) {\n <div class=\"border border-lightest rounded-md p-3 flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <strong class=\"text-sm\">{{ tool.toolName || tool.spanId }}</strong>\n @if (tool.status) {\n <ax-badge\n [text]=\"tool.status\"\n [color]=\"tool.status === 'error' ? 'danger' : 'success'\"\n size=\"sm\"\n ></ax-badge>\n }\n <span class=\"text-sm text-muted ms-auto\">{{ tool.duration }} ms</span>\n </div>\n @if (tool.toolCallId) {\n <div class=\"text-sm\">\n <div class=\"text-muted mb-1\">\n {{ '@extensions-ai-otel:pages.trace-ai.tool-call-id' | translate | async }}\n </div>\n <div class=\"font-mono break-all\">{{ tool.toolCallId }}</div>\n </div>\n }\n @if (tool.error) {\n <pre class=\"text-sm whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-32 text-danger\">{{\n tool.error\n }}</pre>\n }\n </div>\n }\n </div>\n }\n <!-- ----- No tool spans ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-tools' | translate | async }}\n </p>\n }\n </div>\n\n <div>\n <div class=\"text-sm font-medium mb-2\">\n {{ '@extensions-ai-otel:pages.trace-ai.transcript' | translate | async }}\n </div>\n <!--->\n <!-- ----- Recorded prompt / completion ----- -->\n @if (hasValue(data.inputMessages) || hasValue(data.outputMessages)) {\n <!--->\n <!-- ----- Input messages ----- -->\n @if (hasValue(data.inputMessages)) {\n <div class=\"mb-3\">\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.input' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(data.inputMessages)\n }}</pre>\n </div>\n }\n <!--->\n <!-- ----- Output messages ----- -->\n @if (hasValue(data.outputMessages)) {\n <div>\n <div class=\"text-muted mb-1 text-sm\">\n {{ '@extensions-ai-otel:pages.trace-ai.output' | translate | async }}\n </div>\n <pre class=\"text-xs whitespace-pre-wrap m-0 p-2 rounded-md bg-lightest overflow-auto max-h-56\">{{\n formatJson(data.outputMessages)\n }}</pre>\n </div>\n }\n }\n <!-- ----- Transcript omitted when recording is off ----- -->\n @else {\n <p class=\"typo-caption text-muted m-0\">\n {{ '@extensions-ai-otel:pages.trace-ai.no-transcript' | translate | async }}\n </p>\n }\n </div>\n }\n</div>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import { AXP_PAGE_COMPONENT_PROVIDER } from '@acorex-platform/framework-client/layout/components';
2
+ import { AXP_ENTITY_MODIFIER } from '@acorex-platform/framework-client/layout/entity';
3
+ import * as i0 from '@angular/core';
4
+ import { Injectable, NgModule } from '@angular/core';
5
+ import { RootConfig } from '@acorex-platform/module-system-insight-client/contracts';
6
+
7
+ /** Registry key for the GenAI tab contributed onto TraceEntry. */
8
+ const TRACE_AI_PAGE_COMPONENT_KEY = 'trace-ai-page';
9
+
10
+ //#region ----- Imports -----
11
+ //#endregion
12
+ //#region ----- Constants -----
13
+ const TRACE_AI_PAGE_ORDER = 3;
14
+ //#endregion
15
+ //#region ----- Modifier -----
16
+ /** Adds the GenAI tab to SystemInsight.TraceEntry detail views. */
17
+ const AXETraceEntryAiEntityModifier = {
18
+ entityName: RootConfig.entities.traceEntry.name,
19
+ order: 60,
20
+ modifier: (ctx) => {
21
+ if (ctx.module.get() !== RootConfig.module.name) {
22
+ return;
23
+ }
24
+ ctx.entity.pages ??= [];
25
+ const page = {
26
+ componentKey: TRACE_AI_PAGE_COMPONENT_KEY,
27
+ title: '@extensions-ai-otel:pages.trace-ai.title',
28
+ icon: 'fa-light fa-sparkles',
29
+ layout: { order: TRACE_AI_PAGE_ORDER, position: 'after' },
30
+ hidden: `{{ !context.eval("hasAi") }}`,
31
+ };
32
+ const existingIndex = ctx.entity.pages.findIndex((entry) => entry.componentKey === TRACE_AI_PAGE_COMPONENT_KEY);
33
+ if (existingIndex >= 0) {
34
+ ctx.entity.pages[existingIndex] = page;
35
+ return;
36
+ }
37
+ ctx.entity.pages.push(page);
38
+ },
39
+ };
40
+ //#endregion
41
+
42
+ //#endregion
43
+ //#region ----- Provider -----
44
+ class AXETraceAiPageComponentProvider {
45
+ async components() {
46
+ return [
47
+ {
48
+ key: TRACE_AI_PAGE_COMPONENT_KEY,
49
+ loader: () => import('./acorex-platform-extension-ai-otel-client-trace-ai-page.component-DJtbLNgP.mjs').then((m) => m.AXETraceAiPageComponent),
50
+ },
51
+ ];
52
+ }
53
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXETraceAiPageComponentProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
54
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXETraceAiPageComponentProvider }); }
55
+ }
56
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXETraceAiPageComponentProvider, decorators: [{
57
+ type: Injectable
58
+ }] });
59
+
60
+ //#region ----- Imports -----
61
+ //#endregion
62
+ //#region ----- Module -----
63
+ /**
64
+ * AI SDK OpenTelemetry UI — contributes the GenAI tab on SystemInsight.TraceEntry.
65
+ */
66
+ class AXEAiOtelExtensionModule {
67
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiOtelExtensionModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
68
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXEAiOtelExtensionModule }); }
69
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiOtelExtensionModule, providers: [
70
+ { provide: AXP_ENTITY_MODIFIER, useValue: AXETraceEntryAiEntityModifier, multi: true },
71
+ {
72
+ provide: AXP_PAGE_COMPONENT_PROVIDER,
73
+ useClass: AXETraceAiPageComponentProvider,
74
+ multi: true,
75
+ },
76
+ ] }); }
77
+ }
78
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXEAiOtelExtensionModule, decorators: [{
79
+ type: NgModule,
80
+ args: [{
81
+ providers: [
82
+ { provide: AXP_ENTITY_MODIFIER, useValue: AXETraceEntryAiEntityModifier, multi: true },
83
+ {
84
+ provide: AXP_PAGE_COMPONENT_PROVIDER,
85
+ useClass: AXETraceAiPageComponentProvider,
86
+ multi: true,
87
+ },
88
+ ],
89
+ }]
90
+ }] });
91
+
92
+ /**
93
+ * Generated bundle index. Do not edit.
94
+ */
95
+
96
+ export { AXEAiOtelExtensionModule };
97
+ //# sourceMappingURL=acorex-platform-extension-ai-otel-client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acorex-platform-extension-ai-otel-client.mjs","sources":["../../../../libs/extensions/ai/otel/client/src/lib/features/traces/trace-ai-page.constants.ts","../../../../libs/extensions/ai/otel/client/src/lib/features/traces/trace-entry-ai.entity-modifier.ts","../../../../libs/extensions/ai/otel/client/src/lib/features/traces/trace-ai-page-component.provider.ts","../../../../libs/extensions/ai/otel/client/src/lib/ai-otel-extension.module.ts","../../../../libs/extensions/ai/otel/client/src/acorex-platform-extension-ai-otel-client.ts"],"sourcesContent":["/** Registry key for the GenAI tab contributed onto TraceEntry. */\nexport const TRACE_AI_PAGE_COMPONENT_KEY = 'trace-ai-page';\n","//#region ----- Imports -----\n\nimport { AXPEntityModifierProvider } from '@acorex-platform/framework-client/layout/entity';\nimport { RootConfig as InsightRootConfig } from '@acorex-platform/module-system-insight-client/contracts';\nimport { TRACE_AI_PAGE_COMPONENT_KEY } from './trace-ai-page.constants';\n\n//#endregion\n\n//#region ----- Constants -----\n\nconst TRACE_AI_PAGE_ORDER = 3;\n\n//#endregion\n\n//#region ----- Modifier -----\n\n/** Adds the GenAI tab to SystemInsight.TraceEntry detail views. */\nexport const AXETraceEntryAiEntityModifier: AXPEntityModifierProvider = {\n entityName: InsightRootConfig.entities.traceEntry.name,\n order: 60,\n modifier: (ctx) => {\n if (ctx.module.get() !== InsightRootConfig.module.name) {\n return;\n }\n\n ctx.entity.pages ??= [];\n const page = {\n componentKey: TRACE_AI_PAGE_COMPONENT_KEY,\n title: '@extensions-ai-otel:pages.trace-ai.title',\n icon: 'fa-light fa-sparkles',\n layout: { order: TRACE_AI_PAGE_ORDER, position: 'after' as const },\n hidden: `{{ !context.eval(\"hasAi\") }}`,\n };\n const existingIndex = ctx.entity.pages.findIndex(\n (entry) => entry.componentKey === TRACE_AI_PAGE_COMPONENT_KEY,\n );\n if (existingIndex >= 0) {\n ctx.entity.pages[existingIndex] = page;\n return;\n }\n\n ctx.entity.pages.push(page);\n },\n};\n\n//#endregion\n","//#region ----- Imports -----\n\nimport {\n AXPPageComponentDefinition,\n AXPPageComponentProvider,\n} from '@acorex-platform/framework-client/layout/components';\nimport { Injectable } from '@angular/core';\nimport { TRACE_AI_PAGE_COMPONENT_KEY } from './trace-ai-page.constants';\n\n//#endregion\n\n//#region ----- Provider -----\n\n@Injectable()\nexport class AXETraceAiPageComponentProvider implements AXPPageComponentProvider {\n async components(): Promise<AXPPageComponentDefinition[]> {\n return [\n {\n key: TRACE_AI_PAGE_COMPONENT_KEY,\n loader: () => import('./trace-ai-page.component').then((m) => m.AXETraceAiPageComponent),\n },\n ];\n }\n}\n\n//#endregion\n","//#region ----- Imports -----\n\nimport { AXP_PAGE_COMPONENT_PROVIDER } from '@acorex-platform/framework-client/layout/components';\nimport { AXP_ENTITY_MODIFIER } from '@acorex-platform/framework-client/layout/entity';\nimport { NgModule } from '@angular/core';\nimport { AXETraceEntryAiEntityModifier } from './features/traces/trace-entry-ai.entity-modifier';\nimport { AXETraceAiPageComponentProvider } from './features/traces/trace-ai-page-component.provider';\n\n//#endregion\n\n//#region ----- Module -----\n\n/**\n * AI SDK OpenTelemetry UI — contributes the GenAI tab on SystemInsight.TraceEntry.\n */\n@NgModule({\n providers: [\n { provide: AXP_ENTITY_MODIFIER, useValue: AXETraceEntryAiEntityModifier, multi: true },\n {\n provide: AXP_PAGE_COMPONENT_PROVIDER,\n useClass: AXETraceAiPageComponentProvider,\n multi: true,\n },\n ],\n})\nexport class AXEAiOtelExtensionModule {}\n\n//#endregion\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["InsightRootConfig"],"mappings":";;;;;;AAAA;AACO,MAAM,2BAA2B,GAAG,eAAe;;ACD1D;AAMA;AAEA;AAEA,MAAM,mBAAmB,GAAG,CAAC;AAE7B;AAEA;AAEA;AACO,MAAM,6BAA6B,GAA8B;AACtE,IAAA,UAAU,EAAEA,UAAiB,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI;AACtD,IAAA,KAAK,EAAE,EAAE;AACT,IAAA,QAAQ,EAAE,CAAC,GAAG,KAAI;AAChB,QAAA,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,KAAKA,UAAiB,CAAC,MAAM,CAAC,IAAI,EAAE;YACtD;QACF;AAEA,QAAA,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,EAAE;AACvB,QAAA,MAAM,IAAI,GAAG;AACX,YAAA,YAAY,EAAE,2BAA2B;AACzC,YAAA,KAAK,EAAE,0CAA0C;AACjD,YAAA,IAAI,EAAE,sBAAsB;YAC5B,MAAM,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,OAAgB,EAAE;AAClE,YAAA,MAAM,EAAE,CAAA,4BAAA,CAA8B;SACvC;QACD,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAC9C,CAAC,KAAK,KAAK,KAAK,CAAC,YAAY,KAAK,2BAA2B,CAC9D;AACD,QAAA,IAAI,aAAa,IAAI,CAAC,EAAE;YACtB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,IAAI;YACtC;QACF;QAEA,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,CAAC;CACF;AAED;;ACpCA;AAEA;MAGa,+BAA+B,CAAA;AAC1C,IAAA,MAAM,UAAU,GAAA;QACd,OAAO;AACL,YAAA;AACE,gBAAA,GAAG,EAAE,2BAA2B;AAChC,gBAAA,MAAM,EAAE,MAAM,OAAO,iFAA2B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,uBAAuB,CAAC;AACzF,aAAA;SACF;IACH;8GARW,+BAA+B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;kHAA/B,+BAA+B,EAAA,CAAA,CAAA;;2FAA/B,+BAA+B,EAAA,UAAA,EAAA,CAAA;kBAD3C;;;ACbD;AAQA;AAEA;AAEA;;AAEG;MAWU,wBAAwB,CAAA;8GAAxB,wBAAwB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA,CAAA;+GAAxB,wBAAwB,EAAA,CAAA,CAAA;AAAxB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,wBAAwB,EAAA,SAAA,EATxB;YACT,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,6BAA6B,EAAE,KAAK,EAAE,IAAI,EAAE;AACtF,YAAA;AACE,gBAAA,OAAO,EAAE,2BAA2B;AACpC,gBAAA,QAAQ,EAAE,+BAA+B;AACzC,gBAAA,KAAK,EAAE,IAAI;AACZ,aAAA;AACF,SAAA,EAAA,CAAA,CAAA;;2FAEU,wBAAwB,EAAA,UAAA,EAAA,CAAA;kBAVpC,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;AACR,oBAAA,SAAS,EAAE;wBACT,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,6BAA6B,EAAE,KAAK,EAAE,IAAI,EAAE;AACtF,wBAAA;AACE,4BAAA,OAAO,EAAE,2BAA2B;AACpC,4BAAA,QAAQ,EAAE,+BAA+B;AACzC,4BAAA,KAAK,EAAE,IAAI;AACZ,yBAAA;AACF,qBAAA;AACF,iBAAA;;;ACxBD;;AAEG;;;;"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@acorex-platform/extension-ai-otel-client",
3
+ "version": "0.0.21",
4
+ "sideEffects": false,
5
+ "peerDependencies": {
6
+ "@angular/common": "^21.0.0",
7
+ "@angular/core": "^21.0.0"
8
+ },
9
+ "dependencies": {
10
+ "tslib": "^2.3.0"
11
+ },
12
+ "module": "fesm2022/acorex-platform-extension-ai-otel-client.mjs",
13
+ "typings": "types/acorex-platform-extension-ai-otel-client.d.ts",
14
+ "exports": {
15
+ "./package.json": {
16
+ "default": "./package.json"
17
+ },
18
+ ".": {
19
+ "types": "./types/acorex-platform-extension-ai-otel-client.d.ts",
20
+ "default": "./fesm2022/acorex-platform-extension-ai-otel-client.mjs"
21
+ }
22
+ },
23
+ "type": "module"
24
+ }
@@ -0,0 +1,12 @@
1
+ import * as i0 from '@angular/core';
2
+
3
+ /**
4
+ * AI SDK OpenTelemetry UI — contributes the GenAI tab on SystemInsight.TraceEntry.
5
+ */
6
+ declare class AXEAiOtelExtensionModule {
7
+ static ɵfac: i0.ɵɵFactoryDeclaration<AXEAiOtelExtensionModule, never>;
8
+ static ɵmod: i0.ɵɵNgModuleDeclaration<AXEAiOtelExtensionModule, never, never, never>;
9
+ static ɵinj: i0.ɵɵInjectorDeclaration<AXEAiOtelExtensionModule>;
10
+ }
11
+
12
+ export { AXEAiOtelExtensionModule };