@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, catchError, of, filter, take } from 'rxjs';
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 this.http.post(`${this.baseUrl}/turn/stream/start`, request, { headers: this.buildHeaders(), withCredentials: true }).pipe(timeout({ first: this.streamStartTimeoutMs() }), switchMap((start) => this.connectTurnStream(start)));
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: { intentResolution, preview },
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: { intentResolution, preview },
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: { intentResolution },
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
- return from(this.buildTurnStreamRequest(request, prompt)).pipe(concatMap((streamRequest) => this.service.streamTurn(streamRequest)), concatMap((event) => from(this.toTurnResultFromStreamEvent(event, request, prompt))), catchError((error) => {
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: 'component-capability-catalog',
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: 'component-capability-catalog',
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
- return {
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(assistantMessage, false),
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: { intentResolution, preview },
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: { intentResolution, preview },
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: { intentResolution, preview },
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: { intentResolution, preview },
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: { intentResolution, preview },
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: { intentResolution, preview },
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
- return this.context.tx('agentic.status.contextBundle', 'Preparing context...');
15479
+ status = this.context.tx('agentic.status.contextBundle', 'Preparing context...');
15480
+ break;
14640
15481
  case 'intent.resolve':
14641
15482
  if (this.isSecondPassStreamPayload(payload)) {
14642
- return this.context.tx('agentic.status.refinedCandidates', 'Reviewing the retrieved resources with the AI...');
15483
+ status = this.context.tx('agentic.status.refinedCandidates', 'Reviewing the retrieved resources with the AI...');
15484
+ break;
14643
15485
  }
14644
- return this.context.tx('agentic.status.resolvingIntent', 'Resolving intent...');
15486
+ status = this.context.tx('agentic.status.resolvingIntent', 'Resolving intent...');
15487
+ break;
14645
15488
  case 'resource.discovery':
14646
15489
  if (this.isBackendResourceDiscoveryPayload(payload)) {
14647
- return this.context.tx('agentic.status.resourceDiscoveryBackend', 'Found API resources in the backend catalog.');
15490
+ status = this.context.tx('agentic.status.resourceDiscoveryBackend', 'Found API resources in the backend catalog.');
15491
+ break;
14648
15492
  }
14649
- return this.context.tx('agentic.status.resourceDiscovery', 'Finding API resources...');
15493
+ status = this.context.tx('agentic.status.resourceDiscovery', 'Finding API resources...');
15494
+ break;
14650
15495
  case 'projectKnowledge.retrieve':
14651
- return this.projectKnowledgeStatusForStreamPayload(payload);
15496
+ status = this.projectKnowledgeStatusForStreamPayload(payload);
15497
+ break;
14652
15498
  case 'preview.plan':
14653
- return this.context.tx('agentic.status.previewing', 'Generating preview...');
15499
+ status = this.context.tx('agentic.status.previewing', 'Generating preview...');
15500
+ break;
14654
15501
  case 'preview.compile':
14655
- return this.context.tx('agentic.status.previewCompile', 'Compiling preview...');
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
- return this.readString(payload, 'summary') || this.readString(payload, 'message');
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
- return this.toRecord(intentResolution?.['llmDiagnostics']);
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.86",
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.86",
12
- "@praxisui/core": "^8.0.0-beta.86",
13
- "@praxisui/settings-panel": "^8.0.0-beta.86",
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;