@praxisui/table 8.0.0-beta.92 → 8.0.0-beta.93
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/fesm2022/{praxisui-table-praxisui-table-bE4K3OK_.mjs → praxisui-table-praxisui-table-DXpnCND9.mjs} +67 -17
- package/fesm2022/{praxisui-table-table-agentic-authoring-turn-flow-CGr29WF0.mjs → praxisui-table-table-agentic-authoring-turn-flow-DUu4AIDF.mjs} +926 -59
- package/fesm2022/{praxisui-table-table-ai.adapter-D6gyv_KU.mjs → praxisui-table-table-ai.adapter-BWnL5QfP.mjs} +24 -13
- package/fesm2022/praxisui-table.mjs +1 -1
- package/package.json +10 -10
- package/types/praxisui-table.d.ts +4 -2
|
@@ -40,6 +40,10 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
40
40
|
if (pendingCompletion) {
|
|
41
41
|
return Promise.resolve(this.toTurnResult(this.compileAdapterResponse(pendingCompletion, request), request));
|
|
42
42
|
}
|
|
43
|
+
const pendingNumericBandStyle = this.completePendingNumericBandStyleClarification(request);
|
|
44
|
+
if (pendingNumericBandStyle) {
|
|
45
|
+
return Promise.resolve(this.toTurnResult(this.compileAdapterResponse(pendingNumericBandStyle, request), request));
|
|
46
|
+
}
|
|
43
47
|
const run = async () => {
|
|
44
48
|
await this.prepareAuthoringContext();
|
|
45
49
|
const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
|
|
@@ -313,15 +317,15 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
313
317
|
const guidedSelectedRecordsFilter = this.localGuidedSelectedRecordsFilterResponse(request, contextHints);
|
|
314
318
|
if (guidedSelectedRecordsFilter)
|
|
315
319
|
return guidedSelectedRecordsFilter;
|
|
320
|
+
const selectedRecordDerivedFilter = this.localSelectedRecordDerivedFilterResponse(request, contextHints);
|
|
321
|
+
if (selectedRecordDerivedFilter)
|
|
322
|
+
return selectedRecordDerivedFilter;
|
|
316
323
|
const declaredFilter = this.localDeclaredFilterApplyResponse(request, contextHints);
|
|
317
324
|
if (declaredFilter)
|
|
318
325
|
return declaredFilter;
|
|
319
326
|
const recommendedIntent = this.toRecord(contextHints?.['recommendedIntent']);
|
|
320
327
|
const intentId = this.stringValue(recommendedIntent?.['id'])
|
|
321
328
|
|| this.stringValue(contextHints?.['opportunityId']);
|
|
322
|
-
if (intentId === 'table-discover-capabilities' || intentId === 'table.capabilities.discover') {
|
|
323
|
-
return this.capabilityDiscoveryGuidedResponse(request, contextHints);
|
|
324
|
-
}
|
|
325
329
|
if (intentId === 'table-enable-advanced-filters' || intentId === 'table.filters.advanced.enable') {
|
|
326
330
|
return this.advancedFiltersRecommendedIntentResponse(request, contextHints);
|
|
327
331
|
}
|
|
@@ -380,58 +384,6 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
380
384
|
],
|
|
381
385
|
};
|
|
382
386
|
}
|
|
383
|
-
capabilityDiscoveryGuidedResponse(request, contextHints) {
|
|
384
|
-
const recommendations = Array.isArray(contextHints?.['availableRecommendations'])
|
|
385
|
-
? contextHints['availableRecommendations']
|
|
386
|
-
: [];
|
|
387
|
-
const optionPayloads = recommendations
|
|
388
|
-
.map((entry) => this.toRecord(entry))
|
|
389
|
-
.filter((entry) => !!entry && !!this.stringValue(entry['label']))
|
|
390
|
-
.slice(0, 6)
|
|
391
|
-
.map((entry, index) => this.capabilityDiscoveryOptionPayload(entry, index));
|
|
392
|
-
if (!optionPayloads.length) {
|
|
393
|
-
return {
|
|
394
|
-
type: 'info',
|
|
395
|
-
sessionId: request.sessionId,
|
|
396
|
-
message: 'Esta tabela já está em um estado estável. Posso orientar ajustes de filtros, colunas, ações, exportação e seleção conforme você precisar.',
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
return {
|
|
400
|
-
type: 'clarification',
|
|
401
|
-
sessionId: request.sessionId,
|
|
402
|
-
message: 'Encontrei estes próximos recursos úteis para esta tabela.',
|
|
403
|
-
questions: ['Por onde você quer começar?'],
|
|
404
|
-
optionPayloads,
|
|
405
|
-
warnings: [
|
|
406
|
-
'capability-discovery-local-guided-clarification',
|
|
407
|
-
'Resposta local gerada a partir do catalogo canonico de recommendedIntents da tabela; nao houve roteamento textual primario.',
|
|
408
|
-
],
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
capabilityDiscoveryOptionPayload(entry, index) {
|
|
412
|
-
const label = this.stringValue(entry['label']) || `Opção ${index + 1}`;
|
|
413
|
-
const contextHints = this.toRecord(entry['contextHints']) ?? {};
|
|
414
|
-
const responseContract = this.toRecord(contextHints['responseContract']);
|
|
415
|
-
return {
|
|
416
|
-
value: label,
|
|
417
|
-
label,
|
|
418
|
-
contextHints: {
|
|
419
|
-
...contextHints,
|
|
420
|
-
recommendedIntent: {
|
|
421
|
-
id: this.stringValue(entry['id']) || `table-discovery-option-${index + 1}`,
|
|
422
|
-
label,
|
|
423
|
-
sourceCandidateIds: Array.isArray(entry['sourceCandidateIds']) ? entry['sourceCandidateIds'] : [],
|
|
424
|
-
},
|
|
425
|
-
...(responseContract ? {} : { responseContract: this.toRecord(this.contextHintsForTurn?.['responseContract']) ?? undefined }),
|
|
426
|
-
presentation: {
|
|
427
|
-
kind: 'guided-option',
|
|
428
|
-
icon: this.stringValue(entry['icon']) || 'auto_awesome',
|
|
429
|
-
description: this.stringValue(entry['description']),
|
|
430
|
-
ctaLabel: this.stringValue(entry['requiresConfirmation']) === 'true' ? 'Revisar opção' : 'Usar opção',
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
387
|
selectedRecordsGuidedAnalysisResponse(request, contextHints) {
|
|
436
388
|
const selectedCount = this.extractSelectedRecordsCount(contextHints);
|
|
437
389
|
if (selectedCount <= 0) {
|
|
@@ -523,6 +475,55 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
523
475
|
],
|
|
524
476
|
};
|
|
525
477
|
}
|
|
478
|
+
localSelectedRecordDerivedFilterResponse(request, contextHints) {
|
|
479
|
+
if (this.selectedRecordsCountForTurn <= 0)
|
|
480
|
+
return null;
|
|
481
|
+
if (!this.runtimeOperationAllowed(contextHints, 'table.filter.apply'))
|
|
482
|
+
return null;
|
|
483
|
+
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
484
|
+
if (!normalizedPrompt || !this.promptRequestsRuntimeFilter(normalizedPrompt))
|
|
485
|
+
return null;
|
|
486
|
+
if (this.promptRequestsVisualOrStructuralEdit(normalizedPrompt))
|
|
487
|
+
return null;
|
|
488
|
+
if (this.promptRequestsExportOperation(normalizedPrompt))
|
|
489
|
+
return null;
|
|
490
|
+
const candidates = this.selectedRecordFilterCandidates(contextHints)
|
|
491
|
+
.map((candidate) => {
|
|
492
|
+
const score = this.selectedRecordFilterPromptGroundingScore(normalizedPrompt, candidate);
|
|
493
|
+
const criteria = this.toRecord(candidate['criteria']);
|
|
494
|
+
return { candidate, score, criteria };
|
|
495
|
+
})
|
|
496
|
+
.filter((entry) => entry.score > 0 && entry.criteria && Object.keys(entry.criteria).length > 0)
|
|
497
|
+
.sort((left, right) => right.score - left.score);
|
|
498
|
+
const top = candidates[0];
|
|
499
|
+
if (!top || top.score < 70)
|
|
500
|
+
return null;
|
|
501
|
+
const secondScore = candidates[1]?.score ?? 0;
|
|
502
|
+
if (secondScore > 0 && top.score - secondScore < 30)
|
|
503
|
+
return null;
|
|
504
|
+
return {
|
|
505
|
+
type: 'patch',
|
|
506
|
+
sessionId: request.sessionId,
|
|
507
|
+
patch: {
|
|
508
|
+
tableRuntimeOperations: {
|
|
509
|
+
kind: 'praxis.table.runtime-operation.batch',
|
|
510
|
+
source: 'selected-record-filter-candidate-local-grounding',
|
|
511
|
+
operations: [{
|
|
512
|
+
operationId: 'table.filter.apply',
|
|
513
|
+
input: {
|
|
514
|
+
criteria: top.criteria,
|
|
515
|
+
source: 'selected-records',
|
|
516
|
+
},
|
|
517
|
+
}],
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
explanation: `Filtro por ${this.stringValue(top.candidate['label']) || this.humanizeFilterField(this.stringValue(top.candidate['field']))} preparado a partir dos registros selecionados.`,
|
|
521
|
+
warnings: [
|
|
522
|
+
'selected-record-filter-candidate-materialized',
|
|
523
|
+
'Resposta local gerada somente depois de confirmar table.filter.apply no contrato runtime e ranquear candidatos canônicos de selectedRecordsContext.filterCandidates.',
|
|
524
|
+
],
|
|
525
|
+
};
|
|
526
|
+
}
|
|
526
527
|
localDeclaredFilterApplyResponse(request, contextHints) {
|
|
527
528
|
if (!this.runtimeOperationAllowed(contextHints, 'table.filter.apply'))
|
|
528
529
|
return null;
|
|
@@ -1492,6 +1493,21 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
1492
1493
|
'salário',
|
|
1493
1494
|
].some((token) => normalizedPrompt.includes(this.normalizeLabel(token)));
|
|
1494
1495
|
}
|
|
1496
|
+
promptRequestsExportOperation(normalizedPrompt) {
|
|
1497
|
+
return [
|
|
1498
|
+
'exporta',
|
|
1499
|
+
'exportar',
|
|
1500
|
+
'exportacao',
|
|
1501
|
+
'exportação',
|
|
1502
|
+
'csv',
|
|
1503
|
+
'json',
|
|
1504
|
+
'excel',
|
|
1505
|
+
'xlsx',
|
|
1506
|
+
'pdf',
|
|
1507
|
+
'arquivo',
|
|
1508
|
+
'download',
|
|
1509
|
+
].some((token) => normalizedPrompt.includes(this.normalizeLabel(token)));
|
|
1510
|
+
}
|
|
1495
1511
|
promptRequestsVisualOrStructuralEdit(normalizedPrompt) {
|
|
1496
1512
|
return [
|
|
1497
1513
|
'badge',
|
|
@@ -2167,6 +2183,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2167
2183
|
if (response.sessionId && response.sessionId !== request.sessionId) {
|
|
2168
2184
|
request = { ...request, sessionId: response.sessionId };
|
|
2169
2185
|
}
|
|
2186
|
+
response = this.normalizeComputedColumnAuxiliaryMaterializations(response);
|
|
2170
2187
|
if (response.type === 'clarification') {
|
|
2171
2188
|
const questions = this.toClarificationQuestions(response, request);
|
|
2172
2189
|
const diagnostics = this.buildClarificationDiagnostics(response);
|
|
@@ -2270,6 +2287,10 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2270
2287
|
}
|
|
2271
2288
|
compileAdapterResponse(response, request) {
|
|
2272
2289
|
response = this.normalizeCategoricalRendererPalette(response, request);
|
|
2290
|
+
response = this.normalizeStatusPresentationPlan(response, request);
|
|
2291
|
+
response = this.normalizeNumericBandConditionalStylePlan(response, request);
|
|
2292
|
+
response = this.normalizeCompoundColumnPresentationPlan(response, request);
|
|
2293
|
+
response = this.normalizeComputedColumnAuxiliaryMaterializations(response);
|
|
2273
2294
|
if (response.type === 'clarification' || response.type === 'info' || response.type === 'error') {
|
|
2274
2295
|
const continuedEarlyColumnPlan = this.columnPlanForMisroutedVisualSemanticsClarification(response, request);
|
|
2275
2296
|
if (continuedEarlyColumnPlan) {
|
|
@@ -4671,6 +4692,14 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4671
4692
|
?? this.booleanSimNaoOperationsFromGroundedClarification(prompt, responseText, columns)
|
|
4672
4693
|
?? this.booleanSimNaoOperationsFromSingleGroundedBooleanColumn(prompt, responseText, columns)
|
|
4673
4694
|
?? this.booleanSimNaoOperationsFromGroundedResponseField(prompt, responseText, columns);
|
|
4695
|
+
const statusPresentationOperations = this.statusPresentationOperationsFromPrompt(prompt, columns);
|
|
4696
|
+
if (statusPresentationOperations.length) {
|
|
4697
|
+
return this.componentEditPlanResponse(statusPresentationOperations, 'Vou ajustar a apresentacao visual do status sem filtrar as linhas.', [
|
|
4698
|
+
...(response.warnings ?? []),
|
|
4699
|
+
'status-renderer-continued-from-filter-clarification',
|
|
4700
|
+
'Residual continuity guard acted only after the LLM scoped a visual status formatting request but answered with a filter clarification instead of a componentEditPlan.',
|
|
4701
|
+
]);
|
|
4702
|
+
}
|
|
4674
4703
|
const promptRequestsSimNao = this.normalizedTextIncludesAny(this.normalizeLabel(prompt), [
|
|
4675
4704
|
'sim nao',
|
|
4676
4705
|
'sim/nao',
|
|
@@ -4712,6 +4741,14 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4712
4741
|
'Residual continuity guard acted only after the LLM scoped the boolean column and Sim/Nao option but answered informationally or as confirmation instead of materializing a direct request.',
|
|
4713
4742
|
]);
|
|
4714
4743
|
}
|
|
4744
|
+
const numericBandStyleOperations = this.numericBandConditionalStyleOperationsFromPrompt(prompt, responseText, columns);
|
|
4745
|
+
if (numericBandStyleOperations.length) {
|
|
4746
|
+
return this.componentEditPlanResponse(numericBandStyleOperations, 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.', [
|
|
4747
|
+
...(response.warnings ?? []),
|
|
4748
|
+
'numeric-band-conditional-style-continued-from-filter-clarification',
|
|
4749
|
+
'Residual continuity guard acted only after the LLM scoped a numeric conditional-formatting request but answered with a filter clarification instead of a componentEditPlan.',
|
|
4750
|
+
]);
|
|
4751
|
+
}
|
|
4715
4752
|
if (!visualSemanticClarification && !booleanInfo)
|
|
4716
4753
|
return null;
|
|
4717
4754
|
const formatOperations = this.formatOperationsFromPrompt(prompt, responseText, columns);
|
|
@@ -4753,6 +4790,447 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4753
4790
|
warnings,
|
|
4754
4791
|
};
|
|
4755
4792
|
}
|
|
4793
|
+
normalizeCompoundColumnPresentationPlan(response, request) {
|
|
4794
|
+
if (!request)
|
|
4795
|
+
return response;
|
|
4796
|
+
if ((response.warnings ?? []).includes('compound-column-presentation-normalized'))
|
|
4797
|
+
return response;
|
|
4798
|
+
if (this.responseHasComputedColumnAuthoringDecision(response)) {
|
|
4799
|
+
return response;
|
|
4800
|
+
}
|
|
4801
|
+
const prompt = this.componentPresentationPromptForTurn(request);
|
|
4802
|
+
if (!this.promptRequestsCompoundColumnPresentationEdit(this.normalizeLabel(prompt)))
|
|
4803
|
+
return response;
|
|
4804
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
4805
|
+
const columns = Array.isArray(currentConfig?.['columns'])
|
|
4806
|
+
? currentConfig['columns']
|
|
4807
|
+
.map((column) => this.toRecord(column))
|
|
4808
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
4809
|
+
: [];
|
|
4810
|
+
if (!columns.length)
|
|
4811
|
+
return response;
|
|
4812
|
+
const intendedOperations = this.compoundColumnPresentationOperationsFromPrompt(prompt, columns);
|
|
4813
|
+
if (!intendedOperations.length)
|
|
4814
|
+
return response;
|
|
4815
|
+
const originalPlan = this.toRecord(response.componentEditPlan);
|
|
4816
|
+
const originalOperations = this.componentEditOperations(originalPlan);
|
|
4817
|
+
const normalizedOperations = this.mergeCompoundColumnPresentationOperations(originalOperations, intendedOperations);
|
|
4818
|
+
if (!normalizedOperations.length)
|
|
4819
|
+
return response;
|
|
4820
|
+
return {
|
|
4821
|
+
...response,
|
|
4822
|
+
...(response.type === 'clarification' || response.type === 'info' ? { type: 'patch' } : {}),
|
|
4823
|
+
componentEditPlan: {
|
|
4824
|
+
...(originalPlan ?? {}),
|
|
4825
|
+
kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
|
|
4826
|
+
version: this.stringValue(originalPlan?.['version']) || '1.0',
|
|
4827
|
+
componentId: this.stringValue(originalPlan?.['componentId'])
|
|
4828
|
+
|| this.adapter.componentId
|
|
4829
|
+
|| request.componentId
|
|
4830
|
+
|| 'praxis-table',
|
|
4831
|
+
operations: normalizedOperations,
|
|
4832
|
+
},
|
|
4833
|
+
explanation: response.explanation || 'Vou preparar a visualizacao solicitada preservando cada acao por coluna.',
|
|
4834
|
+
warnings: [
|
|
4835
|
+
...(response.warnings ?? []),
|
|
4836
|
+
'compound-column-presentation-normalized',
|
|
4837
|
+
'Residual continuity guard preserved field-level actions after the LLM authored or clarified a compound visibility/sticky/highlight request.',
|
|
4838
|
+
],
|
|
4839
|
+
};
|
|
4840
|
+
}
|
|
4841
|
+
normalizeStatusPresentationPlan(response, request) {
|
|
4842
|
+
if (!request)
|
|
4843
|
+
return response;
|
|
4844
|
+
if ((response.warnings ?? []).includes('status-presentation-normalized'))
|
|
4845
|
+
return response;
|
|
4846
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
4847
|
+
const columns = Array.isArray(currentConfig?.['columns'])
|
|
4848
|
+
? currentConfig['columns']
|
|
4849
|
+
.map((column) => this.toRecord(column))
|
|
4850
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
4851
|
+
: [];
|
|
4852
|
+
if (!columns.length)
|
|
4853
|
+
return response;
|
|
4854
|
+
const operations = this.statusPresentationOperationsFromPrompt(this.componentPresentationPromptForTurn(request), columns);
|
|
4855
|
+
if (!operations.length)
|
|
4856
|
+
return response;
|
|
4857
|
+
const statusFields = new Set(operations
|
|
4858
|
+
.map((operation) => this.operationTargetField(operation))
|
|
4859
|
+
.filter((field) => !!field));
|
|
4860
|
+
const originalPlan = this.toRecord(response.componentEditPlan);
|
|
4861
|
+
const originalOperations = this.componentEditOperations(originalPlan)
|
|
4862
|
+
.filter((operation) => {
|
|
4863
|
+
const field = this.operationTargetField(operation);
|
|
4864
|
+
const operationId = this.stringValue(operation['operationId']);
|
|
4865
|
+
return !(field && statusFields.has(field) && (operationId === 'column.visibility.set'
|
|
4866
|
+
|| operationId === 'column.renderer.set'
|
|
4867
|
+
|| operationId === 'column.conditionalRenderer.add'
|
|
4868
|
+
|| operationId === 'column.conditionalStyle.add'));
|
|
4869
|
+
});
|
|
4870
|
+
return {
|
|
4871
|
+
...response,
|
|
4872
|
+
...(response.type === 'clarification' || response.type === 'info' ? { type: 'patch' } : {}),
|
|
4873
|
+
componentEditPlan: {
|
|
4874
|
+
...(originalPlan ?? {}),
|
|
4875
|
+
kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
|
|
4876
|
+
version: this.stringValue(originalPlan?.['version']) || '1.0',
|
|
4877
|
+
componentId: this.stringValue(originalPlan?.['componentId'])
|
|
4878
|
+
|| this.adapter.componentId
|
|
4879
|
+
|| request.componentId
|
|
4880
|
+
|| 'praxis-table',
|
|
4881
|
+
operations: [...originalOperations, ...operations],
|
|
4882
|
+
},
|
|
4883
|
+
explanation: response.explanation || 'Vou ajustar a apresentacao visual do status sem filtrar as linhas.',
|
|
4884
|
+
warnings: [
|
|
4885
|
+
...(response.warnings ?? []),
|
|
4886
|
+
'status-presentation-normalized',
|
|
4887
|
+
'Residual continuity guard preserved a status visual presentation request after the LLM authored or clarified a weaker status operation.',
|
|
4888
|
+
],
|
|
4889
|
+
};
|
|
4890
|
+
}
|
|
4891
|
+
normalizeNumericBandConditionalStylePlan(response, request) {
|
|
4892
|
+
if (!request)
|
|
4893
|
+
return response;
|
|
4894
|
+
if ((response.warnings ?? []).includes('numeric-band-conditional-style-normalized'))
|
|
4895
|
+
return response;
|
|
4896
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
4897
|
+
const columns = Array.isArray(currentConfig?.['columns'])
|
|
4898
|
+
? currentConfig['columns']
|
|
4899
|
+
.map((column) => this.toRecord(column))
|
|
4900
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
4901
|
+
: [];
|
|
4902
|
+
if (!columns.length)
|
|
4903
|
+
return response;
|
|
4904
|
+
const operations = this.numericBandConditionalStyleOperationsFromPrompt(this.componentPresentationPromptForTurn(request), [
|
|
4905
|
+
response.message,
|
|
4906
|
+
response.explanation,
|
|
4907
|
+
...(response.questions ?? []),
|
|
4908
|
+
].join(' '), columns);
|
|
4909
|
+
if (!operations.length)
|
|
4910
|
+
return response;
|
|
4911
|
+
const fields = new Set(operations
|
|
4912
|
+
.map((operation) => this.operationTargetField(operation))
|
|
4913
|
+
.filter((field) => !!field));
|
|
4914
|
+
const originalPlan = this.toRecord(response.componentEditPlan);
|
|
4915
|
+
const originalOperations = this.componentEditOperations(originalPlan)
|
|
4916
|
+
.filter((operation) => {
|
|
4917
|
+
const field = this.operationTargetField(operation);
|
|
4918
|
+
return !(field && fields.has(field) && this.stringValue(operation['operationId']) === 'column.conditionalStyle.add');
|
|
4919
|
+
});
|
|
4920
|
+
return {
|
|
4921
|
+
...response,
|
|
4922
|
+
...(response.type === 'clarification' || response.type === 'info' ? { type: 'patch' } : {}),
|
|
4923
|
+
componentEditPlan: {
|
|
4924
|
+
...(originalPlan ?? {}),
|
|
4925
|
+
kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
|
|
4926
|
+
version: this.stringValue(originalPlan?.['version']) || '1.0',
|
|
4927
|
+
componentId: this.stringValue(originalPlan?.['componentId'])
|
|
4928
|
+
|| this.adapter.componentId
|
|
4929
|
+
|| request.componentId
|
|
4930
|
+
|| 'praxis-table',
|
|
4931
|
+
operations: [...originalOperations, ...operations],
|
|
4932
|
+
},
|
|
4933
|
+
explanation: response.explanation || 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.',
|
|
4934
|
+
warnings: [
|
|
4935
|
+
...(response.warnings ?? []),
|
|
4936
|
+
'numeric-band-conditional-style-normalized',
|
|
4937
|
+
'Residual continuity guard grounded numeric band thresholds from the canonical dataProfile when available, avoiding accidental thresholds from prose or visual token numbers.',
|
|
4938
|
+
],
|
|
4939
|
+
};
|
|
4940
|
+
}
|
|
4941
|
+
componentEditPlanHasAnyOperation(componentEditPlan, operationIds) {
|
|
4942
|
+
if (!operationIds.length)
|
|
4943
|
+
return false;
|
|
4944
|
+
const expected = new Set(operationIds);
|
|
4945
|
+
return this.componentEditOperations(componentEditPlan)
|
|
4946
|
+
.some((operation) => expected.has(this.componentEditPlanOperationIdentity(operation)));
|
|
4947
|
+
}
|
|
4948
|
+
responseHasComputedColumnAuthoringDecision(response) {
|
|
4949
|
+
if (this.componentEditPlanHasAnyOperation(response.componentEditPlan, ['column.computed.add', 'column.computed.set'])) {
|
|
4950
|
+
return true;
|
|
4951
|
+
}
|
|
4952
|
+
const patch = this.toRecord(response.patch);
|
|
4953
|
+
const columns = Array.isArray(patch?.['columns']) ? patch['columns'] : [];
|
|
4954
|
+
return columns
|
|
4955
|
+
.map((column) => this.toRecord(column))
|
|
4956
|
+
.some((column) => !!column?.['computed']);
|
|
4957
|
+
}
|
|
4958
|
+
normalizeComputedColumnAuxiliaryMaterializations(response) {
|
|
4959
|
+
const plan = this.toRecord(response.componentEditPlan);
|
|
4960
|
+
const operations = this.componentEditOperations(plan);
|
|
4961
|
+
if (!plan || operations.length < 2)
|
|
4962
|
+
return response;
|
|
4963
|
+
const computedFields = new Set(operations
|
|
4964
|
+
.filter((operation) => this.componentEditPlanOperationIdentity(operation) === 'column.computed.add'
|
|
4965
|
+
|| this.componentEditPlanOperationIdentity(operation) === 'column.computed.set')
|
|
4966
|
+
.map((operation) => this.operationTargetField(operation))
|
|
4967
|
+
.filter((field) => !!field));
|
|
4968
|
+
if (!computedFields.size) {
|
|
4969
|
+
return response;
|
|
4970
|
+
}
|
|
4971
|
+
const auxiliaryAddedFields = new Set();
|
|
4972
|
+
for (const operation of operations) {
|
|
4973
|
+
const operationId = this.componentEditPlanOperationIdentity(operation);
|
|
4974
|
+
const field = this.operationTargetField(operation);
|
|
4975
|
+
if (!field)
|
|
4976
|
+
continue;
|
|
4977
|
+
if (operationId === 'column.add' && !computedFields.has(field)) {
|
|
4978
|
+
auxiliaryAddedFields.add(field);
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
const removableFields = auxiliaryAddedFields;
|
|
4982
|
+
if (!removableFields.size)
|
|
4983
|
+
return response;
|
|
4984
|
+
const normalizedOperations = operations.filter((operation) => {
|
|
4985
|
+
const operationId = this.componentEditPlanOperationIdentity(operation);
|
|
4986
|
+
const field = this.operationTargetField(operation);
|
|
4987
|
+
return !(field && removableFields.has(field) && (operationId === 'column.add'
|
|
4988
|
+
|| operationId === 'column.renderer.set'));
|
|
4989
|
+
});
|
|
4990
|
+
if (normalizedOperations.length === operations.length)
|
|
4991
|
+
return response;
|
|
4992
|
+
return {
|
|
4993
|
+
...response,
|
|
4994
|
+
componentEditPlan: {
|
|
4995
|
+
...plan,
|
|
4996
|
+
operations: normalizedOperations,
|
|
4997
|
+
},
|
|
4998
|
+
warnings: [
|
|
4999
|
+
...(response.warnings ?? []),
|
|
5000
|
+
'computed-column-auxiliary-materialization-normalized',
|
|
5001
|
+
'Removed auxiliary column.add/column.renderer.set operations that conflicted with a canonical computed-column decision in the same componentEditPlan.',
|
|
5002
|
+
],
|
|
5003
|
+
};
|
|
5004
|
+
}
|
|
5005
|
+
componentEditPlanOperationIdentity(operation) {
|
|
5006
|
+
const operationId = this.stringValue(operation['operationId']);
|
|
5007
|
+
if (operationId) {
|
|
5008
|
+
return this.canonicalComponentEditPlanOperationIdentity(operationId);
|
|
5009
|
+
}
|
|
5010
|
+
const changeKind = this.stringValue(operation['changeKind']);
|
|
5011
|
+
return this.canonicalComponentEditPlanOperationIdentity(changeKind);
|
|
5012
|
+
}
|
|
5013
|
+
canonicalComponentEditPlanOperationIdentity(operationId) {
|
|
5014
|
+
switch (operationId) {
|
|
5015
|
+
case 'addColumn':
|
|
5016
|
+
case 'add_column':
|
|
5017
|
+
case 'column_add':
|
|
5018
|
+
return 'column.add';
|
|
5019
|
+
case 'setColumnRenderer':
|
|
5020
|
+
case 'set_column_renderer':
|
|
5021
|
+
case 'column_renderer_set':
|
|
5022
|
+
return 'column.renderer.set';
|
|
5023
|
+
case 'set_column_computed':
|
|
5024
|
+
case 'add_column_computed':
|
|
5025
|
+
case 'add_computed_column':
|
|
5026
|
+
case 'set_computed_column':
|
|
5027
|
+
case 'column_computed_add':
|
|
5028
|
+
return 'column.computed.add';
|
|
5029
|
+
case 'column.computed.set':
|
|
5030
|
+
return 'column.computed.add';
|
|
5031
|
+
default:
|
|
5032
|
+
return operationId;
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
componentPresentationPromptForTurn(request) {
|
|
5036
|
+
const parts = [
|
|
5037
|
+
request.pendingClarification?.sourcePrompt,
|
|
5038
|
+
...(request.messages ?? [])
|
|
5039
|
+
.filter((message) => message.role === 'user')
|
|
5040
|
+
.map((message) => message.text),
|
|
5041
|
+
request.prompt,
|
|
5042
|
+
];
|
|
5043
|
+
return parts
|
|
5044
|
+
.map((part) => (part ?? '').trim())
|
|
5045
|
+
.filter((part) => !!part)
|
|
5046
|
+
.join(' ');
|
|
5047
|
+
}
|
|
5048
|
+
promptRequestsCompoundColumnPresentationEdit(normalizedPrompt) {
|
|
5049
|
+
const hasVisibility = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5050
|
+
'oculta',
|
|
5051
|
+
'ocultar',
|
|
5052
|
+
'oculte',
|
|
5053
|
+
'esconde',
|
|
5054
|
+
'esconder',
|
|
5055
|
+
'esconda',
|
|
5056
|
+
'visao publica',
|
|
5057
|
+
'visão publica',
|
|
5058
|
+
'visualizacao publica',
|
|
5059
|
+
'visualização publica',
|
|
5060
|
+
]);
|
|
5061
|
+
const hasStickyOrHighlight = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5062
|
+
'fixa',
|
|
5063
|
+
'fixe',
|
|
5064
|
+
'fixar',
|
|
5065
|
+
'pin',
|
|
5066
|
+
'sticky',
|
|
5067
|
+
'destaca',
|
|
5068
|
+
'destaque',
|
|
5069
|
+
'destacar',
|
|
5070
|
+
'realca',
|
|
5071
|
+
'realce',
|
|
5072
|
+
'realçar',
|
|
5073
|
+
]);
|
|
5074
|
+
return hasVisibility && hasStickyOrHighlight;
|
|
5075
|
+
}
|
|
5076
|
+
compoundColumnPresentationOperationsFromPrompt(prompt, columns) {
|
|
5077
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
5078
|
+
const operations = [];
|
|
5079
|
+
const seen = new Set();
|
|
5080
|
+
for (const column of columns) {
|
|
5081
|
+
const field = this.stringValue(column['field']);
|
|
5082
|
+
if (!field)
|
|
5083
|
+
continue;
|
|
5084
|
+
const action = this.columnPresentationActionFromPrompt(normalizedPrompt, column);
|
|
5085
|
+
if (!action)
|
|
5086
|
+
continue;
|
|
5087
|
+
const key = `${action}:${field}`;
|
|
5088
|
+
if (seen.has(key))
|
|
5089
|
+
continue;
|
|
5090
|
+
seen.add(key);
|
|
5091
|
+
if (action === 'hide') {
|
|
5092
|
+
operations.push({
|
|
5093
|
+
operationId: 'column.visibility.set',
|
|
5094
|
+
target: { kind: 'column', field },
|
|
5095
|
+
input: { visible: false },
|
|
5096
|
+
});
|
|
5097
|
+
continue;
|
|
5098
|
+
}
|
|
5099
|
+
if (action === 'sticky') {
|
|
5100
|
+
operations.push({
|
|
5101
|
+
operationId: 'column.sticky.set',
|
|
5102
|
+
target: { kind: 'column', field },
|
|
5103
|
+
input: { sticky: 'start' },
|
|
5104
|
+
});
|
|
5105
|
+
continue;
|
|
5106
|
+
}
|
|
5107
|
+
operations.push({
|
|
5108
|
+
operationId: 'column.renderer.set',
|
|
5109
|
+
target: { kind: 'column', field },
|
|
5110
|
+
input: {
|
|
5111
|
+
renderer: {
|
|
5112
|
+
type: 'chip',
|
|
5113
|
+
chip: {
|
|
5114
|
+
textField: field,
|
|
5115
|
+
color: 'info',
|
|
5116
|
+
variant: 'outlined',
|
|
5117
|
+
},
|
|
5118
|
+
},
|
|
5119
|
+
},
|
|
5120
|
+
});
|
|
5121
|
+
}
|
|
5122
|
+
return operations;
|
|
5123
|
+
}
|
|
5124
|
+
columnPresentationActionFromPrompt(normalizedPrompt, column) {
|
|
5125
|
+
const mentionIndex = this.columnMentionIndex(normalizedPrompt, column);
|
|
5126
|
+
if (mentionIndex < 0)
|
|
5127
|
+
return null;
|
|
5128
|
+
const beforeMention = normalizedPrompt.slice(0, mentionIndex);
|
|
5129
|
+
const tokens = [];
|
|
5130
|
+
this.collectActionTokenIndexes(beforeMention, ['oculta', 'ocultar', 'oculte', 'esconde', 'esconder', 'esconda'], 'hide', tokens);
|
|
5131
|
+
this.collectActionTokenIndexes(beforeMention, ['fixa', 'fixe', 'fixar', 'pin', 'sticky'], 'sticky', tokens);
|
|
5132
|
+
this.collectActionTokenIndexes(beforeMention, ['destaca', 'destaque', 'destacar', 'realca', 'realce', 'realçar'], 'highlight', tokens);
|
|
5133
|
+
tokens.sort((left, right) => right.index - left.index);
|
|
5134
|
+
return tokens[0]?.action ?? null;
|
|
5135
|
+
}
|
|
5136
|
+
collectActionTokenIndexes(text, candidates, action, bucket) {
|
|
5137
|
+
for (const candidate of candidates) {
|
|
5138
|
+
const normalized = this.normalizeLabel(candidate);
|
|
5139
|
+
let index = text.indexOf(normalized);
|
|
5140
|
+
while (index >= 0) {
|
|
5141
|
+
bucket.push({ action, index });
|
|
5142
|
+
index = text.indexOf(normalized, index + normalized.length);
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
columnMentionIndex(normalizedPrompt, column) {
|
|
5147
|
+
return this.columnPresentationAliases(column)
|
|
5148
|
+
.map((alias) => normalizedPrompt.indexOf(alias))
|
|
5149
|
+
.filter((index) => index >= 0)
|
|
5150
|
+
.sort((left, right) => left - right)[0] ?? -1;
|
|
5151
|
+
}
|
|
5152
|
+
columnPresentationAliases(column) {
|
|
5153
|
+
const aliases = new Set(this.columnMentionAliases(column));
|
|
5154
|
+
const field = this.normalizeLabel(this.stringValue(column['field']));
|
|
5155
|
+
const header = this.normalizeLabel(this.stringValue(column['header']));
|
|
5156
|
+
if (field.startsWith('nome') || header.includes('nome'))
|
|
5157
|
+
aliases.add('nome');
|
|
5158
|
+
if (field.includes('funcionario') || header.includes('funcionario'))
|
|
5159
|
+
aliases.add('nome');
|
|
5160
|
+
if (field.includes('salario') || header.includes('salario'))
|
|
5161
|
+
aliases.add('salario');
|
|
5162
|
+
if (field.includes('salario') || header.includes('salario'))
|
|
5163
|
+
aliases.add('salário');
|
|
5164
|
+
return [...aliases].sort((left, right) => right.length - left.length);
|
|
5165
|
+
}
|
|
5166
|
+
mergeCompoundColumnPresentationOperations(originalOperations, intendedOperations) {
|
|
5167
|
+
const protectedFields = new Map();
|
|
5168
|
+
const protectedSemanticKeys = new Map();
|
|
5169
|
+
for (const operation of intendedOperations) {
|
|
5170
|
+
const field = this.operationTargetField(operation);
|
|
5171
|
+
const action = this.compoundOperationAction(operation);
|
|
5172
|
+
if (!field || !action)
|
|
5173
|
+
continue;
|
|
5174
|
+
const actions = protectedFields.get(field) ?? new Set();
|
|
5175
|
+
actions.add(action);
|
|
5176
|
+
protectedFields.set(field, actions);
|
|
5177
|
+
const semanticKey = this.compoundOperationFieldSemanticKey(field);
|
|
5178
|
+
if (semanticKey) {
|
|
5179
|
+
const semanticActions = protectedSemanticKeys.get(semanticKey) ?? new Set();
|
|
5180
|
+
semanticActions.add(action);
|
|
5181
|
+
protectedSemanticKeys.set(semanticKey, semanticActions);
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
const filteredOriginal = originalOperations.filter((operation) => {
|
|
5185
|
+
const action = this.compoundOperationAction(operation);
|
|
5186
|
+
if (!action)
|
|
5187
|
+
return true;
|
|
5188
|
+
const field = this.operationTargetField(operation);
|
|
5189
|
+
if (!field)
|
|
5190
|
+
return false;
|
|
5191
|
+
const intended = protectedFields.get(field);
|
|
5192
|
+
if (intended)
|
|
5193
|
+
return false;
|
|
5194
|
+
const semanticKey = this.compoundOperationFieldSemanticKey(field);
|
|
5195
|
+
if (semanticKey && protectedSemanticKeys.has(semanticKey))
|
|
5196
|
+
return false;
|
|
5197
|
+
return true;
|
|
5198
|
+
});
|
|
5199
|
+
return [...filteredOriginal, ...intendedOperations];
|
|
5200
|
+
}
|
|
5201
|
+
compoundOperationFieldSemanticKey(field) {
|
|
5202
|
+
const normalized = this.normalizeLabel(field);
|
|
5203
|
+
if (!normalized)
|
|
5204
|
+
return '';
|
|
5205
|
+
if (normalized.startsWith('nome') || normalized.includes('funcionario'))
|
|
5206
|
+
return 'record-name';
|
|
5207
|
+
return '';
|
|
5208
|
+
}
|
|
5209
|
+
compoundOperationAction(operation) {
|
|
5210
|
+
const operationId = this.stringValue(operation['operationId']);
|
|
5211
|
+
const input = this.toRecord(operation['input']) ?? {};
|
|
5212
|
+
if (operationId === 'column.visibility.set' && this.booleanInput(input['visible']) === false)
|
|
5213
|
+
return 'hide';
|
|
5214
|
+
if (operationId === 'column.sticky.set')
|
|
5215
|
+
return 'sticky';
|
|
5216
|
+
if (operationId === 'column.renderer.set' || operationId === 'column.conditionalStyle.add')
|
|
5217
|
+
return 'highlight';
|
|
5218
|
+
return null;
|
|
5219
|
+
}
|
|
5220
|
+
operationTargetField(operation) {
|
|
5221
|
+
const target = this.toRecord(operation['target']);
|
|
5222
|
+
const input = this.toRecord(operation['input']) ?? {};
|
|
5223
|
+
return this.stringValue(target?.['field'])
|
|
5224
|
+
|| this.stringValue(target?.['id'])
|
|
5225
|
+
|| this.stringValue(target?.['name'])
|
|
5226
|
+
|| this.stringValue(operation['target'])
|
|
5227
|
+
|| this.stringValue(operation['targetField'])
|
|
5228
|
+
|| this.stringValue(operation['field'])
|
|
5229
|
+
|| this.stringValue(input['targetField'])
|
|
5230
|
+
|| this.stringValue(input['field'])
|
|
5231
|
+
|| this.stringValue(input['id'])
|
|
5232
|
+
|| this.stringValue(input['name']);
|
|
5233
|
+
}
|
|
4756
5234
|
formatOperationsFromPrompt(prompt, responseText, columns) {
|
|
4757
5235
|
const canonicalFormats = new Set([
|
|
4758
5236
|
'dd/MM/yyyy',
|
|
@@ -4795,6 +5273,291 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4795
5273
|
});
|
|
4796
5274
|
return this.booleanSimNaoOperationsForColumn(column);
|
|
4797
5275
|
}
|
|
5276
|
+
statusPresentationOperationsFromPrompt(prompt, columns) {
|
|
5277
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
5278
|
+
const asksStatusVisual = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5279
|
+
'status',
|
|
5280
|
+
'ativo',
|
|
5281
|
+
'inativo',
|
|
5282
|
+
'inativos',
|
|
5283
|
+
]) && this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5284
|
+
'badge',
|
|
5285
|
+
'badges',
|
|
5286
|
+
'chip',
|
|
5287
|
+
'chips',
|
|
5288
|
+
'semantico',
|
|
5289
|
+
'semântico',
|
|
5290
|
+
'verde',
|
|
5291
|
+
'vermelho',
|
|
5292
|
+
'discreto',
|
|
5293
|
+
'discretos',
|
|
5294
|
+
'format',
|
|
5295
|
+
'renderer',
|
|
5296
|
+
]);
|
|
5297
|
+
if (!asksStatusVisual)
|
|
5298
|
+
return [];
|
|
5299
|
+
const column = columns.find((candidate) => {
|
|
5300
|
+
const text = this.normalizeLabel([
|
|
5301
|
+
candidate['field'],
|
|
5302
|
+
candidate['header'],
|
|
5303
|
+
candidate['type'],
|
|
5304
|
+
candidate['dataType'],
|
|
5305
|
+
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5306
|
+
].map((value) => this.stringValue(value)).join(' '));
|
|
5307
|
+
return this.normalizedTextIncludesAny(text, ['status', 'ativo', 'boolean', 'bool']);
|
|
5308
|
+
});
|
|
5309
|
+
const field = this.stringValue(column?.['field']);
|
|
5310
|
+
if (!field)
|
|
5311
|
+
return [];
|
|
5312
|
+
const operations = [
|
|
5313
|
+
this.statusConditionalRendererOperation(field, true, 'Ativo', 'success'),
|
|
5314
|
+
this.statusConditionalRendererOperation(field, false, 'Inativo', 'warn'),
|
|
5315
|
+
];
|
|
5316
|
+
if (this.normalizedTextIncludesAny(normalizedPrompt, ['discreto', 'discretos', 'suave', 'suavizar', 'inativo', 'inativos'])) {
|
|
5317
|
+
operations.push({
|
|
5318
|
+
operationId: 'column.conditionalStyle.add',
|
|
5319
|
+
target: { kind: 'rule', field },
|
|
5320
|
+
input: {
|
|
5321
|
+
id: `${field}-inactive-muted`,
|
|
5322
|
+
condition: { '===': [{ var: field }, false] },
|
|
5323
|
+
style: { opacity: '0.62' },
|
|
5324
|
+
tooltip: { text: 'Funcionário inativo' },
|
|
5325
|
+
description: 'Funcionário inativo com leitura discreta.',
|
|
5326
|
+
},
|
|
5327
|
+
});
|
|
5328
|
+
}
|
|
5329
|
+
return operations;
|
|
5330
|
+
}
|
|
5331
|
+
numericBandConditionalStyleOperationsFromPrompt(prompt, responseText, columns) {
|
|
5332
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
5333
|
+
const asksConditionalBands = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5334
|
+
'formatacao condicional',
|
|
5335
|
+
'formatação condicional',
|
|
5336
|
+
'conditional formatting',
|
|
5337
|
+
'condicional',
|
|
5338
|
+
'faixa',
|
|
5339
|
+
'faixas',
|
|
5340
|
+
'destaca',
|
|
5341
|
+
'destaque',
|
|
5342
|
+
'destacando',
|
|
5343
|
+
'realca',
|
|
5344
|
+
'realce',
|
|
5345
|
+
'realçando',
|
|
5346
|
+
]) && this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5347
|
+
'alta',
|
|
5348
|
+
'alto',
|
|
5349
|
+
'media',
|
|
5350
|
+
'média',
|
|
5351
|
+
'medio',
|
|
5352
|
+
'médio',
|
|
5353
|
+
'baixa',
|
|
5354
|
+
'baixo',
|
|
5355
|
+
]);
|
|
5356
|
+
if (!asksConditionalBands)
|
|
5357
|
+
return [];
|
|
5358
|
+
const column = columns.find((candidate) => this.numericColumnCandidate(candidate)
|
|
5359
|
+
&& (this.textMentionsColumn(prompt, candidate) || this.textMentionsColumn(responseText, candidate)));
|
|
5360
|
+
const field = this.stringValue(column?.['field']);
|
|
5361
|
+
if (!field)
|
|
5362
|
+
return [];
|
|
5363
|
+
const thresholds = this.numericBandThresholdsForField(field, responseText);
|
|
5364
|
+
if (!thresholds)
|
|
5365
|
+
return [];
|
|
5366
|
+
return [
|
|
5367
|
+
this.numericBandConditionalStyleOperation(field, 'high', {
|
|
5368
|
+
condition: { '>=': [{ var: field }, thresholds.highStart] },
|
|
5369
|
+
description: 'Faixa alta',
|
|
5370
|
+
style: {
|
|
5371
|
+
backgroundColor: 'rgba(46, 125, 50, 0.16)',
|
|
5372
|
+
color: 'rgb(27, 94, 32)',
|
|
5373
|
+
fontWeight: '600',
|
|
5374
|
+
},
|
|
5375
|
+
}),
|
|
5376
|
+
this.numericBandConditionalStyleOperation(field, 'medium', {
|
|
5377
|
+
condition: {
|
|
5378
|
+
and: [
|
|
5379
|
+
{ '>=': [{ var: field }, thresholds.mediumStart] },
|
|
5380
|
+
{ '<': [{ var: field }, thresholds.highStart] },
|
|
5381
|
+
],
|
|
5382
|
+
},
|
|
5383
|
+
description: 'Faixa media',
|
|
5384
|
+
style: {
|
|
5385
|
+
backgroundColor: 'rgba(245, 158, 11, 0.16)',
|
|
5386
|
+
color: 'rgb(146, 64, 14)',
|
|
5387
|
+
fontWeight: '600',
|
|
5388
|
+
},
|
|
5389
|
+
}),
|
|
5390
|
+
this.numericBandConditionalStyleOperation(field, 'low', {
|
|
5391
|
+
condition: { '<': [{ var: field }, thresholds.mediumStart] },
|
|
5392
|
+
description: 'Faixa baixa',
|
|
5393
|
+
style: {
|
|
5394
|
+
backgroundColor: 'rgba(220, 38, 38, 0.12)',
|
|
5395
|
+
color: 'rgb(153, 27, 27)',
|
|
5396
|
+
fontWeight: '600',
|
|
5397
|
+
},
|
|
5398
|
+
}),
|
|
5399
|
+
];
|
|
5400
|
+
}
|
|
5401
|
+
completePendingNumericBandStyleClarification(request) {
|
|
5402
|
+
if (request.action?.kind !== 'clarify')
|
|
5403
|
+
return null;
|
|
5404
|
+
const sourcePrompt = this.pendingClarificationSourcePromptForTurn(request, this.toRecord(request.pendingClarification?.diagnostics) ?? undefined);
|
|
5405
|
+
if (!sourcePrompt)
|
|
5406
|
+
return null;
|
|
5407
|
+
const answerText = [
|
|
5408
|
+
request.action?.displayPrompt,
|
|
5409
|
+
request.action?.value,
|
|
5410
|
+
request.prompt,
|
|
5411
|
+
].map((value) => this.stringValue(value)).filter(Boolean).join(' ');
|
|
5412
|
+
if (!this.normalizedTextIncludesAny(this.normalizeLabel(answerText), [
|
|
5413
|
+
'terco',
|
|
5414
|
+
'terços',
|
|
5415
|
+
'tertil',
|
|
5416
|
+
'automatic',
|
|
5417
|
+
'automático',
|
|
5418
|
+
'automatico',
|
|
5419
|
+
'limites absolutos',
|
|
5420
|
+
'faixa',
|
|
5421
|
+
'faixas',
|
|
5422
|
+
])) {
|
|
5423
|
+
return null;
|
|
5424
|
+
}
|
|
5425
|
+
const columns = this.toArray(this.adapter.getCurrentConfig?.()?.['columns'])
|
|
5426
|
+
.map((column) => this.toRecord(column))
|
|
5427
|
+
.filter((column) => !!column && !!this.stringValue(column['field']));
|
|
5428
|
+
if (!columns.length)
|
|
5429
|
+
return null;
|
|
5430
|
+
const responseText = [
|
|
5431
|
+
request.pendingClarification?.assistantMessage,
|
|
5432
|
+
...(request.pendingClarification?.questions ?? []).map((question) => this.stringValue(question['prompt'])
|
|
5433
|
+
|| this.stringValue(question['question'])
|
|
5434
|
+
|| this.stringValue(question['label'])),
|
|
5435
|
+
answerText,
|
|
5436
|
+
].join(' ');
|
|
5437
|
+
const operations = this.numericBandConditionalStyleOperationsFromPrompt(sourcePrompt, responseText, columns);
|
|
5438
|
+
if (!operations.length)
|
|
5439
|
+
return null;
|
|
5440
|
+
return this.componentEditPlanResponse(operations, 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.', [
|
|
5441
|
+
'numeric-band-conditional-style-continued-from-clarification-answer',
|
|
5442
|
+
'A resposta de clarificacao continuou uma intencao visual ja ancorada em componentEditPlan; o grounding de campo veio das colunas declaradas e os limites vieram do dataProfile quando a clarificacao nao trouxe valores numericos.',
|
|
5443
|
+
]);
|
|
5444
|
+
}
|
|
5445
|
+
numericColumnCandidate(column) {
|
|
5446
|
+
const text = this.normalizeLabel([
|
|
5447
|
+
column['field'],
|
|
5448
|
+
column['header'],
|
|
5449
|
+
column['type'],
|
|
5450
|
+
column['dataType'],
|
|
5451
|
+
column['format'],
|
|
5452
|
+
this.humanizeField(this.stringValue(column['field'])),
|
|
5453
|
+
].map((value) => this.stringValue(value)).join(' '));
|
|
5454
|
+
return this.normalizedTextIncludesAny(text, [
|
|
5455
|
+
'number',
|
|
5456
|
+
'numeric',
|
|
5457
|
+
'decimal',
|
|
5458
|
+
'integer',
|
|
5459
|
+
'currency',
|
|
5460
|
+
'moeda',
|
|
5461
|
+
'valor',
|
|
5462
|
+
'preco',
|
|
5463
|
+
'preço',
|
|
5464
|
+
'salario',
|
|
5465
|
+
'salário',
|
|
5466
|
+
'brl',
|
|
5467
|
+
]);
|
|
5468
|
+
}
|
|
5469
|
+
numericBandThresholdsForField(field, text) {
|
|
5470
|
+
return this.numericBandThresholdsFromDataProfile(field)
|
|
5471
|
+
?? this.numericBandThresholdsFromText(text);
|
|
5472
|
+
}
|
|
5473
|
+
numericBandThresholdsFromText(text) {
|
|
5474
|
+
const values = this.extractNumericBandSamples(text);
|
|
5475
|
+
if (values.length < 2)
|
|
5476
|
+
return null;
|
|
5477
|
+
const sorted = [...new Set(values)]
|
|
5478
|
+
.sort((left, right) => left - right);
|
|
5479
|
+
if (sorted.length < 2)
|
|
5480
|
+
return null;
|
|
5481
|
+
const min = sorted[0];
|
|
5482
|
+
const max = sorted[sorted.length - 1];
|
|
5483
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min)
|
|
5484
|
+
return null;
|
|
5485
|
+
const range = max - min;
|
|
5486
|
+
return {
|
|
5487
|
+
mediumStart: this.roundBandThreshold(min + range / 3),
|
|
5488
|
+
highStart: this.roundBandThreshold(min + (range * 2) / 3),
|
|
5489
|
+
};
|
|
5490
|
+
}
|
|
5491
|
+
numericBandThresholdsFromDataProfile(field) {
|
|
5492
|
+
const dataProfile = this.toRecord(this.adapter.getDataProfile?.());
|
|
5493
|
+
const columns = this.toRecord(dataProfile?.['columns']);
|
|
5494
|
+
const stats = this.toRecord(columns?.[field]);
|
|
5495
|
+
const min = this.numberValue(stats?.['min']);
|
|
5496
|
+
const max = this.numberValue(stats?.['max']);
|
|
5497
|
+
if (min === null || max === null || max <= min)
|
|
5498
|
+
return null;
|
|
5499
|
+
const range = max - min;
|
|
5500
|
+
return {
|
|
5501
|
+
mediumStart: this.roundBandThreshold(min + range / 3),
|
|
5502
|
+
highStart: this.roundBandThreshold(min + (range * 2) / 3),
|
|
5503
|
+
};
|
|
5504
|
+
}
|
|
5505
|
+
extractNumericBandSamples(text) {
|
|
5506
|
+
const matches = text.match(/(?:R\$\s*)?\d{1,3}(?:[.\s]\d{3})*(?:,\d+)?|\d+(?:\.\d+)?/g) ?? [];
|
|
5507
|
+
return matches
|
|
5508
|
+
.map((match) => this.parseLocalizedNumber(match))
|
|
5509
|
+
.filter((value) => Number.isFinite(value));
|
|
5510
|
+
}
|
|
5511
|
+
parseLocalizedNumber(value) {
|
|
5512
|
+
const cleaned = value.replace(/[^\d.,-]+/g, '').trim();
|
|
5513
|
+
if (!cleaned)
|
|
5514
|
+
return null;
|
|
5515
|
+
const normalized = cleaned.includes(',')
|
|
5516
|
+
? cleaned.replace(/\./g, '').replace(',', '.')
|
|
5517
|
+
: cleaned.replace(/\s/g, '');
|
|
5518
|
+
const parsed = Number(normalized);
|
|
5519
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
5520
|
+
}
|
|
5521
|
+
roundBandThreshold(value) {
|
|
5522
|
+
if (Math.abs(value) >= 1000)
|
|
5523
|
+
return Math.round(value / 100) * 100;
|
|
5524
|
+
if (Math.abs(value) >= 100)
|
|
5525
|
+
return Math.round(value / 10) * 10;
|
|
5526
|
+
return Math.round(value);
|
|
5527
|
+
}
|
|
5528
|
+
numericBandConditionalStyleOperation(field, band, input) {
|
|
5529
|
+
return {
|
|
5530
|
+
operationId: 'column.conditionalStyle.add',
|
|
5531
|
+
target: { kind: 'rule', field },
|
|
5532
|
+
input: {
|
|
5533
|
+
id: `${field}-band-${band}`,
|
|
5534
|
+
condition: input.condition,
|
|
5535
|
+
style: input.style,
|
|
5536
|
+
tooltip: { text: input.description },
|
|
5537
|
+
description: input.description,
|
|
5538
|
+
},
|
|
5539
|
+
};
|
|
5540
|
+
}
|
|
5541
|
+
statusConditionalRendererOperation(field, value, text, color) {
|
|
5542
|
+
return {
|
|
5543
|
+
operationId: 'column.conditionalRenderer.add',
|
|
5544
|
+
target: { kind: 'conditionalRenderer', field },
|
|
5545
|
+
input: {
|
|
5546
|
+
id: `${field}-${value ? 'active' : 'inactive'}-badge`,
|
|
5547
|
+
condition: { '===': [{ var: field }, value] },
|
|
5548
|
+
renderer: {
|
|
5549
|
+
type: 'badge',
|
|
5550
|
+
badge: {
|
|
5551
|
+
text,
|
|
5552
|
+
color,
|
|
5553
|
+
variant: 'soft',
|
|
5554
|
+
},
|
|
5555
|
+
},
|
|
5556
|
+
tooltip: { text },
|
|
5557
|
+
description: text,
|
|
5558
|
+
},
|
|
5559
|
+
};
|
|
5560
|
+
}
|
|
4798
5561
|
booleanSimNaoOperationsFromGroundedClarification(prompt, responseText, columns) {
|
|
4799
5562
|
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
4800
5563
|
const normalizedResponse = this.normalizeLabel(responseText.replace(/[^\p{Letter}\p{Number}\s/]+/gu, ' '));
|
|
@@ -5691,6 +6454,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5691
6454
|
columnVisibilityPlanForInvalidExecutableError(response, request, compiledError) {
|
|
5692
6455
|
if (!request)
|
|
5693
6456
|
return null;
|
|
6457
|
+
if ((response.warnings ?? []).includes('column-visibility-continued-from-invalid-executable-plan'))
|
|
6458
|
+
return null;
|
|
5694
6459
|
const prompt = this.columnVisibilityPromptForTurn(request);
|
|
5695
6460
|
if (!this.promptRequestsColumnVisibilityEdit(this.normalizeLabel(prompt)))
|
|
5696
6461
|
return null;
|
|
@@ -5718,10 +6483,15 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5718
6483
|
.map((column) => this.toRecord(column))
|
|
5719
6484
|
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
5720
6485
|
: [];
|
|
5721
|
-
const
|
|
6486
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
6487
|
+
const operations = this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt)
|
|
6488
|
+
? this.compoundColumnPresentationOperationsFromPrompt(prompt, columns)
|
|
6489
|
+
: this.columnVisibilityOperationsFromPrompt(prompt, columns);
|
|
5722
6490
|
if (!operations.length)
|
|
5723
6491
|
return null;
|
|
5724
|
-
return this.componentEditPlanResponse(operations,
|
|
6492
|
+
return this.componentEditPlanResponse(operations, this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt)
|
|
6493
|
+
? 'Vou preparar a visualizacao solicitada preservando cada acao por coluna.'
|
|
6494
|
+
: 'Vou ocultar as colunas indicadas para a visualizacao solicitada.', [
|
|
5725
6495
|
...(response.warnings ?? []),
|
|
5726
6496
|
...(compiledError.warnings ?? []),
|
|
5727
6497
|
'column-visibility-continued-from-invalid-executable-plan',
|
|
@@ -7189,12 +7959,97 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7189
7959
|
}
|
|
7190
7960
|
withCapabilitySystemMessages(messages, contextHints, prompt = '') {
|
|
7191
7961
|
const policies = [
|
|
7962
|
+
this.buildRecommendedCapabilitiesSystemPolicy(contextHints),
|
|
7963
|
+
this.buildComponentPresentationAuthoringSystemPolicy(contextHints, prompt),
|
|
7192
7964
|
this.buildComponentLayoutAuthoringSystemPolicy(contextHints, prompt),
|
|
7193
7965
|
this.buildFilterExpressionSystemPolicy(contextHints),
|
|
7194
|
-
this.buildFilterAuthoringSystemPolicy(contextHints),
|
|
7966
|
+
this.buildFilterAuthoringSystemPolicy(contextHints, prompt),
|
|
7195
7967
|
].filter((message) => !!message);
|
|
7196
7968
|
return policies.length ? [...policies, ...messages] : messages;
|
|
7197
7969
|
}
|
|
7970
|
+
buildRecommendedCapabilitiesSystemPolicy(contextHints) {
|
|
7971
|
+
const recommendedIntent = this.toRecord(contextHints?.['recommendedIntent']);
|
|
7972
|
+
const intentId = this.stringValue(recommendedIntent?.['id'])
|
|
7973
|
+
|| this.stringValue(contextHints?.['opportunityId']);
|
|
7974
|
+
const authoringMode = this.stringValue(contextHints?.['authoringMode']);
|
|
7975
|
+
if (intentId !== 'table-discover-capabilities'
|
|
7976
|
+
&& intentId !== 'table.capabilities.discover'
|
|
7977
|
+
&& authoringMode !== 'table.recommended-capabilities.analysis') {
|
|
7978
|
+
return null;
|
|
7979
|
+
}
|
|
7980
|
+
const candidates = Array.isArray(contextHints?.['availableRecommendations'])
|
|
7981
|
+
? contextHints['availableRecommendations']
|
|
7982
|
+
: [];
|
|
7983
|
+
const candidateLines = candidates
|
|
7984
|
+
.map((entry, index) => {
|
|
7985
|
+
const record = this.toRecord(entry);
|
|
7986
|
+
if (!record)
|
|
7987
|
+
return null;
|
|
7988
|
+
const id = this.stringValue(record['id']) || `candidate-${index + 1}`;
|
|
7989
|
+
const label = this.stringValue(record['label']) || id;
|
|
7990
|
+
const description = this.stringValue(record['description']);
|
|
7991
|
+
const group = this.stringValue(record['groupLabel']);
|
|
7992
|
+
const sourceCandidateIds = Array.isArray(record['sourceCandidateIds'])
|
|
7993
|
+
? record['sourceCandidateIds'].map((value) => this.stringValue(value)).filter(Boolean)
|
|
7994
|
+
: [];
|
|
7995
|
+
return [
|
|
7996
|
+
`- ${id}: ${label}`,
|
|
7997
|
+
description ? `description=${description}` : null,
|
|
7998
|
+
group ? `group=${group}` : null,
|
|
7999
|
+
sourceCandidateIds.length ? `canonicalCandidates=${sourceCandidateIds.join(',')}` : null,
|
|
8000
|
+
].filter(Boolean).join('; ');
|
|
8001
|
+
})
|
|
8002
|
+
.filter((line) => !!line);
|
|
8003
|
+
return {
|
|
8004
|
+
role: 'system',
|
|
8005
|
+
content: [
|
|
8006
|
+
'Praxis table recommended capabilities policy:',
|
|
8007
|
+
'- The user selected the canonical recommended-capabilities intent. Do not merely list all table features.',
|
|
8008
|
+
'- Analyze currentState, runtimeState, schemaFields, dataProfile, and the declared availableRecommendations before answering.',
|
|
8009
|
+
'- Exclude capabilities already materialized in currentState unless the recommendation is a refinement backed by column/data evidence.',
|
|
8010
|
+
'- Prefer recommendations tied to specific columns, data profile signals, selected rows, resource surfaces, navigation destinations, or missing runtime capabilities.',
|
|
8011
|
+
'- Return a concise ranked recommendation set. When a recommendation can be materialized by componentEditPlan or a declared runtime operation, include the safest executable proposal or guided option for review.',
|
|
8012
|
+
'- Do not expose internal ids, operationIds, endpoint paths, or payload details unless the user explicitly asks for technical details.',
|
|
8013
|
+
candidateLines.length ? 'Curated canonical candidate recommendations:' : 'Curated canonical candidate recommendations: none declared.',
|
|
8014
|
+
...candidateLines,
|
|
8015
|
+
].join('\n'),
|
|
8016
|
+
};
|
|
8017
|
+
}
|
|
8018
|
+
buildComponentPresentationAuthoringSystemPolicy(contextHints, prompt) {
|
|
8019
|
+
void contextHints;
|
|
8020
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
8021
|
+
const presentationIntent = this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
8022
|
+
|| this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt);
|
|
8023
|
+
if (!presentationIntent)
|
|
8024
|
+
return null;
|
|
8025
|
+
const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
8026
|
+
.map((column) => this.toRecord(column))
|
|
8027
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
8028
|
+
.slice(0, 20);
|
|
8029
|
+
if (!columns.length)
|
|
8030
|
+
return null;
|
|
8031
|
+
const columnLines = columns.map((column) => {
|
|
8032
|
+
const field = this.stringValue(column['field']);
|
|
8033
|
+
const label = this.stringValue(column['header']) || this.humanizeField(field);
|
|
8034
|
+
const visible = column['visible'] === false ? 'hidden' : 'visible';
|
|
8035
|
+
const type = this.stringValue(column['type'] ?? column['dataType']) || 'unknown';
|
|
8036
|
+
return `- ${field} (${label}; ${type}; ${visible})`;
|
|
8037
|
+
});
|
|
8038
|
+
return {
|
|
8039
|
+
role: 'system',
|
|
8040
|
+
content: [
|
|
8041
|
+
'Praxis table component presentation authoring policy:',
|
|
8042
|
+
'- Use componentEditPlan operations for visual or structural table changes: renderers, badges, chips, conditional styles, column visibility, sticky columns, formats, and computed columns.',
|
|
8043
|
+
'- Explicit presentation requests such as badge, chip, renderer, color, highlight, format, inactive visual state, public view, hide/show column, pin/sticky column, or calculated column are edit/componentEditPlan requests, not table.filter.apply.',
|
|
8044
|
+
'- When the user asks to create a new column by joining, combining, concatenating, composing, deriving, calculating, merging, juntar, unir, concatenar, combinar, compor, derivar, calcular, or mesclar values from existing declared columns, author exactly one canonical column.computed.add operation for the new field. Use a JSON Logic cat expression for textual joins, include dependencies, and preserve the user requested header/name. Derive a stable camelCase input.field from the requested header/name when the field id is not explicitly provided; do not ask the user to choose a technical field id. Do not materialize derived columns as column.add plus column.renderer.set.',
|
|
8045
|
+
'- For compound prompts, preserve the action attached to each declared column: hide only columns in the hide/privacy scope, pin/fix columns with column.sticky.set, and highlight/destacar columns with renderer or style operations. Never hide a column that the user asked to pin or highlight.',
|
|
8046
|
+
'- If the user answers a previous component-edit clarification with a short option such as "sim", "opcao 2", or "prosseguir", continue the previous component-edit intent instead of treating the answer as a new filter request.',
|
|
8047
|
+
'- Ask clarification only when the declared source columns or authoring capabilities are insufficient to choose a safe componentEditPlan. Do not ask clarification solely to pick an internal field id for a newly named computed column.',
|
|
8048
|
+
'Declared table columns:',
|
|
8049
|
+
...columnLines,
|
|
8050
|
+
].join('\n'),
|
|
8051
|
+
};
|
|
8052
|
+
}
|
|
7198
8053
|
buildComponentLayoutAuthoringSystemPolicy(contextHints, prompt) {
|
|
7199
8054
|
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
7200
8055
|
const position = this.relativeOrderPosition(normalizedPrompt);
|
|
@@ -7227,7 +8082,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7227
8082
|
].join('\n'),
|
|
7228
8083
|
};
|
|
7229
8084
|
}
|
|
7230
|
-
buildFilterAuthoringSystemPolicy(contextHints) {
|
|
8085
|
+
buildFilterAuthoringSystemPolicy(contextHints, prompt = '') {
|
|
7231
8086
|
if (!this.runtimeOperationAllowed(contextHints, 'table.filter.apply'))
|
|
7232
8087
|
return null;
|
|
7233
8088
|
const entries = this.filterFieldCatalogEntries.length
|
|
@@ -7235,6 +8090,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7235
8090
|
: this.extractFilterFieldCatalogEntries(contextHints);
|
|
7236
8091
|
if (!entries.length)
|
|
7237
8092
|
return null;
|
|
8093
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
7238
8094
|
const catalogLines = entries.slice(0, 12).map((entry) => {
|
|
7239
8095
|
const relatedFields = entry.relatedColumnFields.length
|
|
7240
8096
|
? ` related columns: ${entry.relatedColumnFields.join(', ')}.`
|
|
@@ -7248,6 +8104,9 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7248
8104
|
'Praxis table filter authoring policy:',
|
|
7249
8105
|
'- Use the declared filterFieldCatalog as the canonical grounding for row filtering decisions.',
|
|
7250
8106
|
'- When the user asks to restrict, search, list, show only, compare values, define ranges, or filter rows, prefer a table.filter.apply runtime operation over visual renderer or column formatting operations.',
|
|
8107
|
+
...(this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
8108
|
+
? ['- This turn contains an explicit visual or structural table-edit signal; do not reinterpret that signal as a row filter unless the user also explicitly asks to restrict table rows.']
|
|
8109
|
+
: []),
|
|
7251
8110
|
'- For numeric and date comparisons, use the declared range filter field from filterFieldCatalog instead of the raw listing column field.',
|
|
7252
8111
|
'- If the user intent is ambiguous between filtering rows and changing visual presentation, ask a short clarification instead of choosing a renderer by default.',
|
|
7253
8112
|
'- Do not invent filter field names; use only declared filter fields and their related listing columns for grounding.',
|
|
@@ -8189,6 +9048,14 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8189
9048
|
stringValue(value) {
|
|
8190
9049
|
return typeof value === 'string' ? value.trim() : '';
|
|
8191
9050
|
}
|
|
9051
|
+
numberValue(value) {
|
|
9052
|
+
const parsed = typeof value === 'number'
|
|
9053
|
+
? value
|
|
9054
|
+
: typeof value === 'string'
|
|
9055
|
+
? Number(value.trim())
|
|
9056
|
+
: Number.NaN;
|
|
9057
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
9058
|
+
}
|
|
8192
9059
|
stringifySurfaceEvidence(value) {
|
|
8193
9060
|
try {
|
|
8194
9061
|
return JSON.stringify(value)?.slice(0, 4000) ?? '';
|