@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 +4 -1
- package/fesm2022/praxisui-manual-form.mjs +711 -5
- package/index.d.ts +43 -1
- package/package.json +6 -6
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
|
|
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,
|
|
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 [
|
|
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
|
-
|
|
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 [
|
|
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.
|
|
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.
|
|
9
|
-
"@praxisui/dynamic-fields": "^8.0.0-beta.
|
|
10
|
-
"@praxisui/settings-panel": "^8.0.0-beta.
|
|
11
|
-
"@praxisui/metadata-editor": "^8.0.0-beta.
|
|
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.
|
|
16
|
+
"@praxisui/ai": "^8.0.0-beta.22",
|
|
17
17
|
"rxjs": "~7.8.0"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|