@praxisui/table 8.0.0-beta.100 → 8.0.0-beta.102
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-MXizgJYx.mjs → praxisui-table-praxisui-table-DDUl1Vav.mjs} +279 -15
- package/fesm2022/{praxisui-table-table-agentic-authoring-turn-flow-BJWah3jp.mjs → praxisui-table-table-agentic-authoring-turn-flow-CC5bOKf5.mjs} +1540 -192
- package/fesm2022/{praxisui-table-table-ai.adapter-DFyMRt4g.mjs → praxisui-table-table-ai.adapter-D1VAQO63.mjs} +42 -2
- package/fesm2022/praxisui-table.mjs +1 -1
- package/package.json +10 -10
- package/types/praxisui-table.d.ts +10 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Observable, firstValueFrom } from 'rxjs';
|
|
2
2
|
|
|
3
3
|
class TableAgenticAuthoringTurnFlow {
|
|
4
4
|
adapter;
|
|
5
5
|
aiApi;
|
|
6
|
+
turnClient;
|
|
6
7
|
mode = 'agentic-authoring';
|
|
7
8
|
filterFieldLabels = new Map();
|
|
8
9
|
filterFieldCatalogEntries = [];
|
|
@@ -19,9 +20,10 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
19
20
|
streamTerminalReconnectLimit = 2;
|
|
20
21
|
selectedRecordStreamTerminalWatchdogMs = 45_000;
|
|
21
22
|
selectedRecordSurfaceStreamTerminalWatchdogMs = 18_000;
|
|
22
|
-
constructor(adapter, aiApi) {
|
|
23
|
+
constructor(adapter, aiApi, turnClient) {
|
|
23
24
|
this.adapter = adapter;
|
|
24
25
|
this.aiApi = aiApi;
|
|
26
|
+
this.turnClient = turnClient;
|
|
25
27
|
}
|
|
26
28
|
submit(request) {
|
|
27
29
|
const prompt = (request.prompt ?? '').trim();
|
|
@@ -50,7 +52,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
50
52
|
const schemaFields = this.adapter.getSchemaFields?.()
|
|
51
53
|
?.map((field) => this.toAiJsonObject(field))
|
|
52
54
|
.filter((field) => Object.keys(field).length > 0);
|
|
53
|
-
const
|
|
55
|
+
const baseContextHints = this.mergeJsonObjects(this.mergeJsonObjects(this.optionalJsonObject(this.adapter.getAuthoringContext?.()), this.tableConversationMemoryHints(request)), this.optionalJsonObject(request.action?.contextHints)) ?? null;
|
|
56
|
+
const contextHints = this.mergeJsonObjects(baseContextHints ?? undefined, this.componentPresentationTargetGroundingHints(prompt) ?? undefined) ?? baseContextHints;
|
|
54
57
|
this.filterFieldCatalogEntries = this.extractFilterFieldCatalogEntries(contextHints);
|
|
55
58
|
this.filterFieldLabels = this.extractFilterFieldLabels(this.filterFieldCatalogEntries);
|
|
56
59
|
this.selectionDerivedFilterCandidateFields = this.extractSelectionDerivedFilterCandidateFields(contextHints);
|
|
@@ -82,11 +85,100 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
82
85
|
};
|
|
83
86
|
};
|
|
84
87
|
const progressPrompt = this.progressPromptFor(request, prompt);
|
|
88
|
+
if (this.canUseAgenticTurnStream()) {
|
|
89
|
+
return this.submitViaAgenticStream(request, progressPrompt, currentState, componentId, run);
|
|
90
|
+
}
|
|
85
91
|
if (this.canUsePatchStream()) {
|
|
86
92
|
return this.submitViaStream(request, progressPrompt, currentState, componentId, run);
|
|
87
93
|
}
|
|
88
94
|
return this.submitViaSnapshot(request, currentState, run);
|
|
89
95
|
}
|
|
96
|
+
submitViaAgenticStream(request, prompt, currentState, componentId, buildRequest) {
|
|
97
|
+
return new Observable((subscriber) => {
|
|
98
|
+
let closed = false;
|
|
99
|
+
let subscription = null;
|
|
100
|
+
const close = () => {
|
|
101
|
+
closed = true;
|
|
102
|
+
subscription?.unsubscribe();
|
|
103
|
+
subscription = null;
|
|
104
|
+
};
|
|
105
|
+
const emitProgress = (statusText) => {
|
|
106
|
+
if (closed || !statusText.trim())
|
|
107
|
+
return;
|
|
108
|
+
subscriber.next({
|
|
109
|
+
state: 'processing',
|
|
110
|
+
phase: 'contextualize',
|
|
111
|
+
statusText,
|
|
112
|
+
canApply: false,
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
const fallbackToSnapshot = async () => {
|
|
116
|
+
if (closed)
|
|
117
|
+
return;
|
|
118
|
+
try {
|
|
119
|
+
emitProgress(this.buildSnapshotFallbackMessage(prompt, componentId));
|
|
120
|
+
const result = await this.submitViaSnapshot(request, currentState, buildRequest);
|
|
121
|
+
if (!closed) {
|
|
122
|
+
subscriber.next(result);
|
|
123
|
+
subscriber.complete();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (!closed) {
|
|
128
|
+
subscriber.next(this.toAssistantErrorResult(error));
|
|
129
|
+
subscriber.complete();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
close();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
void (async () => {
|
|
137
|
+
try {
|
|
138
|
+
emitProgress(this.buildInitialProgressMessage(prompt, componentId));
|
|
139
|
+
const { patchRequest, contextHints } = await buildRequest();
|
|
140
|
+
const localResponse = this.localRecommendedIntentResponse(request, contextHints ?? null);
|
|
141
|
+
if (localResponse) {
|
|
142
|
+
subscriber.next(this.toTurnResult(localResponse, request));
|
|
143
|
+
subscriber.complete();
|
|
144
|
+
close();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (this.shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints ?? null)) {
|
|
148
|
+
await fallbackToSnapshot();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
subscription = this.turnClient?.streamEvents(this.toAgenticAuthoringTurnRequest(patchRequest), {
|
|
152
|
+
resultTimeoutMs: this.streamTerminalWatchdogDelay(contextHints ?? null),
|
|
153
|
+
streamTimeoutMs: this.streamTerminalWatchdogDelay(contextHints ?? null),
|
|
154
|
+
}).subscribe({
|
|
155
|
+
next: (event) => {
|
|
156
|
+
if (closed)
|
|
157
|
+
return;
|
|
158
|
+
const terminal = this.toAgenticStreamTurnResult(event, request, currentState);
|
|
159
|
+
if (terminal) {
|
|
160
|
+
subscriber.next(terminal);
|
|
161
|
+
subscriber.complete();
|
|
162
|
+
close();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const progress = this.toAgenticStreamProgress(event, prompt, componentId);
|
|
166
|
+
if (progress) {
|
|
167
|
+
emitProgress(progress);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
error: () => {
|
|
171
|
+
void fallbackToSnapshot();
|
|
172
|
+
},
|
|
173
|
+
}) ?? null;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
await fallbackToSnapshot();
|
|
177
|
+
}
|
|
178
|
+
})();
|
|
179
|
+
return () => close();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
90
182
|
async submitViaSnapshot(request, currentState, buildRequest) {
|
|
91
183
|
const { patchRequest, contextHints } = await buildRequest();
|
|
92
184
|
const localResponse = this.localRecommendedIntentResponse(request, contextHints ?? null);
|
|
@@ -317,16 +409,20 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
317
409
|
const guidedSelectedRecordsFilter = this.localGuidedSelectedRecordsFilterResponse(request, contextHints);
|
|
318
410
|
if (guidedSelectedRecordsFilter)
|
|
319
411
|
return guidedSelectedRecordsFilter;
|
|
412
|
+
if (this.selectedRecordLocalReadonlyAnalysisRequested(request)
|
|
413
|
+
&& this.selectedRecordSampleRows(contextHints).length > 0) {
|
|
414
|
+
return this.selectedRecordsReadonlyInfoResponse(request, contextHints);
|
|
415
|
+
}
|
|
416
|
+
if (this.selectedRecordGenericCommonalityAnalysisRequested(request)
|
|
417
|
+
&& this.selectedRecordSampleRows(contextHints).length > 0) {
|
|
418
|
+
return this.selectedRecordsGuidedAnalysisResponse(request, contextHints);
|
|
419
|
+
}
|
|
320
420
|
const selectedRecordDerivedFilter = this.localSelectedRecordDerivedFilterResponse(request, contextHints);
|
|
321
421
|
if (selectedRecordDerivedFilter)
|
|
322
422
|
return selectedRecordDerivedFilter;
|
|
323
423
|
const declaredFilter = this.localDeclaredFilterApplyResponse(request, contextHints);
|
|
324
424
|
if (declaredFilter)
|
|
325
425
|
return declaredFilter;
|
|
326
|
-
if (this.selectedRecordGenericCommonalityAnalysisRequested(request)
|
|
327
|
-
&& this.selectedRecordSampleRows(contextHints).length > 0) {
|
|
328
|
-
return this.selectedRecordsGuidedAnalysisResponse(request, contextHints);
|
|
329
|
-
}
|
|
330
426
|
const recommendedIntent = this.toRecord(contextHints?.['recommendedIntent']);
|
|
331
427
|
const intentId = this.stringValue(recommendedIntent?.['id'])
|
|
332
428
|
|| this.stringValue(contextHints?.['opportunityId']);
|
|
@@ -398,15 +494,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
398
494
|
};
|
|
399
495
|
}
|
|
400
496
|
if (this.selectedRecordReadOnlyAnalysisRequested(request)) {
|
|
401
|
-
return
|
|
402
|
-
type: 'info',
|
|
403
|
-
sessionId: request.sessionId,
|
|
404
|
-
message: this.selectedRecordsAnalyticalSummary(contextHints),
|
|
405
|
-
warnings: [
|
|
406
|
-
'recommended-intent-local-selected-records-analysis',
|
|
407
|
-
'Resposta informativa gerada a partir do recommendedIntent canonico table.selection.analyze e selectedRecordsContext.sampleRows; nao houve roteamento textual primario.',
|
|
408
|
-
],
|
|
409
|
-
};
|
|
497
|
+
return this.selectedRecordsReadonlyInfoResponse(request, contextHints);
|
|
410
498
|
}
|
|
411
499
|
const candidates = this.selectedRecordFilterCandidates(contextHints);
|
|
412
500
|
const fields = candidates
|
|
@@ -433,6 +521,17 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
433
521
|
],
|
|
434
522
|
};
|
|
435
523
|
}
|
|
524
|
+
selectedRecordsReadonlyInfoResponse(request, contextHints) {
|
|
525
|
+
return {
|
|
526
|
+
type: 'info',
|
|
527
|
+
sessionId: request.sessionId,
|
|
528
|
+
message: this.selectedRecordsAnalyticalSummary(contextHints),
|
|
529
|
+
warnings: [
|
|
530
|
+
'recommended-intent-local-selected-records-analysis',
|
|
531
|
+
'Resposta informativa gerada a partir do selectedRecordsContext.sampleRows e de uma intencao consultiva ja contextualizada; nao houve roteamento textual primario.',
|
|
532
|
+
],
|
|
533
|
+
};
|
|
534
|
+
}
|
|
436
535
|
localGuidedRuntimeOperationResponse(request, contextHints) {
|
|
437
536
|
const runtimeOperation = this.toRecord(contextHints?.['tableRuntimeOperation']);
|
|
438
537
|
const operationId = this.stringValue(runtimeOperation?.['operationId']);
|
|
@@ -2026,6 +2125,40 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2026
2125
|
return typeof this.aiApi.startPatchStream === 'function'
|
|
2027
2126
|
&& typeof this.aiApi.connectPatchStream === 'function';
|
|
2028
2127
|
}
|
|
2128
|
+
canUseAgenticTurnStream() {
|
|
2129
|
+
return typeof this.turnClient?.streamEvents === 'function';
|
|
2130
|
+
}
|
|
2131
|
+
toAgenticAuthoringTurnRequest(patchRequest) {
|
|
2132
|
+
return {
|
|
2133
|
+
...patchRequest,
|
|
2134
|
+
targetApp: 'praxis-ui-angular',
|
|
2135
|
+
targetComponentId: patchRequest.componentId,
|
|
2136
|
+
currentPage: patchRequest.currentState,
|
|
2137
|
+
contextHints: patchRequest.contextHints ?? null,
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
toAgenticStreamTurnResult(event, request, currentState) {
|
|
2141
|
+
if (event.kind !== 'stream-event') {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2144
|
+
return this.toTerminalStreamResult(event.event, request, currentState);
|
|
2145
|
+
}
|
|
2146
|
+
toAgenticStreamProgress(event, prompt, componentId) {
|
|
2147
|
+
if (event.kind === 'stream-started') {
|
|
2148
|
+
return this.buildStreamProgressMessage('status', { message: 'Stream iniciado.', phase: 'request' }, prompt, componentId);
|
|
2149
|
+
}
|
|
2150
|
+
if (event.kind === 'stream-lifecycle') {
|
|
2151
|
+
return this.buildStreamProgressMessage('status', {
|
|
2152
|
+
message: event.statusText,
|
|
2153
|
+
phase: event.phase,
|
|
2154
|
+
...(event.diagnostics ?? {}),
|
|
2155
|
+
}, prompt, componentId);
|
|
2156
|
+
}
|
|
2157
|
+
if (event.event.type === 'heartbeat') {
|
|
2158
|
+
return null;
|
|
2159
|
+
}
|
|
2160
|
+
return this.buildStreamProgressMessage(event.event.type, this.toRecord(event.event.payload) ?? {}, prompt, componentId);
|
|
2161
|
+
}
|
|
2029
2162
|
toTerminalStreamResult(event, request, currentState) {
|
|
2030
2163
|
if (!event)
|
|
2031
2164
|
return null;
|
|
@@ -2189,14 +2322,27 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2189
2322
|
});
|
|
2190
2323
|
}
|
|
2191
2324
|
retry(request) {
|
|
2325
|
+
const retryAction = this.retryActionFromDiagnostics(request.diagnostics);
|
|
2192
2326
|
const lastPrompt = [...(request.messages ?? [])].reverse()
|
|
2193
2327
|
.find((message) => message.role === 'user')?.text;
|
|
2194
2328
|
return this.submit({
|
|
2195
2329
|
...request,
|
|
2196
|
-
prompt: lastPrompt
|
|
2197
|
-
action:
|
|
2330
|
+
prompt: this.stringValue(retryAction?.['value']) || lastPrompt || request.prompt,
|
|
2331
|
+
action: retryAction
|
|
2332
|
+
? { ...retryAction, kind: this.stringValue(retryAction['kind']) || 'clarify' }
|
|
2333
|
+
: { kind: 'retry' },
|
|
2198
2334
|
});
|
|
2199
2335
|
}
|
|
2336
|
+
retryActionFromDiagnostics(diagnostics) {
|
|
2337
|
+
const retryAction = this.toRecord(diagnostics?.['retryAction']);
|
|
2338
|
+
if (!retryAction)
|
|
2339
|
+
return null;
|
|
2340
|
+
const contextHints = this.toRecord(retryAction['contextHints']);
|
|
2341
|
+
return {
|
|
2342
|
+
...retryAction,
|
|
2343
|
+
...(contextHints ? { contextHints } : {}),
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2200
2346
|
toTurnResult(response, request) {
|
|
2201
2347
|
if (!response) {
|
|
2202
2348
|
return {
|
|
@@ -2261,6 +2407,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2261
2407
|
}
|
|
2262
2408
|
if (response.type === 'error') {
|
|
2263
2409
|
const message = response.message || 'Falha ao gerar alteração de tabela.';
|
|
2410
|
+
const retryDiagnostics = this.errorRetryDiagnostics(request, response);
|
|
2264
2411
|
return {
|
|
2265
2412
|
state: 'error',
|
|
2266
2413
|
phase: 'capture',
|
|
@@ -2268,7 +2415,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2268
2415
|
observationId: response.observationId ?? request.observationId ?? null,
|
|
2269
2416
|
assistantMessage: message,
|
|
2270
2417
|
errorText: message,
|
|
2271
|
-
diagnostics:
|
|
2418
|
+
diagnostics: retryDiagnostics,
|
|
2272
2419
|
};
|
|
2273
2420
|
}
|
|
2274
2421
|
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
@@ -2320,10 +2467,22 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2320
2467
|
}
|
|
2321
2468
|
compileAdapterResponse(response, request) {
|
|
2322
2469
|
response = this.normalizeCategoricalRendererPalette(response, request);
|
|
2470
|
+
const incompatibleBooleanStatusPresentation = this.incompatibleBooleanStatusPresentationBoundary(response, request);
|
|
2471
|
+
if (incompatibleBooleanStatusPresentation) {
|
|
2472
|
+
return incompatibleBooleanStatusPresentation;
|
|
2473
|
+
}
|
|
2474
|
+
const visualPresentationTargetMismatch = this.visualPresentationTargetMismatchBoundary(response, request);
|
|
2475
|
+
if (visualPresentationTargetMismatch) {
|
|
2476
|
+
return visualPresentationTargetMismatch;
|
|
2477
|
+
}
|
|
2323
2478
|
response = this.normalizeStatusPresentationPlan(response, request);
|
|
2324
2479
|
response = this.normalizeNumericBandConditionalStylePlan(response, request);
|
|
2325
2480
|
response = this.normalizeCompoundColumnPresentationPlan(response, request);
|
|
2326
2481
|
response = this.normalizeComputedColumnAuxiliaryMaterializations(response);
|
|
2482
|
+
const visualPresentationChoice = this.visualPresentationChoiceClarificationBoundary(response, request);
|
|
2483
|
+
if (visualPresentationChoice) {
|
|
2484
|
+
return visualPresentationChoice;
|
|
2485
|
+
}
|
|
2327
2486
|
if (response.type === 'clarification' || response.type === 'info' || response.type === 'error') {
|
|
2328
2487
|
const selectedRecordReadonlyClarificationBoundary = this.selectedRecordReadonlyClarificationBoundaryResponse(response, request);
|
|
2329
2488
|
if (selectedRecordReadonlyClarificationBoundary) {
|
|
@@ -2333,6 +2492,22 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2333
2492
|
if (continuedEarlyColumnPlan) {
|
|
2334
2493
|
return this.compileAdapterResponse(continuedEarlyColumnPlan, request);
|
|
2335
2494
|
}
|
|
2495
|
+
const governedCategoricalSemanticsChoice = this.governedCategoricalSemanticsClarificationBoundary(response);
|
|
2496
|
+
if (governedCategoricalSemanticsChoice) {
|
|
2497
|
+
return governedCategoricalSemanticsChoice;
|
|
2498
|
+
}
|
|
2499
|
+
const governedCategoricalDiscovery = this.governedCategoricalDiscoveryBoundary(response, request);
|
|
2500
|
+
if (governedCategoricalDiscovery) {
|
|
2501
|
+
return governedCategoricalDiscovery;
|
|
2502
|
+
}
|
|
2503
|
+
const canonicalRendererSelectionBoundary = this.canonicalRendererSelectionBoundary(response, request);
|
|
2504
|
+
if (canonicalRendererSelectionBoundary) {
|
|
2505
|
+
return canonicalRendererSelectionBoundary;
|
|
2506
|
+
}
|
|
2507
|
+
const governedCategoricalSelectionBoundary = this.governedCategoricalSelectionBoundary(response, request);
|
|
2508
|
+
if (governedCategoricalSelectionBoundary) {
|
|
2509
|
+
return governedCategoricalSelectionBoundary;
|
|
2510
|
+
}
|
|
2336
2511
|
const continuedColumnVisibilityPlan = this.columnVisibilityPlanForGroundedClarification(response, request);
|
|
2337
2512
|
if (continuedColumnVisibilityPlan) {
|
|
2338
2513
|
return this.compileAdapterResponse(continuedColumnVisibilityPlan, request);
|
|
@@ -2375,7 +2550,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2375
2550
|
if (selectedRecordReadonlyBoundary) {
|
|
2376
2551
|
return selectedRecordReadonlyBoundary;
|
|
2377
2552
|
}
|
|
2378
|
-
return executableResponse;
|
|
2553
|
+
return this.normalizeGovernedCategoricalSelectionExecutableResponse(executableResponse, request);
|
|
2379
2554
|
}
|
|
2380
2555
|
if (compiledExecutable?.type === 'error') {
|
|
2381
2556
|
const continuedColumnVisibilityError = this.columnVisibilityPlanForMisroutedSurfaceError(response, request, compiledExecutable);
|
|
@@ -2581,7 +2756,93 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2581
2756
|
return visualSurfaceMismatch;
|
|
2582
2757
|
}
|
|
2583
2758
|
const openOnlySurfaceRuntimeOperation = this.selectedRecordSurfaceRuntimeOperationForOpenOnlyExecutable(compiledResponse, request);
|
|
2584
|
-
|
|
2759
|
+
if (openOnlySurfaceRuntimeOperation)
|
|
2760
|
+
return openOnlySurfaceRuntimeOperation;
|
|
2761
|
+
return this.normalizeGovernedCategoricalSelectionExecutableResponse(compiledResponse, request);
|
|
2762
|
+
}
|
|
2763
|
+
visualPresentationChoiceClarificationBoundary(response, request) {
|
|
2764
|
+
const carriesVisualPayload = (response.optionPayloads ?? [])
|
|
2765
|
+
.some((payload) => this.isVisualPresentationClarificationPayload(payload));
|
|
2766
|
+
if (!carriesVisualPayload)
|
|
2767
|
+
return null;
|
|
2768
|
+
if (!this.promptRequestsPresentationOptions(request?.prompt ?? ''))
|
|
2769
|
+
return null;
|
|
2770
|
+
const message = response.message || 'Encontrei opções de apresentação para essa coluna. Escolha uma opção para aplicar.';
|
|
2771
|
+
return {
|
|
2772
|
+
...response,
|
|
2773
|
+
type: 'clarification',
|
|
2774
|
+
message,
|
|
2775
|
+
explanation: message,
|
|
2776
|
+
componentEditPlan: undefined,
|
|
2777
|
+
patch: undefined,
|
|
2778
|
+
warnings: [
|
|
2779
|
+
...(response.warnings ?? []),
|
|
2780
|
+
'visual-presentation-choice-kept-as-clarification',
|
|
2781
|
+
],
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
governedCategoricalSemanticsClarificationBoundary(response) {
|
|
2785
|
+
if (!this.responseHasGovernedCategoricalSemanticsChoice(response))
|
|
2786
|
+
return null;
|
|
2787
|
+
const message = response.message
|
|
2788
|
+
|| 'Esta apresentação precisa de uma decisão semântica governada antes de materializar a tabela.';
|
|
2789
|
+
return {
|
|
2790
|
+
...response,
|
|
2791
|
+
type: 'clarification',
|
|
2792
|
+
message,
|
|
2793
|
+
explanation: message,
|
|
2794
|
+
componentEditPlan: undefined,
|
|
2795
|
+
patch: undefined,
|
|
2796
|
+
warnings: [
|
|
2797
|
+
...(response.warnings ?? []),
|
|
2798
|
+
'governed-categorical-semantics-choice-kept-as-clarification',
|
|
2799
|
+
],
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
responseHasGovernedCategoricalSemanticsChoice(response) {
|
|
2803
|
+
if ((response.optionPayloads ?? [])
|
|
2804
|
+
.some((payload) => this.isGovernedCategoricalSemanticsChoicePayload(payload))) {
|
|
2805
|
+
return true;
|
|
2806
|
+
}
|
|
2807
|
+
return this.isGovernedCategoricalSemanticsChoiceText([
|
|
2808
|
+
response.message,
|
|
2809
|
+
response.explanation,
|
|
2810
|
+
...(response.questions ?? []),
|
|
2811
|
+
...(response.options ?? []),
|
|
2812
|
+
].filter(Boolean).join(' '));
|
|
2813
|
+
}
|
|
2814
|
+
promptRequestsPresentationOptions(prompt) {
|
|
2815
|
+
const normalized = this.normalizeLabel(prompt);
|
|
2816
|
+
if (!normalized)
|
|
2817
|
+
return false;
|
|
2818
|
+
const asksForOptions = [
|
|
2819
|
+
'opcao',
|
|
2820
|
+
'opcoes',
|
|
2821
|
+
'opção',
|
|
2822
|
+
'opções',
|
|
2823
|
+
'alternativa',
|
|
2824
|
+
'alternativas',
|
|
2825
|
+
'recomenda',
|
|
2826
|
+
'recomende',
|
|
2827
|
+
'sugere',
|
|
2828
|
+
'sugira',
|
|
2829
|
+
'compare',
|
|
2830
|
+
].some((token) => this.normalizedTextContainsApproxPhrase(normalized, token));
|
|
2831
|
+
if (!asksForOptions)
|
|
2832
|
+
return false;
|
|
2833
|
+
return [
|
|
2834
|
+
'apresentacao',
|
|
2835
|
+
'apresentação',
|
|
2836
|
+
'formatacao',
|
|
2837
|
+
'formatação',
|
|
2838
|
+
'badge',
|
|
2839
|
+
'icone',
|
|
2840
|
+
'ícone',
|
|
2841
|
+
'alinhamento',
|
|
2842
|
+
'duas linhas',
|
|
2843
|
+
'duaslinhas',
|
|
2844
|
+
'visual',
|
|
2845
|
+
].some((token) => this.normalizedTextContainsApproxPhrase(normalized, token));
|
|
2585
2846
|
}
|
|
2586
2847
|
normalizeCategoricalRendererPalette(response, request) {
|
|
2587
2848
|
const plan = this.toRecord(response.componentEditPlan);
|
|
@@ -2711,7 +2972,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2711
2972
|
relativeColumnOrderPlanForMisroutedClarification(response, request) {
|
|
2712
2973
|
if (!request?.prompt)
|
|
2713
2974
|
return null;
|
|
2714
|
-
const prompt = this.normalizeLabel(request.prompt);
|
|
2975
|
+
const prompt = this.normalizeLabel(request.prompt ?? '');
|
|
2715
2976
|
const position = this.relativeOrderPosition(prompt);
|
|
2716
2977
|
if (!position)
|
|
2717
2978
|
return null;
|
|
@@ -4703,6 +4964,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4703
4964
|
const responseType = this.stringValue(response.type);
|
|
4704
4965
|
if (!request || !['clarification', 'info', 'success'].includes(responseType))
|
|
4705
4966
|
return null;
|
|
4967
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
4968
|
+
return null;
|
|
4706
4969
|
const responseText = [
|
|
4707
4970
|
response.message ?? '',
|
|
4708
4971
|
response.explanation ?? '',
|
|
@@ -4737,7 +5000,9 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4737
5000
|
?? this.booleanSimNaoOperationsFromGroundedClarification(prompt, responseText, columns)
|
|
4738
5001
|
?? this.booleanSimNaoOperationsFromSingleGroundedBooleanColumn(prompt, responseText, columns)
|
|
4739
5002
|
?? this.booleanSimNaoOperationsFromGroundedResponseField(prompt, responseText, columns);
|
|
4740
|
-
const statusPresentationOperations = this.
|
|
5003
|
+
const statusPresentationOperations = this.responseCarriesClarificationChoices(response)
|
|
5004
|
+
? []
|
|
5005
|
+
: this.statusPresentationOperationsFromPrompt(prompt, columns);
|
|
4741
5006
|
if (statusPresentationOperations.length) {
|
|
4742
5007
|
return this.componentEditPlanResponse(statusPresentationOperations, 'Vou ajustar a apresentacao visual do status sem filtrar as linhas.', [
|
|
4743
5008
|
...(response.warnings ?? []),
|
|
@@ -4813,6 +5078,9 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4813
5078
|
]);
|
|
4814
5079
|
}
|
|
4815
5080
|
const categoricalRenderer = this.categoricalChipOperationsFromVisualClarification(prompt, responseText, columns);
|
|
5081
|
+
if (categoricalRenderer.length && this.responseHasGovernedCategoricalSemanticsChoice(response)) {
|
|
5082
|
+
return null;
|
|
5083
|
+
}
|
|
4816
5084
|
if (categoricalRenderer.length) {
|
|
4817
5085
|
return this.componentEditPlanResponse(categoricalRenderer, 'Vou aplicar chips discretos na coluna indicada.', [
|
|
4818
5086
|
...(response.warnings ?? []),
|
|
@@ -4888,6 +5156,14 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4888
5156
|
return response;
|
|
4889
5157
|
if ((response.warnings ?? []).includes('status-presentation-normalized'))
|
|
4890
5158
|
return response;
|
|
5159
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
5160
|
+
return response;
|
|
5161
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5162
|
+
return response;
|
|
5163
|
+
if ((response.optionPayloads?.length ?? 0) > 0)
|
|
5164
|
+
return response;
|
|
5165
|
+
if (this.responseHasGovernedCategoricalSemanticsChoice(response))
|
|
5166
|
+
return response;
|
|
4891
5167
|
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
4892
5168
|
const columns = Array.isArray(currentConfig?.['columns'])
|
|
4893
5169
|
? currentConfig['columns']
|
|
@@ -4933,94 +5209,666 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4933
5209
|
],
|
|
4934
5210
|
};
|
|
4935
5211
|
}
|
|
4936
|
-
|
|
4937
|
-
if (
|
|
4938
|
-
return
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
const
|
|
4942
|
-
const
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
5212
|
+
requestIsCanonicalRendererSelection(request) {
|
|
5213
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5214
|
+
return false;
|
|
5215
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5216
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5217
|
+
const actionSelection = this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection']);
|
|
5218
|
+
const requestSelection = this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
|
|
5219
|
+
const mode = this.normalizeLabel(this.stringValue(actionSelection?.['mode']) || this.stringValue(requestSelection?.['mode']));
|
|
5220
|
+
if (mode === 'renderer')
|
|
5221
|
+
return true;
|
|
5222
|
+
const prompt = this.normalizeLabel(request.prompt ?? '');
|
|
5223
|
+
return this.normalizedTextIncludesAny(prompt, [
|
|
5224
|
+
'two_lines',
|
|
5225
|
+
'two lines',
|
|
5226
|
+
'two line',
|
|
5227
|
+
'two-line',
|
|
5228
|
+
'twolines',
|
|
5229
|
+
'twoline',
|
|
5230
|
+
'duas linhas',
|
|
5231
|
+
'duas_linhas',
|
|
5232
|
+
'duaslinhas',
|
|
5233
|
+
'segunda linha',
|
|
5234
|
+
]);
|
|
5235
|
+
}
|
|
5236
|
+
canonicalRendererSelectionBoundary(response, request) {
|
|
5237
|
+
if (!request || !this.requestIsCanonicalRendererSelection(request))
|
|
5238
|
+
return null;
|
|
5239
|
+
if (response.type === 'error')
|
|
5240
|
+
return null;
|
|
5241
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5242
|
+
return null;
|
|
5243
|
+
const selection = this.canonicalRendererSelection(request);
|
|
5244
|
+
const field = this.stringValue(selection?.['field']);
|
|
5245
|
+
if (!field)
|
|
5246
|
+
return null;
|
|
5247
|
+
const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
|
|
5248
|
+
|| this.componentEditOperations(response.componentEditPlan).length > 0
|
|
5249
|
+
|| !!this.toRecord(response.patch);
|
|
5250
|
+
if (hasExecutableEnvelope && this.responseTargetsSelectedRendererField(response, field)) {
|
|
5251
|
+
return null;
|
|
5252
|
+
}
|
|
5253
|
+
const label = this.stringValue(request.action?.displayPrompt)
|
|
5254
|
+
|| this.stringValue(request.action?.value)
|
|
5255
|
+
|| this.stringValue(selection?.['value'])
|
|
5256
|
+
|| 'opção selecionada';
|
|
5257
|
+
const header = this.humanizeField(field);
|
|
4965
5258
|
return {
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
|
|
4971
|
-
version: this.stringValue(originalPlan?.['version']) || '1.0',
|
|
4972
|
-
componentId: this.stringValue(originalPlan?.['componentId'])
|
|
4973
|
-
|| this.adapter.componentId
|
|
4974
|
-
|| request.componentId
|
|
4975
|
-
|| 'praxis-table',
|
|
4976
|
-
operations: [...originalOperations, ...operations],
|
|
4977
|
-
},
|
|
4978
|
-
explanation: response.explanation || 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.',
|
|
5259
|
+
type: 'info',
|
|
5260
|
+
sessionId: response.sessionId,
|
|
5261
|
+
observationId: response.observationId,
|
|
5262
|
+
message: `A opção "${label}" foi selecionada, mas a resposta ainda não trouxe um plano governado aplicável para ${header}.`,
|
|
4979
5263
|
warnings: [
|
|
4980
5264
|
...(response.warnings ?? []),
|
|
4981
|
-
'
|
|
4982
|
-
'
|
|
5265
|
+
'canonical-renderer-selection-kept-non-executable',
|
|
5266
|
+
'A guided renderer option was selected from canonical optionSelected context; the returned answer did not target the selected field with an executable presentation plan, so the table runtime kept the turn non-applicable instead of synthesizing a status boolean patch.',
|
|
4983
5267
|
],
|
|
4984
5268
|
};
|
|
4985
5269
|
}
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
.some((operation) => expected.has(this.componentEditPlanOperationIdentity(operation)));
|
|
5270
|
+
canonicalRendererSelection(request) {
|
|
5271
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5272
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5273
|
+
return this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection'])
|
|
5274
|
+
?? this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
|
|
4992
5275
|
}
|
|
4993
|
-
|
|
4994
|
-
|
|
5276
|
+
responseTargetsSelectedRendererField(response, field) {
|
|
5277
|
+
const normalizedField = this.normalizeLabel(field);
|
|
5278
|
+
if (!normalizedField)
|
|
5279
|
+
return false;
|
|
5280
|
+
const operations = this.componentEditOperations(response.componentEditPlan);
|
|
5281
|
+
if (operations.some((operation) => this.normalizeLabel(this.operationTargetField(operation)) === normalizedField)) {
|
|
4995
5282
|
return true;
|
|
4996
5283
|
}
|
|
4997
5284
|
const patch = this.toRecord(response.patch);
|
|
4998
|
-
const columns =
|
|
4999
|
-
return columns
|
|
5285
|
+
const columns = this.toArray(patch?.['columns'])
|
|
5000
5286
|
.map((column) => this.toRecord(column))
|
|
5001
|
-
.
|
|
5287
|
+
.filter((column) => !!column);
|
|
5288
|
+
return columns.some((column) => this.normalizeLabel(this.stringValue(column['field'])) === normalizedField);
|
|
5002
5289
|
}
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
if (
|
|
5007
|
-
return
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
.
|
|
5012
|
-
.
|
|
5013
|
-
if (
|
|
5014
|
-
return
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5290
|
+
governedCategoricalSelectionBoundary(response, request) {
|
|
5291
|
+
if (!request || !this.requestHasGovernedCategoricalSelection(request))
|
|
5292
|
+
return null;
|
|
5293
|
+
if (response.type === 'error')
|
|
5294
|
+
return null;
|
|
5295
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5296
|
+
return null;
|
|
5297
|
+
const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
|
|
5298
|
+
|| this.componentEditOperations(response.componentEditPlan).length > 0
|
|
5299
|
+
|| !!this.toRecord(response.patch);
|
|
5300
|
+
if (hasExecutableEnvelope)
|
|
5301
|
+
return null;
|
|
5302
|
+
const field = this.governedCategoricalSelectionTargetField(request, response);
|
|
5303
|
+
return {
|
|
5304
|
+
type: 'info',
|
|
5305
|
+
sessionId: response.sessionId,
|
|
5306
|
+
observationId: response.observationId,
|
|
5307
|
+
message: field
|
|
5308
|
+
? `A opção governada foi selecionada, mas a resposta ainda não trouxe um plano aplicável para ${this.humanizeField(field)}.`
|
|
5309
|
+
: 'A opção governada foi selecionada, mas a resposta ainda não trouxe um plano aplicável para esta coluna.',
|
|
5310
|
+
warnings: [
|
|
5311
|
+
...(response.warnings ?? []),
|
|
5312
|
+
'governed-categorical-selection-kept-non-executable',
|
|
5313
|
+
],
|
|
5314
|
+
};
|
|
5315
|
+
}
|
|
5316
|
+
governedCategoricalDiscoveryBoundary(response, request) {
|
|
5317
|
+
if (!request || !this.requestHasGovernedCategoricalSelection(request))
|
|
5318
|
+
return null;
|
|
5319
|
+
if (!this.requestSelectsCategoricalFieldSemanticsDiscovery(request))
|
|
5320
|
+
return null;
|
|
5321
|
+
if (response.type === 'error')
|
|
5322
|
+
return null;
|
|
5323
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5324
|
+
return null;
|
|
5325
|
+
const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
|
|
5326
|
+
|| this.componentEditOperations(response.componentEditPlan).length > 0
|
|
5327
|
+
|| !!this.toRecord(response.patch);
|
|
5328
|
+
if (hasExecutableEnvelope)
|
|
5329
|
+
return null;
|
|
5330
|
+
const field = this.governedCategoricalSelectionTargetField(request, response);
|
|
5331
|
+
if (!field)
|
|
5332
|
+
return null;
|
|
5333
|
+
const column = this.currentConfigColumns()
|
|
5334
|
+
.find((candidate) => this.stringValue(candidate['field']) === field);
|
|
5335
|
+
const label = this.stringValue(column?.['header']) || this.humanizeField(field);
|
|
5336
|
+
const values = this.currentCategoricalValuesForField(field);
|
|
5337
|
+
const valuesMessage = values.length
|
|
5338
|
+
? `Valores observados em ${label}: ${values.join(', ')}.`
|
|
5339
|
+
: `Ainda não recebi amostras de valores para ${label}.`;
|
|
5340
|
+
return {
|
|
5341
|
+
type: 'clarification',
|
|
5342
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5343
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5344
|
+
message: `${valuesMessage} Posso aplicar uma apresentação categórica neutra agora; para ícones, cores ou prioridade por valor, preciso de uma política semântica governada para estes valores.`,
|
|
5345
|
+
questions: [`Como quer materializar ${label} agora?`],
|
|
5346
|
+
optionPayloads: [
|
|
5347
|
+
this.categoricalPresentationOptionPayload(field, label, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${label}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
|
|
5348
|
+
],
|
|
5349
|
+
warnings: [
|
|
5350
|
+
...(response.warnings ?? []),
|
|
5351
|
+
'governed-categorical-discovery-grounded-in-current-rows',
|
|
5352
|
+
'The guided categorical discovery option was continued with current table values and kept non-executable because no governed value-level semantic policy was returned.',
|
|
5353
|
+
],
|
|
5354
|
+
};
|
|
5355
|
+
}
|
|
5356
|
+
normalizeGovernedCategoricalSelectionExecutableResponse(response, request) {
|
|
5357
|
+
if (!request || !this.requestHasGovernedCategoricalSelection(request))
|
|
5358
|
+
return response;
|
|
5359
|
+
if (this.requestSelectsCategoricalFieldSemanticsDiscovery(request)) {
|
|
5360
|
+
return this.governedCategoricalDiscoveryBoundary({
|
|
5361
|
+
type: 'info',
|
|
5362
|
+
sessionId: response.sessionId,
|
|
5363
|
+
observationId: response.observationId ?? null,
|
|
5364
|
+
warnings: [
|
|
5365
|
+
...(response.warnings ?? []),
|
|
5366
|
+
'governed-categorical-discovery-executable-response-blocked',
|
|
5367
|
+
],
|
|
5368
|
+
}, request) ?? {
|
|
5369
|
+
type: 'info',
|
|
5370
|
+
sessionId: response.sessionId,
|
|
5371
|
+
observationId: response.observationId ?? null,
|
|
5372
|
+
message: 'A descoberta governada foi mantida sem aplicação porque a resposta retornou um plano executável antes de uma política semântica por valor.',
|
|
5373
|
+
warnings: [
|
|
5374
|
+
...(response.warnings ?? []),
|
|
5375
|
+
'governed-categorical-discovery-executable-response-blocked',
|
|
5376
|
+
],
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
const field = this.governedCategoricalSelectionTargetField(request, response);
|
|
5380
|
+
if (field && !this.responseTargetsSelectedRendererField(response, field)) {
|
|
5381
|
+
return {
|
|
5382
|
+
type: 'info',
|
|
5383
|
+
sessionId: response.sessionId,
|
|
5384
|
+
observationId: response.observationId,
|
|
5385
|
+
message: `A opção governada foi selecionada, mas o plano retornado não mira ${this.humanizeField(field)}.`,
|
|
5386
|
+
warnings: [
|
|
5387
|
+
...(response.warnings ?? []),
|
|
5388
|
+
'governed-categorical-selection-target-mismatch',
|
|
5389
|
+
],
|
|
5390
|
+
};
|
|
5391
|
+
}
|
|
5392
|
+
const label = this.stringValue(request.action?.displayPrompt)
|
|
5393
|
+
|| this.stringValue(request.action?.value)
|
|
5394
|
+
|| this.stringValue(request.prompt)
|
|
5395
|
+
|| 'opção governada';
|
|
5396
|
+
const target = field ? this.humanizeField(field) : 'a coluna categórica';
|
|
5397
|
+
const isNeutralFallback = this.normalizeLabel(label).includes('neutro');
|
|
5398
|
+
const booleanPlanBlocked = this.governedCategoricalBooleanPlanBoundary(response, request, field, isNeutralFallback);
|
|
5399
|
+
if (booleanPlanBlocked)
|
|
5400
|
+
return booleanPlanBlocked;
|
|
5401
|
+
if (!isNeutralFallback && !this.responseHasBooleanStatusNarrative(response))
|
|
5402
|
+
return response;
|
|
5403
|
+
const normalizedExplanation = isNeutralFallback
|
|
5404
|
+
? `Vou aplicar chips neutros em ${target}, sem política por valor.`
|
|
5405
|
+
: `Vou aplicar a opção governada em ${target}.`;
|
|
5406
|
+
return {
|
|
5407
|
+
...response,
|
|
5408
|
+
explanation: normalizedExplanation,
|
|
5409
|
+
message: undefined,
|
|
5410
|
+
componentEditPlan: undefined,
|
|
5411
|
+
warnings: [
|
|
5412
|
+
...(response.warnings ?? []),
|
|
5413
|
+
'governed-categorical-selection-review-text-normalized',
|
|
5414
|
+
isNeutralFallback
|
|
5415
|
+
? 'The executable plan was preserved, but the neutral categorical guided option explanation was kept as the human review text instead of a generic renderer summary.'
|
|
5416
|
+
: 'The executable plan was preserved, but a stale boolean Status/Ativo/Inativo narrative was replaced because the selected guided option targets categorical presentation semantics.',
|
|
5417
|
+
],
|
|
5418
|
+
};
|
|
5419
|
+
}
|
|
5420
|
+
governedCategoricalBooleanPlanBoundary(response, request, field, isNeutralFallback) {
|
|
5421
|
+
if (!field)
|
|
5422
|
+
return null;
|
|
5423
|
+
const column = this.currentConfigColumns()
|
|
5424
|
+
.find((candidate) => this.stringValue(candidate['field']) === field);
|
|
5425
|
+
if (!column || this.booleanColumnCandidate(column))
|
|
5426
|
+
return null;
|
|
5427
|
+
const hasBooleanOperations = this.responseHasBooleanStatusOperations(response);
|
|
5428
|
+
if (!hasBooleanOperations && (isNeutralFallback || !this.responseHasBooleanStatusNarrative(response))) {
|
|
5429
|
+
return null;
|
|
5430
|
+
}
|
|
5431
|
+
const label = this.stringValue(column['header']) || this.humanizeField(field);
|
|
5432
|
+
return {
|
|
5433
|
+
type: 'clarification',
|
|
5434
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5435
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5436
|
+
message: `A coluna ${label} não é booleana no estado atual. Mantive a opção governada sem aplicação para não materializar Ativo/Inativo.`,
|
|
5437
|
+
questions: [`Como quer materializar ${label} agora?`],
|
|
5438
|
+
optionPayloads: [
|
|
5439
|
+
this.categoricalPresentationOptionPayload(field, label, 'author_categorical_field_semantics', `Descobrir semântica dos valores de ${label}`, 'Gerar chips/ícones por valor real da coluna.', 'auto_awesome', 'primary'),
|
|
5440
|
+
this.categoricalPresentationOptionPayload(field, label, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${label}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
|
|
5441
|
+
],
|
|
5442
|
+
warnings: [
|
|
5443
|
+
...(response.warnings ?? []),
|
|
5444
|
+
'governed-categorical-boolean-plan-blocked',
|
|
5445
|
+
'The selected governed categorical option returned boolean Status/Ativo/Inativo semantics for a non-boolean field; the executable plan was blocked.',
|
|
5446
|
+
],
|
|
5447
|
+
};
|
|
5448
|
+
}
|
|
5449
|
+
requestHasGovernedCategoricalSelection(request) {
|
|
5450
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5451
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5452
|
+
const actionText = this.normalizeLabel([
|
|
5453
|
+
request.action?.displayPrompt,
|
|
5454
|
+
request.action?.value,
|
|
5455
|
+
request.prompt,
|
|
5456
|
+
].map((value) => this.stringValue(value)).join(' '));
|
|
5457
|
+
const actionRawText = [
|
|
5458
|
+
request.action?.displayPrompt,
|
|
5459
|
+
request.action?.value,
|
|
5460
|
+
request.prompt,
|
|
5461
|
+
].map((value) => this.stringValue(value)).join(' ');
|
|
5462
|
+
return this.isGovernedCategoricalSemanticsChoiceText(actionText)
|
|
5463
|
+
|| actionRawText.includes('apply_neutral_categorical_renderer')
|
|
5464
|
+
|| actionRawText.includes('author_categorical_field_semantics')
|
|
5465
|
+
|| !!this.toRecord(actionHints?.['categoricalFieldSemantics'])
|
|
5466
|
+
|| !!this.toRecord(requestHints?.['categoricalFieldSemantics'])
|
|
5467
|
+
|| this.governanceStatusIsNeutralCategoricalFallback(this.toRecord(actionHints?.['badge'])?.['governanceStatus'])
|
|
5468
|
+
|| this.governanceStatusIsNeutralCategoricalFallback(this.toRecord(requestHints?.['badge'])?.['governanceStatus']);
|
|
5469
|
+
}
|
|
5470
|
+
requestSelectsCategoricalFieldSemanticsDiscovery(request) {
|
|
5471
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5472
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5473
|
+
const actionSelection = this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection']);
|
|
5474
|
+
const requestSelection = this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
|
|
5475
|
+
const text = [
|
|
5476
|
+
request.action?.displayPrompt,
|
|
5477
|
+
request.action?.value,
|
|
5478
|
+
request.prompt,
|
|
5479
|
+
actionSelection?.['value'],
|
|
5480
|
+
requestSelection?.['value'],
|
|
5481
|
+
].map((value) => this.stringValue(value)).join(' ');
|
|
5482
|
+
return text.includes('author_categorical_field_semantics');
|
|
5483
|
+
}
|
|
5484
|
+
governanceStatusIsNeutralCategoricalFallback(value) {
|
|
5485
|
+
const raw = this.stringValue(value);
|
|
5486
|
+
const normalized = this.normalizeLabel(raw);
|
|
5487
|
+
return normalized.includes('ungoverned neutral fallback')
|
|
5488
|
+
|| raw.includes('ungoverned_neutral_fallback');
|
|
5489
|
+
}
|
|
5490
|
+
governedCategoricalSelectionTargetField(request, response) {
|
|
5491
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5492
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5493
|
+
const actionSemantics = this.toRecord(actionHints?.['categoricalFieldSemantics']);
|
|
5494
|
+
const requestSemantics = this.toRecord(requestHints?.['categoricalFieldSemantics']);
|
|
5495
|
+
return this.stringValue(actionSemantics?.['field'])
|
|
5496
|
+
|| this.stringValue(requestSemantics?.['field'])
|
|
5497
|
+
|| this.visualPresentationTargetField(response, request);
|
|
5498
|
+
}
|
|
5499
|
+
responseHasBooleanStatusNarrative(response) {
|
|
5500
|
+
const text = this.normalizeLabel([
|
|
5501
|
+
response.message,
|
|
5502
|
+
response.explanation,
|
|
5503
|
+
].map((value) => this.stringValue(value)).join(' '));
|
|
5504
|
+
return this.normalizedTextIncludesAny(text, [
|
|
5505
|
+
'ativo para verdadeiro',
|
|
5506
|
+
'inativo para falso',
|
|
5507
|
+
'status como badge ativo',
|
|
5508
|
+
'status como badge inativo',
|
|
5509
|
+
]);
|
|
5510
|
+
}
|
|
5511
|
+
incompatibleBooleanStatusPresentationBoundary(response, request) {
|
|
5512
|
+
if (!request)
|
|
5513
|
+
return null;
|
|
5514
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
5515
|
+
return null;
|
|
5516
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5517
|
+
return null;
|
|
5518
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5519
|
+
return null;
|
|
5520
|
+
if (!this.promptRequestsVisualOrStructuralEdit(this.normalizeLabel(this.componentPresentationPromptForTurn(request)))) {
|
|
5521
|
+
return null;
|
|
5522
|
+
}
|
|
5523
|
+
const columns = this.currentConfigColumns();
|
|
5524
|
+
if (!columns.length)
|
|
5525
|
+
return null;
|
|
5526
|
+
const targetField = this.booleanStatusPresentationTargetField(response, request, columns);
|
|
5527
|
+
if (!targetField)
|
|
5528
|
+
return null;
|
|
5529
|
+
const targetColumn = columns.find((column) => this.stringValue(column['field']) === targetField);
|
|
5530
|
+
if (!targetColumn || this.booleanColumnCandidate(targetColumn))
|
|
5531
|
+
return null;
|
|
5532
|
+
if (!this.responseHasBooleanStatusNarrative(response) && !this.responseHasBooleanStatusOperations(response)) {
|
|
5533
|
+
return null;
|
|
5534
|
+
}
|
|
5535
|
+
const label = this.stringValue(targetColumn['header']) || this.humanizeField(targetField);
|
|
5536
|
+
return {
|
|
5537
|
+
type: 'clarification',
|
|
5538
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5539
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5540
|
+
message: `A coluna ${label} não é booleana no estado atual. Posso formatá-la como categoria, mas não vou assumir Ativo/Inativo.`,
|
|
5541
|
+
questions: [`Qual apresentação categórica você quer aplicar em ${label}?`],
|
|
5542
|
+
optionPayloads: [
|
|
5543
|
+
this.categoricalPresentationOptionPayload(targetField, label, 'author_categorical_field_semantics', `Descobrir semântica dos valores de ${label}`, 'Gerar chips/ícones por valor real da coluna.', 'auto_awesome', 'primary'),
|
|
5544
|
+
this.categoricalPresentationOptionPayload(targetField, label, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${label}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
|
|
5545
|
+
],
|
|
5546
|
+
warnings: [
|
|
5547
|
+
...(response.warnings ?? []),
|
|
5548
|
+
'boolean-status-presentation-blocked-for-categorical-column',
|
|
5549
|
+
'The LLM returned boolean Status/Ativo/Inativo presentation semantics, but the current table column is not boolean; the table runtime kept the turn as governed categorical clarification instead of compiling an incompatible patch.',
|
|
5550
|
+
],
|
|
5551
|
+
};
|
|
5552
|
+
}
|
|
5553
|
+
currentConfigColumns() {
|
|
5554
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
5555
|
+
return Array.isArray(currentConfig?.['columns'])
|
|
5556
|
+
? currentConfig['columns']
|
|
5557
|
+
.map((column) => this.toRecord(column))
|
|
5558
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
5559
|
+
: [];
|
|
5560
|
+
}
|
|
5561
|
+
currentConfigRows() {
|
|
5562
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
5563
|
+
for (const source of [currentConfig]) {
|
|
5564
|
+
for (const key of ['data', 'rows', 'items']) {
|
|
5565
|
+
const rows = this.toArray(source?.[key])
|
|
5566
|
+
.map((row) => this.toRecord(row))
|
|
5567
|
+
.filter((row) => !!row);
|
|
5568
|
+
if (rows.length)
|
|
5569
|
+
return rows;
|
|
5570
|
+
}
|
|
5571
|
+
const dataSourceRows = this.toArray(this.toRecord(source?.['dataSource'])?.['data'])
|
|
5572
|
+
.map((row) => this.toRecord(row))
|
|
5573
|
+
.filter((row) => !!row);
|
|
5574
|
+
if (dataSourceRows.length)
|
|
5575
|
+
return dataSourceRows;
|
|
5576
|
+
}
|
|
5577
|
+
return [];
|
|
5578
|
+
}
|
|
5579
|
+
currentCategoricalValuesForField(field) {
|
|
5580
|
+
const scopedSamples = this.adapterFieldValueSamples(field);
|
|
5581
|
+
if (scopedSamples.length)
|
|
5582
|
+
return scopedSamples;
|
|
5583
|
+
const values = this.currentConfigRows()
|
|
5584
|
+
.map((row) => this.readableCellValue(row[field]))
|
|
5585
|
+
.filter((value) => !!value);
|
|
5586
|
+
return Array.from(new Set(values)).slice(0, 12);
|
|
5587
|
+
}
|
|
5588
|
+
adapterFieldValueSamples(field) {
|
|
5589
|
+
const adapter = this.adapter;
|
|
5590
|
+
const result = adapter.getFieldValueSamples?.(field, 12);
|
|
5591
|
+
const rawValues = Array.isArray(result)
|
|
5592
|
+
? result
|
|
5593
|
+
: this.toArray(this.toRecord(result)?.['values']);
|
|
5594
|
+
const values = rawValues
|
|
5595
|
+
.map((value) => this.readableCellValue(value))
|
|
5596
|
+
.filter((value) => !!value);
|
|
5597
|
+
return Array.from(new Set(values)).slice(0, 12);
|
|
5598
|
+
}
|
|
5599
|
+
booleanStatusPresentationTargetField(response, request, columns) {
|
|
5600
|
+
const operationFields = this.componentEditOperations(response.componentEditPlan)
|
|
5601
|
+
.map((operation) => this.operationTargetField(operation))
|
|
5602
|
+
.filter((field) => !!field);
|
|
5603
|
+
const patch = this.toRecord(response.patch);
|
|
5604
|
+
const patchFields = this.toArray(patch?.['columns'])
|
|
5605
|
+
.map((column) => this.stringValue(this.toRecord(column)?.['field']))
|
|
5606
|
+
.filter((field) => !!field);
|
|
5607
|
+
const fields = [...operationFields, ...patchFields];
|
|
5608
|
+
for (const field of fields) {
|
|
5609
|
+
if (columns.some((column) => this.stringValue(column['field']) === field))
|
|
5610
|
+
return field;
|
|
5611
|
+
}
|
|
5612
|
+
return this.explicitVisualPresentationTargetField(response, request, columns);
|
|
5613
|
+
}
|
|
5614
|
+
explicitVisualPresentationTargetField(response, request, columns) {
|
|
5615
|
+
const text = this.normalizeLabel([
|
|
5616
|
+
request.prompt,
|
|
5617
|
+
request.pendingClarification?.sourcePrompt,
|
|
5618
|
+
request.pendingClarification?.assistantMessage,
|
|
5619
|
+
response.message,
|
|
5620
|
+
response.explanation,
|
|
5621
|
+
...(response.questions ?? []),
|
|
5622
|
+
].filter(Boolean).join(' '));
|
|
5623
|
+
const matched = columns
|
|
5624
|
+
.map((column) => {
|
|
5625
|
+
const field = this.stringValue(column['field']);
|
|
5626
|
+
if (!field)
|
|
5627
|
+
return null;
|
|
5628
|
+
const header = this.stringValue(column['header']) || field;
|
|
5629
|
+
const score = Math.max(this.visualPresentationTargetFieldScore(text, field), this.visualPresentationTargetFieldScore(text, header));
|
|
5630
|
+
return score > 0 ? { field, score } : null;
|
|
5631
|
+
})
|
|
5632
|
+
.filter((entry) => !!entry)
|
|
5633
|
+
.sort((a, b) => b.score - a.score)[0];
|
|
5634
|
+
return matched?.field ?? null;
|
|
5635
|
+
}
|
|
5636
|
+
responseHasBooleanStatusOperations(response) {
|
|
5637
|
+
const hasBooleanOperation = this.componentEditOperations(response.componentEditPlan).some((operation) => {
|
|
5638
|
+
const operationId = this.stringValue(operation['operationId']);
|
|
5639
|
+
if (operationId !== 'column.conditionalRenderer.add'
|
|
5640
|
+
&& operationId !== 'column.conditionalStyle.add'
|
|
5641
|
+
&& operationId !== 'column.renderer.set') {
|
|
5642
|
+
return false;
|
|
5643
|
+
}
|
|
5644
|
+
const input = this.toRecord(operation['input']);
|
|
5645
|
+
if (!input)
|
|
5646
|
+
return false;
|
|
5647
|
+
return this.booleanConditionValue(input['condition']) !== null
|
|
5648
|
+
|| this.safeJsonText(input).includes('"Ativo"')
|
|
5649
|
+
|| this.safeJsonText(input).includes('"Inativo"');
|
|
5650
|
+
});
|
|
5651
|
+
if (hasBooleanOperation)
|
|
5652
|
+
return true;
|
|
5653
|
+
const patchText = this.safeJsonText(response.patch);
|
|
5654
|
+
return patchText.includes('"Ativo"')
|
|
5655
|
+
|| patchText.includes('"Inativo"')
|
|
5656
|
+
|| patchText.includes('"true"')
|
|
5657
|
+
|| patchText.includes('"false"');
|
|
5658
|
+
}
|
|
5659
|
+
categoricalPresentationOptionPayload(field, label, value, optionLabel, description, icon, tone) {
|
|
5660
|
+
return {
|
|
5661
|
+
label: optionLabel,
|
|
5662
|
+
value,
|
|
5663
|
+
description,
|
|
5664
|
+
contextHints: {
|
|
5665
|
+
categoricalFieldSemantics: {
|
|
5666
|
+
field,
|
|
5667
|
+
label,
|
|
5668
|
+
},
|
|
5669
|
+
optionSelected: {
|
|
5670
|
+
targetField: field,
|
|
5671
|
+
selection: {
|
|
5672
|
+
mode: value === 'author_categorical_field_semantics'
|
|
5673
|
+
? 'categorical_semantics'
|
|
5674
|
+
: 'renderer',
|
|
5675
|
+
field,
|
|
5676
|
+
value,
|
|
5677
|
+
},
|
|
5678
|
+
},
|
|
5679
|
+
},
|
|
5680
|
+
presentation: {
|
|
5681
|
+
kind: 'guided-option',
|
|
5682
|
+
icon,
|
|
5683
|
+
tone,
|
|
5684
|
+
ctaLabel: 'Aplicar opção',
|
|
5685
|
+
},
|
|
5686
|
+
};
|
|
5687
|
+
}
|
|
5688
|
+
visualPresentationTargetMismatchBoundary(response, request) {
|
|
5689
|
+
if (!request)
|
|
5690
|
+
return null;
|
|
5691
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
5692
|
+
return null;
|
|
5693
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5694
|
+
return null;
|
|
5695
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5696
|
+
return null;
|
|
5697
|
+
if (!this.promptRequestsVisualOrStructuralEdit(this.normalizeLabel(this.componentPresentationPromptForTurn(request)))) {
|
|
5698
|
+
return null;
|
|
5699
|
+
}
|
|
5700
|
+
const columns = this.currentConfigColumns();
|
|
5701
|
+
if (!columns.length)
|
|
5702
|
+
return null;
|
|
5703
|
+
const requestedField = this.explicitVisualPresentationTargetFieldFromRequest(request, columns);
|
|
5704
|
+
if (!requestedField)
|
|
5705
|
+
return null;
|
|
5706
|
+
const responseFields = this.responsePresentationTargetFields(response, columns);
|
|
5707
|
+
if (!responseFields.length)
|
|
5708
|
+
return null;
|
|
5709
|
+
if (responseFields.some((field) => field === requestedField))
|
|
5710
|
+
return null;
|
|
5711
|
+
const requestedColumn = columns.find((column) => this.stringValue(column['field']) === requestedField);
|
|
5712
|
+
const requestedLabel = this.stringValue(requestedColumn?.['header']) || this.humanizeField(requestedField);
|
|
5713
|
+
return {
|
|
5714
|
+
type: 'clarification',
|
|
5715
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5716
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5717
|
+
message: `Entendi que o alvo do ajuste é ${requestedLabel}. Para evitar aplicar uma formatação em outra coluna, vou seguir com uma decisão categórica segura para ${requestedLabel}.`,
|
|
5718
|
+
questions: [`Qual apresentação categórica você quer aplicar em ${requestedLabel}?`],
|
|
5719
|
+
optionPayloads: [
|
|
5720
|
+
this.categoricalPresentationOptionPayload(requestedField, requestedLabel, 'author_categorical_field_semantics', `Descobrir semântica dos valores de ${requestedLabel}`, 'Gerar chips/ícones por valor real da coluna.', 'auto_awesome', 'primary'),
|
|
5721
|
+
this.categoricalPresentationOptionPayload(requestedField, requestedLabel, 'apply_neutral_categorical_renderer', `Aplicar chips neutros em ${requestedLabel}`, 'Usar leitura visual categórica sem política por valor.', 'sell', 'neutral'),
|
|
5722
|
+
],
|
|
5723
|
+
warnings: [
|
|
5724
|
+
...(response.warnings ?? []),
|
|
5725
|
+
'visual-presentation-target-mismatch-blocked',
|
|
5726
|
+
'The LLM returned an executable visual presentation plan for a different column than the explicit user target; the table runtime kept the turn non-applicable instead of compiling a patch for the wrong column.',
|
|
5727
|
+
],
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
explicitVisualPresentationTargetFieldFromRequest(request, columns) {
|
|
5731
|
+
const text = this.normalizeLabel([
|
|
5732
|
+
request.prompt,
|
|
5733
|
+
request.action?.displayPrompt,
|
|
5734
|
+
request.pendingClarification?.sourcePrompt,
|
|
5735
|
+
].filter(Boolean).join(' '));
|
|
5736
|
+
return this.explicitColumnMentionFromText(text, columns);
|
|
5737
|
+
}
|
|
5738
|
+
explicitColumnMentionFromText(normalizedText, columns) {
|
|
5739
|
+
const matched = columns
|
|
5740
|
+
.map((column) => {
|
|
5741
|
+
const field = this.stringValue(column['field']);
|
|
5742
|
+
if (!field)
|
|
5743
|
+
return null;
|
|
5744
|
+
const header = this.stringValue(column['header']) || field;
|
|
5745
|
+
const score = Math.max(this.visualPresentationTargetFieldScore(normalizedText, field), this.visualPresentationTargetFieldScore(normalizedText, header));
|
|
5746
|
+
return score > 0 ? { field, score } : null;
|
|
5747
|
+
})
|
|
5748
|
+
.filter((entry) => !!entry)
|
|
5749
|
+
.sort((a, b) => b.score - a.score);
|
|
5750
|
+
const mentionedFields = [...new Set(matched.map((entry) => entry.field))];
|
|
5751
|
+
return mentionedFields.length === 1 ? mentionedFields[0] : null;
|
|
5752
|
+
}
|
|
5753
|
+
responsePresentationTargetFields(response, columns) {
|
|
5754
|
+
const operationFields = this.componentEditOperations(response.componentEditPlan)
|
|
5755
|
+
.filter((operation) => this.operationIsColumnPresentationEdit(operation))
|
|
5756
|
+
.map((operation) => this.operationTargetField(operation));
|
|
5757
|
+
const patch = this.toRecord(response.patch);
|
|
5758
|
+
const patchFields = this.toArray(patch?.['columns'])
|
|
5759
|
+
.map((column) => this.toRecord(column))
|
|
5760
|
+
.filter((column) => !!column)
|
|
5761
|
+
.filter((column) => this.patchColumnCarriesPresentationEdit(column))
|
|
5762
|
+
.map((column) => this.stringValue(column['field']));
|
|
5763
|
+
const knownFields = new Set(columns.map((column) => this.stringValue(column['field'])).filter((field) => !!field));
|
|
5764
|
+
return [...new Set([...operationFields, ...patchFields].filter((field) => knownFields.has(field)))];
|
|
5765
|
+
}
|
|
5766
|
+
operationIsColumnPresentationEdit(operation) {
|
|
5767
|
+
const operationId = this.stringValue(operation['operationId']);
|
|
5768
|
+
return [
|
|
5769
|
+
'column.renderer.set',
|
|
5770
|
+
'column.conditionalRenderer.add',
|
|
5771
|
+
'column.conditionalStyle.add',
|
|
5772
|
+
'column.format.set',
|
|
5773
|
+
'column.alignment.set',
|
|
5774
|
+
].includes(operationId);
|
|
5775
|
+
}
|
|
5776
|
+
patchColumnCarriesPresentationEdit(column) {
|
|
5777
|
+
return !!this.toRecord(column['renderer'])
|
|
5778
|
+
|| Array.isArray(column['conditionalRenderers'])
|
|
5779
|
+
|| Array.isArray(column['conditionalStyles'])
|
|
5780
|
+
|| !!this.stringValue(column['format'])
|
|
5781
|
+
|| !!this.stringValue(column['align'])
|
|
5782
|
+
|| !!this.stringValue(column['alignment']);
|
|
5783
|
+
}
|
|
5784
|
+
normalizeNumericBandConditionalStylePlan(response, request) {
|
|
5785
|
+
if (!request)
|
|
5786
|
+
return response;
|
|
5787
|
+
if ((response.warnings ?? []).includes('numeric-band-conditional-style-normalized'))
|
|
5788
|
+
return response;
|
|
5789
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
5790
|
+
const columns = Array.isArray(currentConfig?.['columns'])
|
|
5791
|
+
? currentConfig['columns']
|
|
5792
|
+
.map((column) => this.toRecord(column))
|
|
5793
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
5794
|
+
: [];
|
|
5795
|
+
if (!columns.length)
|
|
5796
|
+
return response;
|
|
5797
|
+
const operations = this.numericBandConditionalStyleOperationsFromPrompt(this.componentPresentationPromptForTurn(request), [
|
|
5798
|
+
response.message,
|
|
5799
|
+
response.explanation,
|
|
5800
|
+
...(response.questions ?? []),
|
|
5801
|
+
].join(' '), columns);
|
|
5802
|
+
if (!operations.length)
|
|
5803
|
+
return response;
|
|
5804
|
+
const fields = new Set(operations
|
|
5805
|
+
.map((operation) => this.operationTargetField(operation))
|
|
5806
|
+
.filter((field) => !!field));
|
|
5807
|
+
const originalPlan = this.toRecord(response.componentEditPlan);
|
|
5808
|
+
const originalOperations = this.componentEditOperations(originalPlan)
|
|
5809
|
+
.filter((operation) => {
|
|
5810
|
+
const field = this.operationTargetField(operation);
|
|
5811
|
+
return !(field && fields.has(field) && this.stringValue(operation['operationId']) === 'column.conditionalStyle.add');
|
|
5812
|
+
});
|
|
5813
|
+
return {
|
|
5814
|
+
...response,
|
|
5815
|
+
...(response.type === 'clarification' || response.type === 'info' ? { type: 'patch' } : {}),
|
|
5816
|
+
componentEditPlan: {
|
|
5817
|
+
...(originalPlan ?? {}),
|
|
5818
|
+
kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
|
|
5819
|
+
version: this.stringValue(originalPlan?.['version']) || '1.0',
|
|
5820
|
+
componentId: this.stringValue(originalPlan?.['componentId'])
|
|
5821
|
+
|| this.adapter.componentId
|
|
5822
|
+
|| request.componentId
|
|
5823
|
+
|| 'praxis-table',
|
|
5824
|
+
operations: [...originalOperations, ...operations],
|
|
5825
|
+
},
|
|
5826
|
+
explanation: response.explanation || 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.',
|
|
5827
|
+
warnings: [
|
|
5828
|
+
...(response.warnings ?? []),
|
|
5829
|
+
'numeric-band-conditional-style-normalized',
|
|
5830
|
+
'Residual continuity guard grounded numeric band thresholds from the canonical dataProfile when available, avoiding accidental thresholds from prose or visual token numbers.',
|
|
5831
|
+
],
|
|
5832
|
+
};
|
|
5833
|
+
}
|
|
5834
|
+
componentEditPlanHasAnyOperation(componentEditPlan, operationIds) {
|
|
5835
|
+
if (!operationIds.length)
|
|
5836
|
+
return false;
|
|
5837
|
+
const expected = new Set(operationIds);
|
|
5838
|
+
return this.componentEditOperations(componentEditPlan)
|
|
5839
|
+
.some((operation) => expected.has(this.componentEditPlanOperationIdentity(operation)));
|
|
5840
|
+
}
|
|
5841
|
+
responseHasComputedColumnAuthoringDecision(response) {
|
|
5842
|
+
if (this.componentEditPlanHasAnyOperation(response.componentEditPlan, ['column.computed.add', 'column.computed.set'])) {
|
|
5843
|
+
return true;
|
|
5844
|
+
}
|
|
5845
|
+
const patch = this.toRecord(response.patch);
|
|
5846
|
+
const columns = Array.isArray(patch?.['columns']) ? patch['columns'] : [];
|
|
5847
|
+
return columns
|
|
5848
|
+
.map((column) => this.toRecord(column))
|
|
5849
|
+
.some((column) => !!column?.['computed']);
|
|
5850
|
+
}
|
|
5851
|
+
normalizeComputedColumnAuxiliaryMaterializations(response) {
|
|
5852
|
+
const plan = this.toRecord(response.componentEditPlan);
|
|
5853
|
+
const operations = this.componentEditOperations(plan);
|
|
5854
|
+
if (!plan || operations.length < 2)
|
|
5855
|
+
return response;
|
|
5856
|
+
const computedFields = new Set(operations
|
|
5857
|
+
.filter((operation) => this.componentEditPlanOperationIdentity(operation) === 'column.computed.add'
|
|
5858
|
+
|| this.componentEditPlanOperationIdentity(operation) === 'column.computed.set')
|
|
5859
|
+
.map((operation) => this.operationTargetField(operation))
|
|
5860
|
+
.filter((field) => !!field));
|
|
5861
|
+
if (!computedFields.size) {
|
|
5862
|
+
return response;
|
|
5863
|
+
}
|
|
5864
|
+
const auxiliaryAddedFields = new Set();
|
|
5865
|
+
for (const operation of operations) {
|
|
5866
|
+
const operationId = this.componentEditPlanOperationIdentity(operation);
|
|
5867
|
+
const field = this.operationTargetField(operation);
|
|
5868
|
+
if (!field)
|
|
5869
|
+
continue;
|
|
5870
|
+
if (operationId === 'column.add' && !computedFields.has(field)) {
|
|
5871
|
+
auxiliaryAddedFields.add(field);
|
|
5024
5872
|
}
|
|
5025
5873
|
}
|
|
5026
5874
|
const removableFields = auxiliaryAddedFields;
|
|
@@ -5195,18 +6043,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5195
6043
|
.sort((left, right) => left - right)[0] ?? -1;
|
|
5196
6044
|
}
|
|
5197
6045
|
columnPresentationAliases(column) {
|
|
5198
|
-
|
|
5199
|
-
const field = this.normalizeLabel(this.stringValue(column['field']));
|
|
5200
|
-
const header = this.normalizeLabel(this.stringValue(column['header']));
|
|
5201
|
-
if (field.startsWith('nome') || header.includes('nome'))
|
|
5202
|
-
aliases.add('nome');
|
|
5203
|
-
if (field.includes('funcionario') || header.includes('funcionario'))
|
|
5204
|
-
aliases.add('nome');
|
|
5205
|
-
if (field.includes('salario') || header.includes('salario'))
|
|
5206
|
-
aliases.add('salario');
|
|
5207
|
-
if (field.includes('salario') || header.includes('salario'))
|
|
5208
|
-
aliases.add('salário');
|
|
5209
|
-
return [...aliases].sort((left, right) => right.length - left.length);
|
|
6046
|
+
return this.columnMentionAliases(column);
|
|
5210
6047
|
}
|
|
5211
6048
|
mergeCompoundColumnPresentationOperations(originalOperations, intendedOperations) {
|
|
5212
6049
|
const protectedFields = new Map();
|
|
@@ -5306,15 +6143,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5306
6143
|
return null;
|
|
5307
6144
|
}
|
|
5308
6145
|
const column = columns.find((candidate) => {
|
|
5309
|
-
const text = this.normalizeLabel([
|
|
5310
|
-
candidate['field'],
|
|
5311
|
-
candidate['header'],
|
|
5312
|
-
candidate['type'],
|
|
5313
|
-
candidate['dataType'],
|
|
5314
|
-
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5315
|
-
].map((value) => this.stringValue(value)).join(' '));
|
|
5316
6146
|
return (this.textMentionsColumn(prompt, candidate) || this.textMentionsColumn(responseText, candidate))
|
|
5317
|
-
&& this.
|
|
6147
|
+
&& this.booleanColumnCandidate(candidate);
|
|
5318
6148
|
});
|
|
5319
6149
|
return this.booleanSimNaoOperationsForColumn(column);
|
|
5320
6150
|
}
|
|
@@ -5349,7 +6179,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5349
6179
|
candidate['dataType'],
|
|
5350
6180
|
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5351
6181
|
].map((value) => this.stringValue(value)).join(' '));
|
|
5352
|
-
return this.
|
|
6182
|
+
return this.booleanColumnCandidate(candidate)
|
|
6183
|
+
&& this.normalizedTextIncludesAny(text, ['status', 'ativo', 'boolean', 'bool']);
|
|
5353
6184
|
});
|
|
5354
6185
|
const field = this.stringValue(column?.['field']);
|
|
5355
6186
|
if (!field)
|
|
@@ -5488,28 +6319,20 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5488
6319
|
]);
|
|
5489
6320
|
}
|
|
5490
6321
|
numericColumnCandidate(column) {
|
|
5491
|
-
const
|
|
5492
|
-
column['field'],
|
|
5493
|
-
column['header'],
|
|
6322
|
+
const typeText = this.normalizeLabel([
|
|
5494
6323
|
column['type'],
|
|
5495
6324
|
column['dataType'],
|
|
5496
6325
|
column['format'],
|
|
5497
|
-
this.humanizeField(this.stringValue(column['field'])),
|
|
5498
6326
|
].map((value) => this.stringValue(value)).join(' '));
|
|
5499
|
-
return this.normalizedTextIncludesAny(
|
|
6327
|
+
return this.normalizedTextIncludesAny(typeText, [
|
|
5500
6328
|
'number',
|
|
5501
6329
|
'numeric',
|
|
5502
6330
|
'decimal',
|
|
5503
6331
|
'integer',
|
|
5504
6332
|
'currency',
|
|
5505
6333
|
'moeda',
|
|
5506
|
-
'valor',
|
|
5507
|
-
'preco',
|
|
5508
|
-
'preço',
|
|
5509
|
-
'salario',
|
|
5510
|
-
'salário',
|
|
5511
6334
|
'brl',
|
|
5512
|
-
]);
|
|
6335
|
+
]) || this.numericBandThresholdsFromDataProfile(this.stringValue(column['field'])) !== null;
|
|
5513
6336
|
}
|
|
5514
6337
|
numericBandThresholdsForField(field, text) {
|
|
5515
6338
|
return this.numericBandThresholdsFromDataProfile(field)
|
|
@@ -5613,14 +6436,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5613
6436
|
return null;
|
|
5614
6437
|
}
|
|
5615
6438
|
const booleanColumns = columns.filter((candidate) => {
|
|
5616
|
-
|
|
5617
|
-
candidate['field'],
|
|
5618
|
-
candidate['header'],
|
|
5619
|
-
candidate['type'],
|
|
5620
|
-
candidate['dataType'],
|
|
5621
|
-
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5622
|
-
].map((value) => this.stringValue(value)).join(' '));
|
|
5623
|
-
return this.normalizedTextIncludesAny(text, ['ativo', 'status', 'boolean', 'bool'])
|
|
6439
|
+
return this.booleanColumnCandidate(candidate)
|
|
5624
6440
|
&& (this.textMentionsColumn(prompt, candidate) || this.textMentionsColumn(responseText, candidate));
|
|
5625
6441
|
});
|
|
5626
6442
|
return this.booleanSimNaoOperationsForColumn(booleanColumns[0]);
|
|
@@ -5632,16 +6448,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5632
6448
|
|| this.normalizedTextIncludesAny(normalizedResponse, ['sim nao', 'sim não', 'sim ou nao', 'sim ou não']);
|
|
5633
6449
|
if (!simNaoRequested)
|
|
5634
6450
|
return null;
|
|
5635
|
-
const booleanColumns = columns.filter((candidate) =>
|
|
5636
|
-
const text = this.normalizeLabel([
|
|
5637
|
-
candidate['field'],
|
|
5638
|
-
candidate['header'],
|
|
5639
|
-
candidate['type'],
|
|
5640
|
-
candidate['dataType'],
|
|
5641
|
-
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5642
|
-
].map((value) => this.stringValue(value)).join(' '));
|
|
5643
|
-
return this.normalizedTextIncludesAny(text, ['ativo', 'status', 'boolean', 'bool']);
|
|
5644
|
-
});
|
|
6451
|
+
const booleanColumns = columns.filter((candidate) => this.booleanColumnCandidate(candidate));
|
|
5645
6452
|
return booleanColumns.length === 1
|
|
5646
6453
|
? this.booleanSimNaoOperationsForColumn(booleanColumns[0])
|
|
5647
6454
|
: null;
|
|
@@ -5779,19 +6586,22 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5779
6586
|
.filter((value) => value.length >= 2);
|
|
5780
6587
|
}
|
|
5781
6588
|
inferSpecificColumnFormatFromPrompt(prompt, column, availableFormats) {
|
|
5782
|
-
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
5783
|
-
const
|
|
5784
|
-
column['field'],
|
|
5785
|
-
column['header'],
|
|
6589
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
6590
|
+
const columnTypeText = this.normalizeLabel([
|
|
5786
6591
|
column['type'],
|
|
5787
6592
|
column['dataType'],
|
|
5788
|
-
|
|
6593
|
+
column['format'],
|
|
5789
6594
|
].map((value) => this.stringValue(value)).filter(Boolean).join(' '));
|
|
5790
|
-
const dateLikeColumn = this.normalizedTextIncludesAny(
|
|
6595
|
+
const dateLikeColumn = this.normalizedTextIncludesAny(columnTypeText, [
|
|
5791
6596
|
'data',
|
|
5792
6597
|
'date',
|
|
5793
|
-
'
|
|
5794
|
-
'
|
|
6598
|
+
'time',
|
|
6599
|
+
'dd/mm/yyyy',
|
|
6600
|
+
'yyyy-mm-dd',
|
|
6601
|
+
'shortdate',
|
|
6602
|
+
'mediumdate',
|
|
6603
|
+
'longdate',
|
|
6604
|
+
'fulldate',
|
|
5795
6605
|
]);
|
|
5796
6606
|
const brDateRequested = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5797
6607
|
'dia mes ano',
|
|
@@ -5805,15 +6615,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5805
6615
|
if (dateLikeColumn && brDateRequested && availableFormats.has('dd/MM/yyyy')) {
|
|
5806
6616
|
return 'dd/MM/yyyy';
|
|
5807
6617
|
}
|
|
5808
|
-
const currencyLikeColumn = this.
|
|
5809
|
-
'salario',
|
|
5810
|
-
'salário',
|
|
5811
|
-
'salary',
|
|
5812
|
-
'remuneracao',
|
|
5813
|
-
'remuneração',
|
|
5814
|
-
'currency',
|
|
5815
|
-
'moeda',
|
|
5816
|
-
]);
|
|
6618
|
+
const currencyLikeColumn = this.numericColumnCandidate(column);
|
|
5817
6619
|
const brlRequested = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5818
6620
|
'real',
|
|
5819
6621
|
'reais',
|
|
@@ -6262,8 +7064,6 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6262
7064
|
return null;
|
|
6263
7065
|
if (!response.patch || this.tableFilterApplyOperations(response.patch).length === 0)
|
|
6264
7066
|
return null;
|
|
6265
|
-
if (this.responseDeclaresTableMutation(response))
|
|
6266
|
-
return null;
|
|
6267
7067
|
const contextHints = this.contextHintsFor(request);
|
|
6268
7068
|
return {
|
|
6269
7069
|
type: 'info',
|
|
@@ -6401,6 +7201,97 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6401
7201
|
return !fieldTargets.some((target) => this.normalizedTextHasStandaloneToken(normalizedPrompt, target)
|
|
6402
7202
|
|| this.normalizedTextContainsApproxPhrase(normalizedPrompt, target));
|
|
6403
7203
|
}
|
|
7204
|
+
selectedRecordLocalReadonlyAnalysisRequested(request) {
|
|
7205
|
+
const contextHints = this.contextHintsFor(request);
|
|
7206
|
+
if (!this.selectedRecordSampleRows(contextHints).length)
|
|
7207
|
+
return false;
|
|
7208
|
+
if (this.toRecord(contextHints?.['selectedRecordsFilter'])
|
|
7209
|
+
|| this.toRecord(request?.action?.contextHints?.['selectedRecordsFilter'])
|
|
7210
|
+
|| this.toRecord(contextHints?.['tableRuntimeOperation'])
|
|
7211
|
+
|| this.toRecord(request?.action?.contextHints?.['tableRuntimeOperation'])) {
|
|
7212
|
+
return false;
|
|
7213
|
+
}
|
|
7214
|
+
const normalizedPrompt = this.normalizeLabel(request?.prompt ?? '');
|
|
7215
|
+
if (!normalizedPrompt)
|
|
7216
|
+
return false;
|
|
7217
|
+
if (!this.selectedRecordPromptTargetsSelectedSet(normalizedPrompt))
|
|
7218
|
+
return false;
|
|
7219
|
+
if (this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
7220
|
+
|| this.promptRequestsExportOperation(normalizedPrompt)) {
|
|
7221
|
+
return this.promptExplicitlyForbidsTableMutation(normalizedPrompt);
|
|
7222
|
+
}
|
|
7223
|
+
if (this.promptRequestsSelectedRecordDerivedOperation(normalizedPrompt))
|
|
7224
|
+
return false;
|
|
7225
|
+
return this.promptExplicitlyForbidsTableMutation(normalizedPrompt)
|
|
7226
|
+
|| this.selectedRecordPromptLooksConsultative(normalizedPrompt);
|
|
7227
|
+
}
|
|
7228
|
+
selectedRecordPromptTargetsSelectedSet(normalizedPrompt) {
|
|
7229
|
+
if (!normalizedPrompt)
|
|
7230
|
+
return false;
|
|
7231
|
+
const selectedSetSignals = [
|
|
7232
|
+
'selecionado',
|
|
7233
|
+
'selecionados',
|
|
7234
|
+
'marcado',
|
|
7235
|
+
'marcados',
|
|
7236
|
+
'linha marcada',
|
|
7237
|
+
'linhas marcadas',
|
|
7238
|
+
'registro marcado',
|
|
7239
|
+
'registros marcados',
|
|
7240
|
+
'registro selecionado',
|
|
7241
|
+
'registros selecionados',
|
|
7242
|
+
'esses',
|
|
7243
|
+
'essas',
|
|
7244
|
+
'nestes',
|
|
7245
|
+
'nestas',
|
|
7246
|
+
'nesses',
|
|
7247
|
+
'nessas',
|
|
7248
|
+
'desses',
|
|
7249
|
+
'dessas',
|
|
7250
|
+
'tres',
|
|
7251
|
+
'três',
|
|
7252
|
+
'3',
|
|
7253
|
+
];
|
|
7254
|
+
const analyticalSignals = [
|
|
7255
|
+
'em comum',
|
|
7256
|
+
'tem comum',
|
|
7257
|
+
'igual',
|
|
7258
|
+
'iguais',
|
|
7259
|
+
'compare',
|
|
7260
|
+
'comparar',
|
|
7261
|
+
'resuma',
|
|
7262
|
+
'resume',
|
|
7263
|
+
'resumir',
|
|
7264
|
+
'analise',
|
|
7265
|
+
'analisar',
|
|
7266
|
+
'padrao',
|
|
7267
|
+
'padrão',
|
|
7268
|
+
'fora do padrao',
|
|
7269
|
+
'fora do padrão',
|
|
7270
|
+
'ganha mais',
|
|
7271
|
+
'ganha menos',
|
|
7272
|
+
'quem ganha',
|
|
7273
|
+
'faixa salarial',
|
|
7274
|
+
'faixa de salario',
|
|
7275
|
+
'faixa de salário',
|
|
7276
|
+
'faixa de admissao',
|
|
7277
|
+
'faixa de admissão',
|
|
7278
|
+
'periodo de admissao',
|
|
7279
|
+
'período de admissão',
|
|
7280
|
+
'departamento',
|
|
7281
|
+
'cargo',
|
|
7282
|
+
'cargos',
|
|
7283
|
+
'salario',
|
|
7284
|
+
'salário',
|
|
7285
|
+
'admissao',
|
|
7286
|
+
'admissão',
|
|
7287
|
+
];
|
|
7288
|
+
const hasSelectedScope = selectedSetSignals.some((signal) => this.normalizedTextHasStandaloneToken(normalizedPrompt, signal)
|
|
7289
|
+
|| this.normalizedTextContainsApproxPhrase(normalizedPrompt, signal));
|
|
7290
|
+
if (!hasSelectedScope)
|
|
7291
|
+
return false;
|
|
7292
|
+
return analyticalSignals.some((signal) => this.normalizedTextHasStandaloneToken(normalizedPrompt, signal)
|
|
7293
|
+
|| this.normalizedTextContainsApproxPhrase(normalizedPrompt, signal));
|
|
7294
|
+
}
|
|
6404
7295
|
selectedRecordGenericCommonalityScopeTarget(normalizedTarget) {
|
|
6405
7296
|
if (!normalizedTarget)
|
|
6406
7297
|
return true;
|
|
@@ -6426,8 +7317,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6426
7317
|
selectedRecordPromptLooksConsultative(normalizedPrompt) {
|
|
6427
7318
|
if (!normalizedPrompt)
|
|
6428
7319
|
return false;
|
|
6429
|
-
if (this.
|
|
6430
|
-
|| this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
7320
|
+
if (this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
6431
7321
|
|| this.promptRequestsExportOperation(normalizedPrompt)) {
|
|
6432
7322
|
return this.promptExplicitlyForbidsTableMutation(normalizedPrompt);
|
|
6433
7323
|
}
|
|
@@ -6481,6 +7371,10 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6481
7371
|
'filtre',
|
|
6482
7372
|
'buscar registros',
|
|
6483
7373
|
'procurar registros',
|
|
7374
|
+
'acha quem',
|
|
7375
|
+
'achar quem',
|
|
7376
|
+
'pega a turma',
|
|
7377
|
+
'pegar a turma',
|
|
6484
7378
|
'outros registros',
|
|
6485
7379
|
'registro parecido',
|
|
6486
7380
|
'registros parecidos',
|
|
@@ -6499,7 +7393,20 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6499
7393
|
'quero os mesmo',
|
|
6500
7394
|
'quero os mesmos',
|
|
6501
7395
|
'quero ver gente',
|
|
6502
|
-
|
|
7396
|
+
'mesma banda',
|
|
7397
|
+
'nessa mesma banda',
|
|
7398
|
+
'nessa epoca',
|
|
7399
|
+
'nessa época',
|
|
7400
|
+
].some((signal) => {
|
|
7401
|
+
const normalizedSignal = this.normalizeLabel(signal);
|
|
7402
|
+
if (!normalizedSignal)
|
|
7403
|
+
return false;
|
|
7404
|
+
if (normalizedSignal.includes(' ')) {
|
|
7405
|
+
return this.normalizedTextContainsExactPhrase(normalizedPrompt, normalizedSignal);
|
|
7406
|
+
}
|
|
7407
|
+
return this.normalizedTextHasStandaloneToken(normalizedPrompt, normalizedSignal)
|
|
7408
|
+
|| this.normalizedTextContainsApproxToken(normalizedPrompt, normalizedSignal);
|
|
7409
|
+
});
|
|
6503
7410
|
}
|
|
6504
7411
|
promptExplicitlyForbidsTableMutation(normalizedPrompt) {
|
|
6505
7412
|
if (!normalizedPrompt)
|
|
@@ -6589,16 +7496,17 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6589
7496
|
.slice(0, 4);
|
|
6590
7497
|
const dateRanges = fields
|
|
6591
7498
|
.map((field) => {
|
|
6592
|
-
const
|
|
6593
|
-
.map((row) => this.
|
|
6594
|
-
.filter((value) => value
|
|
6595
|
-
if (
|
|
7499
|
+
const values = rows
|
|
7500
|
+
.map((row) => this.analysisDateValue(row[field]))
|
|
7501
|
+
.filter((value) => !!value);
|
|
7502
|
+
if (values.length < 2)
|
|
6596
7503
|
return null;
|
|
6597
|
-
const
|
|
6598
|
-
const
|
|
6599
|
-
|
|
7504
|
+
const sorted = [...values].sort((left, right) => left.sort - right.sort);
|
|
7505
|
+
const min = sorted[0];
|
|
7506
|
+
const max = sorted[sorted.length - 1];
|
|
7507
|
+
if (!min || !max || min.sort === max.sort)
|
|
6600
7508
|
return null;
|
|
6601
|
-
return `${labelByField.get(field) || this.humanizeField(field)}: ${
|
|
7509
|
+
return `${labelByField.get(field) || this.humanizeField(field)}: ${min.label} até ${max.label}`;
|
|
6602
7510
|
})
|
|
6603
7511
|
.filter((entry) => !!entry)
|
|
6604
7512
|
.slice(0, 3);
|
|
@@ -6654,6 +7562,27 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6654
7562
|
: Number.NaN;
|
|
6655
7563
|
return Number.isFinite(raw) ? raw : null;
|
|
6656
7564
|
}
|
|
7565
|
+
analysisDateValue(value) {
|
|
7566
|
+
if (typeof value === 'string') {
|
|
7567
|
+
const trimmed = value.trim();
|
|
7568
|
+
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(trimmed);
|
|
7569
|
+
if (dateOnly) {
|
|
7570
|
+
const year = Number(dateOnly[1]);
|
|
7571
|
+
const month = Number(dateOnly[2]);
|
|
7572
|
+
const day = Number(dateOnly[3]);
|
|
7573
|
+
if (Number.isInteger(year) && Number.isInteger(month) && Number.isInteger(day)) {
|
|
7574
|
+
return {
|
|
7575
|
+
sort: year * 10000 + month * 100 + day,
|
|
7576
|
+
label: `${String(day).padStart(2, '0')}/${String(month).padStart(2, '0')}/${String(year)}`,
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
const timestamp = this.dateTimestamp(value);
|
|
7582
|
+
return timestamp === null
|
|
7583
|
+
? null
|
|
7584
|
+
: { sort: timestamp, label: this.formatAnalysisDate(timestamp) };
|
|
7585
|
+
}
|
|
6657
7586
|
formatAnalysisDate(timestamp) {
|
|
6658
7587
|
return new Intl.DateTimeFormat('pt-BR', {
|
|
6659
7588
|
day: '2-digit',
|
|
@@ -6990,6 +7919,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6990
7919
|
return operations;
|
|
6991
7920
|
}
|
|
6992
7921
|
requestCarriesVisualPresentationContext(request) {
|
|
7922
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
7923
|
+
return true;
|
|
6993
7924
|
const actionHints = this.toRecord(request.action?.contextHints);
|
|
6994
7925
|
const requestHints = this.toRecord(request.contextHints);
|
|
6995
7926
|
const pendingDiagnostics = this.toRecord(request.pendingClarification?.diagnostics);
|
|
@@ -7472,7 +8403,10 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7472
8403
|
add(header);
|
|
7473
8404
|
add(field);
|
|
7474
8405
|
add(this.humanizeField(field));
|
|
7475
|
-
for (const
|
|
8406
|
+
for (const alias of this.stringArrayValue(column['aliases'])) {
|
|
8407
|
+
add(alias);
|
|
8408
|
+
}
|
|
8409
|
+
for (const source of [header, field, this.humanizeField(field), ...this.stringArrayValue(column['aliases'])]) {
|
|
7476
8410
|
const normalized = this.normalizeLabel(source);
|
|
7477
8411
|
const tokens = normalized.split(' ').filter((token) => token.length >= 3
|
|
7478
8412
|
&& !['nome', 'valor', 'data', 'campo', 'codigo', 'id'].includes(token));
|
|
@@ -7683,6 +8617,24 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7683
8617
|
...(memory ? { tableComponentEditDecision: memory } : {}),
|
|
7684
8618
|
};
|
|
7685
8619
|
}
|
|
8620
|
+
errorRetryDiagnostics(request, response) {
|
|
8621
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
8622
|
+
const retryableAction = request.action && actionHints
|
|
8623
|
+
? {
|
|
8624
|
+
kind: request.action.kind || 'clarify',
|
|
8625
|
+
value: this.stringValue(request.action.value) || request.prompt || '',
|
|
8626
|
+
displayPrompt: this.stringValue(request.action.displayPrompt),
|
|
8627
|
+
contextHints: actionHints,
|
|
8628
|
+
}
|
|
8629
|
+
: null;
|
|
8630
|
+
const warnings = response.warnings?.filter(Boolean) ?? [];
|
|
8631
|
+
if (!retryableAction && !warnings.length)
|
|
8632
|
+
return undefined;
|
|
8633
|
+
return {
|
|
8634
|
+
...(warnings.length ? { warnings } : {}),
|
|
8635
|
+
...(retryableAction ? { retryAction: retryableAction } : {}),
|
|
8636
|
+
};
|
|
8637
|
+
}
|
|
7686
8638
|
tableConversationMemoryHints(request) {
|
|
7687
8639
|
const diagnostics = this.toRecord(request.diagnostics);
|
|
7688
8640
|
const decision = this.toRecord(diagnostics?.['tableComponentEditDecision']);
|
|
@@ -7757,6 +8709,9 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7757
8709
|
completePendingComponentEditClarification(request) {
|
|
7758
8710
|
if (request.action?.kind !== 'clarify')
|
|
7759
8711
|
return null;
|
|
8712
|
+
const computedColumnCreation = this.completePendingComputedColumnClarification(request);
|
|
8713
|
+
if (computedColumnCreation)
|
|
8714
|
+
return computedColumnCreation;
|
|
7760
8715
|
const diagnostics = this.toRecord(request.pendingClarification?.diagnostics);
|
|
7761
8716
|
const continuation = this.toRecord(diagnostics?.['tableComponentEditContinuation']);
|
|
7762
8717
|
if (!continuation)
|
|
@@ -7785,6 +8740,127 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7785
8740
|
explanation: 'Clarificacao aplicada ao ajuste pendente.',
|
|
7786
8741
|
};
|
|
7787
8742
|
}
|
|
8743
|
+
completePendingComputedColumnClarification(request) {
|
|
8744
|
+
const sourcePrompt = request.pendingClarification?.sourcePrompt?.trim()
|
|
8745
|
+
|| this.recoverComputedColumnClarificationSourcePromptFromMessages(request);
|
|
8746
|
+
if (!sourcePrompt)
|
|
8747
|
+
return null;
|
|
8748
|
+
const normalizedSourcePrompt = this.normalizeLabel(sourcePrompt);
|
|
8749
|
+
if (!this.promptRequestsComputedColumnCreation(normalizedSourcePrompt))
|
|
8750
|
+
return null;
|
|
8751
|
+
const selectedField = this.selectedNewComputedFieldFromClarification(request);
|
|
8752
|
+
if (!selectedField)
|
|
8753
|
+
return null;
|
|
8754
|
+
const columns = this.currentTableColumnsForAuthoring();
|
|
8755
|
+
if (columns.some((column) => this.stringValue(column['field']).toLowerCase() === selectedField.toLowerCase())) {
|
|
8756
|
+
return null;
|
|
8757
|
+
}
|
|
8758
|
+
const baseFields = this.computedColumnBaseFieldsFromPrompt(normalizedSourcePrompt, columns);
|
|
8759
|
+
if (baseFields.length < 2)
|
|
8760
|
+
return null;
|
|
8761
|
+
const operation = {
|
|
8762
|
+
operationId: 'column.computed.add',
|
|
8763
|
+
target: { kind: 'column', field: selectedField },
|
|
8764
|
+
input: {
|
|
8765
|
+
field: selectedField,
|
|
8766
|
+
header: this.humanizeField(selectedField),
|
|
8767
|
+
expression: {
|
|
8768
|
+
cat: baseFields.flatMap((field, index) => (index === 0 ? [{ var: field }] : [' - ', { var: field }])),
|
|
8769
|
+
},
|
|
8770
|
+
outputType: 'string',
|
|
8771
|
+
dependencies: baseFields,
|
|
8772
|
+
},
|
|
8773
|
+
};
|
|
8774
|
+
return {
|
|
8775
|
+
type: 'patch',
|
|
8776
|
+
sessionId: request.sessionId,
|
|
8777
|
+
componentEditPlan: {
|
|
8778
|
+
kind: 'praxis.table.component-edit-plan',
|
|
8779
|
+
version: '1.0',
|
|
8780
|
+
componentId: this.adapter.componentId || request.componentId || 'praxis-table',
|
|
8781
|
+
...operation,
|
|
8782
|
+
},
|
|
8783
|
+
explanation: `Vou criar a coluna calculada **${this.humanizeField(selectedField)}** combinando ${baseFields.map((field) => `**${this.humanizeField(field)}**`).join(' e ')}.`,
|
|
8784
|
+
warnings: [
|
|
8785
|
+
'computed-column-clarification-continuation-materialized',
|
|
8786
|
+
'A resposta curta de clarificacao foi materializada usando pendingClarification.sourcePrompt e colunas declaradas da tabela; a intencao primaria ja estava ancorada em coluna calculada.',
|
|
8787
|
+
],
|
|
8788
|
+
};
|
|
8789
|
+
}
|
|
8790
|
+
selectedNewComputedFieldFromClarification(request) {
|
|
8791
|
+
const value = [
|
|
8792
|
+
request.action?.value,
|
|
8793
|
+
request.action?.displayPrompt,
|
|
8794
|
+
request.prompt,
|
|
8795
|
+
]
|
|
8796
|
+
.map((candidate) => this.stringValue(candidate).trim())
|
|
8797
|
+
.find((candidate) => !!candidate);
|
|
8798
|
+
if (!value || /\s/u.test(value) || value.length > 80)
|
|
8799
|
+
return null;
|
|
8800
|
+
const cleaned = value.replace(/[^A-Za-z0-9_]/gu, '');
|
|
8801
|
+
return cleaned || null;
|
|
8802
|
+
}
|
|
8803
|
+
recoverComputedColumnClarificationSourcePromptFromMessages(request) {
|
|
8804
|
+
const messages = [...(request.messages ?? [])];
|
|
8805
|
+
if (messages.length < 2)
|
|
8806
|
+
return null;
|
|
8807
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
8808
|
+
const message = messages[index];
|
|
8809
|
+
if (message.role !== 'assistant')
|
|
8810
|
+
continue;
|
|
8811
|
+
const assistantText = this.normalizeLabel(message.text ?? '');
|
|
8812
|
+
if (!assistantText.includes('coluna correta') && !assistantText.includes('qual coluna'))
|
|
8813
|
+
continue;
|
|
8814
|
+
const sourcePrompt = [...messages.slice(0, index)]
|
|
8815
|
+
.reverse()
|
|
8816
|
+
.find((candidate) => candidate.role === 'user')
|
|
8817
|
+
?.text?.trim();
|
|
8818
|
+
if (sourcePrompt && this.promptRequestsComputedColumnCreation(this.normalizeLabel(sourcePrompt))) {
|
|
8819
|
+
return sourcePrompt;
|
|
8820
|
+
}
|
|
8821
|
+
}
|
|
8822
|
+
return null;
|
|
8823
|
+
}
|
|
8824
|
+
promptRequestsComputedColumnCreation(normalizedPrompt) {
|
|
8825
|
+
if (!normalizedPrompt)
|
|
8826
|
+
return false;
|
|
8827
|
+
const wantsComputed = normalizedPrompt.includes('calculad')
|
|
8828
|
+
|| normalizedPrompt.includes('computed')
|
|
8829
|
+
|| normalizedPrompt.includes('derivad')
|
|
8830
|
+
|| normalizedPrompt.includes('formula');
|
|
8831
|
+
const wantsJoin = normalizedPrompt.includes('combin')
|
|
8832
|
+
|| normalizedPrompt.includes('concat')
|
|
8833
|
+
|| normalizedPrompt.includes('juntar')
|
|
8834
|
+
|| normalizedPrompt.includes('unir')
|
|
8835
|
+
|| normalizedPrompt.includes('mesclar')
|
|
8836
|
+
|| normalizedPrompt.includes('merge');
|
|
8837
|
+
return wantsComputed && wantsJoin;
|
|
8838
|
+
}
|
|
8839
|
+
currentTableColumnsForAuthoring() {
|
|
8840
|
+
return this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
8841
|
+
.map((column) => this.toRecord(column))
|
|
8842
|
+
.filter((column) => !!column && !!this.stringValue(column['field']));
|
|
8843
|
+
}
|
|
8844
|
+
computedColumnBaseFieldsFromPrompt(normalizedPrompt, columns) {
|
|
8845
|
+
const fields = [];
|
|
8846
|
+
for (const column of columns) {
|
|
8847
|
+
const field = this.stringValue(column['field']);
|
|
8848
|
+
const header = this.stringValue(column['header']) || field;
|
|
8849
|
+
if (!field)
|
|
8850
|
+
continue;
|
|
8851
|
+
if (this.normalizedPromptMentionsColumn(normalizedPrompt, field)
|
|
8852
|
+
|| this.normalizedPromptMentionsColumn(normalizedPrompt, header)) {
|
|
8853
|
+
fields.push(field);
|
|
8854
|
+
}
|
|
8855
|
+
}
|
|
8856
|
+
return [...new Set(fields)];
|
|
8857
|
+
}
|
|
8858
|
+
normalizedPromptMentionsColumn(normalizedPrompt, value) {
|
|
8859
|
+
const normalizedValue = this.normalizeLabel(value);
|
|
8860
|
+
if (!normalizedValue)
|
|
8861
|
+
return false;
|
|
8862
|
+
return normalizedPrompt.includes(normalizedValue);
|
|
8863
|
+
}
|
|
7788
8864
|
describeBooleanStateRenderers(operations) {
|
|
7789
8865
|
if (operations.length < 2)
|
|
7790
8866
|
return null;
|
|
@@ -8505,7 +9581,6 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8505
9581
|
};
|
|
8506
9582
|
}
|
|
8507
9583
|
buildComponentPresentationAuthoringSystemPolicy(contextHints, prompt) {
|
|
8508
|
-
void contextHints;
|
|
8509
9584
|
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
8510
9585
|
const presentationIntent = this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
8511
9586
|
|| this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt);
|
|
@@ -8524,6 +9599,11 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8524
9599
|
const type = this.stringValue(column['type'] ?? column['dataType']) || 'unknown';
|
|
8525
9600
|
return `- ${field} (${label}; ${type}; ${visible})`;
|
|
8526
9601
|
});
|
|
9602
|
+
const targetGroundingLines = this.componentPresentationTargetGroundingLines(normalizedPrompt, columns);
|
|
9603
|
+
const contextField = this.stringValue(contextHints?.['targetField']);
|
|
9604
|
+
const contextTargetLine = contextField
|
|
9605
|
+
? `Declared context target: ${contextField}.`
|
|
9606
|
+
: 'Declared context target: none.';
|
|
8527
9607
|
return {
|
|
8528
9608
|
role: 'system',
|
|
8529
9609
|
content: [
|
|
@@ -8534,11 +9614,90 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8534
9614
|
'- 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.',
|
|
8535
9615
|
'- 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.',
|
|
8536
9616
|
'- 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.',
|
|
9617
|
+
'- Target grounding candidates below are ranking evidence after this turn has been resolved as presentation authoring; they are not the primary intent router.',
|
|
9618
|
+
'- When one high-confidence target candidate is declared, prefer that field for componentEditPlan targets unless the user explicitly names another field. Do not substitute a semantically adjacent field such as priority for status.',
|
|
9619
|
+
'- If the generated plan would target a different field than the high-confidence candidate, ask a short clarification instead of returning an executable componentEditPlan.',
|
|
8537
9620
|
'Declared table columns:',
|
|
8538
9621
|
...columnLines,
|
|
9622
|
+
contextTargetLine,
|
|
9623
|
+
targetGroundingLines.length ? 'Target grounding candidates:' : 'Target grounding candidates: none declared.',
|
|
9624
|
+
...targetGroundingLines,
|
|
8539
9625
|
].join('\n'),
|
|
8540
9626
|
};
|
|
8541
9627
|
}
|
|
9628
|
+
componentPresentationTargetGroundingLines(normalizedPrompt, columns) {
|
|
9629
|
+
return this.componentPresentationTargetGroundingCandidates(normalizedPrompt, columns)
|
|
9630
|
+
.map((candidate) => [
|
|
9631
|
+
`- ${candidate.field} (${candidate.label})`,
|
|
9632
|
+
`confidence=${candidate.confidence}`,
|
|
9633
|
+
`evidence=${candidate.evidence.join(', ')}`,
|
|
9634
|
+
].join('; '));
|
|
9635
|
+
}
|
|
9636
|
+
componentPresentationTargetGroundingHints(prompt) {
|
|
9637
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
9638
|
+
const presentationIntent = this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
9639
|
+
|| this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt);
|
|
9640
|
+
if (!presentationIntent)
|
|
9641
|
+
return null;
|
|
9642
|
+
const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
9643
|
+
.map((column) => this.toRecord(column))
|
|
9644
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
9645
|
+
.slice(0, 20);
|
|
9646
|
+
const candidates = this.componentPresentationTargetGroundingCandidates(normalizedPrompt, columns);
|
|
9647
|
+
if (!candidates.length)
|
|
9648
|
+
return null;
|
|
9649
|
+
return {
|
|
9650
|
+
presentationTargetGrounding: {
|
|
9651
|
+
kind: 'praxis.table.presentation-target-grounding.v1',
|
|
9652
|
+
scope: 'presentation-authoring',
|
|
9653
|
+
candidates: candidates.map((candidate) => ({
|
|
9654
|
+
field: candidate.field,
|
|
9655
|
+
label: candidate.label,
|
|
9656
|
+
confidence: candidate.confidence,
|
|
9657
|
+
evidence: candidate.evidence,
|
|
9658
|
+
})),
|
|
9659
|
+
},
|
|
9660
|
+
};
|
|
9661
|
+
}
|
|
9662
|
+
componentPresentationTargetGroundingCandidates(normalizedPrompt, columns) {
|
|
9663
|
+
return columns
|
|
9664
|
+
.map((column) => {
|
|
9665
|
+
const field = this.stringValue(column['field']);
|
|
9666
|
+
if (!field)
|
|
9667
|
+
return null;
|
|
9668
|
+
const label = this.stringValue(column['header']) || this.humanizeField(field);
|
|
9669
|
+
const fieldScore = this.visualPresentationTargetFieldScore(normalizedPrompt, field);
|
|
9670
|
+
const labelScore = this.visualPresentationTargetFieldScore(normalizedPrompt, label);
|
|
9671
|
+
const valueMatches = this.componentPresentationObservedValueMatches(normalizedPrompt, field);
|
|
9672
|
+
const score = Math.max(fieldScore, labelScore) + (valueMatches.length ? 12 : 0);
|
|
9673
|
+
if (score <= 0)
|
|
9674
|
+
return null;
|
|
9675
|
+
const evidence = [
|
|
9676
|
+
fieldScore > 0 ? 'field mention' : null,
|
|
9677
|
+
labelScore > 0 ? 'header mention' : null,
|
|
9678
|
+
...valueMatches.map((value) => `observed value "${value}"`),
|
|
9679
|
+
].filter((value) => !!value);
|
|
9680
|
+
const confidence = score >= 12 ? 'high' : 'medium';
|
|
9681
|
+
return {
|
|
9682
|
+
field,
|
|
9683
|
+
label,
|
|
9684
|
+
score,
|
|
9685
|
+
confidence,
|
|
9686
|
+
evidence,
|
|
9687
|
+
};
|
|
9688
|
+
})
|
|
9689
|
+
.filter((candidate) => !!candidate)
|
|
9690
|
+
.sort((a, b) => b.score - a.score)
|
|
9691
|
+
.slice(0, 5);
|
|
9692
|
+
}
|
|
9693
|
+
componentPresentationObservedValueMatches(normalizedPrompt, field) {
|
|
9694
|
+
return this.adapterFieldValueSamples(field)
|
|
9695
|
+
.map((value) => this.stringValue(value))
|
|
9696
|
+
.filter((value) => !!value)
|
|
9697
|
+
.filter((value) => this.normalizedTextContainsApproxPhrase(normalizedPrompt, value))
|
|
9698
|
+
.map((value) => (value.length > 48 ? `${value.slice(0, 45)}...` : value))
|
|
9699
|
+
.slice(0, 3);
|
|
9700
|
+
}
|
|
8542
9701
|
buildComponentLayoutAuthoringSystemPolicy(contextHints, prompt) {
|
|
8543
9702
|
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
8544
9703
|
const position = this.relativeOrderPosition(normalizedPrompt);
|
|
@@ -8788,6 +9947,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8788
9947
|
const record = this.toRecord(payload);
|
|
8789
9948
|
if (!record)
|
|
8790
9949
|
return false;
|
|
9950
|
+
if (this.isGovernedCategoricalSemanticsChoicePayload(record))
|
|
9951
|
+
return false;
|
|
8791
9952
|
return this.isVisualPresentationClarificationText([
|
|
8792
9953
|
record['label'],
|
|
8793
9954
|
record['value'],
|
|
@@ -8816,6 +9977,48 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8816
9977
|
'apresentação',
|
|
8817
9978
|
].some((token) => this.normalizedTextContainsApproxPhrase(text, token));
|
|
8818
9979
|
}
|
|
9980
|
+
isGovernedCategoricalSemanticsChoicePayload(payload) {
|
|
9981
|
+
const record = this.toRecord(payload);
|
|
9982
|
+
if (!record)
|
|
9983
|
+
return false;
|
|
9984
|
+
const contextHintsText = this.safeJsonText(record['contextHints']);
|
|
9985
|
+
return this.isGovernedCategoricalSemanticsChoiceText([
|
|
9986
|
+
record['label'],
|
|
9987
|
+
record['value'],
|
|
9988
|
+
record['description'],
|
|
9989
|
+
contextHintsText,
|
|
9990
|
+
].filter(Boolean).join(' '));
|
|
9991
|
+
}
|
|
9992
|
+
isGovernedCategoricalSemanticsChoiceText(value) {
|
|
9993
|
+
const text = this.normalizeLabel(value);
|
|
9994
|
+
if (!text)
|
|
9995
|
+
return false;
|
|
9996
|
+
return [
|
|
9997
|
+
'author categorical field semantics',
|
|
9998
|
+
'author categorical semantics',
|
|
9999
|
+
'categorical field semantics',
|
|
10000
|
+
'semantica visual governada',
|
|
10001
|
+
'semântica visual governada',
|
|
10002
|
+
'governed visual semantics',
|
|
10003
|
+
'apply neutral categorical renderer',
|
|
10004
|
+
'apply neutral categorical chip',
|
|
10005
|
+
'aplicar chips neutros',
|
|
10006
|
+
'chips neutros por enquanto',
|
|
10007
|
+
'ungoverned neutral fallback',
|
|
10008
|
+
]
|
|
10009
|
+
.map((token) => this.normalizeLabel(token))
|
|
10010
|
+
.some((token) => text.includes(token));
|
|
10011
|
+
}
|
|
10012
|
+
safeJsonText(value) {
|
|
10013
|
+
if (value === null || value === undefined)
|
|
10014
|
+
return '';
|
|
10015
|
+
try {
|
|
10016
|
+
return JSON.stringify(value) ?? '';
|
|
10017
|
+
}
|
|
10018
|
+
catch {
|
|
10019
|
+
return '';
|
|
10020
|
+
}
|
|
10021
|
+
}
|
|
8819
10022
|
textMentionsFilterCatalogEntry(normalizedText) {
|
|
8820
10023
|
if (!normalizedText || !this.filterFieldCatalogEntries.length)
|
|
8821
10024
|
return false;
|
|
@@ -8919,17 +10122,40 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8919
10122
|
const rawLabel = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
8920
10123
|
const label = this.humanizeClarificationOptionLabel(rawLabel);
|
|
8921
10124
|
const canonicalValue = option.value?.trim() || option.example?.trim() || label;
|
|
10125
|
+
const isGovernedCategoricalSemanticsChoice = this.isGovernedCategoricalSemanticsChoicePayload(option);
|
|
10126
|
+
const isVisualPresentation = !isGovernedCategoricalSemanticsChoice
|
|
10127
|
+
&& this.isVisualPresentationClarificationPayload(option);
|
|
10128
|
+
const visualRenderer = isVisualPresentation
|
|
10129
|
+
? this.visualPresentationRendererValue([rawLabel, canonicalValue].join(' '))
|
|
10130
|
+
: null;
|
|
10131
|
+
const optionHints = this.optionContextHints(option);
|
|
10132
|
+
const optionSelected = this.toRecord(optionHints?.['optionSelected']);
|
|
10133
|
+
const selection = this.toRecord(optionSelected?.['selection']);
|
|
10134
|
+
const visualField = visualRenderer
|
|
10135
|
+
? this.stringValue(selection?.['field'])
|
|
10136
|
+
|| this.stringValue(optionSelected?.['targetField'])
|
|
10137
|
+
|| this.visualPresentationTargetField(response, request)
|
|
10138
|
+
: null;
|
|
10139
|
+
const prompt = visualRenderer
|
|
10140
|
+
? this.visualPresentationClarificationPromptFromSelection(visualRenderer, visualField)
|
|
10141
|
+
: isGovernedCategoricalSemanticsChoice
|
|
10142
|
+
? label
|
|
10143
|
+
: isVisualPresentation
|
|
10144
|
+
? this.visualPresentationClarificationPrompt(option, canonicalValue)
|
|
10145
|
+
: label;
|
|
8922
10146
|
return {
|
|
8923
10147
|
id: `option-${index + 1}`,
|
|
8924
10148
|
label,
|
|
8925
|
-
prompt
|
|
8926
|
-
value: canonicalValue,
|
|
10149
|
+
prompt,
|
|
10150
|
+
value: visualRenderer ?? canonicalValue,
|
|
8927
10151
|
kind: 'clarification-option',
|
|
8928
10152
|
description: this.optionDescription(option),
|
|
8929
10153
|
icon: this.optionIcon(option),
|
|
8930
10154
|
tone: this.optionTone(option),
|
|
8931
10155
|
presentation: this.optionPresentation(option) ?? this.defaultGuidedOptionPresentation(option),
|
|
8932
|
-
contextHints:
|
|
10156
|
+
contextHints: visualRenderer
|
|
10157
|
+
? this.visualPresentationOptionContextHints(visualRenderer, visualField)
|
|
10158
|
+
: optionHints,
|
|
8933
10159
|
};
|
|
8934
10160
|
});
|
|
8935
10161
|
}
|
|
@@ -8938,17 +10164,139 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8938
10164
|
}
|
|
8939
10165
|
return this.enhanceColumnClarificationOptions(response.options ?? [], response, request)
|
|
8940
10166
|
.filter((option) => !!option?.trim())
|
|
8941
|
-
.map((option, index) =>
|
|
8942
|
-
|
|
8943
|
-
label
|
|
8944
|
-
|
|
8945
|
-
|
|
10167
|
+
.map((option, index) => {
|
|
10168
|
+
const rawOption = option.trim();
|
|
10169
|
+
const label = this.humanizeClarificationOptionLabel(rawOption);
|
|
10170
|
+
const visualRenderer = this.visualPresentationRendererValue(rawOption);
|
|
10171
|
+
const visualField = visualRenderer
|
|
10172
|
+
? this.visualPresentationTargetField(response, request)
|
|
10173
|
+
: null;
|
|
10174
|
+
const prompt = visualRenderer
|
|
10175
|
+
? this.visualPresentationClarificationPromptFromSelection(visualRenderer, visualField)
|
|
10176
|
+
: rawOption;
|
|
10177
|
+
return {
|
|
10178
|
+
id: `option-${index + 1}`,
|
|
10179
|
+
label,
|
|
10180
|
+
prompt,
|
|
10181
|
+
value: visualRenderer ?? rawOption,
|
|
10182
|
+
kind: 'clarification-option',
|
|
10183
|
+
presentation: {
|
|
10184
|
+
kind: 'guided-option',
|
|
10185
|
+
icon: visualRenderer ? this.visualPresentationIcon(visualRenderer) : 'check',
|
|
10186
|
+
ctaLabel: visualRenderer ? 'Aplicar opção' : 'Usar esta opção',
|
|
10187
|
+
},
|
|
10188
|
+
...(visualRenderer
|
|
10189
|
+
? { contextHints: this.visualPresentationOptionContextHints(visualRenderer, visualField) }
|
|
10190
|
+
: {}),
|
|
10191
|
+
};
|
|
10192
|
+
});
|
|
10193
|
+
}
|
|
10194
|
+
visualPresentationClarificationPrompt(option, canonicalValue) {
|
|
10195
|
+
const contextHints = this.toRecord(option.contextHints);
|
|
10196
|
+
const optionSelected = this.toRecord(contextHints?.['optionSelected']);
|
|
10197
|
+
const selection = this.toRecord(optionSelected?.['selection']);
|
|
10198
|
+
const field = this.stringValue(selection?.['field']) || this.stringValue(optionSelected?.['targetField']);
|
|
10199
|
+
const rendererValue = this.stringValue(selection?.['value']) || canonicalValue;
|
|
10200
|
+
return this.visualPresentationClarificationPromptFromSelection(rendererValue, field);
|
|
10201
|
+
}
|
|
10202
|
+
visualPresentationClarificationPromptFromSelection(rendererValue, field) {
|
|
10203
|
+
return field
|
|
10204
|
+
? `Aplique a apresentacao visual ${rendererValue} na coluna ${field}.`
|
|
10205
|
+
: `Aplique a apresentacao visual ${rendererValue}.`;
|
|
10206
|
+
}
|
|
10207
|
+
visualPresentationRendererValue(option) {
|
|
10208
|
+
if (!this.isVisualPresentationClarificationText(option))
|
|
10209
|
+
return null;
|
|
10210
|
+
const normalized = this.normalizeLabel(option);
|
|
10211
|
+
if (normalized.includes('two lines')
|
|
10212
|
+
|| normalized.includes('two line')
|
|
10213
|
+
|| normalized.includes('two-line')
|
|
10214
|
+
|| normalized.includes('twolines')
|
|
10215
|
+
|| normalized.includes('twoline')
|
|
10216
|
+
|| normalized.includes('duas linhas')
|
|
10217
|
+
|| normalized.includes('duaslinhas')
|
|
10218
|
+
|| normalized.includes('duas_linhas')
|
|
10219
|
+
|| normalized.includes('segunda linha')) {
|
|
10220
|
+
return 'two_lines';
|
|
10221
|
+
}
|
|
10222
|
+
if (normalized.includes('badge') || normalized.includes('chip') || normalized.includes('etiqueta')) {
|
|
10223
|
+
return 'badge';
|
|
10224
|
+
}
|
|
10225
|
+
if (normalized.includes('icone') || normalized.includes('icon')) {
|
|
10226
|
+
return 'icon';
|
|
10227
|
+
}
|
|
10228
|
+
if (normalized.includes('alinhamento') || normalized.includes('alinhar')) {
|
|
10229
|
+
return 'alignment';
|
|
10230
|
+
}
|
|
10231
|
+
return null;
|
|
10232
|
+
}
|
|
10233
|
+
visualPresentationTargetField(response, request) {
|
|
10234
|
+
const text = this.normalizeLabel([
|
|
10235
|
+
request?.prompt,
|
|
10236
|
+
request?.pendingClarification?.sourcePrompt,
|
|
10237
|
+
request?.pendingClarification?.assistantMessage,
|
|
10238
|
+
response.message,
|
|
10239
|
+
...(response.questions ?? []),
|
|
10240
|
+
].filter(Boolean).join(' '));
|
|
10241
|
+
const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
10242
|
+
.map((column) => this.toRecord(column))
|
|
10243
|
+
.filter((column) => !!column);
|
|
10244
|
+
const matched = columns
|
|
10245
|
+
.map((column) => {
|
|
10246
|
+
const field = this.stringValue(column['field']);
|
|
10247
|
+
if (!field)
|
|
10248
|
+
return null;
|
|
10249
|
+
const header = this.stringValue(column['header']) || field;
|
|
10250
|
+
const score = Math.max(this.visualPresentationTargetFieldScore(text, field), this.visualPresentationTargetFieldScore(text, header));
|
|
10251
|
+
return score > 0 ? { field, score } : null;
|
|
10252
|
+
})
|
|
10253
|
+
.filter((entry) => !!entry)
|
|
10254
|
+
.sort((a, b) => b.score - a.score)[0];
|
|
10255
|
+
if (matched)
|
|
10256
|
+
return matched.field;
|
|
10257
|
+
const visibleColumns = columns.filter((column) => column['visible'] !== false);
|
|
10258
|
+
const lastVisibleField = this.stringValue(visibleColumns[visibleColumns.length - 1]?.['field']);
|
|
10259
|
+
return lastVisibleField || null;
|
|
10260
|
+
}
|
|
10261
|
+
visualPresentationTargetFieldScore(normalizedText, value) {
|
|
10262
|
+
return this.filterFieldMentionVariants(value)
|
|
10263
|
+
.filter((variant) => !!variant && this.normalizedTextContainsApproxPhrase(normalizedText, variant))
|
|
10264
|
+
.reduce((score, variant) => Math.max(score, variant.length), 0);
|
|
10265
|
+
}
|
|
10266
|
+
visualPresentationOptionContextHints(rendererValue, field) {
|
|
10267
|
+
const selected = {
|
|
10268
|
+
selection: {
|
|
10269
|
+
mode: 'renderer',
|
|
10270
|
+
...(field ? { field } : {}),
|
|
10271
|
+
value: rendererValue,
|
|
10272
|
+
},
|
|
10273
|
+
};
|
|
10274
|
+
if (field) {
|
|
10275
|
+
selected['targetField'] = field;
|
|
10276
|
+
}
|
|
10277
|
+
return {
|
|
10278
|
+
optionSelected: selected,
|
|
8946
10279
|
presentation: {
|
|
8947
10280
|
kind: 'guided-option',
|
|
8948
|
-
|
|
8949
|
-
|
|
10281
|
+
ctaLabel: 'Aplicar opção',
|
|
10282
|
+
icon: this.visualPresentationIcon(rendererValue),
|
|
10283
|
+
tone: 'primary',
|
|
8950
10284
|
},
|
|
8951
|
-
}
|
|
10285
|
+
};
|
|
10286
|
+
}
|
|
10287
|
+
visualPresentationIcon(rendererValue) {
|
|
10288
|
+
switch (rendererValue) {
|
|
10289
|
+
case 'badge':
|
|
10290
|
+
return 'sell';
|
|
10291
|
+
case 'icon':
|
|
10292
|
+
return 'stars';
|
|
10293
|
+
case 'alignment':
|
|
10294
|
+
return 'format_align_left';
|
|
10295
|
+
case 'two_lines':
|
|
10296
|
+
return 'format_line_spacing';
|
|
10297
|
+
default:
|
|
10298
|
+
return 'check';
|
|
10299
|
+
}
|
|
8952
10300
|
}
|
|
8953
10301
|
filterFieldCatalogEntryForClarificationLabel(label) {
|
|
8954
10302
|
const normalized = this.normalizeLabel(label);
|