@praxisui/manual-form 8.0.0-beta.3 → 8.0.0-beta.30
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 +30 -1
- package/fesm2022/praxisui-manual-form.mjs +1034 -7
- package/index.d.ts +47 -3
- package/package.json +11 -5
|
@@ -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,
|
|
8
|
+
import { BaseAiAdapter, shouldRoutePromptToGovernedDecision, 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';
|
|
@@ -576,7 +576,7 @@ const ENUMS = {
|
|
|
576
576
|
headerAlign: ['start', 'center'],
|
|
577
577
|
schemaType: ['response', 'request'],
|
|
578
578
|
customizationSource: ['user', 'server', 'system'],
|
|
579
|
-
formRuleTargetType: ['field', 'section', 'action', 'row', 'column'],
|
|
579
|
+
formRuleTargetType: ['field', 'section', 'action', 'row', 'column', 'visualBlock'],
|
|
580
580
|
formRuleContext: ['visibility', 'readOnly', 'style', 'validation', 'notification'],
|
|
581
581
|
mode: ['create', 'edit', 'view'],
|
|
582
582
|
schemaSource: ['resource', 'filter'],
|
|
@@ -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,317 @@ 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
|
+
return shouldRoutePromptToGovernedDecision(prompt, contextHints);
|
|
1454
|
+
}
|
|
1455
|
+
toGovernedDecisionHandoff(prompt, request) {
|
|
1456
|
+
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.';
|
|
1457
|
+
return {
|
|
1458
|
+
state: 'clarification',
|
|
1459
|
+
phase: 'clarify',
|
|
1460
|
+
sessionId: request.sessionId,
|
|
1461
|
+
assistantMessage: message,
|
|
1462
|
+
statusText: 'Handoff governado necessario.',
|
|
1463
|
+
canApply: false,
|
|
1464
|
+
quickReplies: [
|
|
1465
|
+
{
|
|
1466
|
+
id: 'shared-rule-handoff',
|
|
1467
|
+
label: 'Continuar como regra governada',
|
|
1468
|
+
prompt,
|
|
1469
|
+
kind: 'shared-rule-handoff',
|
|
1470
|
+
description: 'Criar intake de domain-rules em vez de aplicar patch local no formulario.',
|
|
1471
|
+
icon: 'rule',
|
|
1472
|
+
tone: 'warning',
|
|
1473
|
+
contextHints: {
|
|
1474
|
+
flowId: 'shared_rule_authoring',
|
|
1475
|
+
source: 'praxis-manual-form',
|
|
1476
|
+
recommendedAction: 'domain-rules/intake',
|
|
1477
|
+
},
|
|
1478
|
+
},
|
|
1479
|
+
],
|
|
1480
|
+
clarificationQuestions: [
|
|
1481
|
+
{
|
|
1482
|
+
id: 'manual-form-governed-rule-confirmation',
|
|
1483
|
+
type: 'confirm',
|
|
1484
|
+
label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
|
|
1485
|
+
description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
|
|
1486
|
+
required: true,
|
|
1487
|
+
options: [
|
|
1488
|
+
{
|
|
1489
|
+
id: 'shared-rule-handoff',
|
|
1490
|
+
label: 'Sim, continuar governado',
|
|
1491
|
+
value: prompt,
|
|
1492
|
+
description: 'Nao aplicar como patch local do formulario.',
|
|
1493
|
+
contextHints: {
|
|
1494
|
+
flowId: 'shared_rule_authoring',
|
|
1495
|
+
source: 'praxis-manual-form',
|
|
1496
|
+
},
|
|
1497
|
+
},
|
|
1498
|
+
],
|
|
1499
|
+
},
|
|
1500
|
+
],
|
|
1501
|
+
diagnostics: {
|
|
1502
|
+
governedDecisionHandoff: {
|
|
1503
|
+
flowId: 'shared_rule_authoring',
|
|
1504
|
+
sourcePrompt: prompt,
|
|
1505
|
+
sourceComponent: 'praxis-manual-form',
|
|
1506
|
+
},
|
|
1507
|
+
},
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
optionalJsonObject(value) {
|
|
1511
|
+
if (value === undefined || value === null) {
|
|
1512
|
+
return undefined;
|
|
1513
|
+
}
|
|
1514
|
+
const object = this.toAiJsonObject(value);
|
|
1515
|
+
return Object.keys(object).length ? object : undefined;
|
|
1516
|
+
}
|
|
1517
|
+
toAiJsonObject(value) {
|
|
1518
|
+
const record = this.toRecord(value);
|
|
1519
|
+
if (!record) {
|
|
1520
|
+
return {};
|
|
1521
|
+
}
|
|
1522
|
+
try {
|
|
1523
|
+
return JSON.parse(JSON.stringify(record));
|
|
1524
|
+
}
|
|
1525
|
+
catch {
|
|
1526
|
+
return {};
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
toRecord(value) {
|
|
1530
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1531
|
+
? value
|
|
1532
|
+
: null;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1181
1536
|
const MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE');
|
|
1182
1537
|
const MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE');
|
|
1183
1538
|
|
|
@@ -1629,6 +1984,17 @@ class ManualFormComponent {
|
|
|
1629
1984
|
catch {
|
|
1630
1985
|
return undefined;
|
|
1631
1986
|
} })();
|
|
1987
|
+
aiApi = inject(AiBackendApiService);
|
|
1988
|
+
assistantSessions = inject(PraxisAssistantSessionRegistryService);
|
|
1989
|
+
aiTurnOrchestrator = inject(PraxisAssistantTurnOrchestratorService);
|
|
1990
|
+
aiAssistantSessionEffect = effect(() => {
|
|
1991
|
+
const session = this.assistantSessions.activeSession();
|
|
1992
|
+
if (!session || session.id !== this.resolveAiAssistantSessionId())
|
|
1993
|
+
return;
|
|
1994
|
+
if (!this.aiAssistantOpen) {
|
|
1995
|
+
this.openAiAssistantFromSession(session);
|
|
1996
|
+
}
|
|
1997
|
+
}, ...(ngDevMode ? [{ debugName: "aiAssistantSessionEffect" }] : []));
|
|
1632
1998
|
warnedMissingId = false;
|
|
1633
1999
|
disableSelectorDefaults = inject(FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, { optional: true }) ?? false;
|
|
1634
2000
|
selectorToControlType = inject(MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, { optional: true }) ??
|
|
@@ -1669,6 +2035,21 @@ class ManualFormComponent {
|
|
|
1669
2035
|
instance;
|
|
1670
2036
|
resolvedActions;
|
|
1671
2037
|
aiAdapter = new ManualFormAiAdapter(this);
|
|
2038
|
+
aiAssistantOpen = false;
|
|
2039
|
+
aiAssistantPrompt = '';
|
|
2040
|
+
aiAssistantViewState = null;
|
|
2041
|
+
aiAssistantLayout = createPraxisAssistantViewportLayout();
|
|
2042
|
+
aiAssistantLabels = {
|
|
2043
|
+
title: 'Copiloto semantico Praxis',
|
|
2044
|
+
subtitle: 'Converse, revise e governe ajustes do formulario manual.',
|
|
2045
|
+
prompt: 'Mensagem',
|
|
2046
|
+
promptPlaceholder: 'Descreva o ajuste que voce precisa no formulario.',
|
|
2047
|
+
emptyConversation: 'Diga o que voce quer alterar no formulario.',
|
|
2048
|
+
submit: 'Interpretar pedido',
|
|
2049
|
+
apply: 'Aplicar ajuste',
|
|
2050
|
+
};
|
|
2051
|
+
aiAssistantController = null;
|
|
2052
|
+
aiAssistantStateSubscription = null;
|
|
1672
2053
|
formGroup = new FormGroup({});
|
|
1673
2054
|
registeredDirectives = [];
|
|
1674
2055
|
constructor() {
|
|
@@ -1720,6 +2101,8 @@ class ManualFormComponent {
|
|
|
1720
2101
|
this.metadataSubscription?.unsubscribe();
|
|
1721
2102
|
this.toolbarOverlayRef?.dispose();
|
|
1722
2103
|
this.outsideClickSubscription?.unsubscribe();
|
|
2104
|
+
this.assistantSessions.removeContextSession(this.buildAiAssistantContextSnapshot().identity);
|
|
2105
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
1723
2106
|
}
|
|
1724
2107
|
// =============================
|
|
1725
2108
|
// ControlContainer interface
|
|
@@ -1945,6 +2328,308 @@ class ManualFormComponent {
|
|
|
1945
2328
|
this.metadataChange.emit(this.instance.currentConfig);
|
|
1946
2329
|
this.cdr.markForCheck();
|
|
1947
2330
|
}
|
|
2331
|
+
openAiAssistant() {
|
|
2332
|
+
this.initializeAiAssistantController();
|
|
2333
|
+
this.aiAssistantOpen = true;
|
|
2334
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
2335
|
+
this.syncAiAssistantSession('active');
|
|
2336
|
+
this.cdr.markForCheck();
|
|
2337
|
+
}
|
|
2338
|
+
openAiAssistantFromSession(session) {
|
|
2339
|
+
if (session.id !== this.resolveAiAssistantSessionId())
|
|
2340
|
+
return;
|
|
2341
|
+
this.initializeAiAssistantController();
|
|
2342
|
+
this.aiAssistantOpen = true;
|
|
2343
|
+
this.aiAssistantController?.setContextItems(this.buildAiAssistantContextItems());
|
|
2344
|
+
this.syncAiAssistantSession('active');
|
|
2345
|
+
this.cdr.markForCheck();
|
|
2346
|
+
}
|
|
2347
|
+
closeAiAssistant() {
|
|
2348
|
+
this.aiAssistantOpen = false;
|
|
2349
|
+
this.syncAiAssistantSession('minimized');
|
|
2350
|
+
this.cdr.markForCheck();
|
|
2351
|
+
}
|
|
2352
|
+
onAiAssistantPromptChange(prompt) {
|
|
2353
|
+
this.aiAssistantPrompt = prompt;
|
|
2354
|
+
this.syncAiAssistantSession();
|
|
2355
|
+
}
|
|
2356
|
+
onAiAssistantSubmit(prompt) {
|
|
2357
|
+
this.aiAssistantController?.submitPrompt(prompt).subscribe((state) => {
|
|
2358
|
+
this.aiAssistantPrompt = '';
|
|
2359
|
+
this.aiAssistantViewState = state;
|
|
2360
|
+
this.syncAiAssistantSession();
|
|
2361
|
+
this.cdr.markForCheck();
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
onAiAssistantApply() {
|
|
2365
|
+
this.aiAssistantController?.apply().subscribe((state) => {
|
|
2366
|
+
this.aiAssistantViewState = state;
|
|
2367
|
+
this.syncAiAssistantSession();
|
|
2368
|
+
this.cdr.markForCheck();
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
onAiAssistantRetry() {
|
|
2372
|
+
this.aiAssistantController?.retry().subscribe((state) => {
|
|
2373
|
+
this.aiAssistantViewState = state;
|
|
2374
|
+
this.syncAiAssistantSession();
|
|
2375
|
+
this.cdr.markForCheck();
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
onAiAssistantCancel() {
|
|
2379
|
+
this.aiAssistantController?.cancel().subscribe((state) => {
|
|
2380
|
+
this.aiAssistantPrompt = '';
|
|
2381
|
+
this.aiAssistantViewState = state;
|
|
2382
|
+
this.syncAiAssistantSession();
|
|
2383
|
+
this.cdr.markForCheck();
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
onAiAssistantQuickReply(reply) {
|
|
2387
|
+
const controller = this.aiAssistantController;
|
|
2388
|
+
if (!controller)
|
|
2389
|
+
return;
|
|
2390
|
+
const state = controller.snapshot();
|
|
2391
|
+
const next$ = state.state === 'clarification'
|
|
2392
|
+
? controller.answerClarification(reply.prompt)
|
|
2393
|
+
: controller.submitPrompt(reply.prompt, {
|
|
2394
|
+
kind: reply.kind || 'quick-reply',
|
|
2395
|
+
id: reply.id,
|
|
2396
|
+
value: reply.prompt,
|
|
2397
|
+
});
|
|
2398
|
+
next$.subscribe((nextState) => {
|
|
2399
|
+
this.aiAssistantPrompt = '';
|
|
2400
|
+
this.aiAssistantViewState = nextState;
|
|
2401
|
+
this.syncAiAssistantSession();
|
|
2402
|
+
this.cdr.markForCheck();
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
onAiAssistantEditMessage(message) {
|
|
2406
|
+
this.aiAssistantPrompt = message.text;
|
|
2407
|
+
this.cdr.markForCheck();
|
|
2408
|
+
}
|
|
2409
|
+
onAiAssistantResendMessage(message) {
|
|
2410
|
+
this.aiAssistantController?.resendMessage(message.id).subscribe((state) => {
|
|
2411
|
+
this.aiAssistantPrompt = '';
|
|
2412
|
+
this.aiAssistantViewState = state;
|
|
2413
|
+
this.syncAiAssistantSession();
|
|
2414
|
+
this.cdr.markForCheck();
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
onAiAssistantLayoutChange(layout) {
|
|
2418
|
+
this.aiAssistantLayout = layout;
|
|
2419
|
+
}
|
|
2420
|
+
initializeAiAssistantController() {
|
|
2421
|
+
if (this.aiAssistantController)
|
|
2422
|
+
return;
|
|
2423
|
+
const flow = new ManualFormAgenticAuthoringTurnFlow(this.aiAdapter, this.aiApi);
|
|
2424
|
+
const controller = this.aiTurnOrchestrator.createController(flow, {
|
|
2425
|
+
componentId: this.aiAdapter.componentId || 'praxis-manual-form',
|
|
2426
|
+
componentType: this.aiAdapter.componentType || 'form',
|
|
2427
|
+
contextItems: this.buildAiAssistantContextItems(),
|
|
2428
|
+
});
|
|
2429
|
+
this.aiAssistantController = controller;
|
|
2430
|
+
this.aiAssistantViewState = controller.snapshot();
|
|
2431
|
+
this.aiAssistantStateSubscription?.unsubscribe();
|
|
2432
|
+
this.aiAssistantStateSubscription = controller.state$.subscribe((state) => {
|
|
2433
|
+
this.aiAssistantViewState = state;
|
|
2434
|
+
this.syncAiAssistantSession();
|
|
2435
|
+
this.cdr.markForCheck();
|
|
2436
|
+
});
|
|
2437
|
+
this.cdr.markForCheck();
|
|
2438
|
+
}
|
|
2439
|
+
buildAiAssistantContextItems() {
|
|
2440
|
+
const items = [
|
|
2441
|
+
{
|
|
2442
|
+
id: 'component',
|
|
2443
|
+
label: 'Componente',
|
|
2444
|
+
value: 'Formulario manual',
|
|
2445
|
+
kind: 'component',
|
|
2446
|
+
icon: 'edit_note',
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
id: 'form-id',
|
|
2450
|
+
label: 'Formulario',
|
|
2451
|
+
value: this.safeAiAssistantFormId(),
|
|
2452
|
+
kind: 'custom',
|
|
2453
|
+
icon: 'tag',
|
|
2454
|
+
},
|
|
2455
|
+
];
|
|
2456
|
+
const fieldsCount = this.instance?.currentConfig?.fieldMetadata?.length ?? 0;
|
|
2457
|
+
if (fieldsCount > 0) {
|
|
2458
|
+
items.push({
|
|
2459
|
+
id: 'fields',
|
|
2460
|
+
label: 'Campos',
|
|
2461
|
+
value: String(fieldsCount),
|
|
2462
|
+
kind: 'custom',
|
|
2463
|
+
icon: 'view_list',
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
return items;
|
|
2467
|
+
}
|
|
2468
|
+
buildAiAssistantContextSnapshot() {
|
|
2469
|
+
const fieldNames = this.collectAiAssistantFieldNames();
|
|
2470
|
+
const counts = this.collectAiAssistantCounts();
|
|
2471
|
+
return {
|
|
2472
|
+
identity: {
|
|
2473
|
+
sessionId: this.resolveAiAssistantSessionId(),
|
|
2474
|
+
ownerId: this.resolveAiAssistantOwnerId(),
|
|
2475
|
+
ownerType: 'manual-form',
|
|
2476
|
+
componentId: 'praxis-manual-form',
|
|
2477
|
+
componentType: 'form',
|
|
2478
|
+
routeKey: this.resolveAiAssistantRouteKey(),
|
|
2479
|
+
},
|
|
2480
|
+
target: {
|
|
2481
|
+
kind: 'component',
|
|
2482
|
+
id: this.resolveAiAssistantOwnerId(),
|
|
2483
|
+
label: this.formTitle() || this.safeAiAssistantFormId(),
|
|
2484
|
+
metadata: {
|
|
2485
|
+
formId: this.safeAiAssistantFormId(),
|
|
2486
|
+
hasCustomization: !!this.enableCustomization(),
|
|
2487
|
+
},
|
|
2488
|
+
},
|
|
2489
|
+
contextItems: this.buildAiAssistantContextItems().map((item) => ({
|
|
2490
|
+
id: item.id,
|
|
2491
|
+
label: item.label,
|
|
2492
|
+
value: item.value || '',
|
|
2493
|
+
kind: item.kind,
|
|
2494
|
+
})),
|
|
2495
|
+
mode: 'agentic-authoring',
|
|
2496
|
+
authoringManifestRef: {
|
|
2497
|
+
componentId: 'praxis-manual-form',
|
|
2498
|
+
source: 'PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST',
|
|
2499
|
+
},
|
|
2500
|
+
schemaFields: fieldNames.length ? fieldNames : undefined,
|
|
2501
|
+
dataProfileDigest: {
|
|
2502
|
+
summary: `${counts.fieldCount} campo(s), ${counts.sectionCount} secao(oes), ${counts.actionCount} acao(oes)`,
|
|
2503
|
+
counts,
|
|
2504
|
+
},
|
|
2505
|
+
runtimeStateDigest: {
|
|
2506
|
+
summary: this.instance
|
|
2507
|
+
? `Formulario ${this.formGroup.valid ? 'valido' : 'invalido'} e ${this.formGroup.dirty ? 'alterado' : 'sem alteracoes'}`
|
|
2508
|
+
: 'Formulario ainda sem instancia runtime.',
|
|
2509
|
+
fields: [
|
|
2510
|
+
'currentConfig',
|
|
2511
|
+
'fieldMetadata',
|
|
2512
|
+
'form',
|
|
2513
|
+
'metadataChange',
|
|
2514
|
+
],
|
|
2515
|
+
},
|
|
2516
|
+
capabilityRefs: [
|
|
2517
|
+
{
|
|
2518
|
+
id: 'manual-form.component-edit-plan',
|
|
2519
|
+
label: 'Plano de edicao de formulario manual',
|
|
2520
|
+
source: 'PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST',
|
|
2521
|
+
risk: 'medium',
|
|
2522
|
+
},
|
|
2523
|
+
],
|
|
2524
|
+
governanceHints: [
|
|
2525
|
+
{
|
|
2526
|
+
kind: 'business-rule-boundary',
|
|
2527
|
+
label: 'Regras compartilhadas exigem governanca',
|
|
2528
|
+
reason: 'Politicas, validacoes reutilizaveis e compliance nao devem ser aplicados como patch local do formulario.',
|
|
2529
|
+
risk: 'high',
|
|
2530
|
+
},
|
|
2531
|
+
],
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
syncAiAssistantSession(visibility = null) {
|
|
2535
|
+
if (!this.enableCustomization())
|
|
2536
|
+
return;
|
|
2537
|
+
if (!this.aiAssistantOpen && !this.hasAiAssistantSessionState() && visibility !== 'minimized')
|
|
2538
|
+
return;
|
|
2539
|
+
const state = this.aiAssistantViewState;
|
|
2540
|
+
this.assistantSessions.upsertContextSession(this.buildAiAssistantContextSnapshot(), {
|
|
2541
|
+
title: 'Copiloto semantico Praxis',
|
|
2542
|
+
summary: this.resolveAiAssistantSummary(),
|
|
2543
|
+
mode: state?.mode || 'agentic-authoring',
|
|
2544
|
+
state: state?.state || 'idle',
|
|
2545
|
+
visibility: visibility ?? (this.aiAssistantOpen ? 'active' : 'minimized'),
|
|
2546
|
+
badge: this.resolveAiAssistantBadge(),
|
|
2547
|
+
icon: this.resolveAiAssistantIcon(),
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
hasAiAssistantSessionState() {
|
|
2551
|
+
return !!this.aiAssistantPrompt.trim()
|
|
2552
|
+
|| !!this.aiAssistantViewState?.messages?.length
|
|
2553
|
+
|| !!this.aiAssistantViewState?.quickReplies?.length
|
|
2554
|
+
|| !!this.aiAssistantViewState?.pendingPatch
|
|
2555
|
+
|| !!this.aiAssistantViewState?.statusText?.trim()
|
|
2556
|
+
|| !!this.aiAssistantViewState?.errorText?.trim();
|
|
2557
|
+
}
|
|
2558
|
+
resolveAiAssistantSessionId() {
|
|
2559
|
+
return `manual-form:${this.resolveAiAssistantRouteKey()}:${this.resolveAiAssistantOwnerId()}`;
|
|
2560
|
+
}
|
|
2561
|
+
resolveAiAssistantOwnerId() {
|
|
2562
|
+
return (this.componentInstanceId() || this.safeAiAssistantFormId() || 'manual-form').trim() || 'manual-form';
|
|
2563
|
+
}
|
|
2564
|
+
safeAiAssistantFormId() {
|
|
2565
|
+
try {
|
|
2566
|
+
return String(this.formId() || '').trim();
|
|
2567
|
+
}
|
|
2568
|
+
catch {
|
|
2569
|
+
return '';
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
resolveAiAssistantRouteKey() {
|
|
2573
|
+
const routePath = this.route?.snapshot?.routeConfig?.path?.trim();
|
|
2574
|
+
return routePath || 'local';
|
|
2575
|
+
}
|
|
2576
|
+
resolveAiAssistantSummary() {
|
|
2577
|
+
const status = this.aiAssistantViewState?.statusText?.trim();
|
|
2578
|
+
if (status)
|
|
2579
|
+
return status;
|
|
2580
|
+
const error = this.aiAssistantViewState?.errorText?.trim();
|
|
2581
|
+
if (error)
|
|
2582
|
+
return error;
|
|
2583
|
+
const prompt = this.aiAssistantPrompt.trim();
|
|
2584
|
+
if (prompt)
|
|
2585
|
+
return prompt.length > 96 ? `${prompt.slice(0, 93)}...` : prompt;
|
|
2586
|
+
const lastMessage = [...(this.aiAssistantViewState?.messages ?? [])].reverse()
|
|
2587
|
+
.find((message) => message.role === 'assistant' || message.role === 'user');
|
|
2588
|
+
if (lastMessage?.text) {
|
|
2589
|
+
return lastMessage.text.length > 96 ? `${lastMessage.text.slice(0, 93)}...` : lastMessage.text;
|
|
2590
|
+
}
|
|
2591
|
+
return 'Assistente contextual do formulario manual.';
|
|
2592
|
+
}
|
|
2593
|
+
resolveAiAssistantBadge() {
|
|
2594
|
+
const state = this.aiAssistantViewState?.state;
|
|
2595
|
+
if (state === 'error')
|
|
2596
|
+
return 'erro';
|
|
2597
|
+
if (state === 'clarification')
|
|
2598
|
+
return 'revisar';
|
|
2599
|
+
if (state === 'review')
|
|
2600
|
+
return 'preview';
|
|
2601
|
+
if (state === 'success')
|
|
2602
|
+
return 'ok';
|
|
2603
|
+
return undefined;
|
|
2604
|
+
}
|
|
2605
|
+
resolveAiAssistantIcon() {
|
|
2606
|
+
const state = this.aiAssistantViewState?.state;
|
|
2607
|
+
if (state === 'error')
|
|
2608
|
+
return 'error';
|
|
2609
|
+
if (state === 'clarification')
|
|
2610
|
+
return 'rule';
|
|
2611
|
+
if (state === 'review')
|
|
2612
|
+
return 'rate_review';
|
|
2613
|
+
return 'auto_awesome';
|
|
2614
|
+
}
|
|
2615
|
+
collectAiAssistantFieldNames() {
|
|
2616
|
+
return Array.from(new Set((this.instance?.currentConfig?.fieldMetadata ?? [])
|
|
2617
|
+
.map((field) => field.name)
|
|
2618
|
+
.filter((name) => typeof name === 'string' && !!name.trim())));
|
|
2619
|
+
}
|
|
2620
|
+
collectAiAssistantCounts() {
|
|
2621
|
+
const config = this.instance?.currentConfig;
|
|
2622
|
+
return {
|
|
2623
|
+
sectionCount: config?.sections?.length ?? 0,
|
|
2624
|
+
fieldCount: config?.fieldMetadata?.length ?? 0,
|
|
2625
|
+
actionCount: [
|
|
2626
|
+
config?.actions?.submit,
|
|
2627
|
+
config?.actions?.cancel,
|
|
2628
|
+
config?.actions?.reset,
|
|
2629
|
+
...(config?.actions?.custom ?? []),
|
|
2630
|
+
].filter(Boolean).length,
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
1948
2633
|
async initialize(fromChanges) {
|
|
1949
2634
|
this.syncHostFormGroupReference();
|
|
1950
2635
|
if (!this.formId()) {
|
|
@@ -2540,20 +3225,20 @@ class ManualFormComponent {
|
|
|
2540
3225
|
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
3226
|
// Provide a ControlContainer at the host boundary so projected formControlName can resolve it
|
|
2542
3227
|
{ 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 [
|
|
3228
|
+
], 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
3229
|
}
|
|
2545
3230
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormComponent, decorators: [{
|
|
2546
3231
|
type: Component,
|
|
2547
3232
|
args: [{ selector: 'praxis-manual-form', standalone: true, imports: [
|
|
2548
3233
|
CommonModule,
|
|
2549
3234
|
ReactiveFormsModule,
|
|
2550
|
-
|
|
3235
|
+
PraxisAiAssistantShellComponent,
|
|
2551
3236
|
ManualFormHeaderComponent,
|
|
2552
3237
|
ManualFormActionsComponent,
|
|
2553
3238
|
], providers: [
|
|
2554
3239
|
// Provide a ControlContainer at the host boundary so projected formControlName can resolve it
|
|
2555
3240
|
{ 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 [
|
|
3241
|
+
], 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
3242
|
}], 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
3243
|
type: ContentChildren,
|
|
2559
3244
|
args: [FormControlName, { descendants: true }]
|
|
@@ -5359,6 +6044,348 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
5359
6044
|
args: ['dblclick']
|
|
5360
6045
|
}] } });
|
|
5361
6046
|
|
|
6047
|
+
const manualFieldAddSchema = {
|
|
6048
|
+
type: 'object',
|
|
6049
|
+
required: ['fieldName', 'controlType'],
|
|
6050
|
+
properties: {
|
|
6051
|
+
fieldName: { type: 'string' },
|
|
6052
|
+
controlType: { type: 'string' },
|
|
6053
|
+
label: { type: 'string' },
|
|
6054
|
+
required: { type: 'boolean' },
|
|
6055
|
+
sectionId: { type: 'string' },
|
|
6056
|
+
source: { enum: ['manual-template', 'detected-component', 'metadata-bridge'] },
|
|
6057
|
+
delegateFieldMetadataTo: { enum: ['praxis-metadata-editor'] },
|
|
6058
|
+
},
|
|
6059
|
+
};
|
|
6060
|
+
const manualFieldRemoveSchema = {
|
|
6061
|
+
type: 'object',
|
|
6062
|
+
required: ['fieldName'],
|
|
6063
|
+
properties: {
|
|
6064
|
+
fieldName: { type: 'string' },
|
|
6065
|
+
removeFromLayout: { type: 'boolean' },
|
|
6066
|
+
clearPersistedValue: { type: 'boolean' },
|
|
6067
|
+
},
|
|
6068
|
+
};
|
|
6069
|
+
const manualFieldLabelSetSchema = {
|
|
6070
|
+
type: 'object',
|
|
6071
|
+
required: ['fieldName', 'label'],
|
|
6072
|
+
properties: {
|
|
6073
|
+
fieldName: { type: 'string' },
|
|
6074
|
+
label: { type: 'string' },
|
|
6075
|
+
updatePlaceholderWhenEmpty: { type: 'boolean' },
|
|
6076
|
+
},
|
|
6077
|
+
};
|
|
6078
|
+
const layoutConfigureSchema = {
|
|
6079
|
+
type: 'object',
|
|
6080
|
+
minProperties: 1,
|
|
6081
|
+
properties: {
|
|
6082
|
+
usePathNames: { type: 'boolean' },
|
|
6083
|
+
sections: { type: 'array' },
|
|
6084
|
+
rows: { type: 'array' },
|
|
6085
|
+
columns: { type: 'array' },
|
|
6086
|
+
fieldOrder: {
|
|
6087
|
+
type: 'array',
|
|
6088
|
+
items: { type: 'string' },
|
|
6089
|
+
},
|
|
6090
|
+
delegateAdvancedFormConfigTo: { enum: ['praxis-dynamic-form'] },
|
|
6091
|
+
},
|
|
6092
|
+
};
|
|
6093
|
+
const toolbarConfigureSchema = {
|
|
6094
|
+
type: 'object',
|
|
6095
|
+
minProperties: 1,
|
|
6096
|
+
properties: {
|
|
6097
|
+
enableCustomization: { type: 'boolean' },
|
|
6098
|
+
editableFlags: {
|
|
6099
|
+
type: 'array',
|
|
6100
|
+
items: { enum: ['required', 'readOnly', 'hidden', 'disabled', 'openMetadataEditor'] },
|
|
6101
|
+
},
|
|
6102
|
+
},
|
|
6103
|
+
};
|
|
6104
|
+
const autosaveEnabledSetSchema = {
|
|
6105
|
+
type: 'object',
|
|
6106
|
+
required: ['enabled'],
|
|
6107
|
+
properties: {
|
|
6108
|
+
enabled: { type: 'boolean' },
|
|
6109
|
+
debounceMs: { type: 'number' },
|
|
6110
|
+
storageKey: { type: 'string' },
|
|
6111
|
+
namespace: { type: 'string' },
|
|
6112
|
+
tenantId: { type: 'string' },
|
|
6113
|
+
profileId: { type: 'string' },
|
|
6114
|
+
},
|
|
6115
|
+
};
|
|
6116
|
+
const submitBehaviorSetSchema = {
|
|
6117
|
+
type: 'object',
|
|
6118
|
+
minProperties: 1,
|
|
6119
|
+
properties: {
|
|
6120
|
+
action: { enum: ['submit', 'saveDraft', 'reset', 'cancel', 'custom'] },
|
|
6121
|
+
clearAfterSave: { type: 'boolean' },
|
|
6122
|
+
focusFirstError: { type: 'boolean' },
|
|
6123
|
+
scrollToErrors: { type: 'boolean' },
|
|
6124
|
+
confirmOnUnsavedChanges: { type: 'boolean' },
|
|
6125
|
+
delegateFormSubmitTo: { enum: ['praxis-dynamic-form'] },
|
|
6126
|
+
},
|
|
6127
|
+
};
|
|
6128
|
+
const metadataBridgeConfigureSchema = {
|
|
6129
|
+
type: 'object',
|
|
6130
|
+
minProperties: 1,
|
|
6131
|
+
properties: {
|
|
6132
|
+
enabled: { type: 'boolean' },
|
|
6133
|
+
fieldName: { type: 'string' },
|
|
6134
|
+
openMode: { enum: ['field-toolbar', 'double-click', 'programmatic'] },
|
|
6135
|
+
persistDraftAfterPatch: { type: 'boolean' },
|
|
6136
|
+
delegateFieldMetadataTo: { enum: ['praxis-metadata-editor'] },
|
|
6137
|
+
delegateControlDiscoveryTo: { enum: ['praxis-dynamic-fields'] },
|
|
6138
|
+
},
|
|
6139
|
+
};
|
|
6140
|
+
const PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST = {
|
|
6141
|
+
schemaVersion: '1.0.0',
|
|
6142
|
+
componentId: 'praxis-manual-form',
|
|
6143
|
+
ownerPackage: '@praxisui/manual-form',
|
|
6144
|
+
configSchemaId: 'ManualFormInstance',
|
|
6145
|
+
manifestVersion: '1.0.0',
|
|
6146
|
+
runtimeInputs: [
|
|
6147
|
+
{ name: 'formId', type: 'string', description: 'Required manual form id used for runtime state and persistence key composition.' },
|
|
6148
|
+
{ name: 'formGroup', type: 'FormGroup | null', description: 'Host-owned typed FormGroup adopted by the manual form runtime when provided.' },
|
|
6149
|
+
{ name: 'formTitle', type: 'string | null', description: 'Manual form title override.' },
|
|
6150
|
+
{ name: 'formDescription', type: 'string | null', description: 'Manual form description override.' },
|
|
6151
|
+
{ name: 'actions', type: 'FormActionsLayout | null', description: 'Manual toolbar/actions layout.' },
|
|
6152
|
+
{ name: 'enableAutoSave', type: 'boolean', description: 'Enables draft persistence through ManualFormInstance.saveDraft.' },
|
|
6153
|
+
{ name: 'autoSaveDebounceMs', type: 'number', description: 'Autosave debounce in milliseconds.' },
|
|
6154
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Enables field toolbar and metadata editor bridge.' },
|
|
6155
|
+
{ name: 'persistenceOptions', type: 'ManualFormPersistenceOptions | null', description: 'Namespace, tenant, profile, locale and storage key controls.' },
|
|
6156
|
+
{ name: 'usePathNames', type: 'boolean', description: 'Uses FormControlName.path as FieldMetadata.name for nested manual forms.' },
|
|
6157
|
+
],
|
|
6158
|
+
editableTargets: [
|
|
6159
|
+
{ kind: 'manualField', resolver: 'manual-form-field-by-name', description: 'Detected or declared manual field tracked by ManualFormInstance field metadata.' },
|
|
6160
|
+
{ kind: 'layout', resolver: 'manual-form-layout', description: 'Manual layout ownership: host template layout plus optional FormConfig sections for authored structure.' },
|
|
6161
|
+
{ kind: 'toolbar', resolver: 'manual-form-customization-toolbar', description: 'Customization toolbar and quick metadata toggles.' },
|
|
6162
|
+
{ kind: 'autosave', resolver: 'manual-form-autosave-policy', description: 'Autosave enablement, debounce and persistence key controls.' },
|
|
6163
|
+
{ kind: 'metadataBridge', resolver: 'manual-field-metadata-bridge', description: 'Bridge from manual fields to @praxisui/metadata-editor patches.' },
|
|
6164
|
+
{ kind: 'submitBehavior', resolver: 'manual-form-submit-behavior', description: 'Submit, reset, validation focus and unsaved-change behavior.' },
|
|
6165
|
+
],
|
|
6166
|
+
operations: [
|
|
6167
|
+
{
|
|
6168
|
+
operationId: 'manualField.add',
|
|
6169
|
+
title: 'Add manual field metadata',
|
|
6170
|
+
scope: 'field',
|
|
6171
|
+
targetKind: 'manualField',
|
|
6172
|
+
target: { kind: 'manualField', resolver: 'manual-form-field-by-name', ambiguityPolicy: 'fail', required: true },
|
|
6173
|
+
inputSchema: manualFieldAddSchema,
|
|
6174
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-field-add', handlerContract: {
|
|
6175
|
+
reads: ['ManualFormInstance.currentConfig', 'ManualFormInstance.currentFieldMetadata', 'ManualFieldKeyService', 'dynamic-fields-control-discovery'],
|
|
6176
|
+
writes: ['currentConfig.fieldMetadata', 'currentFieldMetadata', 'form'],
|
|
6177
|
+
identityKeys: ['fieldName'],
|
|
6178
|
+
inputSchema: manualFieldAddSchema,
|
|
6179
|
+
failureModes: ['duplicate-manual-field-id', 'control-type-not-discovered', 'host-template-field-missing', 'field-metadata-delegation-required'],
|
|
6180
|
+
description: 'Adds metadata for a manual field while preserving host-owned template layout and delegating advanced FieldMetadata edits.',
|
|
6181
|
+
} }],
|
|
6182
|
+
validators: ['manual-field-id-unique', 'control-type-discovered', 'host-template-field-exists', 'metadata-bridge-does-not-redefine-schema', 'manual-form-round-trip'],
|
|
6183
|
+
affectedPaths: ['currentConfig.fieldMetadata', 'currentFieldMetadata', 'form'],
|
|
6184
|
+
submissionImpact: 'affects-schema-backed-data',
|
|
6185
|
+
preconditions: ['manual-form-instance-loaded'],
|
|
6186
|
+
destructive: false,
|
|
6187
|
+
requiresConfirmation: false,
|
|
6188
|
+
},
|
|
6189
|
+
{
|
|
6190
|
+
operationId: 'manualField.remove',
|
|
6191
|
+
title: 'Remove manual field metadata',
|
|
6192
|
+
scope: 'field',
|
|
6193
|
+
targetKind: 'manualField',
|
|
6194
|
+
target: { kind: 'manualField', resolver: 'manual-form-field-by-name', ambiguityPolicy: 'fail', required: true },
|
|
6195
|
+
inputSchema: manualFieldRemoveSchema,
|
|
6196
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-field-remove', handlerContract: {
|
|
6197
|
+
reads: ['ManualFormInstance.currentConfig', 'ManualFormInstance.currentFieldMetadata', 'ManualFormInstance.form', 'FormConfig.sections'],
|
|
6198
|
+
writes: ['currentConfig.fieldMetadata', 'currentConfig.sections', 'currentFieldMetadata', 'form'],
|
|
6199
|
+
identityKeys: ['fieldName'],
|
|
6200
|
+
inputSchema: manualFieldRemoveSchema,
|
|
6201
|
+
failureModes: ['manual-field-not-found', 'field-still-referenced-by-layout', 'persisted-value-removal-not-confirmed'],
|
|
6202
|
+
description: 'Removes a manual field by stable field name and requires explicit confirmation before metadata, layout or value loss.',
|
|
6203
|
+
} }],
|
|
6204
|
+
destructive: true,
|
|
6205
|
+
requiresConfirmation: true,
|
|
6206
|
+
validators: ['manual-field-exists', 'field-removal-confirmed', 'layout-field-references-valid', 'manual-form-round-trip'],
|
|
6207
|
+
affectedPaths: ['currentConfig.fieldMetadata', 'currentConfig.sections', 'currentFieldMetadata', 'form'],
|
|
6208
|
+
submissionImpact: 'affects-schema-backed-data',
|
|
6209
|
+
preconditions: ['manual-form-instance-loaded', 'explicit-confirmation-provided'],
|
|
6210
|
+
},
|
|
6211
|
+
{
|
|
6212
|
+
operationId: 'manualField.label.set',
|
|
6213
|
+
title: 'Set manual field label',
|
|
6214
|
+
scope: 'field',
|
|
6215
|
+
targetKind: 'manualField',
|
|
6216
|
+
target: { kind: 'manualField', resolver: 'manual-form-field-by-name', ambiguityPolicy: 'fail', required: true },
|
|
6217
|
+
inputSchema: manualFieldLabelSetSchema,
|
|
6218
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-field-label-set', handlerContract: {
|
|
6219
|
+
reads: ['ManualFormInstance.currentFieldMetadata', 'ManualFieldMetadataBridgeService'],
|
|
6220
|
+
writes: ['currentConfig.fieldMetadata[].label', 'currentConfig.fieldMetadata[].placeholder', 'currentFieldMetadata'],
|
|
6221
|
+
identityKeys: ['fieldName'],
|
|
6222
|
+
inputSchema: manualFieldLabelSetSchema,
|
|
6223
|
+
failureModes: ['manual-field-not-found', 'label-empty', 'metadata-patch-rejected'],
|
|
6224
|
+
description: 'Sets a manual field label through the metadata bridge without redefining backend schema semantics.',
|
|
6225
|
+
} }],
|
|
6226
|
+
validators: ['manual-field-exists', 'field-label-valid', 'metadata-bridge-does-not-redefine-schema', 'manual-form-round-trip'],
|
|
6227
|
+
affectedPaths: ['currentConfig.fieldMetadata[].label', 'currentConfig.fieldMetadata[].placeholder', 'currentFieldMetadata'],
|
|
6228
|
+
submissionImpact: 'visual-only',
|
|
6229
|
+
preconditions: ['manual-form-instance-loaded'],
|
|
6230
|
+
destructive: false,
|
|
6231
|
+
requiresConfirmation: false,
|
|
6232
|
+
},
|
|
6233
|
+
{
|
|
6234
|
+
operationId: 'layout.configure',
|
|
6235
|
+
title: 'Configure manual form layout',
|
|
6236
|
+
scope: 'layout',
|
|
6237
|
+
targetKind: 'layout',
|
|
6238
|
+
target: { kind: 'layout', resolver: 'manual-form-layout', ambiguityPolicy: 'fail', required: false },
|
|
6239
|
+
inputSchema: layoutConfigureSchema,
|
|
6240
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-layout-configure', handlerContract: {
|
|
6241
|
+
reads: ['ManualFormInstance.currentConfig', 'FormConfig.sections', 'host-template-layout', 'praxis-dynamic-form-authoring-manifest'],
|
|
6242
|
+
writes: ['currentConfig.sections', 'usePathNames'],
|
|
6243
|
+
identityKeys: ['sectionId', 'rowId', 'columnId', 'fieldName'],
|
|
6244
|
+
inputSchema: layoutConfigureSchema,
|
|
6245
|
+
failureModes: ['layout-field-reference-missing', 'advanced-form-config-delegation-required', 'host-template-layout-conflict'],
|
|
6246
|
+
description: 'Configures manual layout metadata and delegates advanced FormConfig layout semantics to the dynamic-form manifest.',
|
|
6247
|
+
} }],
|
|
6248
|
+
validators: ['layout-field-references-valid', 'manual-layout-does-not-replace-host-template', 'delegates-form-config', 'manual-form-round-trip'],
|
|
6249
|
+
affectedPaths: ['currentConfig.sections', 'usePathNames'],
|
|
6250
|
+
submissionImpact: 'config-only',
|
|
6251
|
+
preconditions: ['manual-form-instance-loaded'],
|
|
6252
|
+
destructive: false,
|
|
6253
|
+
requiresConfirmation: false,
|
|
6254
|
+
},
|
|
6255
|
+
{
|
|
6256
|
+
operationId: 'toolbar.configure',
|
|
6257
|
+
title: 'Configure manual form customization toolbar',
|
|
6258
|
+
scope: 'toolbarUi',
|
|
6259
|
+
targetKind: 'toolbar',
|
|
6260
|
+
target: { kind: 'toolbar', resolver: 'manual-form-customization-toolbar', ambiguityPolicy: 'fail', required: false },
|
|
6261
|
+
inputSchema: toolbarConfigureSchema,
|
|
6262
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-toolbar-configure', handlerContract: {
|
|
6263
|
+
reads: ['ManualFormComponent.enableCustomization', 'ManualFieldToolbarComponent', 'ManualFieldEditorOnDblclickDirective'],
|
|
6264
|
+
writes: ['enableCustomization'],
|
|
6265
|
+
identityKeys: ['formId'],
|
|
6266
|
+
inputSchema: toolbarConfigureSchema,
|
|
6267
|
+
failureModes: ['toolbar-disabled-with-required-bridge', 'unsupported-toolbar-flag'],
|
|
6268
|
+
description: 'Configures the manual customization toolbar and keeps metadata edit entry points behind enableCustomization.',
|
|
6269
|
+
} }],
|
|
6270
|
+
validators: ['toolbar-flags-supported', 'metadata-bridge-gated-by-customization', 'manual-form-round-trip'],
|
|
6271
|
+
affectedPaths: ['enableCustomization'],
|
|
6272
|
+
submissionImpact: 'visual-only',
|
|
6273
|
+
preconditions: ['manual-form-component-loaded'],
|
|
6274
|
+
destructive: false,
|
|
6275
|
+
requiresConfirmation: false,
|
|
6276
|
+
},
|
|
6277
|
+
{
|
|
6278
|
+
operationId: 'autosave.enabled.set',
|
|
6279
|
+
title: 'Set manual form autosave policy',
|
|
6280
|
+
scope: 'interaction',
|
|
6281
|
+
targetKind: 'autosave',
|
|
6282
|
+
target: { kind: 'autosave', resolver: 'manual-form-autosave-policy', ambiguityPolicy: 'fail', required: false },
|
|
6283
|
+
inputSchema: autosaveEnabledSetSchema,
|
|
6284
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-autosave-enabled-set', handlerContract: {
|
|
6285
|
+
reads: ['enableAutoSave', 'autoSaveDebounceMs', 'ManualFormPersistenceOptions', 'ASYNC_CONFIG_STORAGE'],
|
|
6286
|
+
writes: ['enableAutoSave', 'autoSaveDebounceMs', 'persistenceOptions'],
|
|
6287
|
+
identityKeys: ['formId', 'storageKey', 'namespace', 'tenantId', 'profileId'],
|
|
6288
|
+
inputSchema: autosaveEnabledSetSchema,
|
|
6289
|
+
failureModes: ['autosave-storage-missing', 'unsafe-debounce', 'ambiguous-storage-key', 'ssr-storage-not-safe'],
|
|
6290
|
+
description: 'Sets autosave explicitly and requires safe debounce plus deterministic persistence key composition.',
|
|
6291
|
+
} }],
|
|
6292
|
+
validators: ['autosave-explicit', 'autosave-debounce-safe', 'autosave-storage-available', 'persistence-key-deterministic', 'manual-form-round-trip'],
|
|
6293
|
+
affectedPaths: ['enableAutoSave', 'autoSaveDebounceMs', 'persistenceOptions'],
|
|
6294
|
+
submissionImpact: 'config-only',
|
|
6295
|
+
preconditions: ['manual-form-instance-loaded'],
|
|
6296
|
+
destructive: false,
|
|
6297
|
+
requiresConfirmation: false,
|
|
6298
|
+
},
|
|
6299
|
+
{
|
|
6300
|
+
operationId: 'submitBehavior.set',
|
|
6301
|
+
title: 'Set manual form submit behavior',
|
|
6302
|
+
scope: 'interaction',
|
|
6303
|
+
targetKind: 'submitBehavior',
|
|
6304
|
+
target: { kind: 'submitBehavior', resolver: 'manual-form-submit-behavior', ambiguityPolicy: 'fail', required: false },
|
|
6305
|
+
inputSchema: submitBehaviorSetSchema,
|
|
6306
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-submit-behavior-set', handlerContract: {
|
|
6307
|
+
reads: ['ManualFormComponent.submitted', 'ManualFormInstance.form', 'FormConfig.behavior', 'praxis-dynamic-form-authoring-manifest'],
|
|
6308
|
+
writes: ['currentConfig.behavior', 'submitted', 'saved', 'resetEvent'],
|
|
6309
|
+
identityKeys: ['formId', 'action'],
|
|
6310
|
+
inputSchema: submitBehaviorSetSchema,
|
|
6311
|
+
failureModes: ['submit-action-unsupported', 'advanced-form-submit-delegation-required', 'validation-focus-target-missing'],
|
|
6312
|
+
description: 'Configures manual submit/reset behavior and delegates dynamic-form submit payload semantics to the dynamic-form manifest.',
|
|
6313
|
+
} }],
|
|
6314
|
+
validators: ['submit-behavior-supported', 'delegates-form-config', 'manual-form-round-trip'],
|
|
6315
|
+
affectedPaths: ['currentConfig.behavior', 'submitted', 'saved', 'resetEvent'],
|
|
6316
|
+
submissionImpact: 'affects-submission',
|
|
6317
|
+
preconditions: ['manual-form-instance-loaded'],
|
|
6318
|
+
destructive: false,
|
|
6319
|
+
requiresConfirmation: false,
|
|
6320
|
+
},
|
|
6321
|
+
{
|
|
6322
|
+
operationId: 'metadataBridge.configure',
|
|
6323
|
+
title: 'Configure manual metadata bridge',
|
|
6324
|
+
scope: 'fieldMetadataPath',
|
|
6325
|
+
targetKind: 'metadataBridge',
|
|
6326
|
+
target: { kind: 'metadataBridge', resolver: 'manual-field-metadata-bridge', ambiguityPolicy: 'fail', required: false },
|
|
6327
|
+
inputSchema: metadataBridgeConfigureSchema,
|
|
6328
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'manual-metadata-bridge-configure', handlerContract: {
|
|
6329
|
+
reads: ['ManualFieldMetadataBridgeService', 'SettingsPanelService', 'PRAXIS_METADATA_EDITOR_AUTHORING_MANIFEST', 'dynamic-fields-discovery'],
|
|
6330
|
+
writes: ['enableCustomization', 'currentFieldMetadata', 'currentConfig.fieldMetadata'],
|
|
6331
|
+
identityKeys: ['formId', 'fieldName', 'openMode'],
|
|
6332
|
+
inputSchema: metadataBridgeConfigureSchema,
|
|
6333
|
+
failureModes: ['metadata-editor-unavailable', 'field-metadata-delegation-required', 'control-discovery-delegation-required', 'draft-persistence-failed'],
|
|
6334
|
+
description: 'Configures the bridge that opens metadata-editor for manual fields and persists accepted patches through ManualFormInstance.',
|
|
6335
|
+
} }],
|
|
6336
|
+
validators: ['metadata-bridge-does-not-redefine-schema', 'delegates-field-metadata', 'control-type-discovered', 'metadata-bridge-gated-by-customization', 'manual-form-round-trip'],
|
|
6337
|
+
affectedPaths: ['enableCustomization', 'currentFieldMetadata', 'currentConfig.fieldMetadata'],
|
|
6338
|
+
submissionImpact: 'affects-schema-backed-data',
|
|
6339
|
+
preconditions: ['manual-form-instance-loaded'],
|
|
6340
|
+
destructive: false,
|
|
6341
|
+
requiresConfirmation: false,
|
|
6342
|
+
},
|
|
6343
|
+
],
|
|
6344
|
+
validators: [
|
|
6345
|
+
{ validatorId: 'manual-field-id-unique', level: 'error', code: 'MANUAL_FORM_FIELD_ID_UNIQUE', description: 'Manual field names must be unique in the instance metadata map.' },
|
|
6346
|
+
{ validatorId: 'manual-field-exists', level: 'error', code: 'MANUAL_FORM_FIELD_EXISTS', description: 'Target manual field must exist before patching or removing it.' },
|
|
6347
|
+
{ validatorId: 'field-removal-confirmed', level: 'error', code: 'MANUAL_FORM_FIELD_REMOVAL_CONFIRMED', description: 'Removing a field or its persisted value requires explicit confirmation.' },
|
|
6348
|
+
{ validatorId: 'field-label-valid', level: 'error', code: 'MANUAL_FORM_FIELD_LABEL_VALID', description: 'Manual field label must be a non-empty user-facing string.' },
|
|
6349
|
+
{ validatorId: 'host-template-field-exists', level: 'error', code: 'MANUAL_FORM_HOST_FIELD_EXISTS', description: 'Manual field metadata must correspond to a host template control or declared metadata bridge target.' },
|
|
6350
|
+
{ validatorId: 'control-type-discovered', level: 'error', code: 'MANUAL_FORM_CONTROL_TYPE_DISCOVERED', description: 'Control type must be discovered through dynamic-fields mappings or delegated discovery.' },
|
|
6351
|
+
{ validatorId: 'metadata-bridge-does-not-redefine-schema', level: 'error', code: 'MANUAL_FORM_METADATA_BRIDGE_SCHEMA_SAFE', description: 'Metadata bridge patches must not silently redefine backend schema or schema-driven FormConfig semantics.' },
|
|
6352
|
+
{ validatorId: 'delegates-field-metadata', level: 'error', code: 'MANUAL_FORM_DELEGATES_FIELD_METADATA', description: 'Advanced FieldMetadata edits must be delegated to the metadata-editor manifest.' },
|
|
6353
|
+
{ validatorId: 'delegates-form-config', level: 'error', code: 'MANUAL_FORM_DELEGATES_FORM_CONFIG', description: 'Advanced FormConfig semantics must be delegated to the dynamic-form manifest.' },
|
|
6354
|
+
{ validatorId: 'layout-field-references-valid', level: 'error', code: 'MANUAL_FORM_LAYOUT_FIELD_REFERENCES_VALID', description: 'Layout field references must resolve to manual fields.' },
|
|
6355
|
+
{ validatorId: 'manual-layout-does-not-replace-host-template', level: 'error', code: 'MANUAL_FORM_LAYOUT_HOST_TEMPLATE_SAFE', description: 'Manual layout metadata must not pretend to rewrite host-owned HTML template structure.' },
|
|
6356
|
+
{ validatorId: 'toolbar-flags-supported', level: 'error', code: 'MANUAL_FORM_TOOLBAR_FLAGS_SUPPORTED', description: 'Toolbar quick edit flags must be supported by the manual field toolbar.' },
|
|
6357
|
+
{ validatorId: 'metadata-bridge-gated-by-customization', level: 'error', code: 'MANUAL_FORM_METADATA_BRIDGE_GATED', description: 'Metadata bridge entry points must respect enableCustomization.' },
|
|
6358
|
+
{ validatorId: 'autosave-explicit', level: 'error', code: 'MANUAL_FORM_AUTOSAVE_EXPLICIT', description: 'Autosave enablement and persistence policy must be explicit.' },
|
|
6359
|
+
{ validatorId: 'autosave-debounce-safe', level: 'error', code: 'MANUAL_FORM_AUTOSAVE_DEBOUNCE_SAFE', description: 'Autosave debounce must be positive and safe for storage backends.' },
|
|
6360
|
+
{ validatorId: 'autosave-storage-available', level: 'error', code: 'MANUAL_FORM_AUTOSAVE_STORAGE_AVAILABLE', description: 'Autosave requires a valid AsyncConfigStorage implementation.' },
|
|
6361
|
+
{ validatorId: 'persistence-key-deterministic', level: 'error', code: 'MANUAL_FORM_PERSISTENCE_KEY_DETERMINISTIC', description: 'Persistence key composition must be deterministic across reopen/reload.' },
|
|
6362
|
+
{ validatorId: 'submit-behavior-supported', level: 'error', code: 'MANUAL_FORM_SUBMIT_BEHAVIOR_SUPPORTED', description: 'Submit behavior must map to supported manual form outputs or delegated dynamic-form semantics.' },
|
|
6363
|
+
{ validatorId: 'manual-form-round-trip', level: 'error', code: 'MANUAL_FORM_ROUND_TRIP', description: 'Open, edit, apply/save, reopen and runtime consume must preserve manual form config.' },
|
|
6364
|
+
],
|
|
6365
|
+
roundTripRequirements: [
|
|
6366
|
+
'ManualFormInstance is the package-level runtime document: config, field metadata map, FormGroup and persistence key must remain coherent.',
|
|
6367
|
+
'Manual field ids are stable field names; array indexes are not canonical identity.',
|
|
6368
|
+
'Field removal is destructive and requires confirmation before metadata, layout references or persisted values are removed.',
|
|
6369
|
+
'Autosave must be explicit, have a safe debounce and use deterministic persistence key composition through ManualFormPersistenceOptions.',
|
|
6370
|
+
'The metadata bridge may apply metadata-editor patches but must not silently redefine backend schema or duplicate FieldMetadata semantics.',
|
|
6371
|
+
'Advanced FormConfig authoring belongs to praxis-dynamic-form; this manifest governs manual form orchestration and delegates deeper FormConfig semantics.',
|
|
6372
|
+
],
|
|
6373
|
+
examples: [
|
|
6374
|
+
{ id: 'add-text-field', request: 'Add a manual text field for customer name.', operationId: 'manualField.add', params: { fieldName: 'customerName', controlType: 'input', label: 'Customer name', source: 'manual-template', delegateFieldMetadataTo: 'praxis-metadata-editor' }, isPositive: true },
|
|
6375
|
+
{ id: 'remove-obsolete-field', request: 'Remove the obsolete nickname field from the manual form.', operationId: 'manualField.remove', params: { fieldName: 'nickname', removeFromLayout: true, clearPersistedValue: true }, isPositive: true },
|
|
6376
|
+
{ id: 'rename-label', request: 'Change the label of cpf to CPF/CNPJ.', operationId: 'manualField.label.set', params: { fieldName: 'cpf', label: 'CPF/CNPJ' }, isPositive: true },
|
|
6377
|
+
{ id: 'enable-path-names', request: 'Use nested form paths as field names.', operationId: 'layout.configure', params: { usePathNames: true }, isPositive: true },
|
|
6378
|
+
{ id: 'configure-toolbar', request: 'Enable customization toolbar with required and hidden toggles.', operationId: 'toolbar.configure', params: { enableCustomization: true, editableFlags: ['required', 'hidden', 'openMetadataEditor'] }, isPositive: true },
|
|
6379
|
+
{ id: 'enable-autosave', request: 'Enable autosave every second for this tenant profile.', operationId: 'autosave.enabled.set', params: { enabled: true, debounceMs: 1000, namespace: 'sales', tenantId: 'acme', profileId: 'admin' }, isPositive: true },
|
|
6380
|
+
{ id: 'set-submit-behavior', request: 'Focus the first invalid field and scroll to errors on submit.', operationId: 'submitBehavior.set', params: { action: 'submit', focusFirstError: true, scrollToErrors: true }, isPositive: true },
|
|
6381
|
+
{ id: 'configure-metadata-bridge', request: 'Open metadata editor from the toolbar and persist draft after patch.', operationId: 'metadataBridge.configure', params: { enabled: true, openMode: 'field-toolbar', persistDraftAfterPatch: true, delegateFieldMetadataTo: 'praxis-metadata-editor', delegateControlDiscoveryTo: 'praxis-dynamic-fields' }, isPositive: true },
|
|
6382
|
+
{ id: 'reject-duplicate-field', request: 'Add another customerName field with a different label.', operationId: 'manualField.add', params: { fieldName: 'customerName', controlType: 'input', label: 'Duplicate customer' }, isPositive: false },
|
|
6383
|
+
{ id: 'reject-unconfirmed-removal', request: 'Remove email and clear its saved value without confirmation.', operationId: 'manualField.remove', params: { fieldName: 'email', clearPersistedValue: true }, isPositive: false },
|
|
6384
|
+
{ id: 'reject-unsafe-autosave', request: 'Autosave every millisecond with no storage identity.', operationId: 'autosave.enabled.set', params: { enabled: true, debounceMs: 1 }, isPositive: false },
|
|
6385
|
+
{ id: 'reject-schema-redefinition', request: 'Use manual form metadata bridge to replace the backend schema for cpf.', operationId: 'metadataBridge.configure', params: { enabled: true, fieldName: 'cpf', backendSchemaOverride: { type: 'number' } }, isPositive: false },
|
|
6386
|
+
],
|
|
6387
|
+
};
|
|
6388
|
+
|
|
5362
6389
|
/*
|
|
5363
6390
|
* Public API Surface of praxis-manual-form
|
|
5364
6391
|
*/
|
|
@@ -5367,4 +6394,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
5367
6394
|
* Generated bundle index. Do not edit.
|
|
5368
6395
|
*/
|
|
5369
6396
|
|
|
5370
|
-
export { MANUAL_FORM_AI_CAPABILITIES, MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE, MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, ManualFieldDirective, ManualFieldEditorOnDblclickDirective, ManualFieldKeyService, ManualFieldMetadataBridgeService, ManualFormActionsComponent, ManualFormComponent, ManualFormConfigEditorComponent, ManualFormDocExampleComponent, ManualFormDocExampleShowcaseComponent, ManualFormHeaderComponent, ManualFormInstance, ManualFormInstanceFactory, createManualFormSeed, toFieldMetadataMap };
|
|
6397
|
+
export { MANUAL_FORM_AI_CAPABILITIES, MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE, MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, ManualFieldDirective, ManualFieldEditorOnDblclickDirective, ManualFieldKeyService, ManualFieldMetadataBridgeService, ManualFormActionsComponent, ManualFormComponent, ManualFormConfigEditorComponent, ManualFormDocExampleComponent, ManualFormDocExampleShowcaseComponent, ManualFormHeaderComponent, ManualFormInstance, ManualFormInstanceFactory, PRAXIS_MANUAL_FORM_AUTHORING_MANIFEST, createManualFormSeed, toFieldMetadataMap };
|