@praxisui/tabs 8.0.0-beta.20 → 8.0.0-beta.22

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Inject, Component, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { inject, Input, Inject, Component, ChangeDetectorRef, effect, EventEmitter, signal, Output, ChangeDetectionStrategy, ViewChild, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
3
  import { ActivatedRoute } from '@angular/router';
4
4
  import * as i1$1 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
@@ -25,11 +25,11 @@ import * as i9 from '@angular/material/slide-toggle';
25
25
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
26
26
  import * as i10 from '@angular/cdk/drag-drop';
27
27
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
28
- import { BehaviorSubject, Subject } from 'rxjs';
28
+ import { BehaviorSubject, firstValueFrom, Subject, Subscription } from 'rxjs';
29
29
  import { produce } from 'immer';
30
30
  import { MatSnackBar } from '@angular/material/snack-bar';
31
31
  import { take, takeUntil } from 'rxjs/operators';
32
- import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
32
+ import { BaseAiAdapter, AiBackendApiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
33
33
 
34
34
  const DOCUMENT_KIND = 'praxis.tabs.editor';
35
35
  const DOCUMENT_VERSION = 1;
@@ -533,6 +533,12 @@ function providePraxisTabsI18n() {
533
533
  class PraxisTabsConfigEditor {
534
534
  registry;
535
535
  i18n = inject(PraxisI18nService);
536
+ set document(value) {
537
+ if (!value) {
538
+ return;
539
+ }
540
+ this.initializeDocument(value);
541
+ }
536
542
  primaryMode = 'group';
537
543
  editedDocument;
538
544
  editedConfig;
@@ -612,12 +618,18 @@ class PraxisTabsConfigEditor {
612
618
  this.initialDocument = structuredClone(incomingDocument);
613
619
  this.editedDocument = structuredClone(incomingDocument);
614
620
  this.editedConfig = this.editedDocument.config;
615
- this.bindings = this.editedDocument.bindings;
616
- this.primaryMode = this.inferPrimaryMode(this.editedConfig);
621
+ this.initializeDocument(incomingDocument);
622
+ this.componentOptions = this.registry.getAll().map((m) => ({ id: m.id, friendlyName: m.friendlyName }));
623
+ }
624
+ initializeDocument(document) {
625
+ const normalized = normalizeTabsAuthoringDocument(document);
626
+ this.initialDocument = structuredClone(normalized);
627
+ this.syncEditorStateFromDocument(normalized);
617
628
  this.jsonText = this.stringify(this.editedDocument);
629
+ this.isValid = true;
630
+ this.errorMsg = '';
618
631
  this.updateDirty();
619
632
  this.refreshDiagnostics();
620
- this.componentOptions = this.registry.getAll().map((m) => ({ id: m.id, friendlyName: m.friendlyName }));
621
633
  }
622
634
  inferPrimaryMode(config) {
623
635
  return config?.nav?.links?.length ? 'nav' : 'group';
@@ -955,7 +967,7 @@ class PraxisTabsConfigEditor {
955
967
  this.onAppearanceChange();
956
968
  }
957
969
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }, { token: i1.ComponentMetadataRegistry }], target: i0.ɵɵFactoryTarget.Component });
958
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsConfigEditor, isStandalone: true, selector: "praxis-tabs-config-editor", providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], ngImport: i0, template: `
970
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsConfigEditor, isStandalone: true, selector: "praxis-tabs-config-editor", inputs: { document: "document" }, providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], ngImport: i0, template: `
959
971
  <div class="editor-shell">
960
972
  <div class="editor-topbar">
961
973
  <mat-form-field appearance="outline" class="editor-mode-field">
@@ -1971,7 +1983,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1971
1983
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1972
1984
  type: Inject,
1973
1985
  args: [SETTINGS_PANEL_DATA]
1974
- }] }, { type: i1.ComponentMetadataRegistry }] });
1986
+ }] }, { type: i1.ComponentMetadataRegistry }], propDecorators: { document: [{
1987
+ type: Input
1988
+ }] } });
1975
1989
 
1976
1990
  class TabsQuickSetupComponent {
1977
1991
  i18n = inject(PraxisI18nService);
@@ -2282,6 +2296,8 @@ const TABS_AI_CAPABILITIES = {
2282
2296
  class TabsAiAdapter extends BaseAiAdapter {
2283
2297
  tabs;
2284
2298
  componentName = 'Praxis Tabs';
2299
+ componentId = 'praxis-tabs';
2300
+ componentType = 'tabs';
2285
2301
  constructor(tabs) {
2286
2302
  super();
2287
2303
  this.tabs = tabs;
@@ -2292,6 +2308,49 @@ class TabsAiAdapter extends BaseAiAdapter {
2292
2308
  getCapabilities() {
2293
2309
  return TABS_AI_CAPABILITIES.capabilities;
2294
2310
  }
2311
+ getDataProfile() {
2312
+ const cfg = this.tabs.config;
2313
+ return {
2314
+ mode: cfg?.nav?.links?.length ? 'nav' : 'group',
2315
+ tabCount: cfg?.tabs?.length ?? 0,
2316
+ linkCount: cfg?.nav?.links?.length ?? 0,
2317
+ widgetCount: [
2318
+ ...(cfg?.tabs ?? []).flatMap((tab) => tab.widgets ?? []),
2319
+ ...(cfg?.nav?.links ?? []).flatMap((link) => link.widgets ?? []),
2320
+ ].length,
2321
+ fieldCount: [
2322
+ ...(cfg?.tabs ?? []).flatMap((tab) => tab.content ?? []),
2323
+ ...(cfg?.nav?.links ?? []).flatMap((link) => link.content ?? []),
2324
+ ].length,
2325
+ };
2326
+ }
2327
+ getSchemaFields() {
2328
+ const cfg = this.tabs.config;
2329
+ return [
2330
+ ...(cfg?.tabs ?? []).flatMap((tab) => tab.content ?? []),
2331
+ ...(cfg?.nav?.links ?? []).flatMap((link) => link.content ?? []),
2332
+ ]
2333
+ .map((field) => field?.name || field?.key || field?.id)
2334
+ .filter((name) => typeof name === 'string' && !!name.trim())
2335
+ .map((name) => ({ name }));
2336
+ }
2337
+ getAuthoringContext() {
2338
+ return {
2339
+ authoringManifestRef: {
2340
+ componentId: 'praxis-tabs',
2341
+ source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
2342
+ },
2343
+ runtimeAuthoringPolicy: {
2344
+ mode: 'agentic-authoring',
2345
+ enableCustomization: !!this.tabs.enableCustomization,
2346
+ canApplyLocalPatch: false,
2347
+ reason: 'praxis-tabs ainda exige componentEditPlan manifest-backed antes de aplicar patch local pelo copiloto.',
2348
+ },
2349
+ domainCatalog: {
2350
+ recommendedAuthoringFlow: 'component_authoring',
2351
+ },
2352
+ };
2353
+ }
2295
2354
  getRuntimeState() {
2296
2355
  const cfg = this.tabs.config;
2297
2356
  return {
@@ -2377,6 +2436,340 @@ class TabsAiAdapter extends BaseAiAdapter {
2377
2436
  }
2378
2437
  }
2379
2438
 
2439
+ class TabsAgenticAuthoringTurnFlow {
2440
+ adapter;
2441
+ aiApi;
2442
+ mode = 'agentic-authoring';
2443
+ constructor(adapter, aiApi) {
2444
+ this.adapter = adapter;
2445
+ this.aiApi = aiApi;
2446
+ }
2447
+ async submit(request) {
2448
+ const prompt = (request.prompt ?? '').trim();
2449
+ if (!prompt) {
2450
+ return {
2451
+ state: 'listening',
2452
+ phase: 'capture',
2453
+ statusText: '',
2454
+ };
2455
+ }
2456
+ const componentId = this.adapter.componentId || request.componentId || 'praxis-tabs';
2457
+ const componentType = this.adapter.componentType || request.componentType || 'tabs';
2458
+ const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
2459
+ const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
2460
+ const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
2461
+ const schemaFields = this.adapter.getSchemaFields?.()
2462
+ ?.map((field) => this.toAiJsonObject(field))
2463
+ .filter((field) => Object.keys(field).length > 0);
2464
+ const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
2465
+ if (this.shouldRouteToGovernedDecision(prompt, contextHints)) {
2466
+ return this.toGovernedDecisionHandoff(prompt, request);
2467
+ }
2468
+ const response = await firstValueFrom(this.aiApi.getPatch({
2469
+ componentId,
2470
+ componentType,
2471
+ userPrompt: prompt,
2472
+ sessionId: request.sessionId,
2473
+ clientTurnId: request.clientTurnId,
2474
+ messages: this.toChatMessages(request.messages, prompt),
2475
+ currentState,
2476
+ currentStateDigest: this.buildCurrentStateDigest(dataProfile),
2477
+ uiContextRef: {
2478
+ componentId,
2479
+ componentType,
2480
+ },
2481
+ ...(dataProfile ? { dataProfile } : {}),
2482
+ ...(runtimeState ? { runtimeState } : {}),
2483
+ ...(schemaFields?.length ? { schemaFields } : {}),
2484
+ ...(contextHints ? { contextHints } : {}),
2485
+ }));
2486
+ return this.toTurnResult(this.compileAdapterResponse(response), request);
2487
+ }
2488
+ async apply(_request) {
2489
+ return {
2490
+ state: 'error',
2491
+ phase: 'apply',
2492
+ assistantMessage: 'As abas ainda exigem componentEditPlan validado pelo manifesto antes de aplicar mudancas locais.',
2493
+ errorText: 'Aplicacao local bloqueada ate existir compilacao manifest-backed para praxis-tabs.',
2494
+ canApply: false,
2495
+ pendingPatch: null,
2496
+ };
2497
+ }
2498
+ cancel() {
2499
+ return Promise.resolve({
2500
+ state: 'listening',
2501
+ phase: 'capture',
2502
+ assistantMessage: 'Solicitacao cancelada.',
2503
+ statusText: '',
2504
+ canApply: false,
2505
+ pendingPatch: null,
2506
+ pendingClarification: null,
2507
+ });
2508
+ }
2509
+ retry(request) {
2510
+ const lastPrompt = [...(request.messages ?? [])].reverse()
2511
+ .find((message) => message.role === 'user')?.text;
2512
+ return this.submit({
2513
+ ...request,
2514
+ prompt: lastPrompt ?? request.prompt,
2515
+ action: { kind: 'retry' },
2516
+ });
2517
+ }
2518
+ toTurnResult(response, request) {
2519
+ if (!response) {
2520
+ return {
2521
+ state: 'error',
2522
+ phase: 'capture',
2523
+ assistantMessage: 'Resposta vazia da IA.',
2524
+ errorText: 'Resposta vazia da IA.',
2525
+ };
2526
+ }
2527
+ if (response.type === 'clarification') {
2528
+ return {
2529
+ state: 'clarification',
2530
+ phase: 'clarify',
2531
+ sessionId: response.sessionId ?? request.sessionId,
2532
+ assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
2533
+ clarificationQuestions: this.toClarificationQuestions(response),
2534
+ quickReplies: this.toQuickReplies(response),
2535
+ canApply: false,
2536
+ };
2537
+ }
2538
+ if (response.type === 'info') {
2539
+ const message = response.message || response.explanation || 'Informacao gerada.';
2540
+ return {
2541
+ state: 'success',
2542
+ phase: 'summarize',
2543
+ sessionId: response.sessionId ?? request.sessionId,
2544
+ assistantMessage: message,
2545
+ statusText: message,
2546
+ canApply: false,
2547
+ };
2548
+ }
2549
+ if (response.type === 'error') {
2550
+ const message = response.message || 'Falha ao gerar alteracao de abas.';
2551
+ return {
2552
+ state: 'error',
2553
+ phase: 'capture',
2554
+ sessionId: response.sessionId ?? request.sessionId,
2555
+ assistantMessage: message,
2556
+ errorText: message,
2557
+ diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
2558
+ };
2559
+ }
2560
+ if (response.patch && Object.keys(response.patch).length > 0) {
2561
+ return {
2562
+ state: 'error',
2563
+ phase: 'review',
2564
+ sessionId: response.sessionId ?? request.sessionId,
2565
+ assistantMessage: 'As abas rejeitaram patch livre. Gere um componentEditPlan validado pelo PRAXIS_TABS_AUTHORING_MANIFEST antes de propor alteracao local.',
2566
+ errorText: 'Patch livre de abas rejeitado.',
2567
+ canApply: false,
2568
+ pendingPatch: null,
2569
+ diagnostics: {
2570
+ warnings: [
2571
+ 'free-tabs-patch-rejected',
2572
+ 'Use componentEditPlan validado contra PRAXIS_TABS_AUTHORING_MANIFEST.',
2573
+ ],
2574
+ },
2575
+ };
2576
+ }
2577
+ return {
2578
+ state: 'success',
2579
+ phase: 'summarize',
2580
+ sessionId: response.sessionId ?? request.sessionId,
2581
+ assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
2582
+ statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
2583
+ canApply: false,
2584
+ };
2585
+ }
2586
+ compileAdapterResponse(response) {
2587
+ const compiled = this.adapter.compileAiResponse?.(response);
2588
+ if (!compiled) {
2589
+ return response;
2590
+ }
2591
+ if (compiled.type === 'error') {
2592
+ return {
2593
+ type: 'error',
2594
+ message: compiled.message || 'O componentEditPlan das abas nao passou na validacao de capacidades.',
2595
+ warnings: compiled.warnings,
2596
+ };
2597
+ }
2598
+ const warnings = [
2599
+ ...(response.warnings ?? []),
2600
+ ...(compiled.warnings ?? []),
2601
+ ];
2602
+ return {
2603
+ ...response,
2604
+ ...compiled,
2605
+ patch: compiled.patch,
2606
+ warnings: warnings.length ? warnings : undefined,
2607
+ };
2608
+ }
2609
+ toChatMessages(messages, prompt) {
2610
+ const supported = (messages ?? [])
2611
+ .filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
2612
+ .map((message) => ({
2613
+ role: message.role,
2614
+ content: message.text,
2615
+ }))
2616
+ .filter((message) => message.content.trim().length > 0);
2617
+ return supported.length ? supported : [{ role: 'user', content: prompt }];
2618
+ }
2619
+ toClarificationQuestions(response) {
2620
+ const labels = response.questions?.length
2621
+ ? response.questions
2622
+ : response.message
2623
+ ? [response.message]
2624
+ : ['Qual ajuste voce quer aplicar nas abas?'];
2625
+ const options = this.toQuickReplies(response).map((reply) => ({
2626
+ id: reply.id,
2627
+ label: reply.label,
2628
+ value: reply.prompt,
2629
+ }));
2630
+ return labels.map((label, index) => ({
2631
+ id: `tabs-clarification-${index + 1}`,
2632
+ type: options.length ? 'single-choice' : 'text',
2633
+ label,
2634
+ allowCustom: true,
2635
+ options,
2636
+ }));
2637
+ }
2638
+ toQuickReplies(response) {
2639
+ const payloads = response.optionPayloads ?? [];
2640
+ if (payloads.length) {
2641
+ return payloads
2642
+ .map((option, index) => {
2643
+ const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
2644
+ const prompt = option.example?.trim() || option.value?.trim() || label;
2645
+ return {
2646
+ id: `option-${index + 1}`,
2647
+ label,
2648
+ prompt,
2649
+ kind: 'clarification-option',
2650
+ };
2651
+ });
2652
+ }
2653
+ return (response.options ?? [])
2654
+ .filter((option) => !!option?.trim())
2655
+ .map((option, index) => ({
2656
+ id: `option-${index + 1}`,
2657
+ label: option.trim(),
2658
+ prompt: option.trim(),
2659
+ kind: 'clarification-option',
2660
+ }));
2661
+ }
2662
+ buildCurrentStateDigest(dataProfile) {
2663
+ const tabCount = typeof dataProfile?.['tabCount'] === 'number' ? dataProfile['tabCount'] : undefined;
2664
+ const linkCount = typeof dataProfile?.['linkCount'] === 'number' ? dataProfile['linkCount'] : undefined;
2665
+ const rowCount = (tabCount ?? 0) + (linkCount ?? 0);
2666
+ return rowCount > 0 ? { rowCount } : {};
2667
+ }
2668
+ shouldRouteToGovernedDecision(prompt, contextHints) {
2669
+ const normalized = prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
2670
+ const recommendedFlow = this.toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
2671
+ if (recommendedFlow === 'shared_rule_authoring')
2672
+ return true;
2673
+ return [
2674
+ 'regra',
2675
+ 'politica',
2676
+ 'policy',
2677
+ 'compliance',
2678
+ 'lgpd',
2679
+ 'privacidade',
2680
+ 'aprovacao',
2681
+ 'aprovar',
2682
+ 'publicar',
2683
+ 'materializar',
2684
+ 'enforcement',
2685
+ 'validacao de negocio',
2686
+ 'validar negocio',
2687
+ 'elegibilidade',
2688
+ 'permissao',
2689
+ 'acesso',
2690
+ ].some((term) => normalized.includes(term));
2691
+ }
2692
+ toGovernedDecisionHandoff(prompt, request) {
2693
+ const message = 'Esse pedido parece alterar uma decisao de negocio compartilhada. As abas podem ajudar a localizar a experiencia afetada, mas a regra deve seguir pelo fluxo governado de domain-rules antes de qualquer materializacao runtime.';
2694
+ return {
2695
+ state: 'clarification',
2696
+ phase: 'clarify',
2697
+ sessionId: request.sessionId,
2698
+ assistantMessage: message,
2699
+ statusText: 'Handoff governado necessario.',
2700
+ canApply: false,
2701
+ quickReplies: [
2702
+ {
2703
+ id: 'shared-rule-handoff',
2704
+ label: 'Continuar como regra governada',
2705
+ prompt,
2706
+ kind: 'shared-rule-handoff',
2707
+ description: 'Criar intake de domain-rules em vez de aplicar patch local nas abas.',
2708
+ icon: 'rule',
2709
+ tone: 'warning',
2710
+ contextHints: {
2711
+ flowId: 'shared_rule_authoring',
2712
+ source: 'praxis-tabs',
2713
+ recommendedAction: 'domain-rules/intake',
2714
+ },
2715
+ },
2716
+ ],
2717
+ clarificationQuestions: [
2718
+ {
2719
+ id: 'tabs-governed-rule-confirmation',
2720
+ type: 'confirm',
2721
+ label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
2722
+ description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
2723
+ required: true,
2724
+ options: [
2725
+ {
2726
+ id: 'shared-rule-handoff',
2727
+ label: 'Sim, continuar governado',
2728
+ value: prompt,
2729
+ description: 'Nao aplicar como patch local das abas.',
2730
+ contextHints: {
2731
+ flowId: 'shared_rule_authoring',
2732
+ source: 'praxis-tabs',
2733
+ },
2734
+ },
2735
+ ],
2736
+ },
2737
+ ],
2738
+ diagnostics: {
2739
+ governedDecisionHandoff: {
2740
+ flowId: 'shared_rule_authoring',
2741
+ sourcePrompt: prompt,
2742
+ sourceComponent: 'praxis-tabs',
2743
+ },
2744
+ },
2745
+ };
2746
+ }
2747
+ optionalJsonObject(value) {
2748
+ if (value === undefined || value === null) {
2749
+ return undefined;
2750
+ }
2751
+ const object = this.toAiJsonObject(value);
2752
+ return Object.keys(object).length ? object : undefined;
2753
+ }
2754
+ toAiJsonObject(value) {
2755
+ const record = this.toRecord(value);
2756
+ if (!record) {
2757
+ return {};
2758
+ }
2759
+ try {
2760
+ return JSON.parse(JSON.stringify(record));
2761
+ }
2762
+ catch {
2763
+ return {};
2764
+ }
2765
+ }
2766
+ toRecord(value) {
2767
+ return value && typeof value === 'object' && !Array.isArray(value)
2768
+ ? value
2769
+ : null;
2770
+ }
2771
+ }
2772
+
2380
2773
  class PraxisTabs {
2381
2774
  i18n = inject(PraxisI18nService);
2382
2775
  settings = inject(SettingsPanelService);
@@ -2384,12 +2777,24 @@ class PraxisTabs {
2384
2777
  snack = inject(MatSnackBar);
2385
2778
  componentKeys = inject(ComponentKeyService);
2386
2779
  logger = inject(LoggerService);
2780
+ cdr = inject(ChangeDetectorRef);
2781
+ aiApi = inject(AiBackendApiService);
2782
+ assistantSessions = inject(PraxisAssistantSessionRegistryService);
2783
+ aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
2387
2784
  route = (() => { try {
2388
2785
  return inject(ActivatedRoute);
2389
2786
  }
2390
2787
  catch {
2391
2788
  return undefined;
2392
2789
  } })();
2790
+ aiAssistantSessionEffect = effect(() => {
2791
+ const session = this.assistantSessions.activeSession();
2792
+ if (!session || session.id !== this.resolveAiAssistantSessionId())
2793
+ return;
2794
+ if (!this.aiAssistantOpen) {
2795
+ this.openAiAssistantFromSession(session);
2796
+ }
2797
+ }, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
2393
2798
  warnedMissingId = false;
2394
2799
  config = null;
2395
2800
  tabsId;
@@ -2411,6 +2816,21 @@ class PraxisTabs {
2411
2816
  selectFocusedIndex = new EventEmitter();
2412
2817
  widgetEvent = new EventEmitter();
2413
2818
  aiAdapter = new TabsAiAdapter(this);
2819
+ aiAssistantOpen = false;
2820
+ aiAssistantPrompt = '';
2821
+ aiAssistantViewState = null;
2822
+ aiAssistantLayout = createPraxisAssistantViewportLayout();
2823
+ aiAssistantLabels = {
2824
+ title: 'Copiloto semantico Praxis',
2825
+ subtitle: 'Converse, revise e governe ajustes das abas.',
2826
+ prompt: 'Mensagem',
2827
+ promptPlaceholder: 'Descreva o ajuste que voce precisa nas abas.',
2828
+ emptyConversation: 'Diga o que voce quer alterar nas abas.',
2829
+ submit: 'Interpretar pedido',
2830
+ apply: 'Aplicar ajuste',
2831
+ };
2832
+ aiAssistantController = null;
2833
+ aiAssistantStateSubscription = null;
2414
2834
  // Signals to manage local state for selection in Nav mode and Group mode
2415
2835
  currentNavIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentNavIndex" }] : []));
2416
2836
  selectedIndexSignal = signal(0, ...(ngDevMode ? [{ debugName: "selectedIndexSignal" }] : []));
@@ -2443,6 +2863,8 @@ class PraxisTabs {
2443
2863
  }
2444
2864
  }
2445
2865
  ngOnDestroy() {
2866
+ this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
2867
+ this.aiAssistantStateSubscription?.unsubscribe();
2446
2868
  this.destroy$.next();
2447
2869
  this.destroy$.complete();
2448
2870
  }
@@ -2681,6 +3103,305 @@ class PraxisTabs {
2681
3103
  ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
2682
3104
  ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
2683
3105
  }
3106
+ openAiAssistant() {
3107
+ this.initializeAiAssistantController();
3108
+ this.aiAssistantOpen = true;
3109
+ this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
3110
+ this.syncAiAssistantSession('active');
3111
+ this.cdr.markForCheck();
3112
+ }
3113
+ openAiAssistantFromSession(session) {
3114
+ if (session.id !== this.resolveAiAssistantSessionId())
3115
+ return;
3116
+ this.initializeAiAssistantController();
3117
+ this.aiAssistantOpen = true;
3118
+ this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
3119
+ this.syncAiAssistantSession('active');
3120
+ this.cdr.markForCheck();
3121
+ }
3122
+ closeAiAssistant() {
3123
+ this.aiAssistantOpen = false;
3124
+ this.syncAiAssistantSession('minimized');
3125
+ this.cdr.markForCheck();
3126
+ }
3127
+ onAiAssistantPromptChange(prompt) {
3128
+ this.aiAssistantPrompt = prompt;
3129
+ this.syncAiAssistantSession();
3130
+ }
3131
+ onAiAssistantSubmit(prompt) {
3132
+ this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
3133
+ this.aiAssistantPrompt = '';
3134
+ this.aiAssistantViewState = state;
3135
+ this.syncAiAssistantSession();
3136
+ this.cdr.markForCheck();
3137
+ });
3138
+ }
3139
+ onAiAssistantApply() {
3140
+ this.aiAssistantController?.apply().subscribe((state) => {
3141
+ this.aiAssistantViewState = state;
3142
+ this.syncAiAssistantSession();
3143
+ this.cdr.markForCheck();
3144
+ });
3145
+ }
3146
+ onAiAssistantRetry() {
3147
+ this.aiAssistantController?.retry().subscribe((state) => {
3148
+ this.aiAssistantViewState = state;
3149
+ this.syncAiAssistantSession();
3150
+ this.cdr.markForCheck();
3151
+ });
3152
+ }
3153
+ onAiAssistantCancel() {
3154
+ this.aiAssistantController?.cancel().subscribe((state) => {
3155
+ this.aiAssistantPrompt = '';
3156
+ this.aiAssistantViewState = state;
3157
+ this.syncAiAssistantSession();
3158
+ this.cdr.markForCheck();
3159
+ });
3160
+ }
3161
+ onAiAssistantQuickReply(reply) {
3162
+ const controller = this.aiAssistantController;
3163
+ if (!controller)
3164
+ return;
3165
+ const state = controller.snapshot();
3166
+ const next$ = state.state === 'clarification'
3167
+ ? controller.answerClarification(reply.prompt)
3168
+ : controller.submitPrompt(reply.prompt, {
3169
+ kind: reply.kind || 'quick-reply',
3170
+ id: reply.id,
3171
+ value: reply.prompt,
3172
+ });
3173
+ next$.subscribe((nextState) => {
3174
+ this.aiAssistantPrompt = '';
3175
+ this.aiAssistantViewState = nextState;
3176
+ this.syncAiAssistantSession();
3177
+ this.cdr.markForCheck();
3178
+ });
3179
+ }
3180
+ onAiAssistantEditMessage(message) {
3181
+ this.aiAssistantPrompt = message.text;
3182
+ this.cdr.markForCheck();
3183
+ }
3184
+ onAiAssistantResendMessage(message) {
3185
+ this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
3186
+ this.aiAssistantPrompt = '';
3187
+ this.aiAssistantViewState = state;
3188
+ this.syncAiAssistantSession();
3189
+ this.cdr.markForCheck();
3190
+ });
3191
+ }
3192
+ onAiAssistantLayoutChange(layout) {
3193
+ this.aiAssistantLayout = layout;
3194
+ }
3195
+ initializeAiAssistantController() {
3196
+ if (this.aiAssistantController)
3197
+ return;
3198
+ const flow = new TabsAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
3199
+ const controller = this.aiTurnOrchestrator.createController(flow, {
3200
+ componentId: this.aiAdapter.componentId || 'praxis-tabs',
3201
+ componentType: this.aiAdapter.componentType || 'tabs',
3202
+ contextItems: this.buildAiAssistantContextItems(),
3203
+ });
3204
+ this.aiAssistantController = controller;
3205
+ this.aiAssistantViewState = controller.snapshot();
3206
+ this.aiAssistantStateSubscription?.unsubscribe();
3207
+ this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
3208
+ this.aiAssistantViewState = state;
3209
+ this.syncAiAssistantSession();
3210
+ this.cdr.markForCheck();
3211
+ });
3212
+ this.cdr.markForCheck();
3213
+ }
3214
+ buildAiAssistantContextItems() {
3215
+ const items = [
3216
+ {
3217
+ id: 'component',
3218
+ label: 'Componente',
3219
+ value: 'Abas',
3220
+ kind: 'component',
3221
+ icon: 'tab',
3222
+ },
3223
+ {
3224
+ id: 'mode',
3225
+ label: 'Modo',
3226
+ value: this.isNavMode() ? 'nav' : 'group',
3227
+ kind: 'custom',
3228
+ icon: 'account_tree',
3229
+ },
3230
+ ];
3231
+ if (this.tabsId) {
3232
+ items.push({
3233
+ id: 'tabs-id',
3234
+ label: 'Tabs',
3235
+ value: this.tabsId,
3236
+ kind: 'custom',
3237
+ icon: 'tag',
3238
+ });
3239
+ }
3240
+ return items;
3241
+ }
3242
+ buildAiAssistantContextSnapshot() {
3243
+ const fieldNames = this.collectContentFieldNames();
3244
+ const counts = this.collectTabsCounts();
3245
+ return {
3246
+ identity: {
3247
+ sessionId: this.resolveAiAssistantSessionId(),
3248
+ ownerId: this.resolveAiAssistantOwnerId(),
3249
+ ownerType: 'tabs',
3250
+ componentId: 'praxis-tabs',
3251
+ componentType: 'tabs',
3252
+ routeKey: this.resolveAiAssistantRouteKey(),
3253
+ },
3254
+ target: {
3255
+ kind: 'component',
3256
+ id: this.resolveAiAssistantOwnerId(),
3257
+ label: this.tabsId || 'Abas',
3258
+ metadata: {
3259
+ mode: this.isNavMode() ? 'nav' : 'group',
3260
+ hasCustomization: !!this.enableCustomization,
3261
+ },
3262
+ },
3263
+ contextItems: this.buildAiAssistantContextItems().map((item) => ({
3264
+ id: item.id,
3265
+ label: item.label,
3266
+ value: item.value || '',
3267
+ kind: item.kind,
3268
+ })),
3269
+ mode: 'agentic-authoring',
3270
+ authoringManifestRef: {
3271
+ componentId: 'praxis-tabs',
3272
+ source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
3273
+ },
3274
+ schemaFields: fieldNames.length ? fieldNames : undefined,
3275
+ dataProfileDigest: {
3276
+ summary: `${counts.tabCount} aba(s), ${counts.linkCount} link(s), ${counts.widgetCount} widget(s), ${counts.fieldCount} campo(s)`,
3277
+ counts,
3278
+ },
3279
+ runtimeStateDigest: {
3280
+ summary: this.isNavMode()
3281
+ ? `Nav ativo no indice ${this.currentNavIndex()}`
3282
+ : `Grupo ativo no indice ${this.selectedIndexSignal()}`,
3283
+ fields: [
3284
+ this.isNavMode() ? 'nav.links' : 'tabs',
3285
+ 'selectedIndex',
3286
+ 'widgetEvent',
3287
+ ],
3288
+ },
3289
+ capabilityRefs: [
3290
+ {
3291
+ id: 'tabs.component-edit-plan',
3292
+ label: 'Plano de edicao de abas',
3293
+ source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
3294
+ risk: 'medium',
3295
+ },
3296
+ ],
3297
+ governanceHints: [
3298
+ {
3299
+ kind: 'business-rule-boundary',
3300
+ label: 'Regras compartilhadas exigem governanca',
3301
+ reason: 'Politicas, validacoes reutilizaveis e compliance nao devem ser aplicados como patch local das abas.',
3302
+ risk: 'high',
3303
+ },
3304
+ ],
3305
+ };
3306
+ }
3307
+ syncAiAssistantSession(visibility = null) {
3308
+ if (!this.enableCustomization)
3309
+ return;
3310
+ if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState())
3311
+ return;
3312
+ const state = this.aiAssistantViewState;
3313
+ this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
3314
+ title: 'Copiloto semantico Praxis',
3315
+ summary: this.resolveAiAssistantSummary(),
3316
+ mode: state?.mode || 'agentic-authoring',
3317
+ state: state?.state || 'idle',
3318
+ visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
3319
+ badge: this.resolveAiAssistantBadge(),
3320
+ icon: this.resolveAiAssistantIcon(),
3321
+ });
3322
+ }
3323
+ hasAiAssistantSessionState() {
3324
+ return !!this.aiAssistantPrompt.trim()
3325
+ || !!this.aiAssistantViewState?.messages?.length
3326
+ || !!this.aiAssistantViewState?.quickReplies?.length
3327
+ || !!this.aiAssistantViewState?.pendingPatch
3328
+ || !!this.aiAssistantViewState?.statusText?.trim()
3329
+ || !!this.aiAssistantViewState?.errorText?.trim();
3330
+ }
3331
+ resolveAiAssistantSessionId() {
3332
+ return `tabs:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
3333
+ }
3334
+ resolveAiAssistantOwnerId() {
3335
+ return (this.componentInstanceId || this.tabsId || 'tabs').trim() || 'tabs';
3336
+ }
3337
+ resolveAiAssistantRouteKey() {
3338
+ const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
3339
+ return routePath || 'local';
3340
+ }
3341
+ resolveAiAssistantSummary() {
3342
+ const status = this.aiAssistantViewState?.statusText?.trim();
3343
+ if (status)
3344
+ return status;
3345
+ const error = this.aiAssistantViewState?.errorText?.trim();
3346
+ if (error)
3347
+ return error;
3348
+ const prompt = this.aiAssistantPrompt.trim();
3349
+ if (prompt)
3350
+ return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
3351
+ const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
3352
+ .find((message) => message.role === 'assistant' || message.role === 'user');
3353
+ if (lastMessage?.text) {
3354
+ return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
3355
+ }
3356
+ return this.isNavMode() ? 'Assistente contextual das abas de navegacao.' : 'Assistente contextual do grupo de abas.';
3357
+ }
3358
+ resolveAiAssistantBadge() {
3359
+ const state = this.aiAssistantViewState?.state;
3360
+ if (state === 'error')
3361
+ return 'erro';
3362
+ if (state === 'clarification')
3363
+ return 'revisar';
3364
+ if (state === 'review')
3365
+ return 'preview';
3366
+ if (state === 'success')
3367
+ return 'ok';
3368
+ return undefined;
3369
+ }
3370
+ resolveAiAssistantIcon() {
3371
+ const state = this.aiAssistantViewState?.state;
3372
+ if (state === 'error')
3373
+ return 'error';
3374
+ if (state === 'clarification')
3375
+ return 'rule';
3376
+ if (state === 'review')
3377
+ return 'rate_review';
3378
+ return 'auto_awesome';
3379
+ }
3380
+ collectContentFieldNames() {
3381
+ const fields = [
3382
+ ...(this.config?.tabs ?? []).flatMap((tab) => tab.content ?? []),
3383
+ ...(this.config?.nav?.links ?? []).flatMap((link) => link.content ?? []),
3384
+ ];
3385
+ return Array.from(new Set(fields
3386
+ .map((field) => field?.name || field?.key || field?.id)
3387
+ .filter((name) => typeof name === 'string' && !!name.trim())));
3388
+ }
3389
+ collectTabsCounts() {
3390
+ const tabs = this.config?.tabs ?? [];
3391
+ const links = this.config?.nav?.links ?? [];
3392
+ return {
3393
+ tabCount: tabs.length,
3394
+ linkCount: links.length,
3395
+ widgetCount: [
3396
+ ...tabs.flatMap((tab) => tab.widgets ?? []),
3397
+ ...links.flatMap((link) => link.widgets ?? []),
3398
+ ].length,
3399
+ fieldCount: [
3400
+ ...tabs.flatMap((tab) => tab.content ?? []),
3401
+ ...links.flatMap((link) => link.content ?? []),
3402
+ ].length,
3403
+ };
3404
+ }
2684
3405
  addEmptyTab() {
2685
3406
  const next = produce(this.config || {}, (draft) => {
2686
3407
  if (!draft.group)
@@ -3067,9 +3788,51 @@ class PraxisTabs {
3067
3788
  <style *ngIf="styleCss() as s" [innerHTML]="s"></style>
3068
3789
 
3069
3790
  <div class="tabs-ai-assistant" *ngIf="enableCustomization">
3070
- <praxis-ai-assistant [adapter]="aiAdapter"></praxis-ai-assistant>
3791
+ <button
3792
+ mat-mini-fab
3793
+ type="button"
3794
+ color="primary"
3795
+ class="tabs-ai-assistant-trigger"
3796
+ (click)="openAiAssistant()"
3797
+ matTooltip="Copiloto semantico Praxis"
3798
+ aria-label="Abrir copiloto semantico Praxis das abas"
3799
+ data-testid="praxis-tabs-ai-assistant-trigger"
3800
+ >
3801
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
3802
+ </button>
3071
3803
  </div>
3072
3804
 
3805
+ <praxis-ai-assistant-shell
3806
+ *ngIf="aiAssistantOpen && aiAssistantViewState"
3807
+ [labels]="aiAssistantLabels"
3808
+ [mode]="aiAssistantViewState.mode"
3809
+ [state]="aiAssistantViewState.state"
3810
+ [contextItems]="aiAssistantViewState.contextItems"
3811
+ [attachments]="aiAssistantViewState.attachments"
3812
+ [messages]="aiAssistantViewState.messages"
3813
+ [quickReplies]="aiAssistantViewState.quickReplies"
3814
+ [prompt]="aiAssistantPrompt"
3815
+ [statusText]="aiAssistantViewState.statusText"
3816
+ [errorText]="aiAssistantViewState.errorText"
3817
+ [busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
3818
+ [canApply]="aiAssistantViewState.canApply"
3819
+ [layout]="aiAssistantLayout"
3820
+ testIdPrefix="praxis-tabs-ai-assistant"
3821
+ panelTestId="praxis-tabs-ai-assistant-panel"
3822
+ submitTestId="praxis-tabs-ai-assistant-submit"
3823
+ applyTestId="praxis-tabs-ai-assistant-apply"
3824
+ (promptChange)="onAiAssistantPromptChange($event)"
3825
+ (submitPrompt)="onAiAssistantSubmit($event)"
3826
+ (apply)="onAiAssistantApply()"
3827
+ (retryTurn)="onAiAssistantRetry()"
3828
+ (cancelTurn)="onAiAssistantCancel()"
3829
+ (quickReply)="onAiAssistantQuickReply($event)"
3830
+ (editMessage)="onAiAssistantEditMessage($event)"
3831
+ (resendMessage)="onAiAssistantResendMessage($event)"
3832
+ (layoutChange)="onAiAssistantLayoutChange($event)"
3833
+ (close)="closeAiAssistant()"
3834
+ ></praxis-ai-assistant-shell>
3835
+
3073
3836
  <!-- Empty state (global) -->
3074
3837
  <ng-container *ngIf="isEmptyGlobal(); else notEmpty">
3075
3838
  <praxis-empty-state-card
@@ -3291,7 +4054,7 @@ class PraxisTabs {
3291
4054
  <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
3292
4055
  </button>
3293
4056
  </div>
3294
- `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { 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: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4057
+ `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { 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: "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 });
3295
4058
  }
3296
4059
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, decorators: [{
3297
4060
  type: Component,
@@ -3307,7 +4070,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3307
4070
  EmptyStateCardComponent,
3308
4071
  DynamicFieldLoaderDirective,
3309
4072
  DynamicWidgetLoaderDirective,
3310
- PraxisAiAssistantComponent,
4073
+ PraxisAiAssistantShellComponent,
3311
4074
  ], template: `
3312
4075
  <div
3313
4076
  class="praxis-tabs-root"
@@ -3325,9 +4088,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3325
4088
  <style *ngIf="styleCss() as s" [innerHTML]="s"></style>
3326
4089
 
3327
4090
  <div class="tabs-ai-assistant" *ngIf="enableCustomization">
3328
- <praxis-ai-assistant [adapter]="aiAdapter"></praxis-ai-assistant>
4091
+ <button
4092
+ mat-mini-fab
4093
+ type="button"
4094
+ color="primary"
4095
+ class="tabs-ai-assistant-trigger"
4096
+ (click)="openAiAssistant()"
4097
+ matTooltip="Copiloto semantico Praxis"
4098
+ aria-label="Abrir copiloto semantico Praxis das abas"
4099
+ data-testid="praxis-tabs-ai-assistant-trigger"
4100
+ >
4101
+ <mat-icon [praxisIcon]="'auto_awesome'"></mat-icon>
4102
+ </button>
3329
4103
  </div>
3330
4104
 
4105
+ <praxis-ai-assistant-shell
4106
+ *ngIf="aiAssistantOpen && aiAssistantViewState"
4107
+ [labels]="aiAssistantLabels"
4108
+ [mode]="aiAssistantViewState.mode"
4109
+ [state]="aiAssistantViewState.state"
4110
+ [contextItems]="aiAssistantViewState.contextItems"
4111
+ [attachments]="aiAssistantViewState.attachments"
4112
+ [messages]="aiAssistantViewState.messages"
4113
+ [quickReplies]="aiAssistantViewState.quickReplies"
4114
+ [prompt]="aiAssistantPrompt"
4115
+ [statusText]="aiAssistantViewState.statusText"
4116
+ [errorText]="aiAssistantViewState.errorText"
4117
+ [busy]="aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'"
4118
+ [canApply]="aiAssistantViewState.canApply"
4119
+ [layout]="aiAssistantLayout"
4120
+ testIdPrefix="praxis-tabs-ai-assistant"
4121
+ panelTestId="praxis-tabs-ai-assistant-panel"
4122
+ submitTestId="praxis-tabs-ai-assistant-submit"
4123
+ applyTestId="praxis-tabs-ai-assistant-apply"
4124
+ (promptChange)="onAiAssistantPromptChange($event)"
4125
+ (submitPrompt)="onAiAssistantSubmit($event)"
4126
+ (apply)="onAiAssistantApply()"
4127
+ (retryTurn)="onAiAssistantRetry()"
4128
+ (cancelTurn)="onAiAssistantCancel()"
4129
+ (quickReply)="onAiAssistantQuickReply($event)"
4130
+ (editMessage)="onAiAssistantEditMessage($event)"
4131
+ (resendMessage)="onAiAssistantResendMessage($event)"
4132
+ (layoutChange)="onAiAssistantLayoutChange($event)"
4133
+ (close)="closeAiAssistant()"
4134
+ ></praxis-ai-assistant-shell>
4135
+
3331
4136
  <!-- Empty state (global) -->
3332
4137
  <ng-container *ngIf="isEmptyGlobal(); else notEmpty">
3333
4138
  <praxis-empty-state-card
@@ -3549,7 +4354,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3549
4354
  <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
3550
4355
  </button>
3551
4356
  </div>
3552
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"] }]
4357
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.tabs-ai-assistant-trigger{box-shadow:var(--md-sys-elevation-level2)}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}.tab-label-icon{font-size:18px;width:18px;height:18px;margin-right:6px;vertical-align:middle}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"] }]
3553
4358
  }], propDecorators: { config: [{
3554
4359
  type: Input
3555
4360
  }], tabsId: [{
@@ -3581,6 +4386,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3581
4386
  type: Output
3582
4387
  }] } });
3583
4388
 
4389
+ class PraxisTabsWidgetConfigEditor {
4390
+ set inputs(value) {
4391
+ this._inputs = value;
4392
+ this.editorDocument = this.createDocument();
4393
+ }
4394
+ get inputs() {
4395
+ return this._inputs;
4396
+ }
4397
+ set widgetKey(value) {
4398
+ this._widgetKey = value;
4399
+ this.editorDocument = this.createDocument();
4400
+ }
4401
+ get widgetKey() {
4402
+ return this._widgetKey;
4403
+ }
4404
+ tabsEditor;
4405
+ isDirty$ = new BehaviorSubject(false);
4406
+ isValid$ = new BehaviorSubject(true);
4407
+ isBusy$ = new BehaviorSubject(false);
4408
+ subscription = new Subscription();
4409
+ emptyConfig = {};
4410
+ _inputs = null;
4411
+ _widgetKey;
4412
+ editorDocument = this.createDocument();
4413
+ get config() {
4414
+ return this.inputs?.config ?? this.emptyConfig;
4415
+ }
4416
+ get tabsId() {
4417
+ return this.inputs?.tabsId ?? this.widgetKey;
4418
+ }
4419
+ get componentInstanceId() {
4420
+ return this.inputs?.componentInstanceId ?? undefined;
4421
+ }
4422
+ ngAfterViewInit() {
4423
+ if (!this.tabsEditor) {
4424
+ return;
4425
+ }
4426
+ this.subscription.add(this.tabsEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
4427
+ this.subscription.add(this.tabsEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
4428
+ this.subscription.add(this.tabsEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
4429
+ }
4430
+ ngOnDestroy() {
4431
+ this.subscription.unsubscribe();
4432
+ }
4433
+ getSettingsValue() {
4434
+ return this.buildValue(this.tabsEditor?.getSettingsValue());
4435
+ }
4436
+ onSave() {
4437
+ return this.buildValue(this.tabsEditor?.onSave?.() ?? this.tabsEditor?.getSettingsValue());
4438
+ }
4439
+ reset() {
4440
+ this.tabsEditor?.reset();
4441
+ }
4442
+ buildValue(document) {
4443
+ const bindings = document?.bindings ?? {};
4444
+ return {
4445
+ inputs: {
4446
+ ...(this.inputs ?? {}),
4447
+ config: document?.config ?? this.config,
4448
+ ...(bindings.tabsId ? { tabsId: bindings.tabsId } : this.tabsId ? { tabsId: this.tabsId } : {}),
4449
+ ...(bindings.componentInstanceId ? { componentInstanceId: bindings.componentInstanceId } : {}),
4450
+ },
4451
+ };
4452
+ }
4453
+ createDocument() {
4454
+ return createTabsAuthoringDocument({
4455
+ config: this.config,
4456
+ bindings: {
4457
+ tabsId: this.tabsId,
4458
+ componentInstanceId: this.componentInstanceId,
4459
+ },
4460
+ });
4461
+ }
4462
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
4463
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsWidgetConfigEditor, isStandalone: true, selector: "praxis-tabs-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "tabsEditor", first: true, predicate: ["tabsEditor"], descendants: true }], ngImport: i0, template: `
4464
+ <section data-testid="tabs-widget-config-editor">
4465
+ <praxis-tabs-config-editor #tabsEditor [document]="editorDocument" />
4466
+ </section>
4467
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: PraxisTabsConfigEditor, selector: "praxis-tabs-config-editor", inputs: ["document"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4468
+ }
4469
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsWidgetConfigEditor, decorators: [{
4470
+ type: Component,
4471
+ args: [{
4472
+ selector: 'praxis-tabs-widget-config-editor',
4473
+ standalone: true,
4474
+ imports: [CommonModule, PraxisTabsConfigEditor],
4475
+ template: `
4476
+ <section data-testid="tabs-widget-config-editor">
4477
+ <praxis-tabs-config-editor #tabsEditor [document]="editorDocument" />
4478
+ </section>
4479
+ `,
4480
+ changeDetection: ChangeDetectionStrategy.OnPush,
4481
+ }]
4482
+ }], propDecorators: { inputs: [{
4483
+ type: Input
4484
+ }], widgetKey: [{
4485
+ type: Input
4486
+ }], tabsEditor: [{
4487
+ type: ViewChild,
4488
+ args: ['tabsEditor']
4489
+ }] } });
4490
+
3584
4491
  const PRAXIS_TABS_PORTS = [
3585
4492
  {
3586
4493
  id: 'context',
@@ -3657,6 +4564,14 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
3657
4564
  friendlyName: 'Praxis Tabs',
3658
4565
  description: 'Abas dinâmicas baseadas em metadata, com MatTabGroup/TabNav e suporte a tokens M3 via appearance.',
3659
4566
  icon: 'tab',
4567
+ authoringManifestRef: {
4568
+ componentId: 'praxis-tabs',
4569
+ source: 'PRAXIS_TABS_AUTHORING_MANIFEST',
4570
+ },
4571
+ configEditor: {
4572
+ component: PraxisTabsWidgetConfigEditor,
4573
+ title: 'Configure tabs',
4574
+ },
3660
4575
  inputs: [
3661
4576
  { name: 'config', type: 'TabsMetadata', label: 'Configuração', description: 'Configuração JSON (tabs/nav, aparência e widgets internos)' },
3662
4577
  {
@@ -4141,4 +5056,4 @@ const PRAXIS_TABS_AUTHORING_MANIFEST = {
4141
5056
  * Generated bundle index. Do not edit.
4142
5057
  */
4143
5058
 
4144
- export { PRAXIS_TABS_AUTHORING_MANIFEST, PRAXIS_TABS_COMPONENT_METADATA, PRAXIS_TABS_I18N_CONFIG, PRAXIS_TABS_I18N_NAMESPACE, PraxisTabs, PraxisTabsConfigEditor, TABS_AI_CAPABILITIES, buildTabsApplyPlan, createPraxisTabsI18nConfig, createTabsAuthoringDocument, normalizeTabsAuthoringDocument, providePraxisTabsI18n, providePraxisTabsMetadata, serializeTabsAuthoringDocument, toCanonicalTabsConfig, validateTabsAuthoringDocument };
5059
+ export { PRAXIS_TABS_AUTHORING_MANIFEST, PRAXIS_TABS_COMPONENT_METADATA, PRAXIS_TABS_I18N_CONFIG, PRAXIS_TABS_I18N_NAMESPACE, PraxisTabs, PraxisTabsConfigEditor, PraxisTabsWidgetConfigEditor, TABS_AI_CAPABILITIES, buildTabsApplyPlan, createPraxisTabsI18nConfig, createTabsAuthoringDocument, normalizeTabsAuthoringDocument, providePraxisTabsI18n, providePraxisTabsMetadata, serializeTabsAuthoringDocument, toCanonicalTabsConfig, validateTabsAuthoringDocument };