@praxisui/expansion 8.0.0-beta.20 → 8.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -240,6 +240,13 @@ Ele modela edicoes em `ExpansionMetadata` por operacoes atomicas sobre paineis,
240
240
  cabecalhos, conteudo lazy, estado expandido/desabilitado e comportamento do
241
241
  accordion.
242
242
 
243
+ Quando `enableCustomization` esta ativo, o runtime abre o `PraxisAiAssistantShellComponent`
244
+ com contexto semantico dos paineis e registra a sessao no dock global de assistentes.
245
+ O assistente envia estado atual, perfil dos paineis, campos de schema e referencia ao
246
+ manifesto de authoring para a IA. Patches JSON livres nao sao aplicados no runtime:
247
+ mudancas locais devem nascer de um `componentEditPlan` validado pelo manifesto, e
248
+ regras compartilhadas seguem por `domain-rules/intake`.
249
+
243
250
  Cobertura principal:
244
251
 
245
252
  - `panel.add`, `panel.remove`, `panel.order.set`
@@ -1,7 +1,7 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { EventEmitter, inject, ChangeDetectorRef, DestroyRef, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Component, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
4
+ import { EventEmitter, inject, ChangeDetectorRef, DestroyRef, effect, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Component, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
5
5
  import { ActivatedRoute } from '@angular/router';
6
6
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
7
  import * as i2 from '@angular/material/expansion';
@@ -21,9 +21,9 @@ import { MatTooltipModule } from '@angular/material/tooltip';
21
21
  import { DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
22
22
  import { deepMerge, ASYNC_CONFIG_STORAGE, DynamicFormService, ComponentKeyService, DynamicWidgetLoaderDirective, PraxisIconDirective, ComponentMetadataRegistry } from '@praxisui/core';
23
23
  import { SettingsPanelService, SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
24
- import { BehaviorSubject } from 'rxjs';
24
+ import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
25
25
  import { take } from 'rxjs/operators';
26
- import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
26
+ import { BaseAiAdapter, AiBackendApiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
27
27
 
28
28
  /**
29
29
  * Capabilities catalog for Praxis Expansion (ExpansionMetadata).
@@ -105,6 +105,8 @@ const EXPANSION_AI_CAPABILITIES = {
105
105
 
106
106
  class ExpansionAiAdapter extends BaseAiAdapter {
107
107
  expansion;
108
+ componentId = 'praxis-expansion';
109
+ componentType = 'expansion';
108
110
  componentName = 'Praxis Expansion';
109
111
  constructor(expansion) {
110
112
  super();
@@ -125,6 +127,43 @@ class ExpansionAiAdapter extends BaseAiAdapter {
125
127
  multi: this.expansion.config?.accordion?.multi ?? false,
126
128
  };
127
129
  }
130
+ getDataProfile() {
131
+ const panels = this.getCurrentConfig().panels || [];
132
+ return {
133
+ expansionId: this.expansion.expansionId,
134
+ panelCount: panels.length,
135
+ contentPanelCount: panels.filter((panel) => Array.isArray(panel.content) && panel.content.length > 0).length,
136
+ widgetPanelCount: panels.filter((panel) => Array.isArray(panel.widgets) && panel.widgets.length > 0).length,
137
+ actionPanelCount: panels.filter((panel) => Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0).length,
138
+ multi: this.expansion.config?.accordion?.multi ?? false,
139
+ displayMode: this.expansion.config?.accordion?.displayMode || 'default',
140
+ };
141
+ }
142
+ getSchemaFields() {
143
+ return (this.getCurrentConfig().panels || []).map((panel, index) => ({
144
+ name: panel.id || panel.title || `panel-${index + 1}`,
145
+ label: panel.title || panel.id || `Painel ${index + 1}`,
146
+ disabled: !!panel.disabled,
147
+ expanded: !!panel.expanded,
148
+ hasContent: Array.isArray(panel.content) && panel.content.length > 0,
149
+ hasWidgets: Array.isArray(panel.widgets) && panel.widgets.length > 0,
150
+ hasActions: Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0,
151
+ }));
152
+ }
153
+ getAuthoringContext() {
154
+ return {
155
+ authoringManifestRef: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
156
+ runtimeAuthoringPolicy: {
157
+ mode: 'agentic-authoring',
158
+ enableCustomization: !!this.expansion.enableCustomization,
159
+ canApplyLocalPatch: false,
160
+ reason: 'praxis-expansion exige componentEditPlan validado pelo manifesto antes de aplicar mudancas runtime.',
161
+ },
162
+ domainCatalog: {
163
+ recommendedAuthoringFlow: 'component_authoring',
164
+ },
165
+ };
166
+ }
128
167
  createSnapshot() {
129
168
  return this.getCurrentConfig();
130
169
  }
@@ -185,6 +224,280 @@ class ExpansionAiAdapter extends BaseAiAdapter {
185
224
  }
186
225
  }
187
226
 
227
+ class ExpansionAgenticAuthoringTurnFlow {
228
+ adapter;
229
+ aiApi;
230
+ mode = 'agentic-authoring';
231
+ constructor(adapter, aiApi) {
232
+ this.adapter = adapter;
233
+ this.aiApi = aiApi;
234
+ }
235
+ async submit(request) {
236
+ const prompt = (request.prompt ?? '').trim();
237
+ if (!prompt)
238
+ return { state: 'listening', phase: 'capture', statusText: '' };
239
+ const componentId = this.adapter.componentId || request.componentId || 'praxis-expansion';
240
+ const componentType = this.adapter.componentType || request.componentType || 'expansion';
241
+ const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
242
+ const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
243
+ const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
244
+ const schemaFields = this.adapter.getSchemaFields?.()
245
+ ?.map((field) => this.toAiJsonObject(field))
246
+ .filter((field) => Object.keys(field).length > 0);
247
+ const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
248
+ if (this.shouldRouteToGovernedDecision(prompt, contextHints)) {
249
+ return this.toGovernedDecisionHandoff(prompt, request);
250
+ }
251
+ const response = await firstValueFrom(this.aiApi.getPatch({
252
+ componentId,
253
+ componentType,
254
+ userPrompt: prompt,
255
+ sessionId: request.sessionId,
256
+ clientTurnId: request.clientTurnId,
257
+ messages: this.toChatMessages(request.messages, prompt),
258
+ currentState,
259
+ currentStateDigest: this.buildCurrentStateDigest(dataProfile),
260
+ uiContextRef: { componentId, componentType },
261
+ ...(dataProfile ? { dataProfile } : {}),
262
+ ...(runtimeState ? { runtimeState } : {}),
263
+ ...(schemaFields?.length ? { schemaFields } : {}),
264
+ ...(contextHints ? { contextHints } : {}),
265
+ }));
266
+ return this.toTurnResult(this.compileAdapterResponse(response), request);
267
+ }
268
+ async apply(_request) {
269
+ return {
270
+ state: 'error',
271
+ phase: 'apply',
272
+ assistantMessage: 'O expansion ainda exige componentEditPlan validado pelo manifesto antes de aplicar mudancas locais.',
273
+ errorText: 'Aplicacao local bloqueada ate existir compilacao manifest-backed para praxis-expansion.',
274
+ canApply: false,
275
+ pendingPatch: null,
276
+ };
277
+ }
278
+ cancel() {
279
+ return Promise.resolve({
280
+ state: 'listening',
281
+ phase: 'capture',
282
+ assistantMessage: 'Solicitacao cancelada.',
283
+ statusText: '',
284
+ canApply: false,
285
+ pendingPatch: null,
286
+ pendingClarification: null,
287
+ });
288
+ }
289
+ retry(request) {
290
+ const lastPrompt = [...(request.messages ?? [])].reverse()
291
+ .find((message) => message.role === 'user')?.text;
292
+ return this.submit({ ...request, prompt: lastPrompt ?? request.prompt, action: { kind: 'retry' } });
293
+ }
294
+ toTurnResult(response, request) {
295
+ if (!response) {
296
+ return { state: 'error', phase: 'capture', assistantMessage: 'Resposta vazia da IA.', errorText: 'Resposta vazia da IA.' };
297
+ }
298
+ if (response.type === 'clarification') {
299
+ return {
300
+ state: 'clarification',
301
+ phase: 'clarify',
302
+ sessionId: response.sessionId ?? request.sessionId,
303
+ assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
304
+ clarificationQuestions: this.toClarificationQuestions(response),
305
+ quickReplies: this.toQuickReplies(response),
306
+ canApply: false,
307
+ };
308
+ }
309
+ if (response.type === 'info') {
310
+ const message = response.message || response.explanation || 'Informacao gerada.';
311
+ return { state: 'success', phase: 'summarize', sessionId: response.sessionId ?? request.sessionId, assistantMessage: message, statusText: message, canApply: false };
312
+ }
313
+ if (response.type === 'error') {
314
+ const message = response.message || 'Falha ao gerar alteracao de expansion.';
315
+ return {
316
+ state: 'error',
317
+ phase: 'capture',
318
+ sessionId: response.sessionId ?? request.sessionId,
319
+ assistantMessage: message,
320
+ errorText: message,
321
+ diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
322
+ };
323
+ }
324
+ if (response.patch && Object.keys(response.patch).length > 0) {
325
+ return {
326
+ state: 'error',
327
+ phase: 'review',
328
+ sessionId: response.sessionId ?? request.sessionId,
329
+ assistantMessage: 'O expansion rejeitou patch livre. Gere um componentEditPlan validado pelo PRAXIS_EXPANSION_AUTHORING_MANIFEST antes de propor alteracao local.',
330
+ errorText: 'Patch livre de expansion rejeitado.',
331
+ canApply: false,
332
+ pendingPatch: null,
333
+ diagnostics: {
334
+ warnings: [
335
+ 'free-expansion-patch-rejected',
336
+ 'Use componentEditPlan validado contra PRAXIS_EXPANSION_AUTHORING_MANIFEST.',
337
+ ],
338
+ },
339
+ };
340
+ }
341
+ return {
342
+ state: 'success',
343
+ phase: 'summarize',
344
+ sessionId: response.sessionId ?? request.sessionId,
345
+ assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
346
+ statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
347
+ canApply: false,
348
+ };
349
+ }
350
+ compileAdapterResponse(response) {
351
+ const compiled = this.adapter.compileAiResponse?.(response);
352
+ if (!compiled)
353
+ return response;
354
+ if (compiled.type === 'error') {
355
+ return {
356
+ type: 'error',
357
+ message: compiled.message || 'O componentEditPlan do expansion nao passou na validacao de capacidades.',
358
+ warnings: compiled.warnings,
359
+ };
360
+ }
361
+ const warnings = [...(response.warnings ?? []), ...(compiled.warnings ?? [])];
362
+ return { ...response, ...compiled, patch: compiled.patch, warnings: warnings.length ? warnings : undefined };
363
+ }
364
+ toChatMessages(messages, prompt) {
365
+ const supported = (messages ?? [])
366
+ .filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
367
+ .map((message) => ({ role: message.role, content: message.text }))
368
+ .filter((message) => message.content.trim().length > 0);
369
+ return supported.length ? supported : [{ role: 'user', content: prompt }];
370
+ }
371
+ toClarificationQuestions(response) {
372
+ const labels = response.questions?.length
373
+ ? response.questions
374
+ : response.message ? [response.message] : ['Qual ajuste voce quer aplicar nos paineis?'];
375
+ const options = this.toQuickReplies(response).map((reply) => ({ id: reply.id, label: reply.label, value: reply.prompt }));
376
+ return labels.map((label, index) => ({
377
+ id: `expansion-clarification-${index + 1}`,
378
+ type: options.length ? 'single-choice' : 'text',
379
+ label,
380
+ allowCustom: true,
381
+ options,
382
+ }));
383
+ }
384
+ toQuickReplies(response) {
385
+ const payloads = response.optionPayloads ?? [];
386
+ if (payloads.length) {
387
+ return payloads.map((option, index) => {
388
+ const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
389
+ const prompt = option.example?.trim() || option.value?.trim() || label;
390
+ return { id: `option-${index + 1}`, label, prompt, kind: 'clarification-option' };
391
+ });
392
+ }
393
+ return (response.options ?? [])
394
+ .filter((option) => !!option?.trim())
395
+ .map((option, index) => ({ id: `option-${index + 1}`, label: option.trim(), prompt: option.trim(), kind: 'clarification-option' }));
396
+ }
397
+ buildCurrentStateDigest(dataProfile) {
398
+ const panelCount = typeof dataProfile?.['panelCount'] === 'number' ? dataProfile['panelCount'] : undefined;
399
+ return panelCount !== undefined ? { rowCount: panelCount } : {};
400
+ }
401
+ shouldRouteToGovernedDecision(prompt, contextHints) {
402
+ const normalized = prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
403
+ const recommendedFlow = this.toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
404
+ if (recommendedFlow === 'shared_rule_authoring')
405
+ return true;
406
+ return [
407
+ 'regra',
408
+ 'politica',
409
+ 'policy',
410
+ 'compliance',
411
+ 'lgpd',
412
+ 'privacidade',
413
+ 'aprovacao',
414
+ 'aprovar',
415
+ 'publicar',
416
+ 'materializar',
417
+ 'enforcement',
418
+ 'validacao de negocio',
419
+ 'validar negocio',
420
+ 'elegibilidade',
421
+ 'permissao',
422
+ 'acesso',
423
+ ].some((term) => normalized.includes(term));
424
+ }
425
+ toGovernedDecisionHandoff(prompt, request) {
426
+ const message = 'Esse pedido parece alterar uma decisao de negocio compartilhada. O expansion pode localizar o painel e a experiencia afetada, mas a regra deve seguir pelo fluxo governado de domain-rules antes de qualquer materializacao runtime.';
427
+ return {
428
+ state: 'clarification',
429
+ phase: 'clarify',
430
+ sessionId: request.sessionId,
431
+ assistantMessage: message,
432
+ statusText: 'Handoff governado necessario.',
433
+ canApply: false,
434
+ quickReplies: [
435
+ {
436
+ id: 'shared-rule-handoff',
437
+ label: 'Continuar como regra governada',
438
+ prompt,
439
+ kind: 'shared-rule-handoff',
440
+ description: 'Criar intake de domain-rules em vez de aplicar patch local no expansion.',
441
+ icon: 'rule',
442
+ tone: 'warning',
443
+ contextHints: {
444
+ flowId: 'shared_rule_authoring',
445
+ source: 'praxis-expansion',
446
+ recommendedAction: 'domain-rules/intake',
447
+ },
448
+ },
449
+ ],
450
+ clarificationQuestions: [
451
+ {
452
+ id: 'expansion-governed-rule-confirmation',
453
+ type: 'confirm',
454
+ label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
455
+ description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
456
+ required: true,
457
+ options: [
458
+ {
459
+ id: 'shared-rule-handoff',
460
+ label: 'Sim, continuar governado',
461
+ value: prompt,
462
+ description: 'Nao aplicar como patch local do expansion.',
463
+ contextHints: { flowId: 'shared_rule_authoring', source: 'praxis-expansion' },
464
+ },
465
+ ],
466
+ },
467
+ ],
468
+ diagnostics: {
469
+ governedDecisionHandoff: {
470
+ flowId: 'shared_rule_authoring',
471
+ sourcePrompt: prompt,
472
+ sourceComponent: 'praxis-expansion',
473
+ },
474
+ },
475
+ };
476
+ }
477
+ optionalJsonObject(value) {
478
+ if (value === undefined || value === null)
479
+ return undefined;
480
+ const object = this.toAiJsonObject(value);
481
+ return Object.keys(object).length ? object : undefined;
482
+ }
483
+ toAiJsonObject(value) {
484
+ const record = this.toRecord(value);
485
+ if (!record)
486
+ return {};
487
+ try {
488
+ return JSON.parse(JSON.stringify(record));
489
+ }
490
+ catch {
491
+ return {};
492
+ }
493
+ }
494
+ toRecord(value) {
495
+ return value && typeof value === 'object' && !Array.isArray(value)
496
+ ? value
497
+ : null;
498
+ }
499
+ }
500
+
188
501
  class PraxisExpansion {
189
502
  config;
190
503
  expansionId;
@@ -212,9 +525,35 @@ class PraxisExpansion {
212
525
  catch {
213
526
  return undefined;
214
527
  } })();
528
+ aiApi = inject(AiBackendApiService);
529
+ assistantSessions = inject(PraxisAssistantSessionRegistryService);
530
+ aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
531
+ aiAssistantSessionEffect = effect(() => {
532
+ const session = this.assistantSessions.activeSession();
533
+ if (!session || session.id !== this.resolveAiAssistantSessionId())
534
+ return;
535
+ if (!this.aiAssistantOpen) {
536
+ this.openAiAssistantFromSession(session);
537
+ }
538
+ }, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
215
539
  warnedMissingId = false;
216
540
  panelForms = new Map();
217
541
  aiAdapter = new ExpansionAiAdapter(this);
542
+ aiAssistantOpen = false;
543
+ aiAssistantPrompt = '';
544
+ aiAssistantViewState = null;
545
+ aiAssistantLayout = createPraxisAssistantViewportLayout();
546
+ aiAssistantLabels = {
547
+ title: 'Copiloto semantico Praxis',
548
+ subtitle: 'Converse, revise e governe ajustes dos paineis.',
549
+ prompt: 'Mensagem',
550
+ promptPlaceholder: 'Descreva o ajuste que voce precisa nos paineis.',
551
+ emptyConversation: 'Diga o que voce quer alterar no expansion.',
552
+ submit: 'Interpretar pedido',
553
+ apply: 'Aplicar ajuste',
554
+ };
555
+ aiAssistantController = null;
556
+ aiAssistantStateSubscription = null;
218
557
  accordionRef;
219
558
  panels;
220
559
  injectedDefaults = inject(MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, { optional: true });
@@ -238,6 +577,10 @@ class PraxisExpansion {
238
577
  this.persistConfig(this.config);
239
578
  }
240
579
  }
580
+ ngOnDestroy() {
581
+ this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
582
+ this.aiAssistantStateSubscription?.unsubscribe();
583
+ }
241
584
  styleCss() {
242
585
  const t = this.config?.appearance?.tokens;
243
586
  const appearance = this.config?.appearance;
@@ -477,6 +820,306 @@ class PraxisExpansion {
477
820
  }
478
821
  this.cdr.markForCheck();
479
822
  }
823
+ openAiAssistant() {
824
+ this.initializeAiAssistantController();
825
+ this.aiAssistantOpen = true;
826
+ this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
827
+ this.syncAiAssistantSession('active');
828
+ this.cdr.markForCheck();
829
+ }
830
+ openAiAssistantFromSession(session) {
831
+ if (session.id !== this.resolveAiAssistantSessionId())
832
+ return;
833
+ this.initializeAiAssistantController();
834
+ this.aiAssistantOpen = true;
835
+ this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
836
+ this.syncAiAssistantSession('active');
837
+ this.cdr.markForCheck();
838
+ }
839
+ closeAiAssistant() {
840
+ this.aiAssistantOpen = false;
841
+ this.syncAiAssistantSession('minimized');
842
+ this.cdr.markForCheck();
843
+ }
844
+ onAiAssistantPromptChange(prompt) {
845
+ this.aiAssistantPrompt = prompt;
846
+ this.syncAiAssistantSession();
847
+ }
848
+ onAiAssistantSubmit(prompt) {
849
+ this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
850
+ this.aiAssistantPrompt = '';
851
+ this.aiAssistantViewState = state;
852
+ this.syncAiAssistantSession();
853
+ this.cdr.markForCheck();
854
+ });
855
+ }
856
+ onAiAssistantApply() {
857
+ this.aiAssistantController?.apply().subscribe((state) => {
858
+ this.aiAssistantViewState = state;
859
+ this.syncAiAssistantSession();
860
+ this.cdr.markForCheck();
861
+ });
862
+ }
863
+ onAiAssistantRetry() {
864
+ this.aiAssistantController?.retry().subscribe((state) => {
865
+ this.aiAssistantViewState = state;
866
+ this.syncAiAssistantSession();
867
+ this.cdr.markForCheck();
868
+ });
869
+ }
870
+ onAiAssistantCancel() {
871
+ this.aiAssistantController?.cancel().subscribe((state) => {
872
+ this.aiAssistantPrompt = '';
873
+ this.aiAssistantViewState = state;
874
+ this.syncAiAssistantSession();
875
+ this.cdr.markForCheck();
876
+ });
877
+ }
878
+ onAiAssistantQuickReply(reply) {
879
+ const controller = this.aiAssistantController;
880
+ if (!controller)
881
+ return;
882
+ const state = controller.snapshot();
883
+ const next$ = state.state === 'clarification'
884
+ ? controller.answerClarification(reply.prompt)
885
+ : controller.submitPrompt(reply.prompt, {
886
+ kind: reply.kind || 'quick-reply',
887
+ id: reply.id,
888
+ value: reply.prompt,
889
+ });
890
+ next$.subscribe((nextState) => {
891
+ this.aiAssistantPrompt = '';
892
+ this.aiAssistantViewState = nextState;
893
+ this.syncAiAssistantSession();
894
+ this.cdr.markForCheck();
895
+ });
896
+ }
897
+ onAiAssistantEditMessage(message) {
898
+ this.aiAssistantPrompt = message.text;
899
+ this.cdr.markForCheck();
900
+ }
901
+ onAiAssistantResendMessage(message) {
902
+ this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
903
+ this.aiAssistantPrompt = '';
904
+ this.aiAssistantViewState = state;
905
+ this.syncAiAssistantSession();
906
+ this.cdr.markForCheck();
907
+ });
908
+ }
909
+ onAiAssistantLayoutChange(layout) {
910
+ this.aiAssistantLayout = layout;
911
+ }
912
+ buildAiAssistantContextItems() {
913
+ const panels = this.config?.panels ?? [];
914
+ const items = [
915
+ {
916
+ id: 'component',
917
+ label: 'Componente',
918
+ value: 'Expansion',
919
+ kind: 'component',
920
+ icon: 'unfold_more',
921
+ },
922
+ {
923
+ id: 'expansion-id',
924
+ label: 'Expansion',
925
+ value: this.safeAiAssistantExpansionId(),
926
+ kind: 'custom',
927
+ icon: 'tag',
928
+ },
929
+ {
930
+ id: 'panels',
931
+ label: 'Paineis',
932
+ value: String(panels.length),
933
+ kind: 'custom',
934
+ icon: 'view_agenda',
935
+ },
936
+ ];
937
+ const expanded = panels.filter((panel) => panel.expanded).length;
938
+ if (expanded > 0) {
939
+ items.push({
940
+ id: 'expanded-panels',
941
+ label: 'Expandidos',
942
+ value: String(expanded),
943
+ kind: 'custom',
944
+ icon: 'expand_less',
945
+ });
946
+ }
947
+ return items;
948
+ }
949
+ initializeAiAssistantController() {
950
+ if (this.aiAssistantController)
951
+ return;
952
+ const flow = new ExpansionAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
953
+ const controller = this.aiTurnOrchestrator.createController(flow, {
954
+ componentId: this.aiAdapter.componentId || 'praxis-expansion',
955
+ componentType: this.aiAdapter.componentType || 'expansion',
956
+ contextItems: this.buildAiAssistantContextItems(),
957
+ });
958
+ this.aiAssistantController = controller;
959
+ this.aiAssistantViewState = controller.snapshot();
960
+ this.aiAssistantStateSubscription?.unsubscribe();
961
+ this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
962
+ this.aiAssistantViewState = state;
963
+ this.syncAiAssistantSession();
964
+ this.cdr.markForCheck();
965
+ });
966
+ this.cdr.markForCheck();
967
+ }
968
+ buildAiAssistantContextSnapshot() {
969
+ const counts = this.collectAiAssistantCounts();
970
+ const panelNames = this.collectAiAssistantPanelNames();
971
+ return {
972
+ identity: {
973
+ sessionId: this.resolveAiAssistantSessionId(),
974
+ ownerId: this.resolveAiAssistantOwnerId(),
975
+ ownerType: 'expansion',
976
+ componentId: 'praxis-expansion',
977
+ componentType: 'expansion',
978
+ routeKey: this.resolveAiAssistantRouteKey(),
979
+ },
980
+ target: {
981
+ kind: 'component',
982
+ id: this.resolveAiAssistantOwnerId(),
983
+ label: this.safeAiAssistantExpansionId(),
984
+ metadata: {
985
+ expansionId: this.safeAiAssistantExpansionId(),
986
+ hasCustomization: !!this.enableCustomization,
987
+ },
988
+ },
989
+ contextItems: this.buildAiAssistantContextItems().map((item) => ({
990
+ id: item.id,
991
+ label: item.label,
992
+ value: item.value || '',
993
+ kind: item.kind,
994
+ })),
995
+ mode: 'agentic-authoring',
996
+ authoringManifestRef: {
997
+ componentId: 'praxis-expansion',
998
+ source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
999
+ },
1000
+ schemaFields: panelNames.length ? panelNames : undefined,
1001
+ dataProfileDigest: {
1002
+ summary: `${counts.panelCount} painel(is), ${counts.contentPanelCount} com campo(s), ${counts.widgetPanelCount} com widget(s)`,
1003
+ counts,
1004
+ },
1005
+ runtimeStateDigest: {
1006
+ summary: `Expansion ${this.config?.accordion?.multi ? 'multi' : 'single'}, ${counts.expandedCount} painel(is) expandido(s)`,
1007
+ fields: [
1008
+ 'panels',
1009
+ 'accordion',
1010
+ 'content',
1011
+ 'widgets',
1012
+ ],
1013
+ },
1014
+ capabilityRefs: [
1015
+ {
1016
+ id: 'expansion.component-edit-plan',
1017
+ label: 'Plano de edicao de paineis',
1018
+ source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
1019
+ risk: 'medium',
1020
+ },
1021
+ ],
1022
+ governanceHints: [
1023
+ {
1024
+ kind: 'business-rule-boundary',
1025
+ label: 'Regras compartilhadas exigem governanca',
1026
+ reason: 'Politicas de acesso, validacoes reutilizaveis e compliance nao devem ser aplicadas como patch local do expansion.',
1027
+ risk: 'high',
1028
+ },
1029
+ ],
1030
+ };
1031
+ }
1032
+ syncAiAssistantSession(visibility = null) {
1033
+ if (!this.enableCustomization)
1034
+ return;
1035
+ if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState() && visibility !== 'minimized')
1036
+ return;
1037
+ const state = this.aiAssistantViewState;
1038
+ this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
1039
+ title: 'Copiloto semantico Praxis',
1040
+ summary: this.resolveAiAssistantSummary(),
1041
+ mode: state?.mode || 'agentic-authoring',
1042
+ state: state?.state || 'idle',
1043
+ visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
1044
+ badge: this.resolveAiAssistantBadge(),
1045
+ icon: this.resolveAiAssistantIcon(),
1046
+ });
1047
+ }
1048
+ hasAiAssistantSessionState() {
1049
+ return !!this.aiAssistantPrompt.trim()
1050
+ || !!this.aiAssistantViewState?.messages?.length
1051
+ || !!this.aiAssistantViewState?.quickReplies?.length
1052
+ || !!this.aiAssistantViewState?.pendingPatch
1053
+ || !!this.aiAssistantViewState?.statusText?.trim()
1054
+ || !!this.aiAssistantViewState?.errorText?.trim();
1055
+ }
1056
+ resolveAiAssistantSessionId() {
1057
+ return `expansion:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
1058
+ }
1059
+ resolveAiAssistantOwnerId() {
1060
+ return (this.componentInstanceId || this.safeAiAssistantExpansionId() || 'expansion').trim() || 'expansion';
1061
+ }
1062
+ safeAiAssistantExpansionId() {
1063
+ return String(this.expansionId || '').trim();
1064
+ }
1065
+ resolveAiAssistantRouteKey() {
1066
+ const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
1067
+ return routePath || 'local';
1068
+ }
1069
+ resolveAiAssistantSummary() {
1070
+ const status = this.aiAssistantViewState?.statusText?.trim();
1071
+ if (status)
1072
+ return status;
1073
+ const error = this.aiAssistantViewState?.errorText?.trim();
1074
+ if (error)
1075
+ return error;
1076
+ const prompt = this.aiAssistantPrompt.trim();
1077
+ if (prompt)
1078
+ return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
1079
+ const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
1080
+ .find((message) => message.role === 'assistant' || message.role === 'user');
1081
+ if (lastMessage?.text) {
1082
+ return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
1083
+ }
1084
+ return 'Assistente contextual dos paineis.';
1085
+ }
1086
+ resolveAiAssistantBadge() {
1087
+ const state = this.aiAssistantViewState?.state;
1088
+ if (state === 'error')
1089
+ return 'erro';
1090
+ if (state === 'clarification')
1091
+ return 'revisar';
1092
+ if (state === 'review')
1093
+ return 'preview';
1094
+ if (state === 'success')
1095
+ return 'ok';
1096
+ return undefined;
1097
+ }
1098
+ resolveAiAssistantIcon() {
1099
+ const state = this.aiAssistantViewState?.state;
1100
+ if (state === 'error')
1101
+ return 'error';
1102
+ if (state === 'clarification')
1103
+ return 'rule';
1104
+ if (state === 'review')
1105
+ return 'rate_review';
1106
+ return 'auto_awesome';
1107
+ }
1108
+ collectAiAssistantPanelNames() {
1109
+ return Array.from(new Set((this.config?.panels ?? [])
1110
+ .map((panel, index) => panel.id || panel.title || `panel-${index + 1}`)
1111
+ .filter((name) => typeof name === 'string' && !!name.trim())));
1112
+ }
1113
+ collectAiAssistantCounts() {
1114
+ const panels = this.config?.panels ?? [];
1115
+ return {
1116
+ panelCount: panels.length,
1117
+ contentPanelCount: panels.filter((panel) => Array.isArray(panel.content) && panel.content.length > 0).length,
1118
+ widgetPanelCount: panels.filter((panel) => Array.isArray(panel.widgets) && panel.widgets.length > 0).length,
1119
+ actionPanelCount: panels.filter((panel) => Array.isArray(panel.actionButtons) && panel.actionButtons.length > 0).length,
1120
+ expandedCount: panels.filter((panel) => !!panel.expanded).length,
1121
+ };
1122
+ }
480
1123
  openEditor() {
481
1124
  const key = this.storageKey() || this.expansionId || 'default';
482
1125
  const ref = this.settings.open({
@@ -574,10 +1217,48 @@ class PraxisExpansion {
574
1217
 
575
1218
  @if (enableCustomization) {
576
1219
  <div class="expansion-ai-assistant">
577
- <praxis-ai-assistant [adapter]="aiAdapter"></praxis-ai-assistant>
1220
+ <button
1221
+ mat-mini-fab
1222
+ color="primary"
1223
+ type="button"
1224
+ class="expansion-ai-assistant-trigger"
1225
+ aria-label="Abrir copiloto semantico Praxis dos paineis"
1226
+ data-testid="praxis-expansion-ai-assistant-trigger"
1227
+ (click)="openAiAssistant()"
1228
+ >
1229
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
1230
+ </button>
578
1231
  </div>
579
1232
  }
580
1233
 
1234
+ <praxis-ai-assistant-shell
1235
+ *ngIf="aiAssistantOpen && aiAssistantViewState"
1236
+ [labels]="aiAssistantLabels"
1237
+ [mode]="aiAssistantViewState.mode"
1238
+ [state]="aiAssistantViewState.state"
1239
+ [contextItems]="aiAssistantViewState.contextItems"
1240
+ [attachments]="aiAssistantViewState.attachments"
1241
+ [messages]="aiAssistantViewState.messages"
1242
+ [quickReplies]="aiAssistantViewState.quickReplies"
1243
+ [prompt]="aiAssistantPrompt"
1244
+ [statusText]="aiAssistantViewState.statusText"
1245
+ [errorText]="aiAssistantViewState.errorText"
1246
+ [busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
1247
+ [canApply]="aiAssistantViewState.canApply"
1248
+ [layout]="aiAssistantLayout"
1249
+ testIdPrefix="praxis-expansion-ai-assistant"
1250
+ (promptChange)="onAiAssistantPromptChange($event)"
1251
+ (submitPrompt)="onAiAssistantSubmit($event)"
1252
+ (apply)="onAiAssistantApply()"
1253
+ (retryTurn)="onAiAssistantRetry()"
1254
+ (cancelTurn)="onAiAssistantCancel()"
1255
+ (quickReply)="onAiAssistantQuickReply($event)"
1256
+ (editMessage)="onAiAssistantEditMessage($event)"
1257
+ (resendMessage)="onAiAssistantResendMessage($event)"
1258
+ (layoutChange)="onAiAssistantLayoutChange($event)"
1259
+ (close)="closeAiAssistant()"
1260
+ ></praxis-ai-assistant-shell>
1261
+
581
1262
  @if (hasMultiple()) {
582
1263
  <mat-accordion
583
1264
  #accordion
@@ -721,7 +1402,7 @@ class PraxisExpansion {
721
1402
  >
722
1403
  <mat-icon fontIcon="edit"></mat-icon>
723
1404
  </button>
724
- `, isInline: true, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i2.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i2.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i2.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i2.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i2.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i2.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i2.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1405
+ `, isInline: true, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i2.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i2.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "directive", type: i2.MatExpansionPanelActionRow, selector: "mat-action-row" }, { kind: "component", type: i2.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i2.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i2.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i2.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisAiAssistantShellComponent, selector: "praxis-ai-assistant-shell", inputs: ["labels", "mode", "state", "contextItems", "attachments", "messages", "quickReplies", "prompt", "statusText", "errorText", "testIdPrefix", "panelTestId", "submitTestId", "applyTestId", "primaryAction", "secondaryActions", "governanceActions", "busy", "canSubmit", "canApply", "submitOnEnter", "showAttachAction", "enablePastedAttachments", "enableFileAttachments", "attachmentAccept", "attachmentMultiple", "draggable", "resizable", "minWidth", "minHeight", "margin", "layout"], outputs: ["promptChange", "submitPrompt", "apply", "retryTurn", "cancelTurn", "shellAction", "close", "attach", "attachmentsPasted", "attachmentsSelected", "removeAttachment", "messageAction", "editMessage", "resendMessage", "quickReply", "layoutChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
725
1406
  }
726
1407
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansion, decorators: [{
727
1408
  type: Component,
@@ -733,7 +1414,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
733
1414
  DynamicFieldLoaderDirective,
734
1415
  DynamicWidgetLoaderDirective,
735
1416
  PraxisIconDirective,
736
- PraxisAiAssistantComponent,
1417
+ PraxisAiAssistantShellComponent,
737
1418
  ], template: `
738
1419
  <div
739
1420
  class="praxis-expansion-root"
@@ -748,10 +1429,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
748
1429
 
749
1430
  @if (enableCustomization) {
750
1431
  <div class="expansion-ai-assistant">
751
- <praxis-ai-assistant [adapter]="aiAdapter"></praxis-ai-assistant>
1432
+ <button
1433
+ mat-mini-fab
1434
+ color="primary"
1435
+ type="button"
1436
+ class="expansion-ai-assistant-trigger"
1437
+ aria-label="Abrir copiloto semantico Praxis dos paineis"
1438
+ data-testid="praxis-expansion-ai-assistant-trigger"
1439
+ (click)="openAiAssistant()"
1440
+ >
1441
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
1442
+ </button>
752
1443
  </div>
753
1444
  }
754
1445
 
1446
+ <praxis-ai-assistant-shell
1447
+ *ngIf="aiAssistantOpen && aiAssistantViewState"
1448
+ [labels]="aiAssistantLabels"
1449
+ [mode]="aiAssistantViewState.mode"
1450
+ [state]="aiAssistantViewState.state"
1451
+ [contextItems]="aiAssistantViewState.contextItems"
1452
+ [attachments]="aiAssistantViewState.attachments"
1453
+ [messages]="aiAssistantViewState.messages"
1454
+ [quickReplies]="aiAssistantViewState.quickReplies"
1455
+ [prompt]="aiAssistantPrompt"
1456
+ [statusText]="aiAssistantViewState.statusText"
1457
+ [errorText]="aiAssistantViewState.errorText"
1458
+ [busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
1459
+ [canApply]="aiAssistantViewState.canApply"
1460
+ [layout]="aiAssistantLayout"
1461
+ testIdPrefix="praxis-expansion-ai-assistant"
1462
+ (promptChange)="onAiAssistantPromptChange($event)"
1463
+ (submitPrompt)="onAiAssistantSubmit($event)"
1464
+ (apply)="onAiAssistantApply()"
1465
+ (retryTurn)="onAiAssistantRetry()"
1466
+ (cancelTurn)="onAiAssistantCancel()"
1467
+ (quickReply)="onAiAssistantQuickReply($event)"
1468
+ (editMessage)="onAiAssistantEditMessage($event)"
1469
+ (resendMessage)="onAiAssistantResendMessage($event)"
1470
+ (layoutChange)="onAiAssistantLayoutChange($event)"
1471
+ (close)="closeAiAssistant()"
1472
+ ></praxis-ai-assistant-shell>
1473
+
755
1474
  @if (hasMultiple()) {
756
1475
  <mat-accordion
757
1476
  #accordion
@@ -895,7 +1614,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
895
1614
  >
896
1615
  <mat-icon fontIcon="edit"></mat-icon>
897
1616
  </button>
898
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}\n"] }]
1617
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative;color:var(--md-sys-color-on-surface)}.praxis-expansion-root{display:block;--p-exp-surface: var(--md-sys-color-surface);--p-exp-surface-container: var(--md-sys-color-surface-container);--p-exp-border: var(--md-sys-color-outline-variant);--p-exp-text: var(--md-sys-color-on-surface);--p-exp-text-muted: var(--md-sys-color-on-surface-variant);--p-exp-focus: var(--md-sys-color-primary);--p-exp-radius: 12px}.mat-expansion-panel{background:var(--p-exp-surface);border:1px solid var(--p-exp-border);border-radius:var(--p-exp-radius);overflow:hidden}.mat-expansion-panel:not(:last-child){margin-bottom:var(--p-exp-panel-gap, 12px)}.mat-expansion-panel-header{background:var(--p-exp-surface-container);color:var(--p-exp-text)}.mat-expansion-panel-header:focus-visible{outline:2px solid var(--p-exp-focus);outline-offset:-2px}.mat-expansion-panel-header-title{font-weight:600}.mat-expansion-panel-header-description{color:var(--p-exp-text-muted)}.mat-expansion-panel-body{padding:12px 16px 16px}.mat-action-row{border-top:1px solid var(--p-exp-border)}.density-compact .mat-expansion-panel-body{padding:8px 12px 12px}.density-compact .mat-expansion-panel-header{min-height:40px;padding:0 12px}.density-comfortable .mat-expansion-panel-body{padding:12px 16px 16px}.density-comfortable .mat-expansion-panel-header{min-height:48px;padding:0 16px}.density-spacious .mat-expansion-panel-body{padding:16px 20px 20px}.density-spacious .mat-expansion-panel-header{min-height:56px;padding:0 20px}.praxis-expansion-empty{display:flex;gap:8px;align-items:center;padding:8px 12px;color:var(--p-exp-text-muted)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.expansion-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.expansion-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}\n"] }]
899
1618
  }], propDecorators: { config: [{
900
1619
  type: Input
901
1620
  }], expansionId: [{
@@ -1104,7 +1823,7 @@ class PraxisExpansionConfigEditor {
1104
1823
  }
1105
1824
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }], target: i0.ɵɵFactoryTarget.Component });
1106
1825
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisExpansionConfigEditor, isStandalone: true, selector: "praxis-expansion-config-editor", inputs: { config: "config", expansionId: "expansionId" }, usesOnChanges: true, ngImport: i0, template: `
1107
- <div class="pdx-expansion-editor">
1826
+ <div class="pdx-expansion-editor" data-testid="expansion-config-editor">
1108
1827
  <section class="sec">
1109
1828
  <h4>Aparência</h4>
1110
1829
  <div class="grid two">
@@ -1357,7 +2076,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1357
2076
  MatTooltipModule,
1358
2077
  PraxisIconDirective,
1359
2078
  ], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1360
- <div class="pdx-expansion-editor">
2079
+ <div class="pdx-expansion-editor" data-testid="expansion-config-editor">
1361
2080
  <section class="sec">
1362
2081
  <h4>Aparência</h4>
1363
2082
  <div class="grid two">
@@ -1606,6 +2325,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1606
2325
  type: Input
1607
2326
  }] } });
1608
2327
 
2328
+ class PraxisExpansionWidgetConfigEditor {
2329
+ inputs = null;
2330
+ widgetKey;
2331
+ expansionEditor;
2332
+ isDirty$ = new BehaviorSubject(false);
2333
+ isValid$ = new BehaviorSubject(true);
2334
+ isBusy$ = new BehaviorSubject(false);
2335
+ subscription = new Subscription();
2336
+ emptyConfig = {};
2337
+ get config() {
2338
+ return this.inputs?.config ?? this.emptyConfig;
2339
+ }
2340
+ get expansionId() {
2341
+ return this.inputs?.expansionId ?? this.widgetKey;
2342
+ }
2343
+ ngAfterViewInit() {
2344
+ if (!this.expansionEditor) {
2345
+ return;
2346
+ }
2347
+ this.subscription.add(this.expansionEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
2348
+ this.subscription.add(this.expansionEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
2349
+ this.subscription.add(this.expansionEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
2350
+ }
2351
+ ngOnDestroy() {
2352
+ this.subscription.unsubscribe();
2353
+ }
2354
+ getSettingsValue() {
2355
+ return this.buildValue(this.expansionEditor?.getSettingsValue());
2356
+ }
2357
+ onSave() {
2358
+ return this.buildValue(this.expansionEditor?.getSettingsValue());
2359
+ }
2360
+ reset() {
2361
+ this.expansionEditor?.reset();
2362
+ }
2363
+ buildValue(config) {
2364
+ return {
2365
+ inputs: {
2366
+ ...(this.inputs ?? {}),
2367
+ config: config ?? this.config,
2368
+ ...(this.expansionId ? { expansionId: this.expansionId } : {}),
2369
+ },
2370
+ };
2371
+ }
2372
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
2373
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisExpansionWidgetConfigEditor, isStandalone: true, selector: "praxis-expansion-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "expansionEditor", first: true, predicate: ["expansionEditor"], descendants: true }], ngImport: i0, template: `
2374
+ <section data-testid="expansion-widget-config-editor">
2375
+ <praxis-expansion-config-editor
2376
+ #expansionEditor
2377
+ [config]="config"
2378
+ [expansionId]="expansionId"
2379
+ />
2380
+ </section>
2381
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisExpansionConfigEditor, selector: "praxis-expansion-config-editor", inputs: ["config", "expansionId"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2382
+ }
2383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisExpansionWidgetConfigEditor, decorators: [{
2384
+ type: Component,
2385
+ args: [{
2386
+ selector: 'praxis-expansion-widget-config-editor',
2387
+ standalone: true,
2388
+ imports: [CommonModule, PraxisExpansionConfigEditor],
2389
+ template: `
2390
+ <section data-testid="expansion-widget-config-editor">
2391
+ <praxis-expansion-config-editor
2392
+ #expansionEditor
2393
+ [config]="config"
2394
+ [expansionId]="expansionId"
2395
+ />
2396
+ </section>
2397
+ `,
2398
+ changeDetection: ChangeDetectionStrategy.OnPush,
2399
+ }]
2400
+ }], propDecorators: { inputs: [{
2401
+ type: Input
2402
+ }], widgetKey: [{
2403
+ type: Input
2404
+ }], expansionEditor: [{
2405
+ type: ViewChild,
2406
+ args: ['expansionEditor']
2407
+ }] } });
2408
+
1609
2409
  const PRAXIS_EXPANSION_PORTS = [
1610
2410
  {
1611
2411
  id: 'config',
@@ -1642,6 +2442,14 @@ const PRAXIS_EXPANSION_COMPONENT_METADATA = {
1642
2442
  friendlyName: 'Praxis Expansion Panel',
1643
2443
  description: 'Acordeão/Painéis de expansão configuráveis por metadata, com aparência e tokens M3.',
1644
2444
  icon: 'unfold_more',
2445
+ authoringManifestRef: {
2446
+ componentId: 'praxis-expansion',
2447
+ source: 'PRAXIS_EXPANSION_AUTHORING_MANIFEST',
2448
+ },
2449
+ configEditor: {
2450
+ component: PraxisExpansionWidgetConfigEditor,
2451
+ title: 'Configure expansion',
2452
+ },
1645
2453
  inputs: [
1646
2454
  { name: 'config', type: 'ExpansionMetadata', label: 'Configuração', description: 'Configuração JSON (acordeão, aparência e painéis)' },
1647
2455
  { name: 'expansionId', type: 'string', label: 'ID', description: 'Identificador para persistência (obrigatório)' },
@@ -2049,4 +2857,4 @@ const PRAXIS_EXPANSION_AUTHORING_MANIFEST = {
2049
2857
  * Generated bundle index. Do not edit.
2050
2858
  */
2051
2859
 
2052
- export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
2860
+ export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, PraxisExpansionWidgetConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
package/index.d.ts CHANGED
@@ -1,19 +1,24 @@
1
1
  import * as i0 from '@angular/core';
2
- import { OnChanges, OnInit, EventEmitter, QueryList, SimpleChanges, Provider } from '@angular/core';
2
+ import { OnChanges, OnDestroy, OnInit, EventEmitter, QueryList, SimpleChanges, AfterViewInit, Provider } from '@angular/core';
3
3
  import { FormGroup } from '@angular/forms';
4
4
  import { MatAccordionDisplayMode, MatAccordionTogglePosition, MatExpansionPanelDefaultOptions, MatAccordion, MatExpansionPanel } from '@angular/material/expansion';
5
- import { AiCapability, FieldMetadata, WidgetDefinition, WidgetEventEnvelope, ComponentDocMeta, AiCapabilityCategory, AiValueKind, AiCapabilityCatalog, ComponentAuthoringManifest } from '@praxisui/core';
5
+ import { AiCapability, FieldMetadata, WidgetDefinition, WidgetEventEnvelope, SettingsValueProvider as SettingsValueProvider$1, ComponentDocMeta, AiCapabilityCategory, AiValueKind, AiCapabilityCatalog, ComponentAuthoringManifest } from '@praxisui/core';
6
6
  import { BehaviorSubject } from 'rxjs';
7
- import { BaseAiAdapter, PatchResult } from '@praxisui/ai';
7
+ import { BaseAiAdapter, PatchResult, PraxisAssistantTurnViewState, PraxisAssistantShellLayout, PraxisAssistantShellLabels, PraxisAssistantSessionSnapshot, PraxisAssistantShellQuickReply, PraxisAssistantShellMessage, PraxisAssistantShellContextItem } from '@praxisui/ai';
8
8
  import { SettingsValueProvider } from '@praxisui/settings-panel';
9
9
 
10
10
  declare class ExpansionAiAdapter extends BaseAiAdapter<ExpansionMetadata> {
11
11
  private expansion;
12
+ componentId: string;
13
+ componentType: string;
12
14
  componentName: string;
13
15
  constructor(expansion: PraxisExpansion);
14
16
  getCurrentConfig(): ExpansionMetadata;
15
17
  getCapabilities(): AiCapability[];
16
18
  getRuntimeState(): Record<string, any>;
19
+ getDataProfile(): Record<string, any>;
20
+ getSchemaFields(): Array<Record<string, any>>;
21
+ getAuthoringContext(): Record<string, any>;
17
22
  createSnapshot(): ExpansionMetadata;
18
23
  restoreSnapshot(snapshot: ExpansionMetadata): Promise<void>;
19
24
  applyPatch(patch: Partial<ExpansionMetadata>): Promise<PatchResult>;
@@ -103,7 +108,7 @@ interface PanelMetadata {
103
108
  action?: string;
104
109
  }>;
105
110
  }
106
- declare class PraxisExpansion implements OnChanges, OnInit {
111
+ declare class PraxisExpansion implements OnChanges, OnDestroy, OnInit {
107
112
  config?: ExpansionMetadata | null;
108
113
  expansionId: string;
109
114
  componentInstanceId?: string;
@@ -144,9 +149,20 @@ declare class PraxisExpansion implements OnChanges, OnInit {
144
149
  private readonly dynamicForm;
145
150
  private readonly componentKeys;
146
151
  private readonly route;
152
+ private readonly aiApi;
153
+ private readonly assistantSessions;
154
+ private readonly aiTurnOrchestrator;
155
+ private readonly aiAssistantSessionEffect;
147
156
  private warnedMissingId;
148
157
  private readonly panelForms;
149
158
  aiAdapter: ExpansionAiAdapter;
159
+ aiAssistantOpen: boolean;
160
+ aiAssistantPrompt: string;
161
+ aiAssistantViewState: PraxisAssistantTurnViewState | null;
162
+ aiAssistantLayout: PraxisAssistantShellLayout;
163
+ readonly aiAssistantLabels: Partial<PraxisAssistantShellLabels>;
164
+ private aiAssistantController;
165
+ private aiAssistantStateSubscription;
150
166
  accordionRef?: MatAccordion;
151
167
  panels?: QueryList<MatExpansionPanel>;
152
168
  injectedDefaults: MatExpansionPanelDefaultOptions | null;
@@ -154,6 +170,7 @@ declare class PraxisExpansion implements OnChanges, OnInit {
154
170
  single: () => PanelMetadata | null;
155
171
  ngOnInit(): void;
156
172
  ngOnChanges(changes: SimpleChanges): void;
173
+ ngOnDestroy(): void;
157
174
  styleCss(): string | null;
158
175
  emitOpened(p: PanelMetadata, index: number): void;
159
176
  emitClosed(p: PanelMetadata, index: number): void;
@@ -174,6 +191,32 @@ declare class PraxisExpansion implements OnChanges, OnInit {
174
191
  panelDomId(p: PanelMetadata | null, index: number): string;
175
192
  panelFormFor(panel: PanelMetadata, index: number): FormGroup<Record<string, any>>;
176
193
  applyConfigFromAdapter(next: ExpansionMetadata): void;
194
+ openAiAssistant(): void;
195
+ openAiAssistantFromSession(session: PraxisAssistantSessionSnapshot): void;
196
+ closeAiAssistant(): void;
197
+ onAiAssistantPromptChange(prompt: string): void;
198
+ onAiAssistantSubmit(prompt: string): void;
199
+ onAiAssistantApply(): void;
200
+ onAiAssistantRetry(): void;
201
+ onAiAssistantCancel(): void;
202
+ onAiAssistantQuickReply(reply: PraxisAssistantShellQuickReply): void;
203
+ onAiAssistantEditMessage(message: PraxisAssistantShellMessage): void;
204
+ onAiAssistantResendMessage(message: PraxisAssistantShellMessage): void;
205
+ onAiAssistantLayoutChange(layout: PraxisAssistantShellLayout): void;
206
+ buildAiAssistantContextItems(): PraxisAssistantShellContextItem[];
207
+ private initializeAiAssistantController;
208
+ private buildAiAssistantContextSnapshot;
209
+ private syncAiAssistantSession;
210
+ private hasAiAssistantSessionState;
211
+ private resolveAiAssistantSessionId;
212
+ private resolveAiAssistantOwnerId;
213
+ private safeAiAssistantExpansionId;
214
+ private resolveAiAssistantRouteKey;
215
+ private resolveAiAssistantSummary;
216
+ private resolveAiAssistantBadge;
217
+ private resolveAiAssistantIcon;
218
+ private collectAiAssistantPanelNames;
219
+ private collectAiAssistantCounts;
177
220
  openEditor(): void;
178
221
  private storageKey;
179
222
  private persistConfig;
@@ -216,6 +259,35 @@ declare class PraxisExpansionConfigEditor implements SettingsValueProvider, OnCh
216
259
  static ɵcmp: i0.ɵɵComponentDeclaration<PraxisExpansionConfigEditor, "praxis-expansion-config-editor", never, { "config": { "alias": "config"; "required": false; }; "expansionId": { "alias": "expansionId"; "required": false; }; }, {}, never, never, true, never>;
217
260
  }
218
261
 
262
+ interface PraxisExpansionWidgetEditorInputs {
263
+ config?: ExpansionMetadata | null;
264
+ expansionId?: string;
265
+ [key: string]: unknown;
266
+ }
267
+ interface PraxisExpansionWidgetEditorValue {
268
+ inputs: PraxisExpansionWidgetEditorInputs;
269
+ }
270
+ declare class PraxisExpansionWidgetConfigEditor implements SettingsValueProvider$1, AfterViewInit, OnDestroy {
271
+ inputs: PraxisExpansionWidgetEditorInputs | null;
272
+ widgetKey?: string;
273
+ expansionEditor?: PraxisExpansionConfigEditor;
274
+ readonly isDirty$: BehaviorSubject<boolean>;
275
+ readonly isValid$: BehaviorSubject<boolean>;
276
+ readonly isBusy$: BehaviorSubject<boolean>;
277
+ private readonly subscription;
278
+ private readonly emptyConfig;
279
+ get config(): ExpansionMetadata;
280
+ get expansionId(): string | undefined;
281
+ ngAfterViewInit(): void;
282
+ ngOnDestroy(): void;
283
+ getSettingsValue(): PraxisExpansionWidgetEditorValue;
284
+ onSave(): PraxisExpansionWidgetEditorValue;
285
+ reset(): void;
286
+ private buildValue;
287
+ static ɵfac: i0.ɵɵFactoryDeclaration<PraxisExpansionWidgetConfigEditor, never>;
288
+ static ɵcmp: i0.ɵɵComponentDeclaration<PraxisExpansionWidgetConfigEditor, "praxis-expansion-widget-config-editor", never, { "inputs": { "alias": "inputs"; "required": false; }; "widgetKey": { "alias": "widgetKey"; "required": false; }; }, {}, never, never, true, never>;
289
+ }
290
+
219
291
  declare const PRAXIS_EXPANSION_COMPONENT_METADATA: ComponentDocMeta;
220
292
  declare function providePraxisExpansionMetadata(): Provider;
221
293
  declare function providePraxisExpansionDefaults(opts: MatExpansionPanelDefaultOptions): Provider;
@@ -245,5 +317,5 @@ declare const EXPANSION_AI_CAPABILITIES: CapabilityCatalog;
245
317
 
246
318
  declare const PRAXIS_EXPANSION_AUTHORING_MANIFEST: ComponentAuthoringManifest;
247
319
 
248
- export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
249
- export type { Capability, CapabilityCatalog, CapabilityCategory, ExpansionAppearanceContainer, ExpansionAppearanceHeader, ExpansionAppearanceStates, ExpansionMetadata, PanelMetadata, ValueKind };
320
+ export { EXPANSION_AI_CAPABILITIES, PRAXIS_EXPANSION_AUTHORING_MANIFEST, PRAXIS_EXPANSION_COMPONENT_METADATA, PraxisExpansion, PraxisExpansionConfigEditor, PraxisExpansionWidgetConfigEditor, providePraxisExpansionDefaults, providePraxisExpansionMetadata };
321
+ export type { Capability, CapabilityCatalog, CapabilityCategory, ExpansionAppearanceContainer, ExpansionAppearanceHeader, ExpansionAppearanceStates, ExpansionMetadata, PanelMetadata, PraxisExpansionWidgetEditorInputs, PraxisExpansionWidgetEditorValue, ValueKind };
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@praxisui/expansion",
3
- "version": "8.0.0-beta.20",
3
+ "version": "8.0.0-beta.21",
4
4
  "description": "Expansion panel (accordion) components for Praxis UI with metadata configuration and editor integration.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.0.0",
7
7
  "@angular/core": "^20.0.0",
8
8
  "@angular/material": "^20.0.0",
9
9
  "@angular/cdk": "^20.0.0",
10
- "@praxisui/core": "^8.0.0-beta.20",
11
- "@praxisui/dynamic-fields": "^8.0.0-beta.20",
12
- "@praxisui/settings-panel": "^8.0.0-beta.20",
10
+ "@praxisui/core": "^8.0.0-beta.21",
11
+ "@praxisui/dynamic-fields": "^8.0.0-beta.21",
12
+ "@praxisui/settings-panel": "^8.0.0-beta.21",
13
13
  "@angular/forms": "^20.0.0",
14
14
  "@angular/router": "^20.0.0",
15
- "@praxisui/ai": "^8.0.0-beta.20",
15
+ "@praxisui/ai": "^8.0.0-beta.21",
16
16
  "rxjs": "~7.8.0"
17
17
  },
18
18
  "dependencies": {