@praxisui/page-builder 8.0.0-beta.86 → 8.0.0-beta.88
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.
|
@@ -25,7 +25,7 @@ import * as i6 from '@angular/material/divider';
|
|
|
25
25
|
import { MatDividerModule } from '@angular/material/divider';
|
|
26
26
|
import * as i6$1 from '@angular/material/select';
|
|
27
27
|
import { MatSelectModule } from '@angular/material/select';
|
|
28
|
-
import { BehaviorSubject, merge, timeout, map, switchMap, Observable, from, firstValueFrom, concatMap,
|
|
28
|
+
import { BehaviorSubject, merge, timeout, map, defer, switchMap, concat, of, Observable, from, firstValueFrom, share, concatMap, timer, takeUntil, catchError, filter, take } from 'rxjs';
|
|
29
29
|
import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
|
|
30
30
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
31
31
|
import * as i7 from '@angular/material/tabs';
|
|
@@ -12056,6 +12056,7 @@ const DEFAULT_AGENTIC_AUTHORING_REQUEST_TIMEOUT_MS = 120000;
|
|
|
12056
12056
|
const DEFAULT_TURN_STREAM_START_TIMEOUT_MS = 10000;
|
|
12057
12057
|
const DEFAULT_TURN_STREAM_TIMEOUT_MS = 240000;
|
|
12058
12058
|
const DEFAULT_TURN_STREAM_RESULT_FALLBACK_MS = 90000;
|
|
12059
|
+
const DEFAULT_TURN_STREAM_SILENCE_STATUS_MS = 20000;
|
|
12059
12060
|
const PAGE_BUILDER_AGENTIC_AUTHORING_OPTIONS = new InjectionToken('PAGE_BUILDER_AGENTIC_AUTHORING_OPTIONS');
|
|
12060
12061
|
class PageBuilderAgenticAuthoringService {
|
|
12061
12062
|
http = inject(HttpClient);
|
|
@@ -12077,7 +12078,15 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12077
12078
|
return this.http.post(`${this.baseUrl}/resource-candidates`, request, { headers: this.buildHeaders() }).pipe(timeout({ first: this.requestTimeoutMs() }));
|
|
12078
12079
|
}
|
|
12079
12080
|
streamTurn(request) {
|
|
12080
|
-
return
|
|
12081
|
+
return defer(() => {
|
|
12082
|
+
const startRequestedAt = Date.now();
|
|
12083
|
+
return this.http.post(`${this.baseUrl}/turn/stream/start`, request, { headers: this.buildHeaders(), withCredentials: true }).pipe(timeout({ first: this.streamStartTimeoutMs() }), map((start) => ({
|
|
12084
|
+
start,
|
|
12085
|
+
startRequestElapsedMs: Math.max(0, Date.now() - startRequestedAt),
|
|
12086
|
+
})));
|
|
12087
|
+
}).pipe(switchMap(({ start, startRequestElapsedMs }) => concat(of(this.streamLifecycleStatusEvent(start, 'stream.start.accepted', {
|
|
12088
|
+
startRequestElapsedMs,
|
|
12089
|
+
})), this.connectTurnStream(start, { startRequestElapsedMs }))));
|
|
12081
12090
|
}
|
|
12082
12091
|
applyPage(request) {
|
|
12083
12092
|
const { ifMatch, ...body } = request;
|
|
@@ -12109,16 +12118,20 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12109
12118
|
}
|
|
12110
12119
|
return new HttpHeaders(merged);
|
|
12111
12120
|
}
|
|
12112
|
-
connectTurnStream(start) {
|
|
12121
|
+
connectTurnStream(start, telemetry) {
|
|
12113
12122
|
return new Observable((observer) => {
|
|
12123
|
+
const connectStartedAt = Date.now();
|
|
12114
12124
|
const url = this.buildStreamUrl(start);
|
|
12115
12125
|
const probeUrl = this.buildStreamProbeUrl(start);
|
|
12116
12126
|
let source = null;
|
|
12117
12127
|
let closed = false;
|
|
12128
|
+
let firstEventReceived = false;
|
|
12118
12129
|
let streamTimeout = null;
|
|
12119
12130
|
let resultFallbackTimeout = null;
|
|
12131
|
+
let silenceStatusTimeout = null;
|
|
12120
12132
|
const streamTimeoutMs = this.streamTurnTimeoutMs();
|
|
12121
12133
|
const resultFallbackMs = this.streamResultFallbackMs();
|
|
12134
|
+
const silenceStatusMs = this.streamSilenceStatusMs();
|
|
12122
12135
|
let probeAbort = typeof AbortController !== 'undefined'
|
|
12123
12136
|
? new AbortController()
|
|
12124
12137
|
: null;
|
|
@@ -12135,6 +12148,12 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12135
12148
|
resultFallbackTimeout = null;
|
|
12136
12149
|
}
|
|
12137
12150
|
};
|
|
12151
|
+
const clearSilenceStatusTimeout = () => {
|
|
12152
|
+
if (silenceStatusTimeout) {
|
|
12153
|
+
clearTimeout(silenceStatusTimeout);
|
|
12154
|
+
silenceStatusTimeout = null;
|
|
12155
|
+
}
|
|
12156
|
+
};
|
|
12138
12157
|
const closeSource = () => {
|
|
12139
12158
|
if (closed) {
|
|
12140
12159
|
return;
|
|
@@ -12142,10 +12161,21 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12142
12161
|
closed = true;
|
|
12143
12162
|
clearStreamTimeout();
|
|
12144
12163
|
clearResultFallbackTimeout();
|
|
12164
|
+
clearSilenceStatusTimeout();
|
|
12145
12165
|
streamAbort?.abort();
|
|
12146
12166
|
streamAbort = null;
|
|
12147
12167
|
source?.close();
|
|
12148
12168
|
};
|
|
12169
|
+
const startSilenceStatusTimer = () => {
|
|
12170
|
+
clearSilenceStatusTimeout();
|
|
12171
|
+
if (silenceStatusMs <= 0)
|
|
12172
|
+
return;
|
|
12173
|
+
silenceStatusTimeout = setTimeout(() => {
|
|
12174
|
+
if (closed)
|
|
12175
|
+
return;
|
|
12176
|
+
observer.next(this.streamSilenceStatusEvent(start, silenceStatusMs, resultFallbackMs));
|
|
12177
|
+
}, silenceStatusMs);
|
|
12178
|
+
};
|
|
12149
12179
|
const startResultFallbackTimer = () => {
|
|
12150
12180
|
clearResultFallbackTimeout();
|
|
12151
12181
|
if (resultFallbackMs <= 0)
|
|
@@ -12185,6 +12215,16 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12185
12215
|
closeSource();
|
|
12186
12216
|
return;
|
|
12187
12217
|
}
|
|
12218
|
+
if (!firstEventReceived) {
|
|
12219
|
+
firstEventReceived = true;
|
|
12220
|
+
observer.next(this.streamLifecycleStatusEvent(start, 'stream.first-event.received', {
|
|
12221
|
+
startRequestElapsedMs: telemetry.startRequestElapsedMs,
|
|
12222
|
+
connectElapsedMs: Math.max(0, Date.now() - connectStartedAt),
|
|
12223
|
+
firstEventSeq: parsed.seq ?? null,
|
|
12224
|
+
firstEventType: parsed.type ?? null,
|
|
12225
|
+
}));
|
|
12226
|
+
}
|
|
12227
|
+
clearSilenceStatusTimeout();
|
|
12188
12228
|
observer.next(parsed);
|
|
12189
12229
|
if (parsed.type === 'result' || parsed.type === 'error' || parsed.type === 'cancelled') {
|
|
12190
12230
|
clearResultFallbackTimeout();
|
|
@@ -12203,9 +12243,20 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12203
12243
|
observer.error(this.streamConnectionError({ status }));
|
|
12204
12244
|
return;
|
|
12205
12245
|
}
|
|
12246
|
+
observer.next(this.streamLifecycleStatusEvent(start, 'stream.probe.ready', {
|
|
12247
|
+
startRequestElapsedMs: telemetry.startRequestElapsedMs,
|
|
12248
|
+
connectElapsedMs: Math.max(0, Date.now() - connectStartedAt),
|
|
12249
|
+
probeStatus: status,
|
|
12250
|
+
}));
|
|
12206
12251
|
if (!this.options?.eventSourceFactory && typeof fetch === 'function' && typeof TextDecoder !== 'undefined') {
|
|
12207
12252
|
streamAbort = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
12253
|
+
observer.next(this.streamLifecycleStatusEvent(start, 'stream.transport.opening', {
|
|
12254
|
+
startRequestElapsedMs: telemetry.startRequestElapsedMs,
|
|
12255
|
+
connectElapsedMs: Math.max(0, Date.now() - connectStartedAt),
|
|
12256
|
+
transport: 'fetch',
|
|
12257
|
+
}));
|
|
12208
12258
|
startResultFallbackTimer();
|
|
12259
|
+
startSilenceStatusTimer();
|
|
12209
12260
|
void this.consumeFetchTurnStream(url, streamAbort, handleMessage, () => closed).catch((error) => {
|
|
12210
12261
|
if (closed) {
|
|
12211
12262
|
return;
|
|
@@ -12223,7 +12274,13 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12223
12274
|
observer.error(this.streamConnectionError(error));
|
|
12224
12275
|
return;
|
|
12225
12276
|
}
|
|
12277
|
+
observer.next(this.streamLifecycleStatusEvent(start, 'stream.transport.opening', {
|
|
12278
|
+
startRequestElapsedMs: telemetry.startRequestElapsedMs,
|
|
12279
|
+
connectElapsedMs: Math.max(0, Date.now() - connectStartedAt),
|
|
12280
|
+
transport: 'event-source',
|
|
12281
|
+
}));
|
|
12226
12282
|
startResultFallbackTimer();
|
|
12283
|
+
startSilenceStatusTimer();
|
|
12227
12284
|
source.onmessage = handleMessage;
|
|
12228
12285
|
if (source.addEventListener) {
|
|
12229
12286
|
for (const type of AI_STREAM_EVENT_TYPES) {
|
|
@@ -12259,6 +12316,7 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12259
12316
|
streamAbort = null;
|
|
12260
12317
|
clearStreamTimeout();
|
|
12261
12318
|
clearResultFallbackTimeout();
|
|
12319
|
+
clearSilenceStatusTimeout();
|
|
12262
12320
|
};
|
|
12263
12321
|
});
|
|
12264
12322
|
}
|
|
@@ -12333,6 +12391,51 @@ class PageBuilderAgenticAuthoringService {
|
|
|
12333
12391
|
streamResultFallbackMs() {
|
|
12334
12392
|
return Math.max(0, this.options?.streamResultFallbackMs ?? DEFAULT_TURN_STREAM_RESULT_FALLBACK_MS);
|
|
12335
12393
|
}
|
|
12394
|
+
streamSilenceStatusMs() {
|
|
12395
|
+
return Math.max(0, this.options?.streamSilenceStatusMs ?? DEFAULT_TURN_STREAM_SILENCE_STATUS_MS);
|
|
12396
|
+
}
|
|
12397
|
+
streamSilenceStatusEvent(start, elapsedMs, resultFallbackMs) {
|
|
12398
|
+
return {
|
|
12399
|
+
streamId: start.streamId,
|
|
12400
|
+
threadId: start.threadId,
|
|
12401
|
+
turnId: start.turnId,
|
|
12402
|
+
seq: 0,
|
|
12403
|
+
eventSchemaVersion: start.eventSchemaVersion,
|
|
12404
|
+
timestamp: new Date().toISOString(),
|
|
12405
|
+
type: 'status',
|
|
12406
|
+
payload: {
|
|
12407
|
+
phase: 'stream.waiting',
|
|
12408
|
+
summary: 'Still waiting for the backend authoring result...',
|
|
12409
|
+
diagnostics: {
|
|
12410
|
+
source: 'frontend-stream-silence-watchdog',
|
|
12411
|
+
elapsedMs,
|
|
12412
|
+
resultFallbackMs,
|
|
12413
|
+
fallbackAuthoringUrl: start.fallbackAuthoringUrl ?? null,
|
|
12414
|
+
},
|
|
12415
|
+
},
|
|
12416
|
+
};
|
|
12417
|
+
}
|
|
12418
|
+
streamLifecycleStatusEvent(start, phase, diagnostics) {
|
|
12419
|
+
return {
|
|
12420
|
+
streamId: start.streamId,
|
|
12421
|
+
threadId: start.threadId,
|
|
12422
|
+
turnId: start.turnId,
|
|
12423
|
+
seq: 0,
|
|
12424
|
+
eventSchemaVersion: start.eventSchemaVersion,
|
|
12425
|
+
timestamp: new Date().toISOString(),
|
|
12426
|
+
type: 'status',
|
|
12427
|
+
payload: {
|
|
12428
|
+
phase,
|
|
12429
|
+
summary: 'Frontend authoring stream lifecycle checkpoint.',
|
|
12430
|
+
diagnostics: {
|
|
12431
|
+
schemaVersion: 'praxis-turn-stream-lifecycle-diagnostics.v1',
|
|
12432
|
+
source: 'frontend-turn-stream-lifecycle',
|
|
12433
|
+
fallbackAuthoringUrl: start.fallbackAuthoringUrl ?? null,
|
|
12434
|
+
...diagnostics,
|
|
12435
|
+
},
|
|
12436
|
+
},
|
|
12437
|
+
};
|
|
12438
|
+
}
|
|
12336
12439
|
requestTimeoutMs() {
|
|
12337
12440
|
return Math.max(1, this.options?.requestTimeoutMs ?? DEFAULT_AGENTIC_AUTHORING_REQUEST_TIMEOUT_MS);
|
|
12338
12441
|
}
|
|
@@ -14036,6 +14139,7 @@ class PageBuilderAiAdapter {
|
|
|
14036
14139
|
}
|
|
14037
14140
|
}
|
|
14038
14141
|
|
|
14142
|
+
const TURN_STREAM_PREPARATION_WAITING_MS = 20000;
|
|
14039
14143
|
class PageBuilderAgenticAuthoringTurnFlow {
|
|
14040
14144
|
service;
|
|
14041
14145
|
context;
|
|
@@ -14185,7 +14289,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14185
14289
|
statusText: '',
|
|
14186
14290
|
errorText: message,
|
|
14187
14291
|
preview: null,
|
|
14188
|
-
diagnostics:
|
|
14292
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14189
14293
|
};
|
|
14190
14294
|
}
|
|
14191
14295
|
const applied = await this.context.applyLocalPreview(preview);
|
|
@@ -14199,20 +14303,20 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14199
14303
|
statusText: '',
|
|
14200
14304
|
errorText: message,
|
|
14201
14305
|
preview: null,
|
|
14202
|
-
diagnostics:
|
|
14306
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14203
14307
|
};
|
|
14204
14308
|
}
|
|
14205
|
-
const status = this.context.describePreviewStatus(preview);
|
|
14309
|
+
const status = this.decorateDashboardReviewStatus(this.context.describePreviewStatus(preview), intentResolution, preview);
|
|
14206
14310
|
return {
|
|
14207
14311
|
state: 'review',
|
|
14208
14312
|
phase: 'review',
|
|
14209
14313
|
assistantMessage: status,
|
|
14210
|
-
quickReplies:
|
|
14314
|
+
quickReplies: this.dashboardQualityQuickReplies(intentResolution, preview),
|
|
14211
14315
|
canApply: true,
|
|
14212
14316
|
statusText: this.reviewStatusText(status, true),
|
|
14213
14317
|
errorText: '',
|
|
14214
14318
|
preview,
|
|
14215
|
-
diagnostics:
|
|
14319
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14216
14320
|
};
|
|
14217
14321
|
}
|
|
14218
14322
|
catch (error) {
|
|
@@ -14229,7 +14333,9 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14229
14333
|
}
|
|
14230
14334
|
}
|
|
14231
14335
|
submitWithTurnStream(request, prompt) {
|
|
14232
|
-
|
|
14336
|
+
const preparedRequest$ = from(this.buildTurnStreamRequest(request, prompt)).pipe(share());
|
|
14337
|
+
const stream$ = preparedRequest$.pipe(concatMap(({ streamRequest }) => this.service.streamTurn(streamRequest)), concatMap((event) => from(this.toTurnResultFromStreamEvent(event, request, prompt))), share());
|
|
14338
|
+
return merge(of(this.toTurnStreamPreparationStatusResult()), preparedRequest$.pipe(map(({ diagnostics }) => this.toTurnStreamPreparedStatusResult(diagnostics))), timer(TURN_STREAM_PREPARATION_WAITING_MS).pipe(map(() => this.toTurnStreamPreparationWaitingResult()), takeUntil(stream$)), stream$).pipe(catchError((error) => {
|
|
14233
14339
|
if (this.shouldFailClosedFromTurnStreamError(request, prompt, error)) {
|
|
14234
14340
|
return of(this.toGovernedRouteFailClosedResult(request, prompt, error));
|
|
14235
14341
|
}
|
|
@@ -14238,6 +14344,49 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14238
14344
|
: of(this.toTurnStreamTransportErrorResult(error));
|
|
14239
14345
|
}));
|
|
14240
14346
|
}
|
|
14347
|
+
toTurnStreamPreparationStatusResult() {
|
|
14348
|
+
return {
|
|
14349
|
+
state: 'processing',
|
|
14350
|
+
phase: 'contextualize',
|
|
14351
|
+
assistantMessage: undefined,
|
|
14352
|
+
canApply: false,
|
|
14353
|
+
statusText: this.context.tx('agentic.status.preparingTurnStream', 'Preparing the AI authoring context...'),
|
|
14354
|
+
errorText: '',
|
|
14355
|
+
preview: null,
|
|
14356
|
+
};
|
|
14357
|
+
}
|
|
14358
|
+
toTurnStreamPreparedStatusResult(diagnostics) {
|
|
14359
|
+
return {
|
|
14360
|
+
state: 'processing',
|
|
14361
|
+
phase: 'contextualize',
|
|
14362
|
+
assistantMessage: undefined,
|
|
14363
|
+
canApply: false,
|
|
14364
|
+
statusText: this.context.tx('agentic.status.turnStreamContextReady', 'AI authoring context is ready. Starting backend stream...'),
|
|
14365
|
+
errorText: '',
|
|
14366
|
+
preview: null,
|
|
14367
|
+
diagnostics: {
|
|
14368
|
+
streamPreparation: diagnostics,
|
|
14369
|
+
},
|
|
14370
|
+
};
|
|
14371
|
+
}
|
|
14372
|
+
toTurnStreamPreparationWaitingResult() {
|
|
14373
|
+
return {
|
|
14374
|
+
state: 'processing',
|
|
14375
|
+
phase: 'contextualize',
|
|
14376
|
+
assistantMessage: undefined,
|
|
14377
|
+
canApply: false,
|
|
14378
|
+
statusText: this.context.tx('agentic.status.preparingTurnStreamWaiting', 'Still preparing the AI authoring stream. Checking catalog, context, and backend availability...'),
|
|
14379
|
+
errorText: '',
|
|
14380
|
+
preview: null,
|
|
14381
|
+
diagnostics: {
|
|
14382
|
+
streamPreparationWatchdog: {
|
|
14383
|
+
schemaVersion: 'praxis-turn-stream-preparation-watchdog.v1',
|
|
14384
|
+
source: 'page-builder-agentic-authoring-turn-flow',
|
|
14385
|
+
elapsedMs: TURN_STREAM_PREPARATION_WAITING_MS,
|
|
14386
|
+
},
|
|
14387
|
+
},
|
|
14388
|
+
};
|
|
14389
|
+
}
|
|
14241
14390
|
async cancel() {
|
|
14242
14391
|
return {
|
|
14243
14392
|
state: 'listening',
|
|
@@ -14257,12 +14406,13 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14257
14406
|
const artifactKind = this.readString(contextHints, 'artifactKind')
|
|
14258
14407
|
|| (componentId === 'praxis-table' ? 'table' : componentId === 'praxis-dynamic-form' ? 'form' : 'chart');
|
|
14259
14408
|
const changeKind = this.readString(contextHints, 'changeKind') || 'contextual_component_action';
|
|
14409
|
+
const authoringProfile = this.readString(contextHints, 'source') || 'component-capability-catalog';
|
|
14260
14410
|
const intentResolution = {
|
|
14261
14411
|
valid: true,
|
|
14262
14412
|
operationKind: 'modify',
|
|
14263
14413
|
artifactKind,
|
|
14264
14414
|
changeKind,
|
|
14265
|
-
authoringProfile
|
|
14415
|
+
authoringProfile,
|
|
14266
14416
|
targetApp: this.context.targetApp,
|
|
14267
14417
|
targetComponentId: this.context.targetComponentId,
|
|
14268
14418
|
target: componentId
|
|
@@ -14281,7 +14431,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14281
14431
|
selectedCandidate: this.contextualActionCandidate(contextHints),
|
|
14282
14432
|
candidates: [],
|
|
14283
14433
|
gate: {
|
|
14284
|
-
gateId:
|
|
14434
|
+
gateId: authoringProfile,
|
|
14285
14435
|
status: 'eligible',
|
|
14286
14436
|
messages: [],
|
|
14287
14437
|
},
|
|
@@ -14298,10 +14448,15 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14298
14448
|
return this.completeExecutableStreamPreview(request, prompt, intentResolution, undefined);
|
|
14299
14449
|
}
|
|
14300
14450
|
async buildTurnStreamRequest(request, prompt) {
|
|
14451
|
+
const startedAt = Date.now();
|
|
14452
|
+
const capabilitiesStartedAt = Date.now();
|
|
14301
14453
|
const componentCapabilities = await this.context.loadComponentCapabilities();
|
|
14454
|
+
const capabilitiesElapsedMs = Math.max(0, Date.now() - capabilitiesStartedAt);
|
|
14455
|
+
const authoringContextStartedAt = Date.now();
|
|
14302
14456
|
const selectedWidgetKey = this.context.selectedWidgetKey();
|
|
14303
14457
|
const authoringContext = this.buildAuthoringContext(request);
|
|
14304
|
-
|
|
14458
|
+
const authoringContextElapsedMs = Math.max(0, Date.now() - authoringContextStartedAt);
|
|
14459
|
+
const streamRequest = {
|
|
14305
14460
|
userPrompt: prompt,
|
|
14306
14461
|
targetApp: this.context.targetApp,
|
|
14307
14462
|
targetComponentId: this.context.targetComponentId,
|
|
@@ -14314,6 +14469,22 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14314
14469
|
componentCapabilities,
|
|
14315
14470
|
...authoringContext,
|
|
14316
14471
|
};
|
|
14472
|
+
return {
|
|
14473
|
+
streamRequest,
|
|
14474
|
+
diagnostics: {
|
|
14475
|
+
schemaVersion: 'praxis-turn-stream-preparation-diagnostics.v1',
|
|
14476
|
+
source: 'page-builder-agentic-authoring-turn-flow',
|
|
14477
|
+
totalElapsedMs: Math.max(0, Date.now() - startedAt),
|
|
14478
|
+
capabilitiesElapsedMs,
|
|
14479
|
+
authoringContextElapsedMs,
|
|
14480
|
+
catalogCount: componentCapabilities.catalogs?.length ?? 0,
|
|
14481
|
+
capabilityCount: componentCapabilities.catalogs
|
|
14482
|
+
.reduce((count, catalog) => count + (catalog.capabilities?.length ?? 0), 0),
|
|
14483
|
+
attachmentCount: authoringContext.attachmentSummaries?.length ?? 0,
|
|
14484
|
+
hasPendingClarification: !!authoringContext.pendingClarification,
|
|
14485
|
+
hasContextHints: !!authoringContext.contextHints,
|
|
14486
|
+
},
|
|
14487
|
+
};
|
|
14317
14488
|
}
|
|
14318
14489
|
async toTurnResultFromStreamEvent(event, request, prompt) {
|
|
14319
14490
|
const payload = this.toJsonObject(event.payload) ?? {};
|
|
@@ -14406,25 +14577,24 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14406
14577
|
errorText: message,
|
|
14407
14578
|
preview: null,
|
|
14408
14579
|
diagnostics: {
|
|
14409
|
-
intentResolution,
|
|
14410
|
-
preview,
|
|
14580
|
+
...this.buildTurnDiagnostics(intentResolution, preview),
|
|
14411
14581
|
decisionDiagnostics: this.toJsonObject(payload['decisionDiagnostics']),
|
|
14412
14582
|
toolLoopTrace: Array.isArray(payload['toolLoopTrace']) ? payload['toolLoopTrace'] : undefined,
|
|
14413
14583
|
},
|
|
14414
14584
|
};
|
|
14415
14585
|
}
|
|
14586
|
+
const status = this.decorateDashboardReviewStatus(assistantMessage || this.context.describePreviewStatus(preview), intentResolution, preview);
|
|
14416
14587
|
return {
|
|
14417
14588
|
state: 'review',
|
|
14418
14589
|
phase: 'review',
|
|
14419
|
-
assistantMessage,
|
|
14420
|
-
quickReplies,
|
|
14590
|
+
assistantMessage: status,
|
|
14591
|
+
quickReplies: this.mergeDashboardQualityQuickReplies(quickReplies, intentResolution, preview),
|
|
14421
14592
|
canApply: false,
|
|
14422
|
-
statusText: this.reviewStatusText(
|
|
14593
|
+
statusText: this.reviewStatusText(status, false),
|
|
14423
14594
|
errorText: '',
|
|
14424
14595
|
preview,
|
|
14425
14596
|
diagnostics: {
|
|
14426
|
-
intentResolution,
|
|
14427
|
-
preview,
|
|
14597
|
+
...this.buildTurnDiagnostics(intentResolution, preview),
|
|
14428
14598
|
decisionDiagnostics: this.toJsonObject(payload['decisionDiagnostics']),
|
|
14429
14599
|
toolLoopTrace: Array.isArray(payload['toolLoopTrace']) ? payload['toolLoopTrace'] : undefined,
|
|
14430
14600
|
},
|
|
@@ -14451,7 +14621,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14451
14621
|
errorText: '',
|
|
14452
14622
|
preview: null,
|
|
14453
14623
|
pendingClarification,
|
|
14454
|
-
diagnostics:
|
|
14624
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14455
14625
|
};
|
|
14456
14626
|
}
|
|
14457
14627
|
const applied = await this.context.applyLocalPreview(preview);
|
|
@@ -14465,20 +14635,20 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14465
14635
|
statusText: '',
|
|
14466
14636
|
errorText: message,
|
|
14467
14637
|
preview: null,
|
|
14468
|
-
diagnostics:
|
|
14638
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14469
14639
|
};
|
|
14470
14640
|
}
|
|
14471
|
-
const status = assistantMessage || this.context.describePreviewStatus(preview);
|
|
14641
|
+
const status = this.decorateDashboardReviewStatus(assistantMessage || this.context.describePreviewStatus(preview), intentResolution, preview);
|
|
14472
14642
|
return {
|
|
14473
14643
|
state: 'review',
|
|
14474
14644
|
phase: 'review',
|
|
14475
14645
|
assistantMessage: status,
|
|
14476
|
-
quickReplies,
|
|
14646
|
+
quickReplies: this.mergeDashboardQualityQuickReplies(quickReplies, intentResolution, preview),
|
|
14477
14647
|
canApply: true,
|
|
14478
14648
|
statusText: this.reviewStatusText(status, true),
|
|
14479
14649
|
errorText: '',
|
|
14480
14650
|
preview,
|
|
14481
|
-
diagnostics:
|
|
14651
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14482
14652
|
};
|
|
14483
14653
|
}
|
|
14484
14654
|
hasBlockingResourceQuickReply(quickReplies) {
|
|
@@ -14525,7 +14695,7 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14525
14695
|
statusText: '',
|
|
14526
14696
|
errorText: message,
|
|
14527
14697
|
preview: null,
|
|
14528
|
-
diagnostics:
|
|
14698
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14529
14699
|
};
|
|
14530
14700
|
}
|
|
14531
14701
|
const applied = await this.context.applyLocalPreview(preview);
|
|
@@ -14539,20 +14709,20 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14539
14709
|
statusText: '',
|
|
14540
14710
|
errorText: message,
|
|
14541
14711
|
preview: null,
|
|
14542
|
-
diagnostics:
|
|
14712
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14543
14713
|
};
|
|
14544
14714
|
}
|
|
14545
|
-
const status = this.context.describePreviewStatus(preview);
|
|
14715
|
+
const status = this.decorateDashboardReviewStatus(this.context.describePreviewStatus(preview), intentResolution, preview);
|
|
14546
14716
|
return {
|
|
14547
14717
|
state: 'review',
|
|
14548
14718
|
phase: 'review',
|
|
14549
14719
|
assistantMessage: status,
|
|
14550
|
-
quickReplies:
|
|
14720
|
+
quickReplies: this.dashboardQualityQuickReplies(intentResolution, preview),
|
|
14551
14721
|
canApply: true,
|
|
14552
14722
|
statusText: this.reviewStatusText(status, true),
|
|
14553
14723
|
errorText: '',
|
|
14554
14724
|
preview,
|
|
14555
|
-
diagnostics:
|
|
14725
|
+
diagnostics: this.buildTurnDiagnostics(intentResolution, preview),
|
|
14556
14726
|
};
|
|
14557
14727
|
}
|
|
14558
14728
|
catch (error) {
|
|
@@ -14618,6 +14788,675 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14618
14788
|
const page = this.toJsonObject(patch)?.['page'];
|
|
14619
14789
|
return !!this.toJsonObject(page);
|
|
14620
14790
|
}
|
|
14791
|
+
buildTurnDiagnostics(intentResolution, preview) {
|
|
14792
|
+
const diagnostics = { intentResolution };
|
|
14793
|
+
if (preview) {
|
|
14794
|
+
diagnostics['preview'] = preview;
|
|
14795
|
+
}
|
|
14796
|
+
const dashboardDiagnostics = this.buildDashboardAuthoringDiagnostics(intentResolution, preview);
|
|
14797
|
+
if (dashboardDiagnostics) {
|
|
14798
|
+
diagnostics['dashboardAuthoringDiagnostics'] = dashboardDiagnostics;
|
|
14799
|
+
}
|
|
14800
|
+
return diagnostics;
|
|
14801
|
+
}
|
|
14802
|
+
buildDashboardAuthoringDiagnostics(intentResolution, preview) {
|
|
14803
|
+
if (intentResolution.artifactKind !== 'dashboard' && !preview?.uiCompositionPlan) {
|
|
14804
|
+
return null;
|
|
14805
|
+
}
|
|
14806
|
+
const plan = this.toJsonObject(preview?.uiCompositionPlan);
|
|
14807
|
+
const patch = this.toJsonObject(preview?.compiledFormPatch)?.['patch'];
|
|
14808
|
+
const page = this.toJsonObject(this.toJsonObject(patch)?.['page']);
|
|
14809
|
+
const selectedCandidate = this.toJsonObject(intentResolution.selectedCandidate);
|
|
14810
|
+
const widgets = this.extractDiagnosticWidgets(plan, page);
|
|
14811
|
+
const connections = this.extractDiagnosticConnections(plan, page);
|
|
14812
|
+
const surfaces = widgets.flatMap((widget) => widget.surfaces);
|
|
14813
|
+
const blueprint = this.extractDashboardBlueprint(intentResolution, preview, plan, page);
|
|
14814
|
+
const validation = this.buildDashboardValidationSummary(widgets, connections, surfaces);
|
|
14815
|
+
const plannerDiagnostics = this.extractDashboardPlannerDiagnostics(plan);
|
|
14816
|
+
return {
|
|
14817
|
+
schemaVersion: 'praxis-dashboard-authoring-diagnostics.v1',
|
|
14818
|
+
generatedAt: new Date().toISOString(),
|
|
14819
|
+
source: 'page-builder-agentic-authoring-turn-flow',
|
|
14820
|
+
intent: {
|
|
14821
|
+
operationKind: intentResolution.operationKind,
|
|
14822
|
+
artifactKind: intentResolution.artifactKind,
|
|
14823
|
+
changeKind: intentResolution.changeKind,
|
|
14824
|
+
authoringProfile: intentResolution.authoringProfile,
|
|
14825
|
+
},
|
|
14826
|
+
dto: {
|
|
14827
|
+
resourcePath: this.readString(selectedCandidate ?? {}, 'resourcePath'),
|
|
14828
|
+
schemaUrl: this.readString(selectedCandidate ?? {}, 'schemaUrl'),
|
|
14829
|
+
operation: this.readString(selectedCandidate ?? {}, 'operation'),
|
|
14830
|
+
submitMethod: this.readString(selectedCandidate ?? {}, 'submitMethod'),
|
|
14831
|
+
},
|
|
14832
|
+
blueprint,
|
|
14833
|
+
preview: {
|
|
14834
|
+
valid: preview?.valid === true,
|
|
14835
|
+
failureCodes: Array.isArray(preview?.failureCodes) ? preview?.failureCodes : [],
|
|
14836
|
+
warnings: Array.isArray(preview?.warnings) ? preview?.warnings : [],
|
|
14837
|
+
hasUiCompositionPlan: !!plan,
|
|
14838
|
+
hasCompiledPagePatch: !!page,
|
|
14839
|
+
},
|
|
14840
|
+
inventory: {
|
|
14841
|
+
widgets: widgets.map(({ surfaces: _surfaces, ...widget }) => widget),
|
|
14842
|
+
connections,
|
|
14843
|
+
surfaces,
|
|
14844
|
+
},
|
|
14845
|
+
...(plannerDiagnostics ? { plannerDiagnostics } : {}),
|
|
14846
|
+
validation,
|
|
14847
|
+
};
|
|
14848
|
+
}
|
|
14849
|
+
extractDashboardPlannerDiagnostics(plan) {
|
|
14850
|
+
const diagnostics = this.toJsonObject(plan?.['diagnostics']);
|
|
14851
|
+
if (!diagnostics) {
|
|
14852
|
+
return null;
|
|
14853
|
+
}
|
|
14854
|
+
const summary = {};
|
|
14855
|
+
const dashboardQualityRepair = this.toJsonObject(diagnostics['dashboardQualityRepair']);
|
|
14856
|
+
if (dashboardQualityRepair) {
|
|
14857
|
+
summary['dashboardQualityRepair'] = dashboardQualityRepair;
|
|
14858
|
+
}
|
|
14859
|
+
const dashboardBlueprint = this.toJsonObject(diagnostics['dashboardBlueprint']);
|
|
14860
|
+
if (dashboardBlueprint) {
|
|
14861
|
+
summary['dashboardBlueprint'] = dashboardBlueprint;
|
|
14862
|
+
}
|
|
14863
|
+
const semanticAxis = this.toJsonObject(diagnostics['semanticAxis']);
|
|
14864
|
+
if (semanticAxis) {
|
|
14865
|
+
summary['semanticAxis'] = semanticAxis;
|
|
14866
|
+
}
|
|
14867
|
+
return Object.keys(summary).length ? summary : diagnostics;
|
|
14868
|
+
}
|
|
14869
|
+
extractDashboardBlueprint(intentResolution, preview, plan, page) {
|
|
14870
|
+
const diagnostics = this.toJsonObject(preview?.diagnostics);
|
|
14871
|
+
const layoutPresetOptions = this.toJsonObject(plan?.['layoutPresetOptions'])
|
|
14872
|
+
?? this.toJsonObject(page?.['layoutPresetOptions']);
|
|
14873
|
+
return {
|
|
14874
|
+
name: this.readString(diagnostics ?? {}, 'blueprint')
|
|
14875
|
+
?? this.readString(layoutPresetOptions ?? {}, 'blueprint')
|
|
14876
|
+
?? this.readString(layoutPresetOptions ?? {}, 'presetFamily')
|
|
14877
|
+
?? null,
|
|
14878
|
+
layoutPreset: this.readString(plan ?? {}, 'layoutPreset')
|
|
14879
|
+
?? this.readString(page ?? {}, 'layoutPreset'),
|
|
14880
|
+
layoutPresetOptions,
|
|
14881
|
+
themePreset: this.readString(plan ?? {}, 'themePreset')
|
|
14882
|
+
?? this.readString(page ?? {}, 'themePreset'),
|
|
14883
|
+
selectedBy: intentResolution.authoringProfile ?? intentResolution.changeKind ?? null,
|
|
14884
|
+
};
|
|
14885
|
+
}
|
|
14886
|
+
extractDiagnosticWidgets(plan, page) {
|
|
14887
|
+
const sourceWidgets = [
|
|
14888
|
+
...(Array.isArray(plan?.['widgets']) ? plan?.['widgets'] : []),
|
|
14889
|
+
...(Array.isArray(page?.['widgets']) ? page?.['widgets'] : []),
|
|
14890
|
+
];
|
|
14891
|
+
const seen = new Set();
|
|
14892
|
+
return sourceWidgets
|
|
14893
|
+
.filter((widget) => !!this.toJsonObject(widget))
|
|
14894
|
+
.filter((widget) => {
|
|
14895
|
+
const definition = this.toJsonObject(widget['definition']);
|
|
14896
|
+
const key = this.readString(widget, 'key')
|
|
14897
|
+
?? this.readString(widget, 'id')
|
|
14898
|
+
?? this.readString(definition ?? {}, 'key')
|
|
14899
|
+
?? this.readString(definition ?? {}, 'id')
|
|
14900
|
+
?? '';
|
|
14901
|
+
if (!key) {
|
|
14902
|
+
return true;
|
|
14903
|
+
}
|
|
14904
|
+
if (seen.has(key)) {
|
|
14905
|
+
return false;
|
|
14906
|
+
}
|
|
14907
|
+
seen.add(key);
|
|
14908
|
+
return true;
|
|
14909
|
+
})
|
|
14910
|
+
.map((widget) => {
|
|
14911
|
+
const definition = this.toJsonObject(widget['definition']);
|
|
14912
|
+
const inputs = this.toJsonObject(widget['inputs'])
|
|
14913
|
+
?? this.toJsonObject(definition?.['inputs'])
|
|
14914
|
+
?? this.toJsonObject(widget['inputValues'])
|
|
14915
|
+
?? this.toJsonObject(widget['props'])
|
|
14916
|
+
?? this.toJsonObject(widget['config'])
|
|
14917
|
+
?? {};
|
|
14918
|
+
const componentId = this.readString(widget, 'componentId')
|
|
14919
|
+
?? this.readString(definition ?? {}, 'id')
|
|
14920
|
+
?? this.readString(widget, 'type')
|
|
14921
|
+
?? 'unknown';
|
|
14922
|
+
const key = this.readString(widget, 'key') ?? this.readString(widget, 'id') ?? 'unknown';
|
|
14923
|
+
const shell = this.toJsonObject(widget['shell']);
|
|
14924
|
+
const surfaces = this.extractSurfacesFromInputs(key, inputs);
|
|
14925
|
+
return {
|
|
14926
|
+
key,
|
|
14927
|
+
componentId,
|
|
14928
|
+
role: this.readString(widget, 'role'),
|
|
14929
|
+
title: this.readString(inputs, 'title')
|
|
14930
|
+
?? this.readString(inputs, 'label')
|
|
14931
|
+
?? this.readString(shell ?? {}, 'title'),
|
|
14932
|
+
subtitle: this.readString(inputs, 'subtitle')
|
|
14933
|
+
?? this.readString(shell ?? {}, 'subtitle'),
|
|
14934
|
+
resourcePath: this.readString(inputs, 'resourcePath')
|
|
14935
|
+
?? this.readString(inputs, 'apiEndpoint')
|
|
14936
|
+
?? this.readString(inputs, 'url'),
|
|
14937
|
+
schemaUrl: this.readString(inputs, 'schemaUrl'),
|
|
14938
|
+
kpiLike: this.isDashboardKpiWidget(componentId, widget, inputs, shell),
|
|
14939
|
+
filterLike: this.isDashboardFilterWidget(componentId, widget, inputs, shell),
|
|
14940
|
+
queryContextBound: inputs['queryContext'] !== undefined,
|
|
14941
|
+
kpiPlaceholderValues: this.extractKpiPlaceholderValues(inputs),
|
|
14942
|
+
surfaces,
|
|
14943
|
+
};
|
|
14944
|
+
});
|
|
14945
|
+
}
|
|
14946
|
+
extractDiagnosticConnections(plan, page) {
|
|
14947
|
+
const planBindings = Array.isArray(plan?.['bindings']) ? plan?.['bindings'] : null;
|
|
14948
|
+
const pageComposition = this.toJsonObject(page?.['composition']);
|
|
14949
|
+
const pageLinks = Array.isArray(pageComposition?.['links']) ? pageComposition?.['links'] : null;
|
|
14950
|
+
return (planBindings ?? pageLinks ?? [])
|
|
14951
|
+
.filter((connection) => !!this.toJsonObject(connection))
|
|
14952
|
+
.map((connection) => ({
|
|
14953
|
+
id: this.readString(connection, 'id') ?? 'unknown',
|
|
14954
|
+
intent: this.readString(connection, 'intent'),
|
|
14955
|
+
from: this.summarizeEndpoint(connection['from']),
|
|
14956
|
+
to: this.summarizeEndpoint(connection['to']),
|
|
14957
|
+
transform: this.summarizeTransform(connection['transform']),
|
|
14958
|
+
}));
|
|
14959
|
+
}
|
|
14960
|
+
extractSurfacesFromInputs(widgetKey, inputs) {
|
|
14961
|
+
const surfaces = [];
|
|
14962
|
+
const candidates = [
|
|
14963
|
+
...this.arrayFromUnknown(inputs['surfaces']),
|
|
14964
|
+
...this.arrayFromUnknown(this.toJsonObject(inputs['recordSurfaces'])?.['surfaces']),
|
|
14965
|
+
...this.arrayFromUnknown(inputs['actions']),
|
|
14966
|
+
...this.arrayFromUnknown(inputs['rowActions']),
|
|
14967
|
+
...this.arrayFromUnknown(inputs['toolbarActions']),
|
|
14968
|
+
];
|
|
14969
|
+
for (const candidate of candidates) {
|
|
14970
|
+
const surface = this.toJsonObject(candidate);
|
|
14971
|
+
if (!surface) {
|
|
14972
|
+
continue;
|
|
14973
|
+
}
|
|
14974
|
+
const action = this.readString(surface, 'action')
|
|
14975
|
+
?? this.readString(surface, 'actionId')
|
|
14976
|
+
?? this.readString(surface, 'operationId');
|
|
14977
|
+
const resourceSurface = this.toJsonObject(surface['resourceSurface']);
|
|
14978
|
+
const widget = this.toJsonObject(surface['widget']);
|
|
14979
|
+
if (action !== 'surface.open' && action !== 'dynamicPage.surface.open' && !resourceSurface && !widget) {
|
|
14980
|
+
continue;
|
|
14981
|
+
}
|
|
14982
|
+
surfaces.push({
|
|
14983
|
+
widgetKey,
|
|
14984
|
+
id: this.readString(surface, 'id') ?? this.readString(resourceSurface ?? {}, 'id'),
|
|
14985
|
+
action,
|
|
14986
|
+
title: this.readString(surface, 'title') ?? this.readString(resourceSurface ?? {}, 'title'),
|
|
14987
|
+
mode: this.readString(resourceSurface ?? {}, 'mode'),
|
|
14988
|
+
presentation: this.readString(surface, 'presentation')
|
|
14989
|
+
?? this.readString(resourceSurface ?? {}, 'presentation'),
|
|
14990
|
+
resourcePath: this.readString(resourceSurface ?? {}, 'resourcePath'),
|
|
14991
|
+
componentId: this.readString(widget ?? {}, 'id')
|
|
14992
|
+
?? this.readString(this.toJsonObject(widget?.['definition']) ?? {}, 'id'),
|
|
14993
|
+
});
|
|
14994
|
+
}
|
|
14995
|
+
return surfaces;
|
|
14996
|
+
}
|
|
14997
|
+
isDashboardKpiWidget(componentId, widget, inputs, shell) {
|
|
14998
|
+
const role = this.readString(widget, 'role')?.toLowerCase() ?? '';
|
|
14999
|
+
if (['kpi', 'metric', 'metrics', 'indicator', 'indicators', 'summary'].includes(role)) {
|
|
15000
|
+
return true;
|
|
15001
|
+
}
|
|
15002
|
+
const searchable = [
|
|
15003
|
+
componentId,
|
|
15004
|
+
this.readString(inputs, 'title'),
|
|
15005
|
+
this.readString(inputs, 'subtitle'),
|
|
15006
|
+
this.readString(inputs, 'label'),
|
|
15007
|
+
this.readString(shell ?? {}, 'title'),
|
|
15008
|
+
this.readString(shell ?? {}, 'subtitle'),
|
|
15009
|
+
]
|
|
15010
|
+
.filter((value) => !!value)
|
|
15011
|
+
.join(' ')
|
|
15012
|
+
.toLowerCase();
|
|
15013
|
+
if (/\b(kpi|metric|metrics|indicator|indicators|indicador|indicadores)\b/.test(searchable)) {
|
|
15014
|
+
return true;
|
|
15015
|
+
}
|
|
15016
|
+
if (componentId === 'praxis-rich-content') {
|
|
15017
|
+
return this.richContentDocumentHasMetrics(this.toJsonObject(inputs['document']));
|
|
15018
|
+
}
|
|
15019
|
+
return false;
|
|
15020
|
+
}
|
|
15021
|
+
richContentDocumentHasMetrics(document) {
|
|
15022
|
+
const nodes = Array.isArray(document?.['nodes']) ? document?.['nodes'] : [];
|
|
15023
|
+
return nodes.some((node) => this.richContentNodeHasMetrics(this.toJsonObject(node)));
|
|
15024
|
+
}
|
|
15025
|
+
richContentNodeHasMetrics(node) {
|
|
15026
|
+
if (!node) {
|
|
15027
|
+
return false;
|
|
15028
|
+
}
|
|
15029
|
+
const type = this.readString(node, 'type')?.toLowerCase();
|
|
15030
|
+
if (type === 'metric' || type === 'statgroup' || type === 'stat-group') {
|
|
15031
|
+
return true;
|
|
15032
|
+
}
|
|
15033
|
+
return ['nodes', 'items', 'children', 'content']
|
|
15034
|
+
.some((key) => this.arrayFromUnknown(node[key])
|
|
15035
|
+
.some((child) => this.richContentNodeHasMetrics(this.toJsonObject(child))));
|
|
15036
|
+
}
|
|
15037
|
+
extractKpiPlaceholderValues(inputs) {
|
|
15038
|
+
const document = this.toJsonObject(inputs['document']);
|
|
15039
|
+
if (!document) {
|
|
15040
|
+
return [];
|
|
15041
|
+
}
|
|
15042
|
+
const placeholders = [];
|
|
15043
|
+
for (const node of this.arrayFromUnknown(document['nodes'])) {
|
|
15044
|
+
this.collectKpiPlaceholderValues(this.toJsonObject(node), placeholders);
|
|
15045
|
+
}
|
|
15046
|
+
return placeholders;
|
|
15047
|
+
}
|
|
15048
|
+
collectKpiPlaceholderValues(node, placeholders) {
|
|
15049
|
+
if (!node) {
|
|
15050
|
+
return;
|
|
15051
|
+
}
|
|
15052
|
+
const type = this.readString(node, 'type')?.toLowerCase();
|
|
15053
|
+
if (type === 'metric' || type === 'stat' || type === 'statitem' || type === 'stat-item') {
|
|
15054
|
+
this.addKpiPlaceholderValue(node, placeholders);
|
|
15055
|
+
}
|
|
15056
|
+
if (type === 'statgroup' || type === 'stat-group') {
|
|
15057
|
+
for (const item of this.arrayFromUnknown(node['items'])) {
|
|
15058
|
+
this.addKpiPlaceholderValue(this.toJsonObject(item), placeholders);
|
|
15059
|
+
}
|
|
15060
|
+
}
|
|
15061
|
+
for (const key of ['nodes', 'items', 'children', 'content']) {
|
|
15062
|
+
for (const child of this.arrayFromUnknown(node[key])) {
|
|
15063
|
+
this.collectKpiPlaceholderValues(this.toJsonObject(child), placeholders);
|
|
15064
|
+
}
|
|
15065
|
+
}
|
|
15066
|
+
}
|
|
15067
|
+
addKpiPlaceholderValue(node, placeholders) {
|
|
15068
|
+
if (!node) {
|
|
15069
|
+
return;
|
|
15070
|
+
}
|
|
15071
|
+
const value = this.readString(node, 'value')?.trim() ?? '';
|
|
15072
|
+
if (!value || value === '-' || value === '--' || value.toLowerCase() === 'n/a') {
|
|
15073
|
+
const label = this.readString(node, 'label') ?? this.readString(node, 'id') ?? 'metric';
|
|
15074
|
+
placeholders.push(label);
|
|
15075
|
+
}
|
|
15076
|
+
}
|
|
15077
|
+
isDashboardFilterWidget(componentId, widget, inputs, shell) {
|
|
15078
|
+
const normalizedComponentId = componentId.toLowerCase();
|
|
15079
|
+
if (normalizedComponentId === 'praxis-filter'
|
|
15080
|
+
|| normalizedComponentId === 'praxis-filter-form'
|
|
15081
|
+
|| normalizedComponentId.includes('filter')) {
|
|
15082
|
+
return true;
|
|
15083
|
+
}
|
|
15084
|
+
const role = this.readString(widget, 'role')?.toLowerCase() ?? '';
|
|
15085
|
+
if (['filter', 'filters', 'search', 'advanced-filter'].includes(role)) {
|
|
15086
|
+
return true;
|
|
15087
|
+
}
|
|
15088
|
+
const searchable = [
|
|
15089
|
+
this.readString(inputs, 'title'),
|
|
15090
|
+
this.readString(inputs, 'subtitle'),
|
|
15091
|
+
this.readString(inputs, 'label'),
|
|
15092
|
+
this.readString(shell ?? {}, 'title'),
|
|
15093
|
+
this.readString(shell ?? {}, 'subtitle'),
|
|
15094
|
+
]
|
|
15095
|
+
.filter((value) => !!value)
|
|
15096
|
+
.join(' ')
|
|
15097
|
+
.toLowerCase();
|
|
15098
|
+
if (/\b(filter|filters|filtro|filtros|search|busca|buscar)\b/.test(searchable)) {
|
|
15099
|
+
return true;
|
|
15100
|
+
}
|
|
15101
|
+
return inputs['alwaysVisibleFields'] !== undefined
|
|
15102
|
+
|| inputs['selectedFieldIds'] !== undefined
|
|
15103
|
+
|| inputs['filterConfig'] !== undefined
|
|
15104
|
+
|| inputs['advancedOpenMode'] !== undefined;
|
|
15105
|
+
}
|
|
15106
|
+
buildDashboardValidationSummary(widgets, connections, surfaces) {
|
|
15107
|
+
const componentIds = widgets.map((widget) => `${widget['componentId']}`);
|
|
15108
|
+
const filterWidgets = widgets.filter((widget) => widget['filterLike'] === true);
|
|
15109
|
+
const chartWidgets = widgets.filter((widget) => `${widget['componentId']}`.includes('chart'));
|
|
15110
|
+
const warnings = [];
|
|
15111
|
+
if (!componentIds.some((id) => id.includes('chart'))) {
|
|
15112
|
+
warnings.push('dashboard-without-chart-widget');
|
|
15113
|
+
}
|
|
15114
|
+
if (!widgets.some((widget) => widget['kpiLike'] === true)) {
|
|
15115
|
+
warnings.push('dashboard-without-kpi-widget');
|
|
15116
|
+
}
|
|
15117
|
+
if (widgets.some((widget) => widget['kpiLike'] === true
|
|
15118
|
+
&& Array.isArray(widget['kpiPlaceholderValues'])
|
|
15119
|
+
&& widget['kpiPlaceholderValues'].length > 0)) {
|
|
15120
|
+
warnings.push('dashboard-kpi-placeholder-values');
|
|
15121
|
+
}
|
|
15122
|
+
if (!widgets.some((widget) => widget['filterLike'] === true)) {
|
|
15123
|
+
warnings.push('dashboard-without-filter-widget');
|
|
15124
|
+
}
|
|
15125
|
+
if (!componentIds.some((id) => id.includes('table') || id.includes('list'))) {
|
|
15126
|
+
warnings.push('dashboard-without-record-detail-widget');
|
|
15127
|
+
}
|
|
15128
|
+
if (!connections.length) {
|
|
15129
|
+
warnings.push('dashboard-without-composition-links');
|
|
15130
|
+
}
|
|
15131
|
+
if (filterWidgets.length
|
|
15132
|
+
&& !filterWidgets.some((widget) => this.hasQueryContextConnectionFrom(`${widget['key']}`, connections))) {
|
|
15133
|
+
warnings.push('dashboard-filter-not-connected');
|
|
15134
|
+
}
|
|
15135
|
+
if (chartWidgets.length
|
|
15136
|
+
&& !chartWidgets.some((widget) => this.hasQueryContextConnectionFrom(`${widget['key']}`, connections)
|
|
15137
|
+
|| surfaces.some((surface) => surface['widgetKey'] === widget['key']))) {
|
|
15138
|
+
warnings.push('dashboard-chart-not-interactive');
|
|
15139
|
+
}
|
|
15140
|
+
if (!surfaces.length) {
|
|
15141
|
+
warnings.push('dashboard-without-surface-actions');
|
|
15142
|
+
}
|
|
15143
|
+
return {
|
|
15144
|
+
status: warnings.length ? 'degraded' : 'ready',
|
|
15145
|
+
warnings,
|
|
15146
|
+
counts: {
|
|
15147
|
+
widgets: widgets.length,
|
|
15148
|
+
kpis: widgets.filter((widget) => widget['kpiLike'] === true).length,
|
|
15149
|
+
kpiPlaceholders: widgets
|
|
15150
|
+
.filter((widget) => Array.isArray(widget['kpiPlaceholderValues']))
|
|
15151
|
+
.reduce((count, widget) => count + widget['kpiPlaceholderValues'].length, 0),
|
|
15152
|
+
filters: widgets.filter((widget) => widget['filterLike'] === true).length,
|
|
15153
|
+
connections: connections.length,
|
|
15154
|
+
connectedFilters: filterWidgets
|
|
15155
|
+
.filter((widget) => this.hasQueryContextConnectionFrom(`${widget['key']}`, connections)).length,
|
|
15156
|
+
interactiveCharts: chartWidgets
|
|
15157
|
+
.filter((widget) => this.hasQueryContextConnectionFrom(`${widget['key']}`, connections)
|
|
15158
|
+
|| surfaces.some((surface) => surface['widgetKey'] === widget['key'])).length,
|
|
15159
|
+
surfaces: surfaces.length,
|
|
15160
|
+
},
|
|
15161
|
+
};
|
|
15162
|
+
}
|
|
15163
|
+
hasQueryContextConnectionFrom(widgetKey, connections) {
|
|
15164
|
+
return connections.some((connection) => {
|
|
15165
|
+
const from = this.toJsonObject(connection['from']);
|
|
15166
|
+
const to = this.toJsonObject(connection['to']);
|
|
15167
|
+
return this.readString(from ?? {}, 'widget') === widgetKey
|
|
15168
|
+
&& this.readString(to ?? {}, 'port') === 'queryContext';
|
|
15169
|
+
});
|
|
15170
|
+
}
|
|
15171
|
+
summarizeEndpoint(value) {
|
|
15172
|
+
const endpoint = this.toJsonObject(value);
|
|
15173
|
+
if (!endpoint) {
|
|
15174
|
+
return null;
|
|
15175
|
+
}
|
|
15176
|
+
const ref = this.toJsonObject(endpoint['ref']);
|
|
15177
|
+
return {
|
|
15178
|
+
kind: this.readString(endpoint, 'kind'),
|
|
15179
|
+
widget: this.readString(endpoint, 'widget') ?? this.readString(ref ?? {}, 'widget'),
|
|
15180
|
+
port: this.readString(endpoint, 'port') ?? this.readString(ref ?? {}, 'port'),
|
|
15181
|
+
direction: this.readString(endpoint, 'direction') ?? this.readString(ref ?? {}, 'direction'),
|
|
15182
|
+
path: this.readString(endpoint, 'path') ?? this.readString(ref ?? {}, 'path'),
|
|
15183
|
+
actionId: this.readString(endpoint, 'actionId') ?? this.readString(ref ?? {}, 'actionId'),
|
|
15184
|
+
};
|
|
15185
|
+
}
|
|
15186
|
+
summarizeTransform(value) {
|
|
15187
|
+
const transform = this.toJsonObject(value);
|
|
15188
|
+
if (!transform) {
|
|
15189
|
+
return null;
|
|
15190
|
+
}
|
|
15191
|
+
const steps = Array.isArray(transform['steps']) ? transform['steps'] : [];
|
|
15192
|
+
return {
|
|
15193
|
+
kind: this.readString(transform, 'kind'),
|
|
15194
|
+
mode: this.readString(transform, 'mode'),
|
|
15195
|
+
stepCount: steps.length,
|
|
15196
|
+
steps: steps
|
|
15197
|
+
.filter((step) => !!this.toJsonObject(step))
|
|
15198
|
+
.map((step) => ({
|
|
15199
|
+
id: this.readString(step, 'id'),
|
|
15200
|
+
kind: this.readString(step, 'kind'),
|
|
15201
|
+
})),
|
|
15202
|
+
};
|
|
15203
|
+
}
|
|
15204
|
+
arrayFromUnknown(value) {
|
|
15205
|
+
return Array.isArray(value) ? value : [];
|
|
15206
|
+
}
|
|
15207
|
+
decorateDashboardReviewStatus(status, intentResolution, preview) {
|
|
15208
|
+
if (!preview.uiCompositionPlan) {
|
|
15209
|
+
return status;
|
|
15210
|
+
}
|
|
15211
|
+
const diagnostics = this.buildDashboardAuthoringDiagnostics(intentResolution, preview);
|
|
15212
|
+
const validation = this.toJsonObject(diagnostics?.['validation']);
|
|
15213
|
+
const warnings = Array.isArray(validation?.['warnings'])
|
|
15214
|
+
? validation?.['warnings'].map((warning) => `${warning}`)
|
|
15215
|
+
: [];
|
|
15216
|
+
if (!warnings.length) {
|
|
15217
|
+
return status;
|
|
15218
|
+
}
|
|
15219
|
+
const labels = warnings
|
|
15220
|
+
.map((warning) => this.describeDashboardQualityWarning(warning))
|
|
15221
|
+
.filter((label) => !!label);
|
|
15222
|
+
if (!labels.length) {
|
|
15223
|
+
return status;
|
|
15224
|
+
}
|
|
15225
|
+
const suffix = this.context
|
|
15226
|
+
.tx('agentic.dashboardQuality.degraded', 'Dashboard created with quality warnings: {warnings}. Review diagnostics before saving.')
|
|
15227
|
+
.replace('{warnings}', labels.join(', '));
|
|
15228
|
+
const normalized = status.trim();
|
|
15229
|
+
return normalized ? `${normalized} ${suffix}` : suffix;
|
|
15230
|
+
}
|
|
15231
|
+
describeDashboardQualityWarning(code) {
|
|
15232
|
+
switch (code) {
|
|
15233
|
+
case 'dashboard-without-chart-widget':
|
|
15234
|
+
return this.context.tx('agentic.dashboardQuality.warning.chart', 'chart widget missing');
|
|
15235
|
+
case 'dashboard-without-kpi-widget':
|
|
15236
|
+
return this.context.tx('agentic.dashboardQuality.warning.kpi', 'KPI block missing');
|
|
15237
|
+
case 'dashboard-kpi-placeholder-values':
|
|
15238
|
+
return this.context.tx('agentic.dashboardQuality.warning.kpiValues', 'KPI values are placeholders');
|
|
15239
|
+
case 'dashboard-without-filter-widget':
|
|
15240
|
+
return this.context.tx('agentic.dashboardQuality.warning.filter', 'filter widget missing');
|
|
15241
|
+
case 'dashboard-without-record-detail-widget':
|
|
15242
|
+
return this.context.tx('agentic.dashboardQuality.warning.details', 'record detail widget missing');
|
|
15243
|
+
case 'dashboard-without-composition-links':
|
|
15244
|
+
return this.context.tx('agentic.dashboardQuality.warning.links', 'widget interactions missing');
|
|
15245
|
+
case 'dashboard-filter-not-connected':
|
|
15246
|
+
return this.context.tx('agentic.dashboardQuality.warning.filterConnection', 'filter is not connected');
|
|
15247
|
+
case 'dashboard-chart-not-interactive':
|
|
15248
|
+
return this.context.tx('agentic.dashboardQuality.warning.chartInteraction', 'chart interaction missing');
|
|
15249
|
+
case 'dashboard-without-surface-actions':
|
|
15250
|
+
return this.context.tx('agentic.dashboardQuality.warning.surfaces', 'surface actions missing');
|
|
15251
|
+
default:
|
|
15252
|
+
return code;
|
|
15253
|
+
}
|
|
15254
|
+
}
|
|
15255
|
+
mergeDashboardQualityQuickReplies(quickReplies, intentResolution, preview) {
|
|
15256
|
+
const repairReplies = this.dashboardQualityQuickReplies(intentResolution, preview);
|
|
15257
|
+
if (!repairReplies.length) {
|
|
15258
|
+
return [...quickReplies];
|
|
15259
|
+
}
|
|
15260
|
+
const seen = new Set(quickReplies.map((reply) => reply.id));
|
|
15261
|
+
return [
|
|
15262
|
+
...quickReplies,
|
|
15263
|
+
...repairReplies.filter((reply) => !seen.has(reply.id)),
|
|
15264
|
+
];
|
|
15265
|
+
}
|
|
15266
|
+
dashboardQualityQuickReplies(intentResolution, preview) {
|
|
15267
|
+
if (!preview.uiCompositionPlan) {
|
|
15268
|
+
return [];
|
|
15269
|
+
}
|
|
15270
|
+
const diagnostics = this.buildDashboardAuthoringDiagnostics(intentResolution, preview);
|
|
15271
|
+
const validation = this.toJsonObject(diagnostics?.['validation']);
|
|
15272
|
+
const warnings = Array.isArray(validation?.['warnings'])
|
|
15273
|
+
? validation?.['warnings'].map((warning) => `${warning}`)
|
|
15274
|
+
: [];
|
|
15275
|
+
if (!warnings.length) {
|
|
15276
|
+
return [];
|
|
15277
|
+
}
|
|
15278
|
+
const resourcePath = this.readString(this.toJsonObject(diagnostics?.['dto']) ?? {}, 'resourcePath');
|
|
15279
|
+
const dashboardQuality = this.toDashboardQualityRepairContext(diagnostics);
|
|
15280
|
+
const dashboardSnapshot = this.dashboardRepairSnapshot(preview);
|
|
15281
|
+
const contextHints = {
|
|
15282
|
+
source: 'dashboard-quality-gate',
|
|
15283
|
+
kind: 'dashboard-repair-action',
|
|
15284
|
+
artifactKind: 'dashboard',
|
|
15285
|
+
resourcePath,
|
|
15286
|
+
warnings,
|
|
15287
|
+
...(dashboardQuality ? { dashboardQuality } : {}),
|
|
15288
|
+
...dashboardSnapshot,
|
|
15289
|
+
};
|
|
15290
|
+
const replies = [];
|
|
15291
|
+
if (warnings.includes('dashboard-without-kpi-widget')) {
|
|
15292
|
+
replies.push(this.dashboardQualityQuickReply({
|
|
15293
|
+
id: 'dashboard-quality-add-kpis',
|
|
15294
|
+
label: this.context.tx('agentic.dashboardQuality.quickReply.addKpis', 'Add KPIs'),
|
|
15295
|
+
prompt: this.context.tx('agentic.dashboardQuality.prompt.addKpis', 'Add a compact KPI block with the most useful metrics for this dashboard, preferably using praxis-rich-content statGroup or metric nodes.'),
|
|
15296
|
+
icon: 'monitoring',
|
|
15297
|
+
changeKind: 'add_dashboard_kpis',
|
|
15298
|
+
contextHints,
|
|
15299
|
+
}));
|
|
15300
|
+
}
|
|
15301
|
+
if (warnings.includes('dashboard-kpi-placeholder-values')) {
|
|
15302
|
+
replies.push(this.dashboardQualityQuickReply({
|
|
15303
|
+
id: 'dashboard-quality-bind-kpis',
|
|
15304
|
+
label: this.context.tx('agentic.dashboardQuality.quickReply.bindKpis', 'Bind KPI values'),
|
|
15305
|
+
prompt: this.context.tx('agentic.dashboardQuality.prompt.bindKpis', 'Replace placeholder KPI values with real metric bindings from the dashboard DTO/source, keeping labels and formatting human-readable.'),
|
|
15306
|
+
icon: 'data_object',
|
|
15307
|
+
changeKind: 'bind_dashboard_kpis',
|
|
15308
|
+
contextHints,
|
|
15309
|
+
}));
|
|
15310
|
+
}
|
|
15311
|
+
if (warnings.includes('dashboard-without-filter-widget')) {
|
|
15312
|
+
replies.push(this.dashboardQualityQuickReply({
|
|
15313
|
+
id: 'dashboard-quality-add-filters',
|
|
15314
|
+
label: this.context.tx('agentic.dashboardQuality.quickReply.addFilters', 'Add filters'),
|
|
15315
|
+
prompt: this.context.tx('agentic.dashboardQuality.prompt.addFilters', 'Add canonical dashboard filters from the DTO fields and connect their requestSearch output to charts and detail widgets through queryContext.'),
|
|
15316
|
+
icon: 'filter_alt',
|
|
15317
|
+
changeKind: 'add_dashboard_filters',
|
|
15318
|
+
contextHints,
|
|
15319
|
+
}));
|
|
15320
|
+
}
|
|
15321
|
+
if (warnings.includes('dashboard-without-chart-widget')) {
|
|
15322
|
+
replies.push(this.dashboardQualityQuickReply({
|
|
15323
|
+
id: 'dashboard-quality-add-chart',
|
|
15324
|
+
label: this.context.tx('agentic.dashboardQuality.quickReply.addChart', 'Add chart'),
|
|
15325
|
+
prompt: this.context.tx('agentic.dashboardQuality.prompt.addChart', 'Add the most relevant analytical chart to this dashboard, using the same data source and connecting it to the detail widgets.'),
|
|
15326
|
+
icon: 'insert_chart',
|
|
15327
|
+
changeKind: 'add_dashboard_chart',
|
|
15328
|
+
contextHints,
|
|
15329
|
+
}));
|
|
15330
|
+
}
|
|
15331
|
+
if (warnings.includes('dashboard-without-record-detail-widget')) {
|
|
15332
|
+
replies.push(this.dashboardQualityQuickReply({
|
|
15333
|
+
id: 'dashboard-quality-add-details',
|
|
15334
|
+
label: this.context.tx('agentic.dashboardQuality.quickReply.addDetails', 'Add details'),
|
|
15335
|
+
prompt: this.context.tx('agentic.dashboardQuality.prompt.addDetails', 'Add a rich record detail widget, preferably a Praxis list or table, using the same dashboard data source.'),
|
|
15336
|
+
icon: 'view_list',
|
|
15337
|
+
changeKind: 'add_dashboard_detail_widget',
|
|
15338
|
+
contextHints,
|
|
15339
|
+
}));
|
|
15340
|
+
}
|
|
15341
|
+
if (warnings.includes('dashboard-without-composition-links')
|
|
15342
|
+
|| warnings.includes('dashboard-filter-not-connected')
|
|
15343
|
+
|| warnings.includes('dashboard-chart-not-interactive')) {
|
|
15344
|
+
replies.push(this.dashboardQualityQuickReply({
|
|
15345
|
+
id: 'dashboard-quality-connect-widgets',
|
|
15346
|
+
label: this.context.tx('agentic.dashboardQuality.quickReply.connectWidgets', 'Connect widgets'),
|
|
15347
|
+
prompt: this.context.tx('agentic.dashboardQuality.prompt.connectWidgets', 'Connect the dashboard widgets so filter and chart selections update the detail widgets through queryContext.'),
|
|
15348
|
+
icon: 'hub',
|
|
15349
|
+
changeKind: 'connect_dashboard_widgets',
|
|
15350
|
+
contextHints,
|
|
15351
|
+
}));
|
|
15352
|
+
}
|
|
15353
|
+
if (warnings.includes('dashboard-without-surface-actions')) {
|
|
15354
|
+
replies.push(this.dashboardQualityQuickReply({
|
|
15355
|
+
id: 'dashboard-quality-add-surfaces',
|
|
15356
|
+
label: this.context.tx('agentic.dashboardQuality.quickReply.addSurfaces', 'Add surfaces'),
|
|
15357
|
+
prompt: this.context.tx('agentic.dashboardQuality.prompt.addSurfaces', 'Add surface.open actions so dashboard selections can open richer drill-down details in a modal or drawer.'),
|
|
15358
|
+
icon: 'open_in_new',
|
|
15359
|
+
changeKind: 'add_dashboard_surfaces',
|
|
15360
|
+
contextHints,
|
|
15361
|
+
}));
|
|
15362
|
+
}
|
|
15363
|
+
return replies.slice(0, 4);
|
|
15364
|
+
}
|
|
15365
|
+
dashboardRepairSnapshot(preview) {
|
|
15366
|
+
const patch = this.toJsonObject(preview.compiledFormPatch)?.['patch'];
|
|
15367
|
+
const materializedPage = this.toJsonObject(this.toJsonObject(patch)?.['page']);
|
|
15368
|
+
if (this.hasDashboardSnapshotWidgets(materializedPage)) {
|
|
15369
|
+
return { materializedPage };
|
|
15370
|
+
}
|
|
15371
|
+
const uiCompositionPlan = this.toJsonObject(preview.uiCompositionPlan);
|
|
15372
|
+
return uiCompositionPlan ? { uiCompositionPlan } : {};
|
|
15373
|
+
}
|
|
15374
|
+
hasDashboardSnapshotWidgets(page) {
|
|
15375
|
+
return Array.isArray(page?.['widgets']) && page?.['widgets'].length > 0;
|
|
15376
|
+
}
|
|
15377
|
+
toDashboardQualityRepairContext(diagnostics) {
|
|
15378
|
+
if (!diagnostics) {
|
|
15379
|
+
return null;
|
|
15380
|
+
}
|
|
15381
|
+
const inventory = this.toJsonObject(diagnostics['inventory']);
|
|
15382
|
+
const validation = this.toJsonObject(diagnostics['validation']);
|
|
15383
|
+
const dto = this.toJsonObject(diagnostics['dto']);
|
|
15384
|
+
const widgets = this.arrayFromUnknown(inventory?.['widgets'])
|
|
15385
|
+
.map((widget) => this.toJsonObject(widget))
|
|
15386
|
+
.filter((widget) => !!widget);
|
|
15387
|
+
const connections = this.arrayFromUnknown(inventory?.['connections'])
|
|
15388
|
+
.map((connection) => this.toJsonObject(connection))
|
|
15389
|
+
.filter((connection) => !!connection);
|
|
15390
|
+
const surfaces = this.arrayFromUnknown(inventory?.['surfaces'])
|
|
15391
|
+
.map((surface) => this.toJsonObject(surface))
|
|
15392
|
+
.filter((surface) => !!surface);
|
|
15393
|
+
const warnings = this.arrayFromUnknown(validation?.['warnings']).map((warning) => `${warning}`);
|
|
15394
|
+
return {
|
|
15395
|
+
schemaVersion: 'praxis-dashboard-quality-repair-context.v1',
|
|
15396
|
+
dto: {
|
|
15397
|
+
resourcePath: this.readString(dto ?? {}, 'resourcePath'),
|
|
15398
|
+
schemaUrl: this.readString(dto ?? {}, 'schemaUrl'),
|
|
15399
|
+
operation: this.readString(dto ?? {}, 'operation'),
|
|
15400
|
+
},
|
|
15401
|
+
blueprint: this.toJsonObject(diagnostics['blueprint']) ?? {},
|
|
15402
|
+
validation: {
|
|
15403
|
+
status: this.readString(validation ?? {}, 'status'),
|
|
15404
|
+
warnings,
|
|
15405
|
+
counts: this.toJsonObject(validation?.['counts']) ?? {},
|
|
15406
|
+
},
|
|
15407
|
+
widgets: widgets.slice(0, 12).map((widget) => {
|
|
15408
|
+
const key = this.readString(widget, 'key') ?? 'unknown';
|
|
15409
|
+
return {
|
|
15410
|
+
key,
|
|
15411
|
+
componentId: this.readString(widget, 'componentId') ?? 'unknown',
|
|
15412
|
+
role: this.readString(widget, 'role'),
|
|
15413
|
+
title: this.readString(widget, 'title'),
|
|
15414
|
+
kpiLike: widget['kpiLike'] === true,
|
|
15415
|
+
filterLike: widget['filterLike'] === true,
|
|
15416
|
+
queryContextBound: widget['queryContextBound'] === true,
|
|
15417
|
+
outgoingQueryContext: this.hasQueryContextConnectionFrom(key, connections),
|
|
15418
|
+
surfaceCount: surfaces.filter((surface) => this.readString(surface, 'widgetKey') === key).length,
|
|
15419
|
+
};
|
|
15420
|
+
}),
|
|
15421
|
+
connections: connections.slice(0, 16).map((connection) => ({
|
|
15422
|
+
id: this.readString(connection, 'id') ?? 'unknown',
|
|
15423
|
+
intent: this.readString(connection, 'intent'),
|
|
15424
|
+
from: this.toJsonObject(connection['from']),
|
|
15425
|
+
to: this.toJsonObject(connection['to']),
|
|
15426
|
+
})),
|
|
15427
|
+
surfaces: surfaces.slice(0, 16).map((surface) => ({
|
|
15428
|
+
widgetKey: this.readString(surface, 'widgetKey'),
|
|
15429
|
+
id: this.readString(surface, 'id'),
|
|
15430
|
+
action: this.readString(surface, 'action'),
|
|
15431
|
+
title: this.readString(surface, 'title'),
|
|
15432
|
+
mode: this.readString(surface, 'mode'),
|
|
15433
|
+
presentation: this.readString(surface, 'presentation'),
|
|
15434
|
+
resourcePath: this.readString(surface, 'resourcePath'),
|
|
15435
|
+
componentId: this.readString(surface, 'componentId'),
|
|
15436
|
+
})),
|
|
15437
|
+
};
|
|
15438
|
+
}
|
|
15439
|
+
dashboardQualityQuickReply(params) {
|
|
15440
|
+
return {
|
|
15441
|
+
id: params.id,
|
|
15442
|
+
kind: 'suggestion',
|
|
15443
|
+
label: params.label,
|
|
15444
|
+
prompt: params.prompt,
|
|
15445
|
+
icon: params.icon,
|
|
15446
|
+
contextHints: {
|
|
15447
|
+
...params.contextHints,
|
|
15448
|
+
changeKind: params.changeKind,
|
|
15449
|
+
presentation: {
|
|
15450
|
+
kind: 'guided-option',
|
|
15451
|
+
categoryLabel: this.context.tx('agentic.dashboardQuality.quickReply.category', 'Dashboard quality'),
|
|
15452
|
+
description: params.prompt,
|
|
15453
|
+
ctaLabel: params.label,
|
|
15454
|
+
icon: params.icon,
|
|
15455
|
+
tone: 'warning',
|
|
15456
|
+
},
|
|
15457
|
+
},
|
|
15458
|
+
};
|
|
15459
|
+
}
|
|
14621
15460
|
phaseForStreamPayload(payload) {
|
|
14622
15461
|
const phase = this.readString(payload, 'phase');
|
|
14623
15462
|
if (phase === 'intent.resolve')
|
|
@@ -14634,28 +15473,67 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
14634
15473
|
}
|
|
14635
15474
|
statusForStreamPayload(payload) {
|
|
14636
15475
|
const phase = this.readString(payload, 'phase');
|
|
15476
|
+
let status = null;
|
|
14637
15477
|
switch (phase) {
|
|
14638
15478
|
case 'context.bundle':
|
|
14639
|
-
|
|
15479
|
+
status = this.context.tx('agentic.status.contextBundle', 'Preparing context...');
|
|
15480
|
+
break;
|
|
14640
15481
|
case 'intent.resolve':
|
|
14641
15482
|
if (this.isSecondPassStreamPayload(payload)) {
|
|
14642
|
-
|
|
15483
|
+
status = this.context.tx('agentic.status.refinedCandidates', 'Reviewing the retrieved resources with the AI...');
|
|
15484
|
+
break;
|
|
14643
15485
|
}
|
|
14644
|
-
|
|
15486
|
+
status = this.context.tx('agentic.status.resolvingIntent', 'Resolving intent...');
|
|
15487
|
+
break;
|
|
14645
15488
|
case 'resource.discovery':
|
|
14646
15489
|
if (this.isBackendResourceDiscoveryPayload(payload)) {
|
|
14647
|
-
|
|
15490
|
+
status = this.context.tx('agentic.status.resourceDiscoveryBackend', 'Found API resources in the backend catalog.');
|
|
15491
|
+
break;
|
|
14648
15492
|
}
|
|
14649
|
-
|
|
15493
|
+
status = this.context.tx('agentic.status.resourceDiscovery', 'Finding API resources...');
|
|
15494
|
+
break;
|
|
14650
15495
|
case 'projectKnowledge.retrieve':
|
|
14651
|
-
|
|
15496
|
+
status = this.projectKnowledgeStatusForStreamPayload(payload);
|
|
15497
|
+
break;
|
|
14652
15498
|
case 'preview.plan':
|
|
14653
|
-
|
|
15499
|
+
status = this.context.tx('agentic.status.previewing', 'Generating preview...');
|
|
15500
|
+
break;
|
|
14654
15501
|
case 'preview.compile':
|
|
14655
|
-
|
|
15502
|
+
status = this.context.tx('agentic.status.previewCompile', 'Compiling preview...');
|
|
15503
|
+
break;
|
|
15504
|
+
case 'stream.waiting':
|
|
15505
|
+
status = this.context.tx('agentic.status.streamWaiting', 'The backend is still working on this dashboard. Keeping the request alive...');
|
|
15506
|
+
break;
|
|
15507
|
+
case 'stream.start.accepted':
|
|
15508
|
+
status = this.context.tx('agentic.status.streamStartAccepted', 'Backend accepted the authoring stream. Opening live progress...');
|
|
15509
|
+
break;
|
|
15510
|
+
case 'stream.probe.ready':
|
|
15511
|
+
status = this.context.tx('agentic.status.streamProbeReady', 'Authoring stream endpoint is reachable.');
|
|
15512
|
+
break;
|
|
15513
|
+
case 'stream.transport.opening':
|
|
15514
|
+
status = this.context.tx('agentic.status.streamTransportOpening', 'Opening the live authoring stream...');
|
|
15515
|
+
break;
|
|
15516
|
+
case 'stream.first-event.received':
|
|
15517
|
+
status = this.context.tx('agentic.status.streamFirstEventReceived', 'Live authoring progress started.');
|
|
15518
|
+
break;
|
|
14656
15519
|
default:
|
|
14657
|
-
|
|
15520
|
+
status = this.readString(payload, 'summary') || this.readString(payload, 'message');
|
|
15521
|
+
}
|
|
15522
|
+
return this.decorateBackendProcessingProgressStatus(payload, status ?? '');
|
|
15523
|
+
}
|
|
15524
|
+
decorateBackendProcessingProgressStatus(payload, status) {
|
|
15525
|
+
const diagnostics = this.toJsonObject(payload['diagnostics']);
|
|
15526
|
+
if (diagnostics?.['source'] !== 'backend-processing-progress-watchdog') {
|
|
15527
|
+
return status;
|
|
15528
|
+
}
|
|
15529
|
+
const elapsedSeconds = this.readNumber(diagnostics, 'elapsedSeconds');
|
|
15530
|
+
if (!elapsedSeconds || elapsedSeconds <= 0) {
|
|
15531
|
+
return status;
|
|
14658
15532
|
}
|
|
15533
|
+
return this.context
|
|
15534
|
+
.tx('agentic.status.backendProgressElapsed', '{status} Backend still working for {seconds}s.')
|
|
15535
|
+
.replace('{status}', status)
|
|
15536
|
+
.replace('{seconds}', `${Math.round(elapsedSeconds)}`);
|
|
14659
15537
|
}
|
|
14660
15538
|
projectKnowledgeStatusForStreamPayload(payload) {
|
|
14661
15539
|
const diagnostics = this.toJsonObject(payload['diagnostics']);
|
|
@@ -15180,6 +16058,8 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
15180
16058
|
const hintSource = this.readString(hints, 'source');
|
|
15181
16059
|
const hintKind = this.readString(hints, 'kind');
|
|
15182
16060
|
return hintSource === 'component-capability-catalog'
|
|
16061
|
+
|| hintSource === 'dashboard-quality-gate'
|
|
16062
|
+
|| hintKind === 'dashboard-repair-action'
|
|
15183
16063
|
|| hintKind === 'contextual-preview-action';
|
|
15184
16064
|
}
|
|
15185
16065
|
contextualPreviewActionContextFromRequest(request) {
|
|
@@ -15714,6 +16594,8 @@ class PageBuilderAgenticAuthoringTurnFlow {
|
|
|
15714
16594
|
? contextHints['kind'].trim()
|
|
15715
16595
|
: '';
|
|
15716
16596
|
return hintSource === 'component-capability-catalog'
|
|
16597
|
+
|| hintSource === 'dashboard-quality-gate'
|
|
16598
|
+
|| hintKind === 'dashboard-repair-action'
|
|
15717
16599
|
|| hintKind === 'contextual-preview-action'
|
|
15718
16600
|
|| (reply.id ?? '').startsWith('chart-')
|
|
15719
16601
|
|| (reply.id ?? '').startsWith('table-export-');
|
|
@@ -18178,6 +19060,7 @@ class DynamicPageBuilderComponent {
|
|
|
18178
19060
|
contextHints: this.cloneAgenticContextHints(this.agenticAuthoringContextHints),
|
|
18179
19061
|
});
|
|
18180
19062
|
}
|
|
19063
|
+
this.agenticTurnController.setContextHints(this.cloneAgenticContextHints(this.agenticAuthoringContextHints));
|
|
18181
19064
|
this.agenticTurnController.setContextItems(this.agenticAuthoringContextItems());
|
|
18182
19065
|
this.agenticTurnController.setMessages(this.agenticAuthoringConversation());
|
|
18183
19066
|
this.agenticTurnController.setAttachments(this.agenticAuthoringAttachments());
|
|
@@ -18430,7 +19313,15 @@ class DynamicPageBuilderComponent {
|
|
|
18430
19313
|
return null;
|
|
18431
19314
|
}
|
|
18432
19315
|
const intentResolution = this.toRecord(diagnostics?.['intentResolution']);
|
|
18433
|
-
|
|
19316
|
+
const llmDiagnostics = this.toRecord(intentResolution?.['llmDiagnostics']);
|
|
19317
|
+
const dashboardAuthoringDiagnostics = this.toRecord(diagnostics?.['dashboardAuthoringDiagnostics']);
|
|
19318
|
+
if (llmDiagnostics && dashboardAuthoringDiagnostics) {
|
|
19319
|
+
return {
|
|
19320
|
+
llmDiagnostics,
|
|
19321
|
+
dashboardAuthoringDiagnostics,
|
|
19322
|
+
};
|
|
19323
|
+
}
|
|
19324
|
+
return llmDiagnostics ?? dashboardAuthoringDiagnostics;
|
|
18434
19325
|
}
|
|
18435
19326
|
resolveAgenticSemanticDecision(diagnostics) {
|
|
18436
19327
|
const intentResolution = this.toRecord(diagnostics?.['intentResolution']);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisui/page-builder",
|
|
3
|
-
"version": "8.0.0-beta.
|
|
3
|
+
"version": "8.0.0-beta.88",
|
|
4
4
|
"description": "Page and widget builder utilities for Praxis UI (grid, dynamic widgets, editors).",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/common": "^21.0.0",
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"@angular/forms": "^21.0.0",
|
|
9
9
|
"@angular/cdk": "^21.0.0",
|
|
10
10
|
"@angular/material": "^21.0.0",
|
|
11
|
-
"@praxisui/ai": "^8.0.0-beta.
|
|
12
|
-
"@praxisui/core": "^8.0.0-beta.
|
|
13
|
-
"@praxisui/settings-panel": "^8.0.0-beta.
|
|
11
|
+
"@praxisui/ai": "^8.0.0-beta.88",
|
|
12
|
+
"@praxisui/core": "^8.0.0-beta.88",
|
|
13
|
+
"@praxisui/settings-panel": "^8.0.0-beta.88",
|
|
14
14
|
"rxjs": "~7.8.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
@@ -1165,6 +1165,7 @@ interface PageBuilderAgenticAuthoringOptions {
|
|
|
1165
1165
|
streamStartTimeoutMs?: number;
|
|
1166
1166
|
streamTurnTimeoutMs?: number;
|
|
1167
1167
|
streamResultFallbackMs?: number;
|
|
1168
|
+
streamSilenceStatusMs?: number;
|
|
1168
1169
|
streamConnectionErrorGraceMs?: number;
|
|
1169
1170
|
}
|
|
1170
1171
|
interface PageBuilderAgenticAuthoringEventSource {
|
|
@@ -1397,6 +1398,9 @@ declare class PageBuilderAgenticAuthoringService {
|
|
|
1397
1398
|
private streamStartTimeoutMs;
|
|
1398
1399
|
private streamTurnTimeoutMs;
|
|
1399
1400
|
private streamResultFallbackMs;
|
|
1401
|
+
private streamSilenceStatusMs;
|
|
1402
|
+
private streamSilenceStatusEvent;
|
|
1403
|
+
private streamLifecycleStatusEvent;
|
|
1400
1404
|
private requestTimeoutMs;
|
|
1401
1405
|
private streamConnectionError;
|
|
1402
1406
|
private buildStreamUrl;
|