@praxisui/manual-form 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.
package/README.md CHANGED
@@ -497,7 +497,10 @@ Apache-2.0 — consulte `LICENSE` neste pacote ou no repositório raiz.
497
497
 
498
498
  ## Notas adicionais
499
499
 
500
- - Quando `enableCustomization` estiver ativo, o container renderiza o assistente AI (Praxis AI) no header do formulario.
500
+ - Quando `enableCustomization` estiver ativo, o container abre o shell compartilhado `PraxisAiAssistantShellComponent` e registra sessao global minimizavel em vez de embutir o assistente legado.
501
+ - O contexto do assistente e seguro e semantico: identidade do formulario, `formId`, contagem de campos/secoes/acoes, nomes de campos, referencia ao `PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST` e hints de governanca.
502
+ - Prompts de regra, politica, compliance, publicacao, materializacao ou enforcement seguem para handoff governado `domain-rules/intake` e nao sao aplicados como patch local do formulario.
503
+ - Patches JSON livres vindos do backend sao rejeitados. O apply local permanece bloqueado ate existir `componentEditPlan` compilado e validado pelo manifesto canonico.
501
504
  - `componentInstanceId` ajuda a compor a chave de persistencia (com `formId` e `persistenceOptions`), evitando conflito entre instancias da mesma tela.
502
505
 
503
506
  ## Comparativo — Manual vs Dinâmico (JSON)
@@ -1,11 +1,11 @@
1
1
  import { ensureIds, DynamicFormService, ASYNC_CONFIG_STORAGE, RULE_PROPERTY_SCHEMA, deepMerge, resolveControlTypeAlias, FieldControlType, normalizeControlTypeKey, FormHooksRegistry, ComponentKeyService, FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, DEFAULT_FIELD_SELECTOR_CONTROL_TYPE_MAP, FieldSelectorRegistry, providePraxisI18n, PraxisI18nService, API_URL } from '@praxisui/core';
2
2
  import * as i0 from '@angular/core';
3
3
  import { inject, Optional, Inject, Injectable, input, output, ChangeDetectionStrategy, Component, Input, InjectionToken, isDevMode, EventEmitter, HostListener, ViewChild, Output, ChangeDetectorRef, DestroyRef, PLATFORM_ID, effect, ContentChildren, signal, computed, Directive } from '@angular/core';
4
- import { BehaviorSubject, debounceTime, fromEvent, of } from 'rxjs';
4
+ import { BehaviorSubject, firstValueFrom, debounceTime, fromEvent, of } from 'rxjs';
5
5
  import { take } from 'rxjs/operators';
6
6
  import { CommonModule, isPlatformBrowser } from '@angular/common';
7
7
  import { ActivatedRoute } from '@angular/router';
8
- import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
8
+ import { BaseAiAdapter, AiBackendApiService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
9
9
  import * as i1 from '@angular/forms';
10
10
  import { FormGroupDirective, FormGroup, FormControl, Validators, ReactiveFormsModule, FormControlName, ControlContainer, FormsModule } from '@angular/forms';
11
11
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -1083,6 +1083,8 @@ const MANUAL_FORM_AI_CAPABILITIES = {
1083
1083
  class ManualFormAiAdapter extends BaseAiAdapter {
1084
1084
  host;
1085
1085
  componentName = 'Manual Form';
1086
+ componentId = 'praxis-manual-form';
1087
+ componentType = 'form';
1086
1088
  constructor(host) {
1087
1089
  super();
1088
1090
  this.host = host;
@@ -1105,6 +1107,48 @@ class ManualFormAiAdapter extends BaseAiAdapter {
1105
1107
  dirty: form?.dirty ?? false,
1106
1108
  };
1107
1109
  }
1110
+ getDataProfile() {
1111
+ const config = this.host.instance?.currentConfig;
1112
+ return {
1113
+ formId: this.safeFormId(),
1114
+ sectionCount: config?.sections?.length ?? 0,
1115
+ fieldCount: config?.fieldMetadata?.length ?? 0,
1116
+ actionCount: [
1117
+ config?.actions?.submit,
1118
+ config?.actions?.cancel,
1119
+ config?.actions?.reset,
1120
+ ...(config?.actions?.custom ?? []),
1121
+ ].filter(Boolean).length,
1122
+ hasPersistence: !!this.host.persistenceOptions(),
1123
+ autoSave: !!this.host.enableAutoSave(),
1124
+ };
1125
+ }
1126
+ getSchemaFields() {
1127
+ return (this.host.instance?.currentConfig?.fieldMetadata ?? [])
1128
+ .map((field) => ({
1129
+ name: field.name,
1130
+ label: field.label,
1131
+ type: field.type,
1132
+ required: !!(field.required || field.validators?.required),
1133
+ readonly: !!field.readonly,
1134
+ hidden: !!field.hidden,
1135
+ }))
1136
+ .filter((field) => typeof field.name === 'string' && !!field.name.trim());
1137
+ }
1138
+ getAuthoringContext() {
1139
+ return {
1140
+ authoringManifestRef: 'PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST',
1141
+ runtimeAuthoringPolicy: {
1142
+ mode: 'agentic-authoring',
1143
+ enableCustomization: !!this.host.enableCustomization(),
1144
+ canApplyLocalPatch: false,
1145
+ reason: 'praxis-manual-form ainda exige componentEditPlan manifest-backed antes de aplicar patch local pelo copiloto.',
1146
+ },
1147
+ domainCatalog: {
1148
+ recommendedAuthoringFlow: 'component_authoring',
1149
+ },
1150
+ };
1151
+ }
1108
1152
  createSnapshot() {
1109
1153
  return this.getCurrentConfig();
1110
1154
  }
@@ -1178,6 +1222,338 @@ class ManualFormAiAdapter extends BaseAiAdapter {
1178
1222
  }
1179
1223
  }
1180
1224
 
1225
+ class ManualFormAgenticAuthoringTurnFlow {
1226
+ adapter;
1227
+ aiApi;
1228
+ mode = 'agentic-authoring';
1229
+ constructor(adapter, aiApi) {
1230
+ this.adapter = adapter;
1231
+ this.aiApi = aiApi;
1232
+ }
1233
+ async submit(request) {
1234
+ const prompt = (request.prompt ?? '').trim();
1235
+ if (!prompt) {
1236
+ return {
1237
+ state: 'listening',
1238
+ phase: 'capture',
1239
+ statusText: '',
1240
+ };
1241
+ }
1242
+ const componentId = this.adapter.componentId || request.componentId || 'praxis-manual-form';
1243
+ const componentType = this.adapter.componentType || request.componentType || 'form';
1244
+ const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
1245
+ const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
1246
+ const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
1247
+ const schemaFields = this.adapter.getSchemaFields?.()
1248
+ ?.map((field) => this.toAiJsonObject(field))
1249
+ .filter((field) => Object.keys(field).length > 0);
1250
+ const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
1251
+ if (this.shouldRouteToGovernedDecision(prompt, contextHints)) {
1252
+ return this.toGovernedDecisionHandoff(prompt, request);
1253
+ }
1254
+ const response = await firstValueFrom(this.aiApi.getPatch({
1255
+ componentId,
1256
+ componentType,
1257
+ userPrompt: prompt,
1258
+ sessionId: request.sessionId,
1259
+ clientTurnId: request.clientTurnId,
1260
+ messages: this.toChatMessages(request.messages, prompt),
1261
+ currentState,
1262
+ currentStateDigest: this.buildCurrentStateDigest(dataProfile),
1263
+ uiContextRef: {
1264
+ componentId,
1265
+ componentType,
1266
+ },
1267
+ ...(dataProfile ? { dataProfile } : {}),
1268
+ ...(runtimeState ? { runtimeState } : {}),
1269
+ ...(schemaFields?.length ? { schemaFields } : {}),
1270
+ ...(contextHints ? { contextHints } : {}),
1271
+ }));
1272
+ return this.toTurnResult(this.compileAdapterResponse(response), request);
1273
+ }
1274
+ async apply(_request) {
1275
+ return {
1276
+ state: 'error',
1277
+ phase: 'apply',
1278
+ assistantMessage: 'O formulario manual ainda exige componentEditPlan validado pelo manifesto antes de aplicar mudancas locais.',
1279
+ errorText: 'Aplicacao local bloqueada ate existir compilacao manifest-backed para praxis-manual-form.',
1280
+ canApply: false,
1281
+ pendingPatch: null,
1282
+ };
1283
+ }
1284
+ cancel() {
1285
+ return Promise.resolve({
1286
+ state: 'listening',
1287
+ phase: 'capture',
1288
+ assistantMessage: 'Solicitacao cancelada.',
1289
+ statusText: '',
1290
+ canApply: false,
1291
+ pendingPatch: null,
1292
+ pendingClarification: null,
1293
+ });
1294
+ }
1295
+ retry(request) {
1296
+ const lastPrompt = [...(request.messages ?? [])].reverse()
1297
+ .find((message) => message.role === 'user')?.text;
1298
+ return this.submit({
1299
+ ...request,
1300
+ prompt: lastPrompt ?? request.prompt,
1301
+ action: { kind: 'retry' },
1302
+ });
1303
+ }
1304
+ toTurnResult(response, request) {
1305
+ if (!response) {
1306
+ return {
1307
+ state: 'error',
1308
+ phase: 'capture',
1309
+ assistantMessage: 'Resposta vazia da IA.',
1310
+ errorText: 'Resposta vazia da IA.',
1311
+ };
1312
+ }
1313
+ if (response.type === 'clarification') {
1314
+ return {
1315
+ state: 'clarification',
1316
+ phase: 'clarify',
1317
+ sessionId: response.sessionId ?? request.sessionId,
1318
+ assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
1319
+ clarificationQuestions: this.toClarificationQuestions(response),
1320
+ quickReplies: this.toQuickReplies(response),
1321
+ canApply: false,
1322
+ };
1323
+ }
1324
+ if (response.type === 'info') {
1325
+ const message = response.message || response.explanation || 'Informacao gerada.';
1326
+ return {
1327
+ state: 'success',
1328
+ phase: 'summarize',
1329
+ sessionId: response.sessionId ?? request.sessionId,
1330
+ assistantMessage: message,
1331
+ statusText: message,
1332
+ canApply: false,
1333
+ };
1334
+ }
1335
+ if (response.type === 'error') {
1336
+ const message = response.message || 'Falha ao gerar alteracao de formulario manual.';
1337
+ return {
1338
+ state: 'error',
1339
+ phase: 'capture',
1340
+ sessionId: response.sessionId ?? request.sessionId,
1341
+ assistantMessage: message,
1342
+ errorText: message,
1343
+ diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
1344
+ };
1345
+ }
1346
+ if (response.patch && Object.keys(response.patch).length > 0) {
1347
+ return {
1348
+ state: 'error',
1349
+ phase: 'review',
1350
+ sessionId: response.sessionId ?? request.sessionId,
1351
+ assistantMessage: 'O formulario manual rejeitou patch livre. Gere um componentEditPlan validado pelo PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST antes de propor alteracao local.',
1352
+ errorText: 'Patch livre de formulario manual rejeitado.',
1353
+ canApply: false,
1354
+ pendingPatch: null,
1355
+ diagnostics: {
1356
+ warnings: [
1357
+ 'free-manual-form-patch-rejected',
1358
+ 'Use componentEditPlan validado contra PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST.',
1359
+ ],
1360
+ },
1361
+ };
1362
+ }
1363
+ return {
1364
+ state: 'success',
1365
+ phase: 'summarize',
1366
+ sessionId: response.sessionId ?? request.sessionId,
1367
+ assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
1368
+ statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
1369
+ canApply: false,
1370
+ };
1371
+ }
1372
+ compileAdapterResponse(response) {
1373
+ const compiled = this.adapter.compileAiResponse?.(response);
1374
+ if (!compiled) {
1375
+ return response;
1376
+ }
1377
+ if (compiled.type === 'error') {
1378
+ return {
1379
+ type: 'error',
1380
+ message: compiled.message || 'O componentEditPlan do formulario manual nao passou na validacao de capacidades.',
1381
+ warnings: compiled.warnings,
1382
+ };
1383
+ }
1384
+ const warnings = [
1385
+ ...(response.warnings ?? []),
1386
+ ...(compiled.warnings ?? []),
1387
+ ];
1388
+ return {
1389
+ ...response,
1390
+ ...compiled,
1391
+ patch: compiled.patch,
1392
+ warnings: warnings.length ? warnings : undefined,
1393
+ };
1394
+ }
1395
+ toChatMessages(messages, prompt) {
1396
+ const supported = (messages ?? [])
1397
+ .filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
1398
+ .map((message) => ({
1399
+ role: message.role,
1400
+ content: message.text,
1401
+ }))
1402
+ .filter((message) => message.content.trim().length > 0);
1403
+ return supported.length ? supported : [{ role: 'user', content: prompt }];
1404
+ }
1405
+ toClarificationQuestions(response) {
1406
+ const labels = response.questions?.length
1407
+ ? response.questions
1408
+ : response.message
1409
+ ? [response.message]
1410
+ : ['Qual ajuste voce quer aplicar no formulario manual?'];
1411
+ const options = this.toQuickReplies(response).map((reply) => ({
1412
+ id: reply.id,
1413
+ label: reply.label,
1414
+ value: reply.prompt,
1415
+ }));
1416
+ return labels.map((label, index) => ({
1417
+ id: `manual-form-clarification-${index + 1}`,
1418
+ type: options.length ? 'single-choice' : 'text',
1419
+ label,
1420
+ allowCustom: true,
1421
+ options,
1422
+ }));
1423
+ }
1424
+ toQuickReplies(response) {
1425
+ const payloads = response.optionPayloads ?? [];
1426
+ if (payloads.length) {
1427
+ return payloads
1428
+ .map((option, index) => {
1429
+ const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
1430
+ const prompt = option.example?.trim() || option.value?.trim() || label;
1431
+ return {
1432
+ id: `option-${index + 1}`,
1433
+ label,
1434
+ prompt,
1435
+ kind: 'clarification-option',
1436
+ };
1437
+ });
1438
+ }
1439
+ return (response.options ?? [])
1440
+ .filter((option) => !!option?.trim())
1441
+ .map((option, index) => ({
1442
+ id: `option-${index + 1}`,
1443
+ label: option.trim(),
1444
+ prompt: option.trim(),
1445
+ kind: 'clarification-option',
1446
+ }));
1447
+ }
1448
+ buildCurrentStateDigest(dataProfile) {
1449
+ const fieldCount = typeof dataProfile?.['fieldCount'] === 'number' ? dataProfile['fieldCount'] : undefined;
1450
+ return fieldCount !== undefined ? { rowCount: fieldCount } : {};
1451
+ }
1452
+ shouldRouteToGovernedDecision(prompt, contextHints) {
1453
+ const normalized = prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
1454
+ const recommendedFlow = this.toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
1455
+ if (recommendedFlow === 'shared_rule_authoring')
1456
+ return true;
1457
+ return [
1458
+ 'regra',
1459
+ 'politica',
1460
+ 'policy',
1461
+ 'compliance',
1462
+ 'lgpd',
1463
+ 'privacidade',
1464
+ 'aprovacao',
1465
+ 'aprovar',
1466
+ 'publicar',
1467
+ 'materializar',
1468
+ 'enforcement',
1469
+ 'validacao de negocio',
1470
+ 'validar negocio',
1471
+ 'elegibilidade',
1472
+ 'permissao',
1473
+ 'acesso',
1474
+ ].some((term) => normalized.includes(term));
1475
+ }
1476
+ toGovernedDecisionHandoff(prompt, request) {
1477
+ const message = 'Esse pedido parece alterar uma decisao de negocio compartilhada. O formulario manual pode localizar campos e experiencia afetada, mas a regra deve seguir pelo fluxo governado de domain-rules antes de qualquer materializacao runtime.';
1478
+ return {
1479
+ state: 'clarification',
1480
+ phase: 'clarify',
1481
+ sessionId: request.sessionId,
1482
+ assistantMessage: message,
1483
+ statusText: 'Handoff governado necessario.',
1484
+ canApply: false,
1485
+ quickReplies: [
1486
+ {
1487
+ id: 'shared-rule-handoff',
1488
+ label: 'Continuar como regra governada',
1489
+ prompt,
1490
+ kind: 'shared-rule-handoff',
1491
+ description: 'Criar intake de domain-rules em vez de aplicar patch local no formulario.',
1492
+ icon: 'rule',
1493
+ tone: 'warning',
1494
+ contextHints: {
1495
+ flowId: 'shared_rule_authoring',
1496
+ source: 'praxis-manual-form',
1497
+ recommendedAction: 'domain-rules/intake',
1498
+ },
1499
+ },
1500
+ ],
1501
+ clarificationQuestions: [
1502
+ {
1503
+ id: 'manual-form-governed-rule-confirmation',
1504
+ type: 'confirm',
1505
+ label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
1506
+ description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
1507
+ required: true,
1508
+ options: [
1509
+ {
1510
+ id: 'shared-rule-handoff',
1511
+ label: 'Sim, continuar governado',
1512
+ value: prompt,
1513
+ description: 'Nao aplicar como patch local do formulario.',
1514
+ contextHints: {
1515
+ flowId: 'shared_rule_authoring',
1516
+ source: 'praxis-manual-form',
1517
+ },
1518
+ },
1519
+ ],
1520
+ },
1521
+ ],
1522
+ diagnostics: {
1523
+ governedDecisionHandoff: {
1524
+ flowId: 'shared_rule_authoring',
1525
+ sourcePrompt: prompt,
1526
+ sourceComponent: 'praxis-manual-form',
1527
+ },
1528
+ },
1529
+ };
1530
+ }
1531
+ optionalJsonObject(value) {
1532
+ if (value === undefined || value === null) {
1533
+ return undefined;
1534
+ }
1535
+ const object = this.toAiJsonObject(value);
1536
+ return Object.keys(object).length ? object : undefined;
1537
+ }
1538
+ toAiJsonObject(value) {
1539
+ const record = this.toRecord(value);
1540
+ if (!record) {
1541
+ return {};
1542
+ }
1543
+ try {
1544
+ return JSON.parse(JSON.stringify(record));
1545
+ }
1546
+ catch {
1547
+ return {};
1548
+ }
1549
+ }
1550
+ toRecord(value) {
1551
+ return value && typeof value === 'object' && !Array.isArray(value)
1552
+ ? value
1553
+ : null;
1554
+ }
1555
+ }
1556
+
1181
1557
  const MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE');
1182
1558
  const MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE');
1183
1559
 
@@ -1629,6 +2005,17 @@ class ManualFormComponent {
1629
2005
  catch {
1630
2006
  return undefined;
1631
2007
  } })();
2008
+ aiApi = inject(AiBackendApiService);
2009
+ assistantSessions = inject(PraxisAssistantSessionRegistryService);
2010
+ aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
2011
+ aiAssistantSessionEffect = effect(() => {
2012
+ const session = this.assistantSessions.activeSession();
2013
+ if (!session || session.id !== this.resolveAiAssistantSessionId())
2014
+ return;
2015
+ if (!this.aiAssistantOpen) {
2016
+ this.openAiAssistantFromSession(session);
2017
+ }
2018
+ }, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
1632
2019
  warnedMissingId = false;
1633
2020
  disableSelectorDefaults = inject(FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, { optional: true }) ?? false;
1634
2021
  selectorToControlType = inject(MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, { optional: true }) ??
@@ -1669,6 +2056,21 @@ class ManualFormComponent {
1669
2056
  instance;
1670
2057
  resolvedActions;
1671
2058
  aiAdapter = new ManualFormAiAdapter(this);
2059
+ aiAssistantOpen = false;
2060
+ aiAssistantPrompt = '';
2061
+ aiAssistantViewState = null;
2062
+ aiAssistantLayout = createPraxisAssistantViewportLayout();
2063
+ aiAssistantLabels = {
2064
+ title: 'Copiloto semantico Praxis',
2065
+ subtitle: 'Converse, revise e governe ajustes do formulario manual.',
2066
+ prompt: 'Mensagem',
2067
+ promptPlaceholder: 'Descreva o ajuste que voce precisa no formulario.',
2068
+ emptyConversation: 'Diga o que voce quer alterar no formulario.',
2069
+ submit: 'Interpretar pedido',
2070
+ apply: 'Aplicar ajuste',
2071
+ };
2072
+ aiAssistantController = null;
2073
+ aiAssistantStateSubscription = null;
1672
2074
  formGroup = new FormGroup({});
1673
2075
  registeredDirectives = [];
1674
2076
  constructor() {
@@ -1720,6 +2122,8 @@ class ManualFormComponent {
1720
2122
  this.metadataSubscription?.unsubscribe();
1721
2123
  this.toolbarOverlayRef?.dispose();
1722
2124
  this.outsideClickSubscription?.unsubscribe();
2125
+ this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
2126
+ this.aiAssistantStateSubscription?.unsubscribe();
1723
2127
  }
1724
2128
  // =============================
1725
2129
  // ControlContainer interface
@@ -1945,6 +2349,308 @@ class ManualFormComponent {
1945
2349
  this.metadataChange.emit(this.instance.currentConfig);
1946
2350
  this.cdr.markForCheck();
1947
2351
  }
2352
+ openAiAssistant() {
2353
+ this.initializeAiAssistantController();
2354
+ this.aiAssistantOpen = true;
2355
+ this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
2356
+ this.syncAiAssistantSession('active');
2357
+ this.cdr.markForCheck();
2358
+ }
2359
+ openAiAssistantFromSession(session) {
2360
+ if (session.id !== this.resolveAiAssistantSessionId())
2361
+ return;
2362
+ this.initializeAiAssistantController();
2363
+ this.aiAssistantOpen = true;
2364
+ this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
2365
+ this.syncAiAssistantSession('active');
2366
+ this.cdr.markForCheck();
2367
+ }
2368
+ closeAiAssistant() {
2369
+ this.aiAssistantOpen = false;
2370
+ this.syncAiAssistantSession('minimized');
2371
+ this.cdr.markForCheck();
2372
+ }
2373
+ onAiAssistantPromptChange(prompt) {
2374
+ this.aiAssistantPrompt = prompt;
2375
+ this.syncAiAssistantSession();
2376
+ }
2377
+ onAiAssistantSubmit(prompt) {
2378
+ this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
2379
+ this.aiAssistantPrompt = '';
2380
+ this.aiAssistantViewState = state;
2381
+ this.syncAiAssistantSession();
2382
+ this.cdr.markForCheck();
2383
+ });
2384
+ }
2385
+ onAiAssistantApply() {
2386
+ this.aiAssistantController?.apply().subscribe((state) => {
2387
+ this.aiAssistantViewState = state;
2388
+ this.syncAiAssistantSession();
2389
+ this.cdr.markForCheck();
2390
+ });
2391
+ }
2392
+ onAiAssistantRetry() {
2393
+ this.aiAssistantController?.retry().subscribe((state) => {
2394
+ this.aiAssistantViewState = state;
2395
+ this.syncAiAssistantSession();
2396
+ this.cdr.markForCheck();
2397
+ });
2398
+ }
2399
+ onAiAssistantCancel() {
2400
+ this.aiAssistantController?.cancel().subscribe((state) => {
2401
+ this.aiAssistantPrompt = '';
2402
+ this.aiAssistantViewState = state;
2403
+ this.syncAiAssistantSession();
2404
+ this.cdr.markForCheck();
2405
+ });
2406
+ }
2407
+ onAiAssistantQuickReply(reply) {
2408
+ const controller = this.aiAssistantController;
2409
+ if (!controller)
2410
+ return;
2411
+ const state = controller.snapshot();
2412
+ const next$ = state.state === 'clarification'
2413
+ ? controller.answerClarification(reply.prompt)
2414
+ : controller.submitPrompt(reply.prompt, {
2415
+ kind: reply.kind || 'quick-reply',
2416
+ id: reply.id,
2417
+ value: reply.prompt,
2418
+ });
2419
+ next$.subscribe((nextState) => {
2420
+ this.aiAssistantPrompt = '';
2421
+ this.aiAssistantViewState = nextState;
2422
+ this.syncAiAssistantSession();
2423
+ this.cdr.markForCheck();
2424
+ });
2425
+ }
2426
+ onAiAssistantEditMessage(message) {
2427
+ this.aiAssistantPrompt = message.text;
2428
+ this.cdr.markForCheck();
2429
+ }
2430
+ onAiAssistantResendMessage(message) {
2431
+ this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
2432
+ this.aiAssistantPrompt = '';
2433
+ this.aiAssistantViewState = state;
2434
+ this.syncAiAssistantSession();
2435
+ this.cdr.markForCheck();
2436
+ });
2437
+ }
2438
+ onAiAssistantLayoutChange(layout) {
2439
+ this.aiAssistantLayout = layout;
2440
+ }
2441
+ initializeAiAssistantController() {
2442
+ if (this.aiAssistantController)
2443
+ return;
2444
+ const flow = new ManualFormAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
2445
+ const controller = this.aiTurnOrchestrator.createController(flow, {
2446
+ componentId: this.aiAdapter.componentId || 'praxis-manual-form',
2447
+ componentType: this.aiAdapter.componentType || 'form',
2448
+ contextItems: this.buildAiAssistantContextItems(),
2449
+ });
2450
+ this.aiAssistantController = controller;
2451
+ this.aiAssistantViewState = controller.snapshot();
2452
+ this.aiAssistantStateSubscription?.unsubscribe();
2453
+ this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
2454
+ this.aiAssistantViewState = state;
2455
+ this.syncAiAssistantSession();
2456
+ this.cdr.markForCheck();
2457
+ });
2458
+ this.cdr.markForCheck();
2459
+ }
2460
+ buildAiAssistantContextItems() {
2461
+ const items = [
2462
+ {
2463
+ id: 'component',
2464
+ label: 'Componente',
2465
+ value: 'Formulario manual',
2466
+ kind: 'component',
2467
+ icon: 'edit_note',
2468
+ },
2469
+ {
2470
+ id: 'form-id',
2471
+ label: 'Formulario',
2472
+ value: this.safeAiAssistantFormId(),
2473
+ kind: 'custom',
2474
+ icon: 'tag',
2475
+ },
2476
+ ];
2477
+ const fieldsCount = this.instance?.currentConfig?.fieldMetadata?.length ?? 0;
2478
+ if (fieldsCount > 0) {
2479
+ items.push({
2480
+ id: 'fields',
2481
+ label: 'Campos',
2482
+ value: String(fieldsCount),
2483
+ kind: 'custom',
2484
+ icon: 'view_list',
2485
+ });
2486
+ }
2487
+ return items;
2488
+ }
2489
+ buildAiAssistantContextSnapshot() {
2490
+ const fieldNames = this.collectAiAssistantFieldNames();
2491
+ const counts = this.collectAiAssistantCounts();
2492
+ return {
2493
+ identity: {
2494
+ sessionId: this.resolveAiAssistantSessionId(),
2495
+ ownerId: this.resolveAiAssistantOwnerId(),
2496
+ ownerType: 'manual-form',
2497
+ componentId: 'praxis-manual-form',
2498
+ componentType: 'form',
2499
+ routeKey: this.resolveAiAssistantRouteKey(),
2500
+ },
2501
+ target: {
2502
+ kind: 'component',
2503
+ id: this.resolveAiAssistantOwnerId(),
2504
+ label: this.formTitle() || this.safeAiAssistantFormId(),
2505
+ metadata: {
2506
+ formId: this.safeAiAssistantFormId(),
2507
+ hasCustomization: !!this.enableCustomization(),
2508
+ },
2509
+ },
2510
+ contextItems: this.buildAiAssistantContextItems().map((item) => ({
2511
+ id: item.id,
2512
+ label: item.label,
2513
+ value: item.value || '',
2514
+ kind: item.kind,
2515
+ })),
2516
+ mode: 'agentic-authoring',
2517
+ authoringManifestRef: {
2518
+ componentId: 'praxis-manual-form',
2519
+ source: 'PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST',
2520
+ },
2521
+ schemaFields: fieldNames.length ? fieldNames : undefined,
2522
+ dataProfileDigest: {
2523
+ summary: `${counts.fieldCount} campo(s), ${counts.sectionCount} secao(oes), ${counts.actionCount} acao(oes)`,
2524
+ counts,
2525
+ },
2526
+ runtimeStateDigest: {
2527
+ summary: this.instance
2528
+ ? `Formulario ${this.formGroup.valid ? 'valido' : 'invalido'} e ${this.formGroup.dirty ? 'alterado' : 'sem alteracoes'}`
2529
+ : 'Formulario ainda sem instancia runtime.',
2530
+ fields: [
2531
+ 'currentConfig',
2532
+ 'fieldMetadata',
2533
+ 'form',
2534
+ 'metadataChange',
2535
+ ],
2536
+ },
2537
+ capabilityRefs: [
2538
+ {
2539
+ id: 'manual-form.component-edit-plan',
2540
+ label: 'Plano de edicao de formulario manual',
2541
+ source: 'PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST',
2542
+ risk: 'medium',
2543
+ },
2544
+ ],
2545
+ governanceHints: [
2546
+ {
2547
+ kind: 'business-rule-boundary',
2548
+ label: 'Regras compartilhadas exigem governanca',
2549
+ reason: 'Politicas, validacoes reutilizaveis e compliance nao devem ser aplicados como patch local do formulario.',
2550
+ risk: 'high',
2551
+ },
2552
+ ],
2553
+ };
2554
+ }
2555
+ syncAiAssistantSession(visibility = null) {
2556
+ if (!this.enableCustomization())
2557
+ return;
2558
+ if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState() && visibility !== 'minimized')
2559
+ return;
2560
+ const state = this.aiAssistantViewState;
2561
+ this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
2562
+ title: 'Copiloto semantico Praxis',
2563
+ summary: this.resolveAiAssistantSummary(),
2564
+ mode: state?.mode || 'agentic-authoring',
2565
+ state: state?.state || 'idle',
2566
+ visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
2567
+ badge: this.resolveAiAssistantBadge(),
2568
+ icon: this.resolveAiAssistantIcon(),
2569
+ });
2570
+ }
2571
+ hasAiAssistantSessionState() {
2572
+ return !!this.aiAssistantPrompt.trim()
2573
+ || !!this.aiAssistantViewState?.messages?.length
2574
+ || !!this.aiAssistantViewState?.quickReplies?.length
2575
+ || !!this.aiAssistantViewState?.pendingPatch
2576
+ || !!this.aiAssistantViewState?.statusText?.trim()
2577
+ || !!this.aiAssistantViewState?.errorText?.trim();
2578
+ }
2579
+ resolveAiAssistantSessionId() {
2580
+ return `manual-form:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
2581
+ }
2582
+ resolveAiAssistantOwnerId() {
2583
+ return (this.componentInstanceId() || this.safeAiAssistantFormId() || 'manual-form').trim() || 'manual-form';
2584
+ }
2585
+ safeAiAssistantFormId() {
2586
+ try {
2587
+ return String(this.formId() || '').trim();
2588
+ }
2589
+ catch {
2590
+ return '';
2591
+ }
2592
+ }
2593
+ resolveAiAssistantRouteKey() {
2594
+ const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
2595
+ return routePath || 'local';
2596
+ }
2597
+ resolveAiAssistantSummary() {
2598
+ const status = this.aiAssistantViewState?.statusText?.trim();
2599
+ if (status)
2600
+ return status;
2601
+ const error = this.aiAssistantViewState?.errorText?.trim();
2602
+ if (error)
2603
+ return error;
2604
+ const prompt = this.aiAssistantPrompt.trim();
2605
+ if (prompt)
2606
+ return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
2607
+ const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
2608
+ .find((message) => message.role === 'assistant' || message.role === 'user');
2609
+ if (lastMessage?.text) {
2610
+ return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
2611
+ }
2612
+ return 'Assistente contextual do formulario manual.';
2613
+ }
2614
+ resolveAiAssistantBadge() {
2615
+ const state = this.aiAssistantViewState?.state;
2616
+ if (state === 'error')
2617
+ return 'erro';
2618
+ if (state === 'clarification')
2619
+ return 'revisar';
2620
+ if (state === 'review')
2621
+ return 'preview';
2622
+ if (state === 'success')
2623
+ return 'ok';
2624
+ return undefined;
2625
+ }
2626
+ resolveAiAssistantIcon() {
2627
+ const state = this.aiAssistantViewState?.state;
2628
+ if (state === 'error')
2629
+ return 'error';
2630
+ if (state === 'clarification')
2631
+ return 'rule';
2632
+ if (state === 'review')
2633
+ return 'rate_review';
2634
+ return 'auto_awesome';
2635
+ }
2636
+ collectAiAssistantFieldNames() {
2637
+ return Array.from(new Set((this.instance?.currentConfig?.fieldMetadata ?? [])
2638
+ .map((field) => field.name)
2639
+ .filter((name) => typeof name === 'string' && !!name.trim())));
2640
+ }
2641
+ collectAiAssistantCounts() {
2642
+ const config = this.instance?.currentConfig;
2643
+ return {
2644
+ sectionCount: config?.sections?.length ?? 0,
2645
+ fieldCount: config?.fieldMetadata?.length ?? 0,
2646
+ actionCount: [
2647
+ config?.actions?.submit,
2648
+ config?.actions?.cancel,
2649
+ config?.actions?.reset,
2650
+ ...(config?.actions?.custom ?? []),
2651
+ ].filter(Boolean).length,
2652
+ };
2653
+ }
1948
2654
  async initialize(fromChanges) {
1949
2655
  this.syncHostFormGroupReference();
1950
2656
  if (!this.formId()) {
@@ -2540,20 +3246,20 @@ class ManualFormComponent {
2540
3246
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ManualFormComponent, isStandalone: true, selector: "praxis-manual-form", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: true, transformFunction: null }, formTitle: { classPropertyName: "formTitle", publicName: "formTitle", isSignal: true, isRequired: false, transformFunction: null }, formDescription: { classPropertyName: "formDescription", publicName: "formDescription", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: true, isRequired: false, transformFunction: null }, enableAutoSave: { classPropertyName: "enableAutoSave", publicName: "enableAutoSave", isSignal: true, isRequired: false, transformFunction: null }, componentInstanceId: { classPropertyName: "componentInstanceId", publicName: "componentInstanceId", isSignal: true, isRequired: false, transformFunction: null }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", isSignal: true, isRequired: false, transformFunction: null }, persistenceOptions: { classPropertyName: "persistenceOptions", publicName: "persistenceOptions", isSignal: true, isRequired: false, transformFunction: null }, usePathNames: { classPropertyName: "usePathNames", publicName: "usePathNames", isSignal: true, isRequired: false, transformFunction: null }, autoSaveDebounceMs: { classPropertyName: "autoSaveDebounceMs", publicName: "autoSaveDebounceMs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { submitted: "submitted", saved: "saved", resetEvent: "reset", metadataChange: "metadataChange" }, providers: [
2541
3247
  // Provide a ControlContainer at the host boundary so projected formControlName can resolve it
2542
3248
  { provide: ControlContainer, useExisting: ManualFormComponent },
2543
- ], queries: [{ propertyName: "formControls", predicate: FormControlName, descendants: true }], ngImport: i0, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header [instance]=\"instance\" [title]=\"formTitle()\" [description]=\"formDescription()\"\n [enableCustomization]=\"enableCustomization()\" (editForm)=\"openFormEditor()\" (save)=\"handleSave()\"\n (reset)=\"handleReset()\"></praxis-manual-form-header>\n }\n\n @if (enableCustomization()) {\n <div class=\"pdx-manual-form__assistant\">\n <praxis-ai-assistant [adapter]=\"aiAdapter\"></praxis-ai-assistant>\n </div>\n }\n\n <form class=\"pdx-manual-form__form\" [formGroup]=\"formGroup\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:var(--pdx-manual-form-gap, 24px);color:var(--md-sys-color-on-surface)}.pdx-manual-form__form{display:grid;gap:var(--pdx-manual-form-field-gap, 16px);padding:var(--pdx-manual-form-padding, 20px);border-radius:var(--pdx-manual-form-radius, 16px);background:var(--pdx-manual-form-surface, var(--md-sys-color-surface-container));border:1px solid var(--pdx-manual-form-outline, var(--md-sys-color-outline-variant));box-shadow:var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__form:focus-within{border-color:var(--pdx-manual-form-focus, var(--md-sys-color-primary));box-shadow:0 0 0 2px var(--md-sys-color-primary),var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__assistant{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }, { kind: "component", type: ManualFormHeaderComponent, selector: "praxis-manual-form-header", inputs: ["instance", "title", "description", "saveLabel", "resetLabel", "enableCustomization", "editFormLabel"], outputs: ["save", "reset", "editForm"] }, { kind: "component", type: ManualFormActionsComponent, selector: "praxis-manual-form-actions", inputs: ["actions", "trackByFn"], outputs: ["actionClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3249
+ ], queries: [{ propertyName: "formControls", predicate: FormControlName, descendants: true }], ngImport: i0, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header [instance]=\"instance\" [title]=\"formTitle()\" [description]=\"formDescription()\"\n [enableCustomization]=\"enableCustomization()\" (editForm)=\"openFormEditor()\" (save)=\"handleSave()\"\n (reset)=\"handleReset()\"></praxis-manual-form-header>\n }\n\n @if (enableCustomization()) {\n <div class=\"pdx-manual-form__assistant\">\n <button\n type=\"button\"\n class=\"pdx-manual-form__assistant-trigger\"\n (click)=\"openAiAssistant()\"\n aria-label=\"Abrir copiloto semantico Praxis do formulario manual\"\n data-testid=\"praxis-manual-form-ai-assistant-trigger\"\n >\n Copiloto Praxis\n </button>\n </div>\n }\n\n @if (aiAssistantOpen && aiAssistantViewState) {\n <praxis-ai-assistant-shell\n [labels]=\"aiAssistantLabels\"\n [mode]=\"aiAssistantViewState.mode\"\n [state]=\"aiAssistantViewState.state\"\n [contextItems]=\"aiAssistantViewState.contextItems\"\n [attachments]=\"aiAssistantViewState.attachments\"\n [messages]=\"aiAssistantViewState.messages\"\n [quickReplies]=\"aiAssistantViewState.quickReplies\"\n [prompt]=\"aiAssistantPrompt\"\n [statusText]=\"aiAssistantViewState.statusText\"\n [errorText]=\"aiAssistantViewState.errorText\"\n [busy]=\"aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'\"\n [canApply]=\"aiAssistantViewState.canApply\"\n [layout]=\"aiAssistantLayout\"\n testIdPrefix=\"praxis-manual-form-ai-assistant\"\n (promptChange)=\"onAiAssistantPromptChange($event)\"\n (submitPrompt)=\"onAiAssistantSubmit($event)\"\n (apply)=\"onAiAssistantApply()\"\n (retryTurn)=\"onAiAssistantRetry()\"\n (cancelTurn)=\"onAiAssistantCancel()\"\n (close)=\"closeAiAssistant()\"\n (quickReply)=\"onAiAssistantQuickReply($event)\"\n (editMessage)=\"onAiAssistantEditMessage($event)\"\n (resendMessage)=\"onAiAssistantResendMessage($event)\"\n (layoutChange)=\"onAiAssistantLayoutChange($event)\"\n ></praxis-ai-assistant-shell>\n }\n\n <form class=\"pdx-manual-form__form\" [formGroup]=\"formGroup\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:var(--pdx-manual-form-gap, 24px);color:var(--md-sys-color-on-surface)}.pdx-manual-form__form{display:grid;gap:var(--pdx-manual-form-field-gap, 16px);padding:var(--pdx-manual-form-padding, 20px);border-radius:var(--pdx-manual-form-radius, 16px);background:var(--pdx-manual-form-surface, var(--md-sys-color-surface-container));border:1px solid var(--pdx-manual-form-outline, var(--md-sys-color-outline-variant));box-shadow:var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__form:focus-within{border-color:var(--pdx-manual-form-focus, var(--md-sys-color-primary));box-shadow:0 0 0 2px var(--md-sys-color-primary),var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__assistant{display:flex;justify-content:flex-end}.pdx-manual-form__assistant-trigger{border:0;border-radius:999px;padding:8px 14px;cursor:pointer;color:var(--md-sys-color-on-primary, #fff);background:var(--md-sys-color-primary, #3451d1);box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}.pdx-manual-form__assistant-trigger:hover{background:var(--md-sys-color-primary-container, #223fb6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { 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"] }, { kind: "component", type: ManualFormHeaderComponent, selector: "praxis-manual-form-header", inputs: ["instance", "title", "description", "saveLabel", "resetLabel", "enableCustomization", "editFormLabel"], outputs: ["save", "reset", "editForm"] }, { kind: "component", type: ManualFormActionsComponent, selector: "praxis-manual-form-actions", inputs: ["actions", "trackByFn"], outputs: ["actionClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2544
3250
  }
2545
3251
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormComponent, decorators: [{
2546
3252
  type: Component,
2547
3253
  args: [{ selector: 'praxis-manual-form', standalone: true, imports: [
2548
3254
  CommonModule,
2549
3255
  ReactiveFormsModule,
2550
- PraxisAiAssistantComponent,
3256
+ PraxisAiAssistantShellComponent,
2551
3257
  ManualFormHeaderComponent,
2552
3258
  ManualFormActionsComponent,
2553
3259
  ], providers: [
2554
3260
  // Provide a ControlContainer at the host boundary so projected formControlName can resolve it
2555
3261
  { provide: ControlContainer, useExisting: ManualFormComponent },
2556
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header [instance]=\"instance\" [title]=\"formTitle()\" [description]=\"formDescription()\"\n [enableCustomization]=\"enableCustomization()\" (editForm)=\"openFormEditor()\" (save)=\"handleSave()\"\n (reset)=\"handleReset()\"></praxis-manual-form-header>\n }\n\n @if (enableCustomization()) {\n <div class=\"pdx-manual-form__assistant\">\n <praxis-ai-assistant [adapter]=\"aiAdapter\"></praxis-ai-assistant>\n </div>\n }\n\n <form class=\"pdx-manual-form__form\" [formGroup]=\"formGroup\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:var(--pdx-manual-form-gap, 24px);color:var(--md-sys-color-on-surface)}.pdx-manual-form__form{display:grid;gap:var(--pdx-manual-form-field-gap, 16px);padding:var(--pdx-manual-form-padding, 20px);border-radius:var(--pdx-manual-form-radius, 16px);background:var(--pdx-manual-form-surface, var(--md-sys-color-surface-container));border:1px solid var(--pdx-manual-form-outline, var(--md-sys-color-outline-variant));box-shadow:var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__form:focus-within{border-color:var(--pdx-manual-form-focus, var(--md-sys-color-primary));box-shadow:0 0 0 2px var(--md-sys-color-primary),var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__assistant{display:flex;justify-content:flex-end}\n"] }]
3262
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header [instance]=\"instance\" [title]=\"formTitle()\" [description]=\"formDescription()\"\n [enableCustomization]=\"enableCustomization()\" (editForm)=\"openFormEditor()\" (save)=\"handleSave()\"\n (reset)=\"handleReset()\"></praxis-manual-form-header>\n }\n\n @if (enableCustomization()) {\n <div class=\"pdx-manual-form__assistant\">\n <button\n type=\"button\"\n class=\"pdx-manual-form__assistant-trigger\"\n (click)=\"openAiAssistant()\"\n aria-label=\"Abrir copiloto semantico Praxis do formulario manual\"\n data-testid=\"praxis-manual-form-ai-assistant-trigger\"\n >\n Copiloto Praxis\n </button>\n </div>\n }\n\n @if (aiAssistantOpen && aiAssistantViewState) {\n <praxis-ai-assistant-shell\n [labels]=\"aiAssistantLabels\"\n [mode]=\"aiAssistantViewState.mode\"\n [state]=\"aiAssistantViewState.state\"\n [contextItems]=\"aiAssistantViewState.contextItems\"\n [attachments]=\"aiAssistantViewState.attachments\"\n [messages]=\"aiAssistantViewState.messages\"\n [quickReplies]=\"aiAssistantViewState.quickReplies\"\n [prompt]=\"aiAssistantPrompt\"\n [statusText]=\"aiAssistantViewState.statusText\"\n [errorText]=\"aiAssistantViewState.errorText\"\n [busy]=\"aiAssistantViewState.state === 'processing' || aiAssistantViewState.state === 'applying'\"\n [canApply]=\"aiAssistantViewState.canApply\"\n [layout]=\"aiAssistantLayout\"\n testIdPrefix=\"praxis-manual-form-ai-assistant\"\n (promptChange)=\"onAiAssistantPromptChange($event)\"\n (submitPrompt)=\"onAiAssistantSubmit($event)\"\n (apply)=\"onAiAssistantApply()\"\n (retryTurn)=\"onAiAssistantRetry()\"\n (cancelTurn)=\"onAiAssistantCancel()\"\n (close)=\"closeAiAssistant()\"\n (quickReply)=\"onAiAssistantQuickReply($event)\"\n (editMessage)=\"onAiAssistantEditMessage($event)\"\n (resendMessage)=\"onAiAssistantResendMessage($event)\"\n (layoutChange)=\"onAiAssistantLayoutChange($event)\"\n ></praxis-ai-assistant-shell>\n }\n\n <form class=\"pdx-manual-form__form\" [formGroup]=\"formGroup\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:var(--pdx-manual-form-gap, 24px);color:var(--md-sys-color-on-surface)}.pdx-manual-form__form{display:grid;gap:var(--pdx-manual-form-field-gap, 16px);padding:var(--pdx-manual-form-padding, 20px);border-radius:var(--pdx-manual-form-radius, 16px);background:var(--pdx-manual-form-surface, var(--md-sys-color-surface-container));border:1px solid var(--pdx-manual-form-outline, var(--md-sys-color-outline-variant));box-shadow:var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__form:focus-within{border-color:var(--pdx-manual-form-focus, var(--md-sys-color-primary));box-shadow:0 0 0 2px var(--md-sys-color-primary),var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__assistant{display:flex;justify-content:flex-end}.pdx-manual-form__assistant-trigger{border:0;border-radius:999px;padding:8px 14px;cursor:pointer;color:var(--md-sys-color-on-primary, #fff);background:var(--md-sys-color-primary, #3451d1);box-shadow:var(--md-sys-elevation-level2, 0 4px 12px rgba(0, 0, 0, .18))}.pdx-manual-form__assistant-trigger:hover{background:var(--md-sys-color-primary-container, #223fb6)}\n"] }]
2557
3263
  }], ctorParameters: () => [], propDecorators: { formId: [{ type: i0.Input, args: [{ isSignal: true, alias: "formId", required: true }] }], formTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "formTitle", required: false }] }], formDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "formDescription", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", required: false }] }], enableAutoSave: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableAutoSave", required: false }] }], componentInstanceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentInstanceId", required: false }] }], enableCustomization: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableCustomization", required: false }] }], persistenceOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "persistenceOptions", required: false }] }], usePathNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "usePathNames", required: false }] }], autoSaveDebounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoSaveDebounceMs", required: false }] }], submitted: [{ type: i0.Output, args: ["submitted"] }], saved: [{ type: i0.Output, args: ["saved"] }], resetEvent: [{ type: i0.Output, args: ["reset"] }], metadataChange: [{ type: i0.Output, args: ["metadataChange"] }], formControls: [{
2558
3264
  type: ContentChildren,
2559
3265
  args: [FormControlName, { descendants: true }]
package/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { FormGroup, FormControlName, FormGroupName, AbstractControl, FormControl
3
3
  import { Observable, BehaviorSubject } from 'rxjs';
4
4
  import * as _angular_core from '@angular/core';
5
5
  import { AfterContentInit, OnDestroy, OnInit, OnChanges, TemplateRef, ViewContainerRef, SimpleChanges, InjectionToken } from '@angular/core';
6
- import { BaseAiAdapter, PatchResult } from '@praxisui/ai';
6
+ import { BaseAiAdapter, PatchResult, PraxisAssistantTurnViewState, PraxisAssistantShellLayout, PraxisAssistantShellLabels, PraxisAssistantSessionSnapshot, PraxisAssistantShellQuickReply, PraxisAssistantShellMessage } from '@praxisui/ai';
7
7
  import { SettingsPanelRef } from '@praxisui/settings-panel';
8
8
 
9
9
  type ManualFormId = string;
@@ -154,10 +154,15 @@ declare class ManualFormActionsComponent {
154
154
  declare class ManualFormAiAdapter extends BaseAiAdapter<FormConfig> {
155
155
  private host;
156
156
  componentName: string;
157
+ componentId: string;
158
+ componentType: string;
157
159
  constructor(host: ManualFormComponent);
158
160
  getCurrentConfig(): FormConfig;
159
161
  getCapabilities(): AiCapability[];
160
162
  getRuntimeState(): Record<string, any>;
163
+ getDataProfile(): Record<string, any>;
164
+ getSchemaFields(): Record<string, any>[];
165
+ getAuthoringContext(): Record<string, any>;
161
166
  createSnapshot(): FormConfig;
162
167
  restoreSnapshot(snapshot: FormConfig): Promise<void>;
163
168
  applyPatch(patch: Partial<FormConfig>, _intent?: string): Promise<PatchResult>;
@@ -179,6 +184,10 @@ declare class ManualFormComponent implements AfterContentInit, OnDestroy {
179
184
  private readonly fieldKeyService;
180
185
  private readonly platformId;
181
186
  private readonly route;
187
+ private readonly aiApi;
188
+ private readonly assistantSessions;
189
+ private readonly aiTurnOrchestrator;
190
+ private readonly aiAssistantSessionEffect;
182
191
  private warnedMissingId;
183
192
  private readonly disableSelectorDefaults;
184
193
  private readonly selectorToControlType;
@@ -216,6 +225,13 @@ declare class ManualFormComponent implements AfterContentInit, OnDestroy {
216
225
  instance?: ManualFormInstance;
217
226
  resolvedActions?: FormActionsLayout;
218
227
  aiAdapter: ManualFormAiAdapter;
228
+ aiAssistantOpen: boolean;
229
+ aiAssistantPrompt: string;
230
+ aiAssistantViewState: PraxisAssistantTurnViewState | null;
231
+ aiAssistantLayout: PraxisAssistantShellLayout;
232
+ readonly aiAssistantLabels: Partial<PraxisAssistantShellLabels>;
233
+ private aiAssistantController;
234
+ private aiAssistantStateSubscription;
219
235
  formGroup: FormGroup;
220
236
  private readonly registeredDirectives;
221
237
  constructor();
@@ -246,6 +262,32 @@ declare class ManualFormComponent implements AfterContentInit, OnDestroy {
246
262
  /** Opens a simple form-level editor listing the fields and their visibility. */
247
263
  openFormEditor(): Promise<void>;
248
264
  applyConfigFromAdapter(config: FormConfig): void;
265
+ openAiAssistant(): void;
266
+ openAiAssistantFromSession(session: PraxisAssistantSessionSnapshot): void;
267
+ closeAiAssistant(): void;
268
+ onAiAssistantPromptChange(prompt: string): void;
269
+ onAiAssistantSubmit(prompt: string): void;
270
+ onAiAssistantApply(): void;
271
+ onAiAssistantRetry(): void;
272
+ onAiAssistantCancel(): void;
273
+ onAiAssistantQuickReply(reply: PraxisAssistantShellQuickReply): void;
274
+ onAiAssistantEditMessage(message: PraxisAssistantShellMessage): void;
275
+ onAiAssistantResendMessage(message: PraxisAssistantShellMessage): void;
276
+ onAiAssistantLayoutChange(layout: PraxisAssistantShellLayout): void;
277
+ private initializeAiAssistantController;
278
+ private buildAiAssistantContextItems;
279
+ private buildAiAssistantContextSnapshot;
280
+ private syncAiAssistantSession;
281
+ private hasAiAssistantSessionState;
282
+ private resolveAiAssistantSessionId;
283
+ private resolveAiAssistantOwnerId;
284
+ private safeAiAssistantFormId;
285
+ private resolveAiAssistantRouteKey;
286
+ private resolveAiAssistantSummary;
287
+ private resolveAiAssistantBadge;
288
+ private resolveAiAssistantIcon;
289
+ private collectAiAssistantFieldNames;
290
+ private collectAiAssistantCounts;
249
291
  private initialize;
250
292
  private collectFields;
251
293
  private componentKeyId;
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@praxisui/manual-form",
3
- "version": "8.0.0-beta.20",
3
+ "version": "8.0.0-beta.22",
4
4
  "description": "Manual form toolkit for Praxis UI: container, instance factory and editor bridge for @praxisui/* fields.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.1.0",
7
7
  "@angular/core": "^20.1.0",
8
- "@praxisui/core": "^8.0.0-beta.20",
9
- "@praxisui/dynamic-fields": "^8.0.0-beta.20",
10
- "@praxisui/settings-panel": "^8.0.0-beta.20",
11
- "@praxisui/metadata-editor": "^8.0.0-beta.20",
8
+ "@praxisui/core": "^8.0.0-beta.22",
9
+ "@praxisui/dynamic-fields": "^8.0.0-beta.22",
10
+ "@praxisui/settings-panel": "^8.0.0-beta.22",
11
+ "@praxisui/metadata-editor": "^8.0.0-beta.22",
12
12
  "@angular/cdk": "^20.1.0",
13
13
  "@angular/forms": "^20.1.0",
14
14
  "@angular/material": "^20.1.0",
15
15
  "@angular/router": "^20.1.0",
16
- "@praxisui/ai": "^8.0.0-beta.20",
16
+ "@praxisui/ai": "^8.0.0-beta.22",
17
17
  "rxjs": "~7.8.0"
18
18
  },
19
19
  "dependencies": {