@praxisui/table 8.0.0-beta.98 → 9.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/docs/dynamic-filter-host-integration-guide.md +7 -0
- package/fesm2022/{praxisui-table-praxisui-table-lLVoKobh.mjs → praxisui-table-praxisui-table-CunEk0vb.mjs} +2502 -488
- package/fesm2022/{praxisui-table-table-agentic-authoring-turn-flow-DZyPFYjL.mjs → praxisui-table-table-agentic-authoring-turn-flow-DL9l0XF7.mjs} +1673 -209
- package/fesm2022/{praxisui-table-table-ai.adapter-CkynHDDv.mjs → praxisui-table-table-ai.adapter-BvaMq2CS.mjs} +42 -2
- package/fesm2022/praxisui-table.mjs +1 -1
- package/package.json +10 -10
- package/src/lib/praxis-table.json-api.md +162 -138
- package/types/praxisui-table.d.ts +131 -21
|
@@ -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,6 +409,14 @@ 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;
|
|
@@ -394,15 +494,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
394
494
|
};
|
|
395
495
|
}
|
|
396
496
|
if (this.selectedRecordReadOnlyAnalysisRequested(request)) {
|
|
397
|
-
return
|
|
398
|
-
type: 'info',
|
|
399
|
-
sessionId: request.sessionId,
|
|
400
|
-
message: this.selectedRecordsAnalyticalSummary(contextHints),
|
|
401
|
-
warnings: [
|
|
402
|
-
'recommended-intent-local-selected-records-analysis',
|
|
403
|
-
'Resposta informativa gerada a partir do recommendedIntent canonico table.selection.analyze e selectedRecordsContext.sampleRows; nao houve roteamento textual primario.',
|
|
404
|
-
],
|
|
405
|
-
};
|
|
497
|
+
return this.selectedRecordsReadonlyInfoResponse(request, contextHints);
|
|
406
498
|
}
|
|
407
499
|
const candidates = this.selectedRecordFilterCandidates(contextHints);
|
|
408
500
|
const fields = candidates
|
|
@@ -429,6 +521,17 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
429
521
|
],
|
|
430
522
|
};
|
|
431
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
|
+
}
|
|
432
535
|
localGuidedRuntimeOperationResponse(request, contextHints) {
|
|
433
536
|
const runtimeOperation = this.toRecord(contextHints?.['tableRuntimeOperation']);
|
|
434
537
|
const operationId = this.stringValue(runtimeOperation?.['operationId']);
|
|
@@ -2022,6 +2125,40 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2022
2125
|
return typeof this.aiApi.startPatchStream === 'function'
|
|
2023
2126
|
&& typeof this.aiApi.connectPatchStream === 'function';
|
|
2024
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
|
+
}
|
|
2025
2162
|
toTerminalStreamResult(event, request, currentState) {
|
|
2026
2163
|
if (!event)
|
|
2027
2164
|
return null;
|
|
@@ -2185,14 +2322,27 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2185
2322
|
});
|
|
2186
2323
|
}
|
|
2187
2324
|
retry(request) {
|
|
2325
|
+
const retryAction = this.retryActionFromDiagnostics(request.diagnostics);
|
|
2188
2326
|
const lastPrompt = [...(request.messages ?? [])].reverse()
|
|
2189
2327
|
.find((message) => message.role === 'user')?.text;
|
|
2190
2328
|
return this.submit({
|
|
2191
2329
|
...request,
|
|
2192
|
-
prompt: lastPrompt
|
|
2193
|
-
action:
|
|
2330
|
+
prompt: this.stringValue(retryAction?.['value']) || lastPrompt || request.prompt,
|
|
2331
|
+
action: retryAction
|
|
2332
|
+
? { ...retryAction, kind: this.stringValue(retryAction['kind']) || 'clarify' }
|
|
2333
|
+
: { kind: 'retry' },
|
|
2194
2334
|
});
|
|
2195
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
|
+
}
|
|
2196
2346
|
toTurnResult(response, request) {
|
|
2197
2347
|
if (!response) {
|
|
2198
2348
|
return {
|
|
@@ -2257,6 +2407,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2257
2407
|
}
|
|
2258
2408
|
if (response.type === 'error') {
|
|
2259
2409
|
const message = response.message || 'Falha ao gerar alteração de tabela.';
|
|
2410
|
+
const retryDiagnostics = this.errorRetryDiagnostics(request, response);
|
|
2260
2411
|
return {
|
|
2261
2412
|
state: 'error',
|
|
2262
2413
|
phase: 'capture',
|
|
@@ -2264,7 +2415,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2264
2415
|
observationId: response.observationId ?? request.observationId ?? null,
|
|
2265
2416
|
assistantMessage: message,
|
|
2266
2417
|
errorText: message,
|
|
2267
|
-
diagnostics:
|
|
2418
|
+
diagnostics: retryDiagnostics,
|
|
2268
2419
|
};
|
|
2269
2420
|
}
|
|
2270
2421
|
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
@@ -2316,10 +2467,26 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2316
2467
|
}
|
|
2317
2468
|
compileAdapterResponse(response, request) {
|
|
2318
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
|
+
}
|
|
2319
2478
|
response = this.normalizeStatusPresentationPlan(response, request);
|
|
2320
2479
|
response = this.normalizeNumericBandConditionalStylePlan(response, request);
|
|
2321
2480
|
response = this.normalizeCompoundColumnPresentationPlan(response, request);
|
|
2322
2481
|
response = this.normalizeComputedColumnAuxiliaryMaterializations(response);
|
|
2482
|
+
const selectedRecordReadonlyPresentationBoundary = this.selectedRecordReadonlyPresentationBoundaryResponse(response, request);
|
|
2483
|
+
if (selectedRecordReadonlyPresentationBoundary) {
|
|
2484
|
+
return selectedRecordReadonlyPresentationBoundary;
|
|
2485
|
+
}
|
|
2486
|
+
const visualPresentationChoice = this.visualPresentationChoiceClarificationBoundary(response, request);
|
|
2487
|
+
if (visualPresentationChoice) {
|
|
2488
|
+
return visualPresentationChoice;
|
|
2489
|
+
}
|
|
2323
2490
|
if (response.type === 'clarification' || response.type === 'info' || response.type === 'error') {
|
|
2324
2491
|
const selectedRecordReadonlyClarificationBoundary = this.selectedRecordReadonlyClarificationBoundaryResponse(response, request);
|
|
2325
2492
|
if (selectedRecordReadonlyClarificationBoundary) {
|
|
@@ -2329,6 +2496,22 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2329
2496
|
if (continuedEarlyColumnPlan) {
|
|
2330
2497
|
return this.compileAdapterResponse(continuedEarlyColumnPlan, request);
|
|
2331
2498
|
}
|
|
2499
|
+
const governedCategoricalSemanticsChoice = this.governedCategoricalSemanticsClarificationBoundary(response);
|
|
2500
|
+
if (governedCategoricalSemanticsChoice) {
|
|
2501
|
+
return governedCategoricalSemanticsChoice;
|
|
2502
|
+
}
|
|
2503
|
+
const governedCategoricalDiscovery = this.governedCategoricalDiscoveryBoundary(response, request);
|
|
2504
|
+
if (governedCategoricalDiscovery) {
|
|
2505
|
+
return governedCategoricalDiscovery;
|
|
2506
|
+
}
|
|
2507
|
+
const canonicalRendererSelectionBoundary = this.canonicalRendererSelectionBoundary(response, request);
|
|
2508
|
+
if (canonicalRendererSelectionBoundary) {
|
|
2509
|
+
return canonicalRendererSelectionBoundary;
|
|
2510
|
+
}
|
|
2511
|
+
const governedCategoricalSelectionBoundary = this.governedCategoricalSelectionBoundary(response, request);
|
|
2512
|
+
if (governedCategoricalSelectionBoundary) {
|
|
2513
|
+
return governedCategoricalSelectionBoundary;
|
|
2514
|
+
}
|
|
2332
2515
|
const continuedColumnVisibilityPlan = this.columnVisibilityPlanForGroundedClarification(response, request);
|
|
2333
2516
|
if (continuedColumnVisibilityPlan) {
|
|
2334
2517
|
return this.compileAdapterResponse(continuedColumnVisibilityPlan, request);
|
|
@@ -2371,7 +2554,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2371
2554
|
if (selectedRecordReadonlyBoundary) {
|
|
2372
2555
|
return selectedRecordReadonlyBoundary;
|
|
2373
2556
|
}
|
|
2374
|
-
return executableResponse;
|
|
2557
|
+
return this.normalizeGovernedCategoricalSelectionExecutableResponse(executableResponse, request);
|
|
2375
2558
|
}
|
|
2376
2559
|
if (compiledExecutable?.type === 'error') {
|
|
2377
2560
|
const continuedColumnVisibilityError = this.columnVisibilityPlanForMisroutedSurfaceError(response, request, compiledExecutable);
|
|
@@ -2577,7 +2760,93 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2577
2760
|
return visualSurfaceMismatch;
|
|
2578
2761
|
}
|
|
2579
2762
|
const openOnlySurfaceRuntimeOperation = this.selectedRecordSurfaceRuntimeOperationForOpenOnlyExecutable(compiledResponse, request);
|
|
2580
|
-
|
|
2763
|
+
if (openOnlySurfaceRuntimeOperation)
|
|
2764
|
+
return openOnlySurfaceRuntimeOperation;
|
|
2765
|
+
return this.normalizeGovernedCategoricalSelectionExecutableResponse(compiledResponse, request);
|
|
2766
|
+
}
|
|
2767
|
+
visualPresentationChoiceClarificationBoundary(response, request) {
|
|
2768
|
+
const carriesVisualPayload = (response.optionPayloads ?? [])
|
|
2769
|
+
.some((payload) => this.isVisualPresentationClarificationPayload(payload));
|
|
2770
|
+
if (!carriesVisualPayload)
|
|
2771
|
+
return null;
|
|
2772
|
+
if (!this.promptRequestsPresentationOptions(request?.prompt ?? ''))
|
|
2773
|
+
return null;
|
|
2774
|
+
const message = response.message || 'Encontrei opções de apresentação para essa coluna. Escolha uma opção para aplicar.';
|
|
2775
|
+
return {
|
|
2776
|
+
...response,
|
|
2777
|
+
type: 'clarification',
|
|
2778
|
+
message,
|
|
2779
|
+
explanation: message,
|
|
2780
|
+
componentEditPlan: undefined,
|
|
2781
|
+
patch: undefined,
|
|
2782
|
+
warnings: [
|
|
2783
|
+
...(response.warnings ?? []),
|
|
2784
|
+
'visual-presentation-choice-kept-as-clarification',
|
|
2785
|
+
],
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
governedCategoricalSemanticsClarificationBoundary(response) {
|
|
2789
|
+
if (!this.responseHasGovernedCategoricalSemanticsChoice(response))
|
|
2790
|
+
return null;
|
|
2791
|
+
const message = response.message
|
|
2792
|
+
|| 'Esta apresentação precisa de uma decisão semântica governada antes de materializar a tabela.';
|
|
2793
|
+
return {
|
|
2794
|
+
...response,
|
|
2795
|
+
type: 'clarification',
|
|
2796
|
+
message,
|
|
2797
|
+
explanation: message,
|
|
2798
|
+
componentEditPlan: undefined,
|
|
2799
|
+
patch: undefined,
|
|
2800
|
+
warnings: [
|
|
2801
|
+
...(response.warnings ?? []),
|
|
2802
|
+
'governed-categorical-semantics-choice-kept-as-clarification',
|
|
2803
|
+
],
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
responseHasGovernedCategoricalSemanticsChoice(response) {
|
|
2807
|
+
if ((response.optionPayloads ?? [])
|
|
2808
|
+
.some((payload) => this.isGovernedCategoricalSemanticsChoicePayload(payload))) {
|
|
2809
|
+
return true;
|
|
2810
|
+
}
|
|
2811
|
+
return this.isGovernedCategoricalSemanticsChoiceText([
|
|
2812
|
+
response.message,
|
|
2813
|
+
response.explanation,
|
|
2814
|
+
...(response.questions ?? []),
|
|
2815
|
+
...(response.options ?? []),
|
|
2816
|
+
].filter(Boolean).join(' '));
|
|
2817
|
+
}
|
|
2818
|
+
promptRequestsPresentationOptions(prompt) {
|
|
2819
|
+
const normalized = this.normalizeLabel(prompt);
|
|
2820
|
+
if (!normalized)
|
|
2821
|
+
return false;
|
|
2822
|
+
const asksForOptions = [
|
|
2823
|
+
'opcao',
|
|
2824
|
+
'opcoes',
|
|
2825
|
+
'opção',
|
|
2826
|
+
'opções',
|
|
2827
|
+
'alternativa',
|
|
2828
|
+
'alternativas',
|
|
2829
|
+
'recomenda',
|
|
2830
|
+
'recomende',
|
|
2831
|
+
'sugere',
|
|
2832
|
+
'sugira',
|
|
2833
|
+
'compare',
|
|
2834
|
+
].some((token) => this.normalizedTextContainsApproxPhrase(normalized, token));
|
|
2835
|
+
if (!asksForOptions)
|
|
2836
|
+
return false;
|
|
2837
|
+
return [
|
|
2838
|
+
'apresentacao',
|
|
2839
|
+
'apresentação',
|
|
2840
|
+
'formatacao',
|
|
2841
|
+
'formatação',
|
|
2842
|
+
'badge',
|
|
2843
|
+
'icone',
|
|
2844
|
+
'ícone',
|
|
2845
|
+
'alinhamento',
|
|
2846
|
+
'duas linhas',
|
|
2847
|
+
'duaslinhas',
|
|
2848
|
+
'visual',
|
|
2849
|
+
].some((token) => this.normalizedTextContainsApproxPhrase(normalized, token));
|
|
2581
2850
|
}
|
|
2582
2851
|
normalizeCategoricalRendererPalette(response, request) {
|
|
2583
2852
|
const plan = this.toRecord(response.componentEditPlan);
|
|
@@ -2707,7 +2976,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
2707
2976
|
relativeColumnOrderPlanForMisroutedClarification(response, request) {
|
|
2708
2977
|
if (!request?.prompt)
|
|
2709
2978
|
return null;
|
|
2710
|
-
const prompt = this.normalizeLabel(request.prompt);
|
|
2979
|
+
const prompt = this.normalizeLabel(request.prompt ?? '');
|
|
2711
2980
|
const position = this.relativeOrderPosition(prompt);
|
|
2712
2981
|
if (!position)
|
|
2713
2982
|
return null;
|
|
@@ -4699,6 +4968,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4699
4968
|
const responseType = this.stringValue(response.type);
|
|
4700
4969
|
if (!request || !['clarification', 'info', 'success'].includes(responseType))
|
|
4701
4970
|
return null;
|
|
4971
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
4972
|
+
return null;
|
|
4702
4973
|
const responseText = [
|
|
4703
4974
|
response.message ?? '',
|
|
4704
4975
|
response.explanation ?? '',
|
|
@@ -4733,7 +5004,9 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4733
5004
|
?? this.booleanSimNaoOperationsFromGroundedClarification(prompt, responseText, columns)
|
|
4734
5005
|
?? this.booleanSimNaoOperationsFromSingleGroundedBooleanColumn(prompt, responseText, columns)
|
|
4735
5006
|
?? this.booleanSimNaoOperationsFromGroundedResponseField(prompt, responseText, columns);
|
|
4736
|
-
const statusPresentationOperations = this.
|
|
5007
|
+
const statusPresentationOperations = this.responseCarriesClarificationChoices(response)
|
|
5008
|
+
? []
|
|
5009
|
+
: this.statusPresentationOperationsFromPrompt(prompt, columns);
|
|
4737
5010
|
if (statusPresentationOperations.length) {
|
|
4738
5011
|
return this.componentEditPlanResponse(statusPresentationOperations, 'Vou ajustar a apresentacao visual do status sem filtrar as linhas.', [
|
|
4739
5012
|
...(response.warnings ?? []),
|
|
@@ -4809,6 +5082,9 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4809
5082
|
]);
|
|
4810
5083
|
}
|
|
4811
5084
|
const categoricalRenderer = this.categoricalChipOperationsFromVisualClarification(prompt, responseText, columns);
|
|
5085
|
+
if (categoricalRenderer.length && this.responseHasGovernedCategoricalSemanticsChoice(response)) {
|
|
5086
|
+
return null;
|
|
5087
|
+
}
|
|
4812
5088
|
if (categoricalRenderer.length) {
|
|
4813
5089
|
return this.componentEditPlanResponse(categoricalRenderer, 'Vou aplicar chips discretos na coluna indicada.', [
|
|
4814
5090
|
...(response.warnings ?? []),
|
|
@@ -4884,6 +5160,14 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4884
5160
|
return response;
|
|
4885
5161
|
if ((response.warnings ?? []).includes('status-presentation-normalized'))
|
|
4886
5162
|
return response;
|
|
5163
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
5164
|
+
return response;
|
|
5165
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5166
|
+
return response;
|
|
5167
|
+
if ((response.optionPayloads?.length ?? 0) > 0)
|
|
5168
|
+
return response;
|
|
5169
|
+
if (this.responseHasGovernedCategoricalSemanticsChoice(response))
|
|
5170
|
+
return response;
|
|
4887
5171
|
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
4888
5172
|
const columns = Array.isArray(currentConfig?.['columns'])
|
|
4889
5173
|
? currentConfig['columns']
|
|
@@ -4929,101 +5213,673 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
4929
5213
|
],
|
|
4930
5214
|
};
|
|
4931
5215
|
}
|
|
4932
|
-
|
|
4933
|
-
if (
|
|
4934
|
-
return
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
const
|
|
4938
|
-
const
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
5216
|
+
requestIsCanonicalRendererSelection(request) {
|
|
5217
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5218
|
+
return false;
|
|
5219
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5220
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5221
|
+
const actionSelection = this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection']);
|
|
5222
|
+
const requestSelection = this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
|
|
5223
|
+
const mode = this.normalizeLabel(this.stringValue(actionSelection?.['mode']) || this.stringValue(requestSelection?.['mode']));
|
|
5224
|
+
if (mode === 'renderer')
|
|
5225
|
+
return true;
|
|
5226
|
+
const prompt = this.normalizeLabel(request.prompt ?? '');
|
|
5227
|
+
return this.normalizedTextIncludesAny(prompt, [
|
|
5228
|
+
'two_lines',
|
|
5229
|
+
'two lines',
|
|
5230
|
+
'two line',
|
|
5231
|
+
'two-line',
|
|
5232
|
+
'twolines',
|
|
5233
|
+
'twoline',
|
|
5234
|
+
'duas linhas',
|
|
5235
|
+
'duas_linhas',
|
|
5236
|
+
'duaslinhas',
|
|
5237
|
+
'segunda linha',
|
|
5238
|
+
]);
|
|
5239
|
+
}
|
|
5240
|
+
canonicalRendererSelectionBoundary(response, request) {
|
|
5241
|
+
if (!request || !this.requestIsCanonicalRendererSelection(request))
|
|
5242
|
+
return null;
|
|
5243
|
+
if (response.type === 'error')
|
|
5244
|
+
return null;
|
|
5245
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5246
|
+
return null;
|
|
5247
|
+
const selection = this.canonicalRendererSelection(request);
|
|
5248
|
+
const field = this.stringValue(selection?.['field']);
|
|
5249
|
+
if (!field)
|
|
5250
|
+
return null;
|
|
5251
|
+
const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
|
|
5252
|
+
|| this.componentEditOperations(response.componentEditPlan).length > 0
|
|
5253
|
+
|| !!this.toRecord(response.patch);
|
|
5254
|
+
if (hasExecutableEnvelope && this.responseTargetsSelectedRendererField(response, field)) {
|
|
5255
|
+
return null;
|
|
5256
|
+
}
|
|
5257
|
+
const label = this.stringValue(request.action?.displayPrompt)
|
|
5258
|
+
|| this.stringValue(request.action?.value)
|
|
5259
|
+
|| this.stringValue(selection?.['value'])
|
|
5260
|
+
|| 'opção selecionada';
|
|
5261
|
+
const header = this.humanizeField(field);
|
|
4961
5262
|
return {
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
|
|
4967
|
-
version: this.stringValue(originalPlan?.['version']) || '1.0',
|
|
4968
|
-
componentId: this.stringValue(originalPlan?.['componentId'])
|
|
4969
|
-
|| this.adapter.componentId
|
|
4970
|
-
|| request.componentId
|
|
4971
|
-
|| 'praxis-table',
|
|
4972
|
-
operations: [...originalOperations, ...operations],
|
|
4973
|
-
},
|
|
4974
|
-
explanation: response.explanation || 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.',
|
|
5263
|
+
type: 'info',
|
|
5264
|
+
sessionId: response.sessionId,
|
|
5265
|
+
observationId: response.observationId,
|
|
5266
|
+
message: `A opção "${label}" foi selecionada, mas a resposta ainda não trouxe um plano governado aplicável para ${header}.`,
|
|
4975
5267
|
warnings: [
|
|
4976
5268
|
...(response.warnings ?? []),
|
|
4977
|
-
'
|
|
4978
|
-
'
|
|
5269
|
+
'canonical-renderer-selection-kept-non-executable',
|
|
5270
|
+
'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.',
|
|
4979
5271
|
],
|
|
4980
5272
|
};
|
|
4981
5273
|
}
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
.some((operation) => expected.has(this.componentEditPlanOperationIdentity(operation)));
|
|
5274
|
+
canonicalRendererSelection(request) {
|
|
5275
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5276
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5277
|
+
return this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection'])
|
|
5278
|
+
?? this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
|
|
4988
5279
|
}
|
|
4989
|
-
|
|
4990
|
-
|
|
5280
|
+
responseTargetsSelectedRendererField(response, field) {
|
|
5281
|
+
const normalizedField = this.normalizeLabel(field);
|
|
5282
|
+
if (!normalizedField)
|
|
5283
|
+
return false;
|
|
5284
|
+
const operations = this.componentEditOperations(response.componentEditPlan);
|
|
5285
|
+
if (operations.some((operation) => this.normalizeLabel(this.operationTargetField(operation)) === normalizedField)) {
|
|
4991
5286
|
return true;
|
|
4992
5287
|
}
|
|
4993
5288
|
const patch = this.toRecord(response.patch);
|
|
4994
|
-
const columns =
|
|
4995
|
-
return columns
|
|
5289
|
+
const columns = this.toArray(patch?.['columns'])
|
|
4996
5290
|
.map((column) => this.toRecord(column))
|
|
4997
|
-
.
|
|
5291
|
+
.filter((column) => !!column);
|
|
5292
|
+
return columns.some((column) => this.normalizeLabel(this.stringValue(column['field'])) === normalizedField);
|
|
4998
5293
|
}
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
if (
|
|
5003
|
-
return
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
.
|
|
5008
|
-
.
|
|
5009
|
-
if (
|
|
5010
|
-
return
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5294
|
+
governedCategoricalSelectionBoundary(response, request) {
|
|
5295
|
+
if (!request || !this.requestHasGovernedCategoricalSelection(request))
|
|
5296
|
+
return null;
|
|
5297
|
+
if (response.type === 'error')
|
|
5298
|
+
return null;
|
|
5299
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5300
|
+
return null;
|
|
5301
|
+
const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
|
|
5302
|
+
|| this.componentEditOperations(response.componentEditPlan).length > 0
|
|
5303
|
+
|| !!this.toRecord(response.patch);
|
|
5304
|
+
if (hasExecutableEnvelope)
|
|
5305
|
+
return null;
|
|
5306
|
+
const field = this.governedCategoricalSelectionTargetField(request, response);
|
|
5307
|
+
return {
|
|
5308
|
+
type: 'info',
|
|
5309
|
+
sessionId: response.sessionId,
|
|
5310
|
+
observationId: response.observationId,
|
|
5311
|
+
message: field
|
|
5312
|
+
? `A opção governada foi selecionada, mas a resposta ainda não trouxe um plano aplicável para ${this.humanizeField(field)}.`
|
|
5313
|
+
: 'A opção governada foi selecionada, mas a resposta ainda não trouxe um plano aplicável para esta coluna.',
|
|
5314
|
+
warnings: [
|
|
5315
|
+
...(response.warnings ?? []),
|
|
5316
|
+
'governed-categorical-selection-kept-non-executable',
|
|
5317
|
+
],
|
|
5318
|
+
};
|
|
5319
|
+
}
|
|
5320
|
+
governedCategoricalDiscoveryBoundary(response, request) {
|
|
5321
|
+
if (!request || !this.requestHasGovernedCategoricalSelection(request))
|
|
5322
|
+
return null;
|
|
5323
|
+
if (!this.requestSelectsCategoricalFieldSemanticsDiscovery(request))
|
|
5324
|
+
return null;
|
|
5325
|
+
if (response.type === 'error')
|
|
5326
|
+
return null;
|
|
5327
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5328
|
+
return null;
|
|
5329
|
+
const hasExecutableEnvelope = this.responseMayContainExecutableEnvelope(response)
|
|
5330
|
+
|| this.componentEditOperations(response.componentEditPlan).length > 0
|
|
5331
|
+
|| !!this.toRecord(response.patch);
|
|
5332
|
+
if (hasExecutableEnvelope)
|
|
5333
|
+
return null;
|
|
5334
|
+
const field = this.governedCategoricalSelectionTargetField(request, response);
|
|
5335
|
+
if (!field)
|
|
5336
|
+
return null;
|
|
5337
|
+
const column = this.currentConfigColumns()
|
|
5338
|
+
.find((candidate) => this.stringValue(candidate['field']) === field);
|
|
5339
|
+
const label = this.stringValue(column?.['header']) || this.humanizeField(field);
|
|
5340
|
+
const values = this.currentCategoricalValuesForField(field);
|
|
5341
|
+
const valuesMessage = values.length
|
|
5342
|
+
? `Valores observados em ${label}: ${values.join(', ')}.`
|
|
5343
|
+
: `Ainda não recebi amostras de valores para ${label}.`;
|
|
5344
|
+
return {
|
|
5345
|
+
type: 'clarification',
|
|
5346
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5347
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5348
|
+
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.`,
|
|
5349
|
+
questions: [`Como quer materializar ${label} agora?`],
|
|
5350
|
+
optionPayloads: [
|
|
5351
|
+
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'),
|
|
5352
|
+
],
|
|
5353
|
+
warnings: [
|
|
5354
|
+
...(response.warnings ?? []),
|
|
5355
|
+
'governed-categorical-discovery-grounded-in-current-rows',
|
|
5356
|
+
'The guided categorical discovery option was continued with current table values and kept non-executable because no governed value-level semantic policy was returned.',
|
|
5357
|
+
],
|
|
5358
|
+
};
|
|
5359
|
+
}
|
|
5360
|
+
normalizeGovernedCategoricalSelectionExecutableResponse(response, request) {
|
|
5361
|
+
if (!request || !this.requestHasGovernedCategoricalSelection(request))
|
|
5362
|
+
return response;
|
|
5363
|
+
if (this.requestSelectsCategoricalFieldSemanticsDiscovery(request)) {
|
|
5364
|
+
return this.governedCategoricalDiscoveryBoundary({
|
|
5365
|
+
type: 'info',
|
|
5366
|
+
sessionId: response.sessionId,
|
|
5367
|
+
observationId: response.observationId ?? null,
|
|
5368
|
+
warnings: [
|
|
5369
|
+
...(response.warnings ?? []),
|
|
5370
|
+
'governed-categorical-discovery-executable-response-blocked',
|
|
5371
|
+
],
|
|
5372
|
+
}, request) ?? {
|
|
5373
|
+
type: 'info',
|
|
5374
|
+
sessionId: response.sessionId,
|
|
5375
|
+
observationId: response.observationId ?? null,
|
|
5376
|
+
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.',
|
|
5377
|
+
warnings: [
|
|
5378
|
+
...(response.warnings ?? []),
|
|
5379
|
+
'governed-categorical-discovery-executable-response-blocked',
|
|
5380
|
+
],
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
5383
|
+
const field = this.governedCategoricalSelectionTargetField(request, response);
|
|
5384
|
+
if (field && !this.responseTargetsSelectedRendererField(response, field)) {
|
|
5385
|
+
return {
|
|
5386
|
+
type: 'info',
|
|
5387
|
+
sessionId: response.sessionId,
|
|
5388
|
+
observationId: response.observationId,
|
|
5389
|
+
message: `A opção governada foi selecionada, mas o plano retornado não mira ${this.humanizeField(field)}.`,
|
|
5390
|
+
warnings: [
|
|
5391
|
+
...(response.warnings ?? []),
|
|
5392
|
+
'governed-categorical-selection-target-mismatch',
|
|
5393
|
+
],
|
|
5394
|
+
};
|
|
5395
|
+
}
|
|
5396
|
+
const label = this.stringValue(request.action?.displayPrompt)
|
|
5397
|
+
|| this.stringValue(request.action?.value)
|
|
5398
|
+
|| this.stringValue(request.prompt)
|
|
5399
|
+
|| 'opção governada';
|
|
5400
|
+
const target = field ? this.humanizeField(field) : 'a coluna categórica';
|
|
5401
|
+
const isNeutralFallback = this.normalizeLabel(label).includes('neutro');
|
|
5402
|
+
const booleanPlanBlocked = this.governedCategoricalBooleanPlanBoundary(response, request, field, isNeutralFallback);
|
|
5403
|
+
if (booleanPlanBlocked)
|
|
5404
|
+
return booleanPlanBlocked;
|
|
5405
|
+
if (!isNeutralFallback && !this.responseHasBooleanStatusNarrative(response))
|
|
5406
|
+
return response;
|
|
5407
|
+
const normalizedExplanation = isNeutralFallback
|
|
5408
|
+
? `Vou aplicar chips neutros em ${target}, sem política por valor.`
|
|
5409
|
+
: `Vou aplicar a opção governada em ${target}.`;
|
|
5410
|
+
return {
|
|
5411
|
+
...response,
|
|
5412
|
+
explanation: normalizedExplanation,
|
|
5413
|
+
message: undefined,
|
|
5414
|
+
componentEditPlan: undefined,
|
|
5415
|
+
warnings: [
|
|
5416
|
+
...(response.warnings ?? []),
|
|
5417
|
+
'governed-categorical-selection-review-text-normalized',
|
|
5418
|
+
isNeutralFallback
|
|
5419
|
+
? '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.'
|
|
5420
|
+
: 'The executable plan was preserved, but a stale boolean Status/Ativo/Inativo narrative was replaced because the selected guided option targets categorical presentation semantics.',
|
|
5421
|
+
],
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5424
|
+
governedCategoricalBooleanPlanBoundary(response, request, field, isNeutralFallback) {
|
|
5425
|
+
if (!field)
|
|
5426
|
+
return null;
|
|
5427
|
+
const column = this.currentConfigColumns()
|
|
5428
|
+
.find((candidate) => this.stringValue(candidate['field']) === field);
|
|
5429
|
+
if (!column || this.booleanColumnCandidate(column))
|
|
5430
|
+
return null;
|
|
5431
|
+
const hasBooleanOperations = this.responseHasBooleanStatusOperations(response);
|
|
5432
|
+
if (!hasBooleanOperations && (isNeutralFallback || !this.responseHasBooleanStatusNarrative(response))) {
|
|
5433
|
+
return null;
|
|
5434
|
+
}
|
|
5435
|
+
const label = this.stringValue(column['header']) || this.humanizeField(field);
|
|
5436
|
+
return {
|
|
5437
|
+
type: 'clarification',
|
|
5438
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5439
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5440
|
+
message: `A coluna ${label} não é booleana no estado atual. Mantive a opção governada sem aplicação para não materializar Ativo/Inativo.`,
|
|
5441
|
+
questions: [`Como quer materializar ${label} agora?`],
|
|
5442
|
+
optionPayloads: [
|
|
5443
|
+
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'),
|
|
5444
|
+
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'),
|
|
5445
|
+
],
|
|
5446
|
+
warnings: [
|
|
5447
|
+
...(response.warnings ?? []),
|
|
5448
|
+
'governed-categorical-boolean-plan-blocked',
|
|
5449
|
+
'The selected governed categorical option returned boolean Status/Ativo/Inativo semantics for a non-boolean field; the executable plan was blocked.',
|
|
5450
|
+
],
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
requestHasGovernedCategoricalSelection(request) {
|
|
5454
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5455
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5456
|
+
const actionText = this.normalizeLabel([
|
|
5457
|
+
request.action?.displayPrompt,
|
|
5458
|
+
request.action?.value,
|
|
5459
|
+
request.prompt,
|
|
5460
|
+
].map((value) => this.stringValue(value)).join(' '));
|
|
5461
|
+
const actionRawText = [
|
|
5462
|
+
request.action?.displayPrompt,
|
|
5463
|
+
request.action?.value,
|
|
5464
|
+
request.prompt,
|
|
5465
|
+
].map((value) => this.stringValue(value)).join(' ');
|
|
5466
|
+
return this.isGovernedCategoricalSemanticsChoiceText(actionText)
|
|
5467
|
+
|| actionRawText.includes('apply_neutral_categorical_renderer')
|
|
5468
|
+
|| actionRawText.includes('author_categorical_field_semantics')
|
|
5469
|
+
|| !!this.toRecord(actionHints?.['categoricalFieldSemantics'])
|
|
5470
|
+
|| !!this.toRecord(requestHints?.['categoricalFieldSemantics'])
|
|
5471
|
+
|| this.governanceStatusIsNeutralCategoricalFallback(this.toRecord(actionHints?.['badge'])?.['governanceStatus'])
|
|
5472
|
+
|| this.governanceStatusIsNeutralCategoricalFallback(this.toRecord(requestHints?.['badge'])?.['governanceStatus']);
|
|
5473
|
+
}
|
|
5474
|
+
requestSelectsCategoricalFieldSemanticsDiscovery(request) {
|
|
5475
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5476
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5477
|
+
const actionSelection = this.toRecord(this.toRecord(actionHints?.['optionSelected'])?.['selection']);
|
|
5478
|
+
const requestSelection = this.toRecord(this.toRecord(requestHints?.['optionSelected'])?.['selection']);
|
|
5479
|
+
const text = [
|
|
5480
|
+
request.action?.displayPrompt,
|
|
5481
|
+
request.action?.value,
|
|
5482
|
+
request.prompt,
|
|
5483
|
+
actionSelection?.['value'],
|
|
5484
|
+
requestSelection?.['value'],
|
|
5485
|
+
].map((value) => this.stringValue(value)).join(' ');
|
|
5486
|
+
return text.includes('author_categorical_field_semantics');
|
|
5487
|
+
}
|
|
5488
|
+
governanceStatusIsNeutralCategoricalFallback(value) {
|
|
5489
|
+
const raw = this.stringValue(value);
|
|
5490
|
+
const normalized = this.normalizeLabel(raw);
|
|
5491
|
+
return normalized.includes('ungoverned neutral fallback')
|
|
5492
|
+
|| raw.includes('ungoverned_neutral_fallback');
|
|
5493
|
+
}
|
|
5494
|
+
governedCategoricalSelectionTargetField(request, response) {
|
|
5495
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
5496
|
+
const requestHints = this.toRecord(request.contextHints);
|
|
5497
|
+
const actionSemantics = this.toRecord(actionHints?.['categoricalFieldSemantics']);
|
|
5498
|
+
const requestSemantics = this.toRecord(requestHints?.['categoricalFieldSemantics']);
|
|
5499
|
+
return this.stringValue(actionSemantics?.['field'])
|
|
5500
|
+
|| this.stringValue(requestSemantics?.['field'])
|
|
5501
|
+
|| this.visualPresentationTargetField(response, request);
|
|
5502
|
+
}
|
|
5503
|
+
responseHasBooleanStatusNarrative(response) {
|
|
5504
|
+
const text = this.normalizeLabel([
|
|
5505
|
+
response.message,
|
|
5506
|
+
response.explanation,
|
|
5507
|
+
].map((value) => this.stringValue(value)).join(' '));
|
|
5508
|
+
return this.normalizedTextIncludesAny(text, [
|
|
5509
|
+
'ativo para verdadeiro',
|
|
5510
|
+
'inativo para falso',
|
|
5511
|
+
'status como badge ativo',
|
|
5512
|
+
'status como badge inativo',
|
|
5513
|
+
]);
|
|
5514
|
+
}
|
|
5515
|
+
incompatibleBooleanStatusPresentationBoundary(response, request) {
|
|
5516
|
+
if (!request)
|
|
5517
|
+
return null;
|
|
5518
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
5519
|
+
return null;
|
|
5520
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5521
|
+
return null;
|
|
5522
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5523
|
+
return null;
|
|
5524
|
+
if (!this.promptRequestsVisualOrStructuralEdit(this.normalizeLabel(this.componentPresentationPromptForTurn(request)))) {
|
|
5525
|
+
return null;
|
|
5526
|
+
}
|
|
5527
|
+
const columns = this.currentConfigColumns();
|
|
5528
|
+
if (!columns.length)
|
|
5529
|
+
return null;
|
|
5530
|
+
const targetField = this.booleanStatusPresentationTargetField(response, request, columns);
|
|
5531
|
+
if (!targetField)
|
|
5532
|
+
return null;
|
|
5533
|
+
const targetColumn = columns.find((column) => this.stringValue(column['field']) === targetField);
|
|
5534
|
+
if (!targetColumn || this.booleanColumnCandidate(targetColumn))
|
|
5535
|
+
return null;
|
|
5536
|
+
if (!this.responseHasBooleanStatusNarrative(response) && !this.responseHasBooleanStatusOperations(response)) {
|
|
5537
|
+
return null;
|
|
5538
|
+
}
|
|
5539
|
+
const label = this.stringValue(targetColumn['header']) || this.humanizeField(targetField);
|
|
5540
|
+
return {
|
|
5541
|
+
type: 'clarification',
|
|
5542
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5543
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5544
|
+
message: `A coluna ${label} não é booleana no estado atual. Posso formatá-la como categoria, mas não vou assumir Ativo/Inativo.`,
|
|
5545
|
+
questions: [`Qual apresentação categórica você quer aplicar em ${label}?`],
|
|
5546
|
+
optionPayloads: [
|
|
5547
|
+
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'),
|
|
5548
|
+
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'),
|
|
5549
|
+
],
|
|
5550
|
+
warnings: [
|
|
5551
|
+
...(response.warnings ?? []),
|
|
5552
|
+
'boolean-status-presentation-blocked-for-categorical-column',
|
|
5553
|
+
'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.',
|
|
5554
|
+
],
|
|
5555
|
+
};
|
|
5556
|
+
}
|
|
5557
|
+
currentConfigColumns() {
|
|
5558
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
5559
|
+
return Array.isArray(currentConfig?.['columns'])
|
|
5560
|
+
? currentConfig['columns']
|
|
5561
|
+
.map((column) => this.toRecord(column))
|
|
5562
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
5563
|
+
: [];
|
|
5564
|
+
}
|
|
5565
|
+
currentConfigRows() {
|
|
5566
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
5567
|
+
for (const source of [currentConfig]) {
|
|
5568
|
+
for (const key of ['data', 'rows', 'items']) {
|
|
5569
|
+
const rows = this.toArray(source?.[key])
|
|
5570
|
+
.map((row) => this.toRecord(row))
|
|
5571
|
+
.filter((row) => !!row);
|
|
5572
|
+
if (rows.length)
|
|
5573
|
+
return rows;
|
|
5574
|
+
}
|
|
5575
|
+
const dataSourceRows = this.toArray(this.toRecord(source?.['dataSource'])?.['data'])
|
|
5576
|
+
.map((row) => this.toRecord(row))
|
|
5577
|
+
.filter((row) => !!row);
|
|
5578
|
+
if (dataSourceRows.length)
|
|
5579
|
+
return dataSourceRows;
|
|
5580
|
+
}
|
|
5581
|
+
return [];
|
|
5582
|
+
}
|
|
5583
|
+
currentCategoricalValuesForField(field) {
|
|
5584
|
+
const scopedSamples = this.adapterFieldValueSamples(field);
|
|
5585
|
+
if (scopedSamples.length)
|
|
5586
|
+
return scopedSamples;
|
|
5587
|
+
const values = this.currentConfigRows()
|
|
5588
|
+
.map((row) => this.readableCellValue(row[field]))
|
|
5589
|
+
.filter((value) => !!value);
|
|
5590
|
+
return Array.from(new Set(values)).slice(0, 12);
|
|
5591
|
+
}
|
|
5592
|
+
adapterFieldValueSamples(field) {
|
|
5593
|
+
const adapter = this.adapter;
|
|
5594
|
+
const result = adapter.getFieldValueSamples?.(field, 12);
|
|
5595
|
+
const rawValues = Array.isArray(result)
|
|
5596
|
+
? result
|
|
5597
|
+
: this.toArray(this.toRecord(result)?.['values']);
|
|
5598
|
+
const values = rawValues
|
|
5599
|
+
.map((value) => this.readableCellValue(value))
|
|
5600
|
+
.filter((value) => !!value);
|
|
5601
|
+
return Array.from(new Set(values)).slice(0, 12);
|
|
5602
|
+
}
|
|
5603
|
+
booleanStatusPresentationTargetField(response, request, columns) {
|
|
5604
|
+
const operationFields = this.componentEditOperations(response.componentEditPlan)
|
|
5605
|
+
.map((operation) => this.operationTargetField(operation))
|
|
5606
|
+
.filter((field) => !!field);
|
|
5607
|
+
const patch = this.toRecord(response.patch);
|
|
5608
|
+
const patchFields = this.toArray(patch?.['columns'])
|
|
5609
|
+
.map((column) => this.stringValue(this.toRecord(column)?.['field']))
|
|
5610
|
+
.filter((field) => !!field);
|
|
5611
|
+
const fields = [...operationFields, ...patchFields];
|
|
5612
|
+
for (const field of fields) {
|
|
5613
|
+
if (columns.some((column) => this.stringValue(column['field']) === field))
|
|
5614
|
+
return field;
|
|
5615
|
+
}
|
|
5616
|
+
return this.explicitVisualPresentationTargetField(response, request, columns);
|
|
5617
|
+
}
|
|
5618
|
+
explicitVisualPresentationTargetField(response, request, columns) {
|
|
5619
|
+
const text = this.normalizeLabel([
|
|
5620
|
+
request.prompt,
|
|
5621
|
+
request.pendingClarification?.sourcePrompt,
|
|
5622
|
+
request.pendingClarification?.assistantMessage,
|
|
5623
|
+
response.message,
|
|
5624
|
+
response.explanation,
|
|
5625
|
+
...(response.questions ?? []),
|
|
5626
|
+
].filter(Boolean).join(' '));
|
|
5627
|
+
const matched = columns
|
|
5628
|
+
.map((column) => {
|
|
5629
|
+
const field = this.stringValue(column['field']);
|
|
5630
|
+
if (!field)
|
|
5631
|
+
return null;
|
|
5632
|
+
const header = this.stringValue(column['header']) || field;
|
|
5633
|
+
const score = Math.max(this.visualPresentationTargetFieldScore(text, field), this.visualPresentationTargetFieldScore(text, header));
|
|
5634
|
+
return score > 0 ? { field, score } : null;
|
|
5635
|
+
})
|
|
5636
|
+
.filter((entry) => !!entry)
|
|
5637
|
+
.sort((a, b) => b.score - a.score)[0];
|
|
5638
|
+
return matched?.field ?? null;
|
|
5639
|
+
}
|
|
5640
|
+
responseHasBooleanStatusOperations(response) {
|
|
5641
|
+
const hasBooleanOperation = this.componentEditOperations(response.componentEditPlan).some((operation) => {
|
|
5642
|
+
const operationId = this.stringValue(operation['operationId']);
|
|
5643
|
+
if (operationId !== 'column.conditionalRenderer.add'
|
|
5644
|
+
&& operationId !== 'column.conditionalStyle.add'
|
|
5645
|
+
&& operationId !== 'column.renderer.set') {
|
|
5646
|
+
return false;
|
|
5647
|
+
}
|
|
5648
|
+
const input = this.toRecord(operation['input']);
|
|
5649
|
+
if (!input)
|
|
5650
|
+
return false;
|
|
5651
|
+
return this.booleanConditionValue(input['condition']) !== null
|
|
5652
|
+
|| this.safeJsonText(input).includes('"Ativo"')
|
|
5653
|
+
|| this.safeJsonText(input).includes('"Inativo"');
|
|
5654
|
+
});
|
|
5655
|
+
if (hasBooleanOperation)
|
|
5656
|
+
return true;
|
|
5657
|
+
const patchText = this.safeJsonText(response.patch);
|
|
5658
|
+
return patchText.includes('"Ativo"')
|
|
5659
|
+
|| patchText.includes('"Inativo"')
|
|
5660
|
+
|| patchText.includes('"true"')
|
|
5661
|
+
|| patchText.includes('"false"');
|
|
5662
|
+
}
|
|
5663
|
+
categoricalPresentationOptionPayload(field, label, value, optionLabel, description, icon, tone) {
|
|
5664
|
+
return {
|
|
5665
|
+
label: optionLabel,
|
|
5666
|
+
value,
|
|
5667
|
+
description,
|
|
5668
|
+
contextHints: {
|
|
5669
|
+
categoricalFieldSemantics: {
|
|
5670
|
+
field,
|
|
5671
|
+
label,
|
|
5672
|
+
},
|
|
5673
|
+
optionSelected: {
|
|
5674
|
+
targetField: field,
|
|
5675
|
+
selection: {
|
|
5676
|
+
mode: value === 'author_categorical_field_semantics'
|
|
5677
|
+
? 'categorical_semantics'
|
|
5678
|
+
: 'renderer',
|
|
5679
|
+
field,
|
|
5680
|
+
value,
|
|
5681
|
+
},
|
|
5682
|
+
},
|
|
5683
|
+
},
|
|
5684
|
+
presentation: {
|
|
5685
|
+
kind: 'guided-option',
|
|
5686
|
+
icon,
|
|
5687
|
+
tone,
|
|
5688
|
+
ctaLabel: 'Aplicar opção',
|
|
5689
|
+
},
|
|
5690
|
+
};
|
|
5691
|
+
}
|
|
5692
|
+
visualPresentationTargetMismatchBoundary(response, request) {
|
|
5693
|
+
if (!request)
|
|
5694
|
+
return null;
|
|
5695
|
+
if (this.requestIsCanonicalRendererSelection(request))
|
|
5696
|
+
return null;
|
|
5697
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
5698
|
+
return null;
|
|
5699
|
+
if (this.responseCarriesClarificationChoices(response))
|
|
5700
|
+
return null;
|
|
5701
|
+
if (!this.promptRequestsVisualOrStructuralEdit(this.normalizeLabel(this.componentPresentationPromptForTurn(request)))) {
|
|
5702
|
+
return null;
|
|
5703
|
+
}
|
|
5704
|
+
const columns = this.currentConfigColumns();
|
|
5705
|
+
if (!columns.length)
|
|
5706
|
+
return null;
|
|
5707
|
+
const requestedField = this.explicitVisualPresentationTargetFieldFromRequest(request, columns);
|
|
5708
|
+
if (!requestedField)
|
|
5709
|
+
return null;
|
|
5710
|
+
const responseFields = this.responsePresentationTargetFields(response, columns);
|
|
5711
|
+
if (!responseFields.length)
|
|
5712
|
+
return null;
|
|
5713
|
+
if (responseFields.some((field) => field === requestedField))
|
|
5714
|
+
return null;
|
|
5715
|
+
const requestedColumn = columns.find((column) => this.stringValue(column['field']) === requestedField);
|
|
5716
|
+
const requestedLabel = this.stringValue(requestedColumn?.['header']) || this.humanizeField(requestedField);
|
|
5717
|
+
return {
|
|
5718
|
+
type: 'clarification',
|
|
5719
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
5720
|
+
observationId: response.observationId ?? request.observationId ?? null,
|
|
5721
|
+
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}.`,
|
|
5722
|
+
questions: [`Qual apresentação categórica você quer aplicar em ${requestedLabel}?`],
|
|
5723
|
+
optionPayloads: [
|
|
5724
|
+
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'),
|
|
5725
|
+
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'),
|
|
5726
|
+
],
|
|
5727
|
+
warnings: [
|
|
5728
|
+
...(response.warnings ?? []),
|
|
5729
|
+
'visual-presentation-target-mismatch-blocked',
|
|
5730
|
+
'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.',
|
|
5731
|
+
],
|
|
5732
|
+
};
|
|
5733
|
+
}
|
|
5734
|
+
explicitVisualPresentationTargetFieldFromRequest(request, columns) {
|
|
5735
|
+
const text = this.normalizeLabel([
|
|
5736
|
+
request.prompt,
|
|
5737
|
+
request.action?.displayPrompt,
|
|
5738
|
+
request.pendingClarification?.sourcePrompt,
|
|
5739
|
+
].filter(Boolean).join(' '));
|
|
5740
|
+
return this.explicitColumnMentionFromText(text, columns);
|
|
5741
|
+
}
|
|
5742
|
+
explicitColumnMentionFromText(normalizedText, columns) {
|
|
5743
|
+
const matched = columns
|
|
5744
|
+
.map((column) => {
|
|
5745
|
+
const field = this.stringValue(column['field']);
|
|
5746
|
+
if (!field)
|
|
5747
|
+
return null;
|
|
5748
|
+
const header = this.stringValue(column['header']) || field;
|
|
5749
|
+
const score = Math.max(this.visualPresentationTargetFieldScore(normalizedText, field), this.visualPresentationTargetFieldScore(normalizedText, header));
|
|
5750
|
+
return score > 0 ? { field, score } : null;
|
|
5751
|
+
})
|
|
5752
|
+
.filter((entry) => !!entry)
|
|
5753
|
+
.sort((a, b) => b.score - a.score);
|
|
5754
|
+
const mentionedFields = [...new Set(matched.map((entry) => entry.field))];
|
|
5755
|
+
return mentionedFields.length === 1 ? mentionedFields[0] : null;
|
|
5756
|
+
}
|
|
5757
|
+
responsePresentationTargetFields(response, columns) {
|
|
5758
|
+
const operationFields = this.componentEditOperations(response.componentEditPlan)
|
|
5759
|
+
.filter((operation) => this.operationIsColumnPresentationEdit(operation))
|
|
5760
|
+
.map((operation) => this.operationTargetField(operation));
|
|
5761
|
+
const patch = this.toRecord(response.patch);
|
|
5762
|
+
const patchFields = this.toArray(patch?.['columns'])
|
|
5763
|
+
.map((column) => this.toRecord(column))
|
|
5764
|
+
.filter((column) => !!column)
|
|
5765
|
+
.filter((column) => this.patchColumnCarriesPresentationEdit(column))
|
|
5766
|
+
.map((column) => this.stringValue(column['field']));
|
|
5767
|
+
const knownFields = new Set(columns.map((column) => this.stringValue(column['field'])).filter((field) => !!field));
|
|
5768
|
+
return [...new Set([...operationFields, ...patchFields].filter((field) => knownFields.has(field)))];
|
|
5769
|
+
}
|
|
5770
|
+
operationIsColumnPresentationEdit(operation) {
|
|
5771
|
+
const operationId = this.stringValue(operation['operationId']);
|
|
5772
|
+
return [
|
|
5773
|
+
'column.renderer.set',
|
|
5774
|
+
'column.conditionalRenderer.add',
|
|
5775
|
+
'column.conditionalStyle.add',
|
|
5776
|
+
'column.format.set',
|
|
5777
|
+
'column.alignment.set',
|
|
5778
|
+
].includes(operationId);
|
|
5779
|
+
}
|
|
5780
|
+
patchColumnCarriesPresentationEdit(column) {
|
|
5781
|
+
return !!this.toRecord(column['renderer'])
|
|
5782
|
+
|| Array.isArray(column['conditionalRenderers'])
|
|
5783
|
+
|| Array.isArray(column['conditionalStyles'])
|
|
5784
|
+
|| !!this.stringValue(column['format'])
|
|
5785
|
+
|| !!this.stringValue(column['align'])
|
|
5786
|
+
|| !!this.stringValue(column['alignment']);
|
|
5787
|
+
}
|
|
5788
|
+
normalizeNumericBandConditionalStylePlan(response, request) {
|
|
5789
|
+
if (!request)
|
|
5790
|
+
return response;
|
|
5791
|
+
if ((response.warnings ?? []).includes('numeric-band-conditional-style-normalized'))
|
|
5792
|
+
return response;
|
|
5793
|
+
const currentConfig = this.adapter.getCurrentConfig?.();
|
|
5794
|
+
const columns = Array.isArray(currentConfig?.['columns'])
|
|
5795
|
+
? currentConfig['columns']
|
|
5796
|
+
.map((column) => this.toRecord(column))
|
|
5797
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
5798
|
+
: [];
|
|
5799
|
+
if (!columns.length)
|
|
5800
|
+
return response;
|
|
5801
|
+
const operations = this.numericBandConditionalStyleOperationsFromPrompt(this.componentPresentationPromptForTurn(request), [
|
|
5802
|
+
response.message,
|
|
5803
|
+
response.explanation,
|
|
5804
|
+
...(response.questions ?? []),
|
|
5805
|
+
].join(' '), columns);
|
|
5806
|
+
if (!operations.length)
|
|
5807
|
+
return response;
|
|
5808
|
+
const fields = new Set(operations
|
|
5809
|
+
.map((operation) => this.operationTargetField(operation))
|
|
5810
|
+
.filter((field) => !!field));
|
|
5811
|
+
const originalPlan = this.toRecord(response.componentEditPlan);
|
|
5812
|
+
const originalOperations = this.componentEditOperations(originalPlan)
|
|
5813
|
+
.filter((operation) => {
|
|
5814
|
+
const field = this.operationTargetField(operation);
|
|
5815
|
+
return !(field && fields.has(field) && this.stringValue(operation['operationId']) === 'column.conditionalStyle.add');
|
|
5816
|
+
});
|
|
5817
|
+
return {
|
|
5818
|
+
...response,
|
|
5819
|
+
...(response.type === 'clarification' || response.type === 'info' ? { type: 'patch' } : {}),
|
|
5820
|
+
componentEditPlan: {
|
|
5821
|
+
...(originalPlan ?? {}),
|
|
5822
|
+
kind: this.stringValue(originalPlan?.['kind']) || 'praxis.table.component-edit-plan',
|
|
5823
|
+
version: this.stringValue(originalPlan?.['version']) || '1.0',
|
|
5824
|
+
componentId: this.stringValue(originalPlan?.['componentId'])
|
|
5825
|
+
|| this.adapter.componentId
|
|
5826
|
+
|| request.componentId
|
|
5827
|
+
|| 'praxis-table',
|
|
5828
|
+
operations: [...originalOperations, ...operations],
|
|
5829
|
+
},
|
|
5830
|
+
explanation: response.explanation || 'Vou aplicar formatacao condicional em faixas visuais na coluna numerica indicada.',
|
|
5831
|
+
warnings: [
|
|
5832
|
+
...(response.warnings ?? []),
|
|
5833
|
+
'numeric-band-conditional-style-normalized',
|
|
5834
|
+
'Residual continuity guard grounded numeric band thresholds from the canonical dataProfile when available, avoiding accidental thresholds from prose or visual token numbers.',
|
|
5835
|
+
],
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
componentEditPlanHasAnyOperation(componentEditPlan, operationIds) {
|
|
5839
|
+
if (!operationIds.length)
|
|
5840
|
+
return false;
|
|
5841
|
+
const expected = new Set(operationIds);
|
|
5842
|
+
return this.componentEditOperations(componentEditPlan)
|
|
5843
|
+
.some((operation) => expected.has(this.componentEditPlanOperationIdentity(operation)));
|
|
5844
|
+
}
|
|
5845
|
+
responseHasComputedColumnAuthoringDecision(response) {
|
|
5846
|
+
if (this.componentEditPlanHasAnyOperation(response.componentEditPlan, ['column.computed.add', 'column.computed.set'])) {
|
|
5847
|
+
return true;
|
|
5848
|
+
}
|
|
5849
|
+
const patch = this.toRecord(response.patch);
|
|
5850
|
+
const columns = Array.isArray(patch?.['columns']) ? patch['columns'] : [];
|
|
5851
|
+
return columns
|
|
5852
|
+
.map((column) => this.toRecord(column))
|
|
5853
|
+
.some((column) => !!column?.['computed']);
|
|
5854
|
+
}
|
|
5855
|
+
normalizeComputedColumnAuxiliaryMaterializations(response) {
|
|
5856
|
+
const plan = this.toRecord(response.componentEditPlan);
|
|
5857
|
+
const operations = this.componentEditOperations(plan);
|
|
5858
|
+
if (!plan || operations.length < 2)
|
|
5859
|
+
return response;
|
|
5860
|
+
const computedFields = new Set(operations
|
|
5861
|
+
.filter((operation) => this.componentEditPlanOperationIdentity(operation) === 'column.computed.add'
|
|
5862
|
+
|| this.componentEditPlanOperationIdentity(operation) === 'column.computed.set')
|
|
5863
|
+
.map((operation) => this.operationTargetField(operation))
|
|
5864
|
+
.filter((field) => !!field));
|
|
5865
|
+
if (!computedFields.size) {
|
|
5866
|
+
return response;
|
|
5867
|
+
}
|
|
5868
|
+
const auxiliaryAddedFields = new Set();
|
|
5869
|
+
for (const operation of operations) {
|
|
5870
|
+
const operationId = this.componentEditPlanOperationIdentity(operation);
|
|
5871
|
+
const field = this.operationTargetField(operation);
|
|
5872
|
+
if (!field)
|
|
5873
|
+
continue;
|
|
5874
|
+
if (operationId === 'column.add' && !computedFields.has(field)) {
|
|
5875
|
+
auxiliaryAddedFields.add(field);
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
const removableFields = auxiliaryAddedFields;
|
|
5879
|
+
if (!removableFields.size)
|
|
5880
|
+
return response;
|
|
5881
|
+
const normalizedOperations = operations.filter((operation) => {
|
|
5882
|
+
const operationId = this.componentEditPlanOperationIdentity(operation);
|
|
5027
5883
|
const field = this.operationTargetField(operation);
|
|
5028
5884
|
return !(field && removableFields.has(field) && (operationId === 'column.add'
|
|
5029
5885
|
|| operationId === 'column.renderer.set'));
|
|
@@ -5191,18 +6047,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5191
6047
|
.sort((left, right) => left - right)[0] ?? -1;
|
|
5192
6048
|
}
|
|
5193
6049
|
columnPresentationAliases(column) {
|
|
5194
|
-
|
|
5195
|
-
const field = this.normalizeLabel(this.stringValue(column['field']));
|
|
5196
|
-
const header = this.normalizeLabel(this.stringValue(column['header']));
|
|
5197
|
-
if (field.startsWith('nome') || header.includes('nome'))
|
|
5198
|
-
aliases.add('nome');
|
|
5199
|
-
if (field.includes('funcionario') || header.includes('funcionario'))
|
|
5200
|
-
aliases.add('nome');
|
|
5201
|
-
if (field.includes('salario') || header.includes('salario'))
|
|
5202
|
-
aliases.add('salario');
|
|
5203
|
-
if (field.includes('salario') || header.includes('salario'))
|
|
5204
|
-
aliases.add('salário');
|
|
5205
|
-
return [...aliases].sort((left, right) => right.length - left.length);
|
|
6050
|
+
return this.columnMentionAliases(column);
|
|
5206
6051
|
}
|
|
5207
6052
|
mergeCompoundColumnPresentationOperations(originalOperations, intendedOperations) {
|
|
5208
6053
|
const protectedFields = new Map();
|
|
@@ -5302,15 +6147,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5302
6147
|
return null;
|
|
5303
6148
|
}
|
|
5304
6149
|
const column = columns.find((candidate) => {
|
|
5305
|
-
const text = this.normalizeLabel([
|
|
5306
|
-
candidate['field'],
|
|
5307
|
-
candidate['header'],
|
|
5308
|
-
candidate['type'],
|
|
5309
|
-
candidate['dataType'],
|
|
5310
|
-
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5311
|
-
].map((value) => this.stringValue(value)).join(' '));
|
|
5312
6150
|
return (this.textMentionsColumn(prompt, candidate) || this.textMentionsColumn(responseText, candidate))
|
|
5313
|
-
&& this.
|
|
6151
|
+
&& this.booleanColumnCandidate(candidate);
|
|
5314
6152
|
});
|
|
5315
6153
|
return this.booleanSimNaoOperationsForColumn(column);
|
|
5316
6154
|
}
|
|
@@ -5345,7 +6183,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5345
6183
|
candidate['dataType'],
|
|
5346
6184
|
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5347
6185
|
].map((value) => this.stringValue(value)).join(' '));
|
|
5348
|
-
return this.
|
|
6186
|
+
return this.booleanColumnCandidate(candidate)
|
|
6187
|
+
&& this.normalizedTextIncludesAny(text, ['status', 'ativo', 'boolean', 'bool']);
|
|
5349
6188
|
});
|
|
5350
6189
|
const field = this.stringValue(column?.['field']);
|
|
5351
6190
|
if (!field)
|
|
@@ -5484,28 +6323,20 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5484
6323
|
]);
|
|
5485
6324
|
}
|
|
5486
6325
|
numericColumnCandidate(column) {
|
|
5487
|
-
const
|
|
5488
|
-
column['field'],
|
|
5489
|
-
column['header'],
|
|
6326
|
+
const typeText = this.normalizeLabel([
|
|
5490
6327
|
column['type'],
|
|
5491
6328
|
column['dataType'],
|
|
5492
6329
|
column['format'],
|
|
5493
|
-
this.humanizeField(this.stringValue(column['field'])),
|
|
5494
6330
|
].map((value) => this.stringValue(value)).join(' '));
|
|
5495
|
-
return this.normalizedTextIncludesAny(
|
|
6331
|
+
return this.normalizedTextIncludesAny(typeText, [
|
|
5496
6332
|
'number',
|
|
5497
6333
|
'numeric',
|
|
5498
6334
|
'decimal',
|
|
5499
6335
|
'integer',
|
|
5500
6336
|
'currency',
|
|
5501
6337
|
'moeda',
|
|
5502
|
-
'valor',
|
|
5503
|
-
'preco',
|
|
5504
|
-
'preço',
|
|
5505
|
-
'salario',
|
|
5506
|
-
'salário',
|
|
5507
6338
|
'brl',
|
|
5508
|
-
]);
|
|
6339
|
+
]) || this.numericBandThresholdsFromDataProfile(this.stringValue(column['field'])) !== null;
|
|
5509
6340
|
}
|
|
5510
6341
|
numericBandThresholdsForField(field, text) {
|
|
5511
6342
|
return this.numericBandThresholdsFromDataProfile(field)
|
|
@@ -5609,14 +6440,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5609
6440
|
return null;
|
|
5610
6441
|
}
|
|
5611
6442
|
const booleanColumns = columns.filter((candidate) => {
|
|
5612
|
-
|
|
5613
|
-
candidate['field'],
|
|
5614
|
-
candidate['header'],
|
|
5615
|
-
candidate['type'],
|
|
5616
|
-
candidate['dataType'],
|
|
5617
|
-
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5618
|
-
].map((value) => this.stringValue(value)).join(' '));
|
|
5619
|
-
return this.normalizedTextIncludesAny(text, ['ativo', 'status', 'boolean', 'bool'])
|
|
6443
|
+
return this.booleanColumnCandidate(candidate)
|
|
5620
6444
|
&& (this.textMentionsColumn(prompt, candidate) || this.textMentionsColumn(responseText, candidate));
|
|
5621
6445
|
});
|
|
5622
6446
|
return this.booleanSimNaoOperationsForColumn(booleanColumns[0]);
|
|
@@ -5628,16 +6452,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5628
6452
|
|| this.normalizedTextIncludesAny(normalizedResponse, ['sim nao', 'sim não', 'sim ou nao', 'sim ou não']);
|
|
5629
6453
|
if (!simNaoRequested)
|
|
5630
6454
|
return null;
|
|
5631
|
-
const booleanColumns = columns.filter((candidate) =>
|
|
5632
|
-
const text = this.normalizeLabel([
|
|
5633
|
-
candidate['field'],
|
|
5634
|
-
candidate['header'],
|
|
5635
|
-
candidate['type'],
|
|
5636
|
-
candidate['dataType'],
|
|
5637
|
-
this.humanizeField(this.stringValue(candidate['field'])),
|
|
5638
|
-
].map((value) => this.stringValue(value)).join(' '));
|
|
5639
|
-
return this.normalizedTextIncludesAny(text, ['ativo', 'status', 'boolean', 'bool']);
|
|
5640
|
-
});
|
|
6455
|
+
const booleanColumns = columns.filter((candidate) => this.booleanColumnCandidate(candidate));
|
|
5641
6456
|
return booleanColumns.length === 1
|
|
5642
6457
|
? this.booleanSimNaoOperationsForColumn(booleanColumns[0])
|
|
5643
6458
|
: null;
|
|
@@ -5776,18 +6591,21 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5776
6591
|
}
|
|
5777
6592
|
inferSpecificColumnFormatFromPrompt(prompt, column, availableFormats) {
|
|
5778
6593
|
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
5779
|
-
const
|
|
5780
|
-
column['field'],
|
|
5781
|
-
column['header'],
|
|
6594
|
+
const columnTypeText = this.normalizeLabel([
|
|
5782
6595
|
column['type'],
|
|
5783
6596
|
column['dataType'],
|
|
5784
|
-
|
|
6597
|
+
column['format'],
|
|
5785
6598
|
].map((value) => this.stringValue(value)).filter(Boolean).join(' '));
|
|
5786
|
-
const dateLikeColumn = this.normalizedTextIncludesAny(
|
|
6599
|
+
const dateLikeColumn = this.normalizedTextIncludesAny(columnTypeText, [
|
|
5787
6600
|
'data',
|
|
5788
6601
|
'date',
|
|
5789
|
-
'
|
|
5790
|
-
'
|
|
6602
|
+
'time',
|
|
6603
|
+
'dd/mm/yyyy',
|
|
6604
|
+
'yyyy-mm-dd',
|
|
6605
|
+
'shortdate',
|
|
6606
|
+
'mediumdate',
|
|
6607
|
+
'longdate',
|
|
6608
|
+
'fulldate',
|
|
5791
6609
|
]);
|
|
5792
6610
|
const brDateRequested = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5793
6611
|
'dia mes ano',
|
|
@@ -5801,15 +6619,7 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
5801
6619
|
if (dateLikeColumn && brDateRequested && availableFormats.has('dd/MM/yyyy')) {
|
|
5802
6620
|
return 'dd/MM/yyyy';
|
|
5803
6621
|
}
|
|
5804
|
-
const currencyLikeColumn = this.
|
|
5805
|
-
'salario',
|
|
5806
|
-
'salário',
|
|
5807
|
-
'salary',
|
|
5808
|
-
'remuneracao',
|
|
5809
|
-
'remuneração',
|
|
5810
|
-
'currency',
|
|
5811
|
-
'moeda',
|
|
5812
|
-
]);
|
|
6622
|
+
const currencyLikeColumn = this.numericColumnCandidate(column);
|
|
5813
6623
|
const brlRequested = this.normalizedTextIncludesAny(normalizedPrompt, [
|
|
5814
6624
|
'real',
|
|
5815
6625
|
'reais',
|
|
@@ -6258,8 +7068,6 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6258
7068
|
return null;
|
|
6259
7069
|
if (!response.patch || this.tableFilterApplyOperations(response.patch).length === 0)
|
|
6260
7070
|
return null;
|
|
6261
|
-
if (this.responseDeclaresTableMutation(response))
|
|
6262
|
-
return null;
|
|
6263
7071
|
const contextHints = this.contextHintsFor(request);
|
|
6264
7072
|
return {
|
|
6265
7073
|
type: 'info',
|
|
@@ -6293,6 +7101,30 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6293
7101
|
],
|
|
6294
7102
|
};
|
|
6295
7103
|
}
|
|
7104
|
+
selectedRecordReadonlyPresentationBoundaryResponse(response, request) {
|
|
7105
|
+
if (!this.selectedRecordReadOnlyAnalysisRequested(request))
|
|
7106
|
+
return null;
|
|
7107
|
+
if (response.patch)
|
|
7108
|
+
return null;
|
|
7109
|
+
const contextHints = this.contextHintsFor(request);
|
|
7110
|
+
if (!this.selectedRecordSampleRows(contextHints).length)
|
|
7111
|
+
return null;
|
|
7112
|
+
const hasPresentationOptions = (response.optionPayloads ?? [])
|
|
7113
|
+
.some((payload) => this.isVisualPresentationClarificationPayload(payload)
|
|
7114
|
+
|| this.isGovernedCategoricalSemanticsChoicePayload(payload));
|
|
7115
|
+
if (!hasPresentationOptions && !this.responseHasGovernedCategoricalSemanticsChoice(response))
|
|
7116
|
+
return null;
|
|
7117
|
+
return {
|
|
7118
|
+
type: 'info',
|
|
7119
|
+
sessionId: response.sessionId ?? request?.sessionId,
|
|
7120
|
+
message: this.selectedRecordsAnalyticalSummary(contextHints),
|
|
7121
|
+
warnings: [
|
|
7122
|
+
...(response.warnings ?? []),
|
|
7123
|
+
'selected-record-readonly-analysis-presentation-misroute-collapsed',
|
|
7124
|
+
'A plataforma respondeu a analise read-only usando selectedRecordsContext.sampleRows porque a resposta semantica retornou opcoes de apresentacao visual para uma pergunta consultiva.',
|
|
7125
|
+
],
|
|
7126
|
+
};
|
|
7127
|
+
}
|
|
6296
7128
|
responseAsksForSelectedRecordAnalysisDimension(response) {
|
|
6297
7129
|
const text = [
|
|
6298
7130
|
response.message,
|
|
@@ -6338,30 +7170,180 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6338
7170
|
return false;
|
|
6339
7171
|
}
|
|
6340
7172
|
const normalizedPrompt = this.normalizeLabel(request?.prompt ?? '');
|
|
6341
|
-
if (this.promptExplicitlyForbidsTableMutation(normalizedPrompt))
|
|
6342
|
-
return true;
|
|
6343
|
-
if (this.promptRequestsSelectedRecordDerivedOperation(normalizedPrompt))
|
|
7173
|
+
if (this.promptExplicitlyForbidsTableMutation(normalizedPrompt))
|
|
7174
|
+
return true;
|
|
7175
|
+
if (this.promptRequestsSelectedRecordDerivedOperation(normalizedPrompt))
|
|
7176
|
+
return false;
|
|
7177
|
+
const responseContract = this.toRecord(contextHints?.['responseContract'])
|
|
7178
|
+
?? this.toRecord(request?.action?.contextHints?.['responseContract']);
|
|
7179
|
+
if (responseContract) {
|
|
7180
|
+
const preferredResponse = this.normalizeLabel(this.stringValue(responseContract['preferredResponse']));
|
|
7181
|
+
const mutatesTable = responseContract['mutatesTable'];
|
|
7182
|
+
if (preferredResponse === 'info' || mutatesTable === false)
|
|
7183
|
+
return true;
|
|
7184
|
+
if (mutatesTable === true)
|
|
7185
|
+
return false;
|
|
7186
|
+
}
|
|
7187
|
+
const authoringMode = this.normalizeLabel(this.stringValue(contextHints?.['authoringMode']).replace(/[._-]+/gu, ' '));
|
|
7188
|
+
if (authoringMode === 'table selection readonly analysis')
|
|
7189
|
+
return true;
|
|
7190
|
+
return this.selectedRecordPromptLooksConsultative(normalizedPrompt);
|
|
7191
|
+
}
|
|
7192
|
+
selectedRecordGenericCommonalityAnalysisRequested(request) {
|
|
7193
|
+
if (!this.selectedRecordReadOnlyAnalysisRequested(request))
|
|
7194
|
+
return false;
|
|
7195
|
+
const normalizedPrompt = this.normalizeLabel(request?.prompt ?? '');
|
|
7196
|
+
if (!normalizedPrompt)
|
|
7197
|
+
return false;
|
|
7198
|
+
if (!this.normalizedTextContainsApproxPhrase(normalizedPrompt, 'em comum')
|
|
7199
|
+
&& !this.normalizedTextContainsApproxPhrase(normalizedPrompt, 'tem comum')) {
|
|
7200
|
+
return false;
|
|
7201
|
+
}
|
|
7202
|
+
if (this.promptRequestsSelectedRecordDerivedOperation(normalizedPrompt))
|
|
7203
|
+
return false;
|
|
7204
|
+
const contextHints = this.contextHintsFor(request);
|
|
7205
|
+
const selectedRecordsContext = this.selectedRecordsContextRecord(contextHints);
|
|
7206
|
+
const fieldTargets = [
|
|
7207
|
+
...(Array.isArray(selectedRecordsContext?.['fields']) ? selectedRecordsContext['fields'] : []),
|
|
7208
|
+
...this.filterFieldCatalogEntries.flatMap((entry) => [
|
|
7209
|
+
entry.name,
|
|
7210
|
+
entry.label,
|
|
7211
|
+
...entry.aliases,
|
|
7212
|
+
...entry.relatedColumnFields,
|
|
7213
|
+
...entry.relatedColumnLabels,
|
|
7214
|
+
]),
|
|
7215
|
+
...this.selectedRecordFilterCandidates(contextHints).flatMap((candidate) => [
|
|
7216
|
+
candidate['field'],
|
|
7217
|
+
candidate['label'],
|
|
7218
|
+
...(Array.isArray(candidate['relatedColumnFields']) ? candidate['relatedColumnFields'] : []),
|
|
7219
|
+
]),
|
|
7220
|
+
...this.selectedRecordSurfaces(contextHints).flatMap((surface) => [
|
|
7221
|
+
surface['id'],
|
|
7222
|
+
surface['label'],
|
|
7223
|
+
surface['title'],
|
|
7224
|
+
]),
|
|
7225
|
+
]
|
|
7226
|
+
.map((target) => this.normalizeLabel(this.stringValue(target)))
|
|
7227
|
+
.filter((target) => target.length > 2)
|
|
7228
|
+
.filter((target) => !this.selectedRecordGenericCommonalityScopeTarget(target));
|
|
7229
|
+
return !fieldTargets.some((target) => this.normalizedTextHasStandaloneToken(normalizedPrompt, target)
|
|
7230
|
+
|| this.normalizedTextContainsApproxPhrase(normalizedPrompt, target));
|
|
7231
|
+
}
|
|
7232
|
+
selectedRecordLocalReadonlyAnalysisRequested(request) {
|
|
7233
|
+
const contextHints = this.contextHintsFor(request);
|
|
7234
|
+
if (!this.selectedRecordSampleRows(contextHints).length)
|
|
7235
|
+
return false;
|
|
7236
|
+
if (this.toRecord(contextHints?.['selectedRecordsFilter'])
|
|
7237
|
+
|| this.toRecord(request?.action?.contextHints?.['selectedRecordsFilter'])
|
|
7238
|
+
|| this.toRecord(contextHints?.['tableRuntimeOperation'])
|
|
7239
|
+
|| this.toRecord(request?.action?.contextHints?.['tableRuntimeOperation'])) {
|
|
7240
|
+
return false;
|
|
7241
|
+
}
|
|
7242
|
+
const normalizedPrompt = this.normalizeLabel(request?.prompt ?? '');
|
|
7243
|
+
if (!normalizedPrompt)
|
|
6344
7244
|
return false;
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
const preferredResponse = this.normalizeLabel(this.stringValue(responseContract['preferredResponse']));
|
|
6349
|
-
const mutatesTable = responseContract['mutatesTable'];
|
|
6350
|
-
if (preferredResponse === 'info' || mutatesTable === false)
|
|
6351
|
-
return true;
|
|
6352
|
-
if (mutatesTable === true)
|
|
6353
|
-
return false;
|
|
7245
|
+
if (this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
7246
|
+
|| this.promptRequestsExportOperation(normalizedPrompt)) {
|
|
7247
|
+
return this.promptExplicitlyForbidsTableMutation(normalizedPrompt);
|
|
6354
7248
|
}
|
|
6355
|
-
|
|
6356
|
-
|
|
7249
|
+
if (this.promptRequestsSelectedRecordDerivedOperation(normalizedPrompt))
|
|
7250
|
+
return false;
|
|
7251
|
+
return this.promptExplicitlyForbidsTableMutation(normalizedPrompt)
|
|
7252
|
+
|| this.selectedRecordPromptTargetsSelectedSet(normalizedPrompt);
|
|
7253
|
+
}
|
|
7254
|
+
selectedRecordPromptTargetsSelectedSet(normalizedPrompt) {
|
|
7255
|
+
if (!normalizedPrompt)
|
|
7256
|
+
return false;
|
|
7257
|
+
const selectedSetSignals = [
|
|
7258
|
+
'selecionado',
|
|
7259
|
+
'selecionados',
|
|
7260
|
+
'marcado',
|
|
7261
|
+
'marcados',
|
|
7262
|
+
'linha marcada',
|
|
7263
|
+
'linhas marcadas',
|
|
7264
|
+
'registro marcado',
|
|
7265
|
+
'registros marcados',
|
|
7266
|
+
'registro selecionado',
|
|
7267
|
+
'registros selecionados',
|
|
7268
|
+
'esses',
|
|
7269
|
+
'essas',
|
|
7270
|
+
'nestes',
|
|
7271
|
+
'nestas',
|
|
7272
|
+
'nesses',
|
|
7273
|
+
'nessas',
|
|
7274
|
+
'desses',
|
|
7275
|
+
'dessas',
|
|
7276
|
+
'tres',
|
|
7277
|
+
'três',
|
|
7278
|
+
'3',
|
|
7279
|
+
];
|
|
7280
|
+
const analyticalSignals = [
|
|
7281
|
+
'em comum',
|
|
7282
|
+
'tem comum',
|
|
7283
|
+
'igual',
|
|
7284
|
+
'iguais',
|
|
7285
|
+
'compare',
|
|
7286
|
+
'comparar',
|
|
7287
|
+
'resuma',
|
|
7288
|
+
'resume',
|
|
7289
|
+
'resumir',
|
|
7290
|
+
'analise',
|
|
7291
|
+
'analisar',
|
|
7292
|
+
'padrao',
|
|
7293
|
+
'padrão',
|
|
7294
|
+
'fora do padrao',
|
|
7295
|
+
'fora do padrão',
|
|
7296
|
+
'ganha mais',
|
|
7297
|
+
'ganha menos',
|
|
7298
|
+
'quem ganha',
|
|
7299
|
+
'faixa salarial',
|
|
7300
|
+
'faixa de salario',
|
|
7301
|
+
'faixa de salário',
|
|
7302
|
+
'faixa de admissao',
|
|
7303
|
+
'faixa de admissão',
|
|
7304
|
+
'periodo de admissao',
|
|
7305
|
+
'período de admissão',
|
|
7306
|
+
'departamento',
|
|
7307
|
+
'cargo',
|
|
7308
|
+
'cargos',
|
|
7309
|
+
'salario',
|
|
7310
|
+
'salário',
|
|
7311
|
+
'admissao',
|
|
7312
|
+
'admissão',
|
|
7313
|
+
];
|
|
7314
|
+
const hasSelectedScope = selectedSetSignals.some((signal) => this.normalizedTextHasStandaloneToken(normalizedPrompt, signal)
|
|
7315
|
+
|| this.normalizedTextContainsApproxPhrase(normalizedPrompt, signal));
|
|
7316
|
+
if (!hasSelectedScope)
|
|
7317
|
+
return false;
|
|
7318
|
+
return analyticalSignals.some((signal) => this.normalizedTextHasStandaloneToken(normalizedPrompt, signal)
|
|
7319
|
+
|| this.normalizedTextContainsApproxPhrase(normalizedPrompt, signal));
|
|
7320
|
+
}
|
|
7321
|
+
selectedRecordGenericCommonalityScopeTarget(normalizedTarget) {
|
|
7322
|
+
if (!normalizedTarget)
|
|
6357
7323
|
return true;
|
|
6358
|
-
return
|
|
7324
|
+
return [
|
|
7325
|
+
'funcionario',
|
|
7326
|
+
'funcionarios',
|
|
7327
|
+
'colaborador',
|
|
7328
|
+
'colaboradores',
|
|
7329
|
+
'registro',
|
|
7330
|
+
'registros',
|
|
7331
|
+
'linha',
|
|
7332
|
+
'linhas',
|
|
7333
|
+
'selecionado',
|
|
7334
|
+
'selecionados',
|
|
7335
|
+
'selected',
|
|
7336
|
+
'record',
|
|
7337
|
+
'records',
|
|
7338
|
+
'row',
|
|
7339
|
+
'rows',
|
|
7340
|
+
].some((scopeTerm) => this.normalizedTextHasStandaloneToken(normalizedTarget, scopeTerm)
|
|
7341
|
+
|| normalizedTarget === scopeTerm);
|
|
6359
7342
|
}
|
|
6360
7343
|
selectedRecordPromptLooksConsultative(normalizedPrompt) {
|
|
6361
7344
|
if (!normalizedPrompt)
|
|
6362
7345
|
return false;
|
|
6363
|
-
if (this.
|
|
6364
|
-
|| this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
7346
|
+
if (this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
6365
7347
|
|| this.promptRequestsExportOperation(normalizedPrompt)) {
|
|
6366
7348
|
return this.promptExplicitlyForbidsTableMutation(normalizedPrompt);
|
|
6367
7349
|
}
|
|
@@ -6408,6 +7390,30 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6408
7390
|
// This ranks explicit selected-record operation wording after selectedRecordsContext is already available.
|
|
6409
7391
|
// The primary intent still comes from the semantic turn/contract; these phrases only prevent a read-only
|
|
6410
7392
|
// safety boundary from cancelling an operation the user explicitly asked to materialize.
|
|
7393
|
+
if (this.selectedRecordPromptLooksConsultative(normalizedPrompt)) {
|
|
7394
|
+
const explicitOperationSignals = [
|
|
7395
|
+
'filtrar',
|
|
7396
|
+
'filtra',
|
|
7397
|
+
'filtro',
|
|
7398
|
+
'filtre',
|
|
7399
|
+
'buscar registros',
|
|
7400
|
+
'procurar registros',
|
|
7401
|
+
'acha quem',
|
|
7402
|
+
'achar quem',
|
|
7403
|
+
'outros registros',
|
|
7404
|
+
'quero outros',
|
|
7405
|
+
'quero os mesmo',
|
|
7406
|
+
'quero os mesmos',
|
|
7407
|
+
'quero ver gente',
|
|
7408
|
+
'pega a turma',
|
|
7409
|
+
'pegar a turma',
|
|
7410
|
+
].map((signal) => this.normalizeLabel(signal));
|
|
7411
|
+
const explicitlyRequestsOperation = explicitOperationSignals.some((signal) => signal.includes(' ')
|
|
7412
|
+
? this.normalizedTextContainsExactPhrase(normalizedPrompt, signal)
|
|
7413
|
+
: this.normalizedTextHasStandaloneToken(normalizedPrompt, signal));
|
|
7414
|
+
if (!explicitlyRequestsOperation)
|
|
7415
|
+
return false;
|
|
7416
|
+
}
|
|
6411
7417
|
return [
|
|
6412
7418
|
'filtrar',
|
|
6413
7419
|
'filtra',
|
|
@@ -6415,6 +7421,10 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6415
7421
|
'filtre',
|
|
6416
7422
|
'buscar registros',
|
|
6417
7423
|
'procurar registros',
|
|
7424
|
+
'acha quem',
|
|
7425
|
+
'achar quem',
|
|
7426
|
+
'pega a turma',
|
|
7427
|
+
'pegar a turma',
|
|
6418
7428
|
'outros registros',
|
|
6419
7429
|
'registro parecido',
|
|
6420
7430
|
'registros parecidos',
|
|
@@ -6433,7 +7443,20 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6433
7443
|
'quero os mesmo',
|
|
6434
7444
|
'quero os mesmos',
|
|
6435
7445
|
'quero ver gente',
|
|
6436
|
-
|
|
7446
|
+
'mesma banda',
|
|
7447
|
+
'nessa mesma banda',
|
|
7448
|
+
'nessa epoca',
|
|
7449
|
+
'nessa época',
|
|
7450
|
+
].some((signal) => {
|
|
7451
|
+
const normalizedSignal = this.normalizeLabel(signal);
|
|
7452
|
+
if (!normalizedSignal)
|
|
7453
|
+
return false;
|
|
7454
|
+
if (normalizedSignal.includes(' ')) {
|
|
7455
|
+
return this.normalizedTextContainsExactPhrase(normalizedPrompt, normalizedSignal);
|
|
7456
|
+
}
|
|
7457
|
+
return this.normalizedTextHasStandaloneToken(normalizedPrompt, normalizedSignal)
|
|
7458
|
+
|| this.normalizedTextContainsApproxToken(normalizedPrompt, normalizedSignal);
|
|
7459
|
+
});
|
|
6437
7460
|
}
|
|
6438
7461
|
promptExplicitlyForbidsTableMutation(normalizedPrompt) {
|
|
6439
7462
|
if (!normalizedPrompt)
|
|
@@ -6523,16 +7546,17 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6523
7546
|
.slice(0, 4);
|
|
6524
7547
|
const dateRanges = fields
|
|
6525
7548
|
.map((field) => {
|
|
6526
|
-
const
|
|
6527
|
-
.map((row) => this.
|
|
6528
|
-
.filter((value) => value
|
|
6529
|
-
if (
|
|
7549
|
+
const values = rows
|
|
7550
|
+
.map((row) => this.analysisDateValue(row[field]))
|
|
7551
|
+
.filter((value) => !!value);
|
|
7552
|
+
if (values.length < 2)
|
|
6530
7553
|
return null;
|
|
6531
|
-
const
|
|
6532
|
-
const
|
|
6533
|
-
|
|
7554
|
+
const sorted = [...values].sort((left, right) => left.sort - right.sort);
|
|
7555
|
+
const min = sorted[0];
|
|
7556
|
+
const max = sorted[sorted.length - 1];
|
|
7557
|
+
if (!min || !max || min.sort === max.sort)
|
|
6534
7558
|
return null;
|
|
6535
|
-
return `${labelByField.get(field) || this.humanizeField(field)}: ${
|
|
7559
|
+
return `${labelByField.get(field) || this.humanizeField(field)}: ${min.label} até ${max.label}`;
|
|
6536
7560
|
})
|
|
6537
7561
|
.filter((entry) => !!entry)
|
|
6538
7562
|
.slice(0, 3);
|
|
@@ -6588,6 +7612,27 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6588
7612
|
: Number.NaN;
|
|
6589
7613
|
return Number.isFinite(raw) ? raw : null;
|
|
6590
7614
|
}
|
|
7615
|
+
analysisDateValue(value) {
|
|
7616
|
+
if (typeof value === 'string') {
|
|
7617
|
+
const trimmed = value.trim();
|
|
7618
|
+
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(trimmed);
|
|
7619
|
+
if (dateOnly) {
|
|
7620
|
+
const year = Number(dateOnly[1]);
|
|
7621
|
+
const month = Number(dateOnly[2]);
|
|
7622
|
+
const day = Number(dateOnly[3]);
|
|
7623
|
+
if (Number.isInteger(year) && Number.isInteger(month) && Number.isInteger(day)) {
|
|
7624
|
+
return {
|
|
7625
|
+
sort: year * 10000 + month * 100 + day,
|
|
7626
|
+
label: `${String(day).padStart(2, '0')}/${String(month).padStart(2, '0')}/${String(year)}`,
|
|
7627
|
+
};
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
}
|
|
7631
|
+
const timestamp = this.dateTimestamp(value);
|
|
7632
|
+
return timestamp === null
|
|
7633
|
+
? null
|
|
7634
|
+
: { sort: timestamp, label: this.formatAnalysisDate(timestamp) };
|
|
7635
|
+
}
|
|
6591
7636
|
formatAnalysisDate(timestamp) {
|
|
6592
7637
|
return new Intl.DateTimeFormat('pt-BR', {
|
|
6593
7638
|
day: '2-digit',
|
|
@@ -6924,6 +7969,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
6924
7969
|
return operations;
|
|
6925
7970
|
}
|
|
6926
7971
|
requestCarriesVisualPresentationContext(request) {
|
|
7972
|
+
if (this.requestHasGovernedCategoricalSelection(request))
|
|
7973
|
+
return true;
|
|
6927
7974
|
const actionHints = this.toRecord(request.action?.contextHints);
|
|
6928
7975
|
const requestHints = this.toRecord(request.contextHints);
|
|
6929
7976
|
const pendingDiagnostics = this.toRecord(request.pendingClarification?.diagnostics);
|
|
@@ -7406,7 +8453,10 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7406
8453
|
add(header);
|
|
7407
8454
|
add(field);
|
|
7408
8455
|
add(this.humanizeField(field));
|
|
7409
|
-
for (const
|
|
8456
|
+
for (const alias of this.stringArrayValue(column['aliases'])) {
|
|
8457
|
+
add(alias);
|
|
8458
|
+
}
|
|
8459
|
+
for (const source of [header, field, this.humanizeField(field), ...this.stringArrayValue(column['aliases'])]) {
|
|
7410
8460
|
const normalized = this.normalizeLabel(source);
|
|
7411
8461
|
const tokens = normalized.split(' ').filter((token) => token.length >= 3
|
|
7412
8462
|
&& !['nome', 'valor', 'data', 'campo', 'codigo', 'id'].includes(token));
|
|
@@ -7617,6 +8667,24 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7617
8667
|
...(memory ? { tableComponentEditDecision: memory } : {}),
|
|
7618
8668
|
};
|
|
7619
8669
|
}
|
|
8670
|
+
errorRetryDiagnostics(request, response) {
|
|
8671
|
+
const actionHints = this.toRecord(request.action?.contextHints);
|
|
8672
|
+
const retryableAction = request.action && actionHints
|
|
8673
|
+
? {
|
|
8674
|
+
kind: request.action.kind || 'clarify',
|
|
8675
|
+
value: this.stringValue(request.action.value) || request.prompt || '',
|
|
8676
|
+
displayPrompt: this.stringValue(request.action.displayPrompt),
|
|
8677
|
+
contextHints: actionHints,
|
|
8678
|
+
}
|
|
8679
|
+
: null;
|
|
8680
|
+
const warnings = response.warnings?.filter(Boolean) ?? [];
|
|
8681
|
+
if (!retryableAction && !warnings.length)
|
|
8682
|
+
return undefined;
|
|
8683
|
+
return {
|
|
8684
|
+
...(warnings.length ? { warnings } : {}),
|
|
8685
|
+
...(retryableAction ? { retryAction: retryableAction } : {}),
|
|
8686
|
+
};
|
|
8687
|
+
}
|
|
7620
8688
|
tableConversationMemoryHints(request) {
|
|
7621
8689
|
const diagnostics = this.toRecord(request.diagnostics);
|
|
7622
8690
|
const decision = this.toRecord(diagnostics?.['tableComponentEditDecision']);
|
|
@@ -7691,6 +8759,9 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7691
8759
|
completePendingComponentEditClarification(request) {
|
|
7692
8760
|
if (request.action?.kind !== 'clarify')
|
|
7693
8761
|
return null;
|
|
8762
|
+
const computedColumnCreation = this.completePendingComputedColumnClarification(request);
|
|
8763
|
+
if (computedColumnCreation)
|
|
8764
|
+
return computedColumnCreation;
|
|
7694
8765
|
const diagnostics = this.toRecord(request.pendingClarification?.diagnostics);
|
|
7695
8766
|
const continuation = this.toRecord(diagnostics?.['tableComponentEditContinuation']);
|
|
7696
8767
|
if (!continuation)
|
|
@@ -7719,6 +8790,127 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
7719
8790
|
explanation: 'Clarificacao aplicada ao ajuste pendente.',
|
|
7720
8791
|
};
|
|
7721
8792
|
}
|
|
8793
|
+
completePendingComputedColumnClarification(request) {
|
|
8794
|
+
const sourcePrompt = request.pendingClarification?.sourcePrompt?.trim()
|
|
8795
|
+
|| this.recoverComputedColumnClarificationSourcePromptFromMessages(request);
|
|
8796
|
+
if (!sourcePrompt)
|
|
8797
|
+
return null;
|
|
8798
|
+
const normalizedSourcePrompt = this.normalizeLabel(sourcePrompt);
|
|
8799
|
+
if (!this.promptRequestsComputedColumnCreation(normalizedSourcePrompt))
|
|
8800
|
+
return null;
|
|
8801
|
+
const selectedField = this.selectedNewComputedFieldFromClarification(request);
|
|
8802
|
+
if (!selectedField)
|
|
8803
|
+
return null;
|
|
8804
|
+
const columns = this.currentTableColumnsForAuthoring();
|
|
8805
|
+
if (columns.some((column) => this.stringValue(column['field']).toLowerCase() === selectedField.toLowerCase())) {
|
|
8806
|
+
return null;
|
|
8807
|
+
}
|
|
8808
|
+
const baseFields = this.computedColumnBaseFieldsFromPrompt(normalizedSourcePrompt, columns);
|
|
8809
|
+
if (baseFields.length < 2)
|
|
8810
|
+
return null;
|
|
8811
|
+
const operation = {
|
|
8812
|
+
operationId: 'column.computed.add',
|
|
8813
|
+
target: { kind: 'column', field: selectedField },
|
|
8814
|
+
input: {
|
|
8815
|
+
field: selectedField,
|
|
8816
|
+
header: this.humanizeField(selectedField),
|
|
8817
|
+
expression: {
|
|
8818
|
+
cat: baseFields.flatMap((field, index) => (index === 0 ? [{ var: field }] : [' - ', { var: field }])),
|
|
8819
|
+
},
|
|
8820
|
+
outputType: 'string',
|
|
8821
|
+
dependencies: baseFields,
|
|
8822
|
+
},
|
|
8823
|
+
};
|
|
8824
|
+
return {
|
|
8825
|
+
type: 'patch',
|
|
8826
|
+
sessionId: request.sessionId,
|
|
8827
|
+
componentEditPlan: {
|
|
8828
|
+
kind: 'praxis.table.component-edit-plan',
|
|
8829
|
+
version: '1.0',
|
|
8830
|
+
componentId: this.adapter.componentId || request.componentId || 'praxis-table',
|
|
8831
|
+
...operation,
|
|
8832
|
+
},
|
|
8833
|
+
explanation: `Vou criar a coluna calculada **${this.humanizeField(selectedField)}** combinando ${baseFields.map((field) => `**${this.humanizeField(field)}**`).join(' e ')}.`,
|
|
8834
|
+
warnings: [
|
|
8835
|
+
'computed-column-clarification-continuation-materialized',
|
|
8836
|
+
'A resposta curta de clarificacao foi materializada usando pendingClarification.sourcePrompt e colunas declaradas da tabela; a intencao primaria ja estava ancorada em coluna calculada.',
|
|
8837
|
+
],
|
|
8838
|
+
};
|
|
8839
|
+
}
|
|
8840
|
+
selectedNewComputedFieldFromClarification(request) {
|
|
8841
|
+
const value = [
|
|
8842
|
+
request.action?.value,
|
|
8843
|
+
request.action?.displayPrompt,
|
|
8844
|
+
request.prompt,
|
|
8845
|
+
]
|
|
8846
|
+
.map((candidate) => this.stringValue(candidate).trim())
|
|
8847
|
+
.find((candidate) => !!candidate);
|
|
8848
|
+
if (!value || /\s/u.test(value) || value.length > 80)
|
|
8849
|
+
return null;
|
|
8850
|
+
const cleaned = value.replace(/[^A-Za-z0-9_]/gu, '');
|
|
8851
|
+
return cleaned || null;
|
|
8852
|
+
}
|
|
8853
|
+
recoverComputedColumnClarificationSourcePromptFromMessages(request) {
|
|
8854
|
+
const messages = [...(request.messages ?? [])];
|
|
8855
|
+
if (messages.length < 2)
|
|
8856
|
+
return null;
|
|
8857
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
8858
|
+
const message = messages[index];
|
|
8859
|
+
if (message.role !== 'assistant')
|
|
8860
|
+
continue;
|
|
8861
|
+
const assistantText = this.normalizeLabel(message.text ?? '');
|
|
8862
|
+
if (!assistantText.includes('coluna correta') && !assistantText.includes('qual coluna'))
|
|
8863
|
+
continue;
|
|
8864
|
+
const sourcePrompt = [...messages.slice(0, index)]
|
|
8865
|
+
.reverse()
|
|
8866
|
+
.find((candidate) => candidate.role === 'user')
|
|
8867
|
+
?.text?.trim();
|
|
8868
|
+
if (sourcePrompt && this.promptRequestsComputedColumnCreation(this.normalizeLabel(sourcePrompt))) {
|
|
8869
|
+
return sourcePrompt;
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
8872
|
+
return null;
|
|
8873
|
+
}
|
|
8874
|
+
promptRequestsComputedColumnCreation(normalizedPrompt) {
|
|
8875
|
+
if (!normalizedPrompt)
|
|
8876
|
+
return false;
|
|
8877
|
+
const wantsComputed = normalizedPrompt.includes('calculad')
|
|
8878
|
+
|| normalizedPrompt.includes('computed')
|
|
8879
|
+
|| normalizedPrompt.includes('derivad')
|
|
8880
|
+
|| normalizedPrompt.includes('formula');
|
|
8881
|
+
const wantsJoin = normalizedPrompt.includes('combin')
|
|
8882
|
+
|| normalizedPrompt.includes('concat')
|
|
8883
|
+
|| normalizedPrompt.includes('juntar')
|
|
8884
|
+
|| normalizedPrompt.includes('unir')
|
|
8885
|
+
|| normalizedPrompt.includes('mesclar')
|
|
8886
|
+
|| normalizedPrompt.includes('merge');
|
|
8887
|
+
return wantsComputed && wantsJoin;
|
|
8888
|
+
}
|
|
8889
|
+
currentTableColumnsForAuthoring() {
|
|
8890
|
+
return this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
8891
|
+
.map((column) => this.toRecord(column))
|
|
8892
|
+
.filter((column) => !!column && !!this.stringValue(column['field']));
|
|
8893
|
+
}
|
|
8894
|
+
computedColumnBaseFieldsFromPrompt(normalizedPrompt, columns) {
|
|
8895
|
+
const fields = [];
|
|
8896
|
+
for (const column of columns) {
|
|
8897
|
+
const field = this.stringValue(column['field']);
|
|
8898
|
+
const header = this.stringValue(column['header']) || field;
|
|
8899
|
+
if (!field)
|
|
8900
|
+
continue;
|
|
8901
|
+
if (this.normalizedPromptMentionsColumn(normalizedPrompt, field)
|
|
8902
|
+
|| this.normalizedPromptMentionsColumn(normalizedPrompt, header)) {
|
|
8903
|
+
fields.push(field);
|
|
8904
|
+
}
|
|
8905
|
+
}
|
|
8906
|
+
return [...new Set(fields)];
|
|
8907
|
+
}
|
|
8908
|
+
normalizedPromptMentionsColumn(normalizedPrompt, value) {
|
|
8909
|
+
const normalizedValue = this.normalizeLabel(value);
|
|
8910
|
+
if (!normalizedValue)
|
|
8911
|
+
return false;
|
|
8912
|
+
return normalizedPrompt.includes(normalizedValue);
|
|
8913
|
+
}
|
|
7722
8914
|
describeBooleanStateRenderers(operations) {
|
|
7723
8915
|
if (operations.length < 2)
|
|
7724
8916
|
return null;
|
|
@@ -8439,7 +9631,6 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8439
9631
|
};
|
|
8440
9632
|
}
|
|
8441
9633
|
buildComponentPresentationAuthoringSystemPolicy(contextHints, prompt) {
|
|
8442
|
-
void contextHints;
|
|
8443
9634
|
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
8444
9635
|
const presentationIntent = this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
8445
9636
|
|| this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt);
|
|
@@ -8458,6 +9649,11 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8458
9649
|
const type = this.stringValue(column['type'] ?? column['dataType']) || 'unknown';
|
|
8459
9650
|
return `- ${field} (${label}; ${type}; ${visible})`;
|
|
8460
9651
|
});
|
|
9652
|
+
const targetGroundingLines = this.componentPresentationTargetGroundingLines(normalizedPrompt, columns);
|
|
9653
|
+
const contextField = this.stringValue(contextHints?.['targetField']);
|
|
9654
|
+
const contextTargetLine = contextField
|
|
9655
|
+
? `Declared context target: ${contextField}.`
|
|
9656
|
+
: 'Declared context target: none.';
|
|
8461
9657
|
return {
|
|
8462
9658
|
role: 'system',
|
|
8463
9659
|
content: [
|
|
@@ -8468,11 +9664,90 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8468
9664
|
'- 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.',
|
|
8469
9665
|
'- 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.',
|
|
8470
9666
|
'- 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.',
|
|
9667
|
+
'- Target grounding candidates below are ranking evidence after this turn has been resolved as presentation authoring; they are not the primary intent router.',
|
|
9668
|
+
'- 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.',
|
|
9669
|
+
'- If the generated plan would target a different field than the high-confidence candidate, ask a short clarification instead of returning an executable componentEditPlan.',
|
|
8471
9670
|
'Declared table columns:',
|
|
8472
9671
|
...columnLines,
|
|
9672
|
+
contextTargetLine,
|
|
9673
|
+
targetGroundingLines.length ? 'Target grounding candidates:' : 'Target grounding candidates: none declared.',
|
|
9674
|
+
...targetGroundingLines,
|
|
8473
9675
|
].join('\n'),
|
|
8474
9676
|
};
|
|
8475
9677
|
}
|
|
9678
|
+
componentPresentationTargetGroundingLines(normalizedPrompt, columns) {
|
|
9679
|
+
return this.componentPresentationTargetGroundingCandidates(normalizedPrompt, columns)
|
|
9680
|
+
.map((candidate) => [
|
|
9681
|
+
`- ${candidate.field} (${candidate.label})`,
|
|
9682
|
+
`confidence=${candidate.confidence}`,
|
|
9683
|
+
`evidence=${candidate.evidence.join(', ')}`,
|
|
9684
|
+
].join('; '));
|
|
9685
|
+
}
|
|
9686
|
+
componentPresentationTargetGroundingHints(prompt) {
|
|
9687
|
+
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
9688
|
+
const presentationIntent = this.promptRequestsVisualOrStructuralEdit(normalizedPrompt)
|
|
9689
|
+
|| this.promptRequestsCompoundColumnPresentationEdit(normalizedPrompt);
|
|
9690
|
+
if (!presentationIntent)
|
|
9691
|
+
return null;
|
|
9692
|
+
const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
9693
|
+
.map((column) => this.toRecord(column))
|
|
9694
|
+
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
9695
|
+
.slice(0, 20);
|
|
9696
|
+
const candidates = this.componentPresentationTargetGroundingCandidates(normalizedPrompt, columns);
|
|
9697
|
+
if (!candidates.length)
|
|
9698
|
+
return null;
|
|
9699
|
+
return {
|
|
9700
|
+
presentationTargetGrounding: {
|
|
9701
|
+
kind: 'praxis.table.presentation-target-grounding.v1',
|
|
9702
|
+
scope: 'presentation-authoring',
|
|
9703
|
+
candidates: candidates.map((candidate) => ({
|
|
9704
|
+
field: candidate.field,
|
|
9705
|
+
label: candidate.label,
|
|
9706
|
+
confidence: candidate.confidence,
|
|
9707
|
+
evidence: candidate.evidence,
|
|
9708
|
+
})),
|
|
9709
|
+
},
|
|
9710
|
+
};
|
|
9711
|
+
}
|
|
9712
|
+
componentPresentationTargetGroundingCandidates(normalizedPrompt, columns) {
|
|
9713
|
+
return columns
|
|
9714
|
+
.map((column) => {
|
|
9715
|
+
const field = this.stringValue(column['field']);
|
|
9716
|
+
if (!field)
|
|
9717
|
+
return null;
|
|
9718
|
+
const label = this.stringValue(column['header']) || this.humanizeField(field);
|
|
9719
|
+
const fieldScore = this.visualPresentationTargetFieldScore(normalizedPrompt, field);
|
|
9720
|
+
const labelScore = this.visualPresentationTargetFieldScore(normalizedPrompt, label);
|
|
9721
|
+
const valueMatches = this.componentPresentationObservedValueMatches(normalizedPrompt, field);
|
|
9722
|
+
const score = Math.max(fieldScore, labelScore) + (valueMatches.length ? 12 : 0);
|
|
9723
|
+
if (score <= 0)
|
|
9724
|
+
return null;
|
|
9725
|
+
const evidence = [
|
|
9726
|
+
fieldScore > 0 ? 'field mention' : null,
|
|
9727
|
+
labelScore > 0 ? 'header mention' : null,
|
|
9728
|
+
...valueMatches.map((value) => `observed value "${value}"`),
|
|
9729
|
+
].filter((value) => !!value);
|
|
9730
|
+
const confidence = score >= 12 ? 'high' : 'medium';
|
|
9731
|
+
return {
|
|
9732
|
+
field,
|
|
9733
|
+
label,
|
|
9734
|
+
score,
|
|
9735
|
+
confidence,
|
|
9736
|
+
evidence,
|
|
9737
|
+
};
|
|
9738
|
+
})
|
|
9739
|
+
.filter((candidate) => !!candidate)
|
|
9740
|
+
.sort((a, b) => b.score - a.score)
|
|
9741
|
+
.slice(0, 5);
|
|
9742
|
+
}
|
|
9743
|
+
componentPresentationObservedValueMatches(normalizedPrompt, field) {
|
|
9744
|
+
return this.adapterFieldValueSamples(field)
|
|
9745
|
+
.map((value) => this.stringValue(value))
|
|
9746
|
+
.filter((value) => !!value)
|
|
9747
|
+
.filter((value) => this.normalizedTextContainsApproxPhrase(normalizedPrompt, value))
|
|
9748
|
+
.map((value) => (value.length > 48 ? `${value.slice(0, 45)}...` : value))
|
|
9749
|
+
.slice(0, 3);
|
|
9750
|
+
}
|
|
8476
9751
|
buildComponentLayoutAuthoringSystemPolicy(contextHints, prompt) {
|
|
8477
9752
|
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
8478
9753
|
const position = this.relativeOrderPosition(normalizedPrompt);
|
|
@@ -8722,6 +9997,8 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8722
9997
|
const record = this.toRecord(payload);
|
|
8723
9998
|
if (!record)
|
|
8724
9999
|
return false;
|
|
10000
|
+
if (this.isGovernedCategoricalSemanticsChoicePayload(record))
|
|
10001
|
+
return false;
|
|
8725
10002
|
return this.isVisualPresentationClarificationText([
|
|
8726
10003
|
record['label'],
|
|
8727
10004
|
record['value'],
|
|
@@ -8750,6 +10027,48 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8750
10027
|
'apresentação',
|
|
8751
10028
|
].some((token) => this.normalizedTextContainsApproxPhrase(text, token));
|
|
8752
10029
|
}
|
|
10030
|
+
isGovernedCategoricalSemanticsChoicePayload(payload) {
|
|
10031
|
+
const record = this.toRecord(payload);
|
|
10032
|
+
if (!record)
|
|
10033
|
+
return false;
|
|
10034
|
+
const contextHintsText = this.safeJsonText(record['contextHints']);
|
|
10035
|
+
return this.isGovernedCategoricalSemanticsChoiceText([
|
|
10036
|
+
record['label'],
|
|
10037
|
+
record['value'],
|
|
10038
|
+
record['description'],
|
|
10039
|
+
contextHintsText,
|
|
10040
|
+
].filter(Boolean).join(' '));
|
|
10041
|
+
}
|
|
10042
|
+
isGovernedCategoricalSemanticsChoiceText(value) {
|
|
10043
|
+
const text = this.normalizeLabel(value);
|
|
10044
|
+
if (!text)
|
|
10045
|
+
return false;
|
|
10046
|
+
return [
|
|
10047
|
+
'author categorical field semantics',
|
|
10048
|
+
'author categorical semantics',
|
|
10049
|
+
'categorical field semantics',
|
|
10050
|
+
'semantica visual governada',
|
|
10051
|
+
'semântica visual governada',
|
|
10052
|
+
'governed visual semantics',
|
|
10053
|
+
'apply neutral categorical renderer',
|
|
10054
|
+
'apply neutral categorical chip',
|
|
10055
|
+
'aplicar chips neutros',
|
|
10056
|
+
'chips neutros por enquanto',
|
|
10057
|
+
'ungoverned neutral fallback',
|
|
10058
|
+
]
|
|
10059
|
+
.map((token) => this.normalizeLabel(token))
|
|
10060
|
+
.some((token) => text.includes(token));
|
|
10061
|
+
}
|
|
10062
|
+
safeJsonText(value) {
|
|
10063
|
+
if (value === null || value === undefined)
|
|
10064
|
+
return '';
|
|
10065
|
+
try {
|
|
10066
|
+
return JSON.stringify(value) ?? '';
|
|
10067
|
+
}
|
|
10068
|
+
catch {
|
|
10069
|
+
return '';
|
|
10070
|
+
}
|
|
10071
|
+
}
|
|
8753
10072
|
textMentionsFilterCatalogEntry(normalizedText) {
|
|
8754
10073
|
if (!normalizedText || !this.filterFieldCatalogEntries.length)
|
|
8755
10074
|
return false;
|
|
@@ -8853,17 +10172,40 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8853
10172
|
const rawLabel = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
8854
10173
|
const label = this.humanizeClarificationOptionLabel(rawLabel);
|
|
8855
10174
|
const canonicalValue = option.value?.trim() || option.example?.trim() || label;
|
|
10175
|
+
const isGovernedCategoricalSemanticsChoice = this.isGovernedCategoricalSemanticsChoicePayload(option);
|
|
10176
|
+
const isVisualPresentation = !isGovernedCategoricalSemanticsChoice
|
|
10177
|
+
&& this.isVisualPresentationClarificationPayload(option);
|
|
10178
|
+
const visualRenderer = isVisualPresentation
|
|
10179
|
+
? this.visualPresentationRendererValue([rawLabel, canonicalValue].join(' '))
|
|
10180
|
+
: null;
|
|
10181
|
+
const optionHints = this.optionContextHints(option);
|
|
10182
|
+
const optionSelected = this.toRecord(optionHints?.['optionSelected']);
|
|
10183
|
+
const selection = this.toRecord(optionSelected?.['selection']);
|
|
10184
|
+
const visualField = visualRenderer
|
|
10185
|
+
? this.stringValue(selection?.['field'])
|
|
10186
|
+
|| this.stringValue(optionSelected?.['targetField'])
|
|
10187
|
+
|| this.visualPresentationTargetField(response, request)
|
|
10188
|
+
: null;
|
|
10189
|
+
const prompt = visualRenderer
|
|
10190
|
+
? this.visualPresentationClarificationPromptFromSelection(visualRenderer, visualField)
|
|
10191
|
+
: isGovernedCategoricalSemanticsChoice
|
|
10192
|
+
? label
|
|
10193
|
+
: isVisualPresentation
|
|
10194
|
+
? this.visualPresentationClarificationPrompt(option, canonicalValue)
|
|
10195
|
+
: label;
|
|
8856
10196
|
return {
|
|
8857
10197
|
id: `option-${index + 1}`,
|
|
8858
10198
|
label,
|
|
8859
|
-
prompt
|
|
8860
|
-
value: canonicalValue,
|
|
10199
|
+
prompt,
|
|
10200
|
+
value: visualRenderer ?? canonicalValue,
|
|
8861
10201
|
kind: 'clarification-option',
|
|
8862
10202
|
description: this.optionDescription(option),
|
|
8863
10203
|
icon: this.optionIcon(option),
|
|
8864
10204
|
tone: this.optionTone(option),
|
|
8865
10205
|
presentation: this.optionPresentation(option) ?? this.defaultGuidedOptionPresentation(option),
|
|
8866
|
-
contextHints:
|
|
10206
|
+
contextHints: visualRenderer
|
|
10207
|
+
? this.visualPresentationOptionContextHints(visualRenderer, visualField)
|
|
10208
|
+
: optionHints,
|
|
8867
10209
|
};
|
|
8868
10210
|
});
|
|
8869
10211
|
}
|
|
@@ -8872,17 +10214,139 @@ class TableAgenticAuthoringTurnFlow {
|
|
|
8872
10214
|
}
|
|
8873
10215
|
return this.enhanceColumnClarificationOptions(response.options ?? [], response, request)
|
|
8874
10216
|
.filter((option) => !!option?.trim())
|
|
8875
|
-
.map((option, index) =>
|
|
8876
|
-
|
|
8877
|
-
label
|
|
8878
|
-
|
|
8879
|
-
|
|
10217
|
+
.map((option, index) => {
|
|
10218
|
+
const rawOption = option.trim();
|
|
10219
|
+
const label = this.humanizeClarificationOptionLabel(rawOption);
|
|
10220
|
+
const visualRenderer = this.visualPresentationRendererValue(rawOption);
|
|
10221
|
+
const visualField = visualRenderer
|
|
10222
|
+
? this.visualPresentationTargetField(response, request)
|
|
10223
|
+
: null;
|
|
10224
|
+
const prompt = visualRenderer
|
|
10225
|
+
? this.visualPresentationClarificationPromptFromSelection(visualRenderer, visualField)
|
|
10226
|
+
: rawOption;
|
|
10227
|
+
return {
|
|
10228
|
+
id: `option-${index + 1}`,
|
|
10229
|
+
label,
|
|
10230
|
+
prompt,
|
|
10231
|
+
value: visualRenderer ?? rawOption,
|
|
10232
|
+
kind: 'clarification-option',
|
|
10233
|
+
presentation: {
|
|
10234
|
+
kind: 'guided-option',
|
|
10235
|
+
icon: visualRenderer ? this.visualPresentationIcon(visualRenderer) : 'check',
|
|
10236
|
+
ctaLabel: visualRenderer ? 'Aplicar opção' : 'Usar esta opção',
|
|
10237
|
+
},
|
|
10238
|
+
...(visualRenderer
|
|
10239
|
+
? { contextHints: this.visualPresentationOptionContextHints(visualRenderer, visualField) }
|
|
10240
|
+
: {}),
|
|
10241
|
+
};
|
|
10242
|
+
});
|
|
10243
|
+
}
|
|
10244
|
+
visualPresentationClarificationPrompt(option, canonicalValue) {
|
|
10245
|
+
const contextHints = this.toRecord(option.contextHints);
|
|
10246
|
+
const optionSelected = this.toRecord(contextHints?.['optionSelected']);
|
|
10247
|
+
const selection = this.toRecord(optionSelected?.['selection']);
|
|
10248
|
+
const field = this.stringValue(selection?.['field']) || this.stringValue(optionSelected?.['targetField']);
|
|
10249
|
+
const rendererValue = this.stringValue(selection?.['value']) || canonicalValue;
|
|
10250
|
+
return this.visualPresentationClarificationPromptFromSelection(rendererValue, field);
|
|
10251
|
+
}
|
|
10252
|
+
visualPresentationClarificationPromptFromSelection(rendererValue, field) {
|
|
10253
|
+
return field
|
|
10254
|
+
? `Aplique a apresentacao visual ${rendererValue} na coluna ${field}.`
|
|
10255
|
+
: `Aplique a apresentacao visual ${rendererValue}.`;
|
|
10256
|
+
}
|
|
10257
|
+
visualPresentationRendererValue(option) {
|
|
10258
|
+
if (!this.isVisualPresentationClarificationText(option))
|
|
10259
|
+
return null;
|
|
10260
|
+
const normalized = this.normalizeLabel(option);
|
|
10261
|
+
if (normalized.includes('two lines')
|
|
10262
|
+
|| normalized.includes('two line')
|
|
10263
|
+
|| normalized.includes('two-line')
|
|
10264
|
+
|| normalized.includes('twolines')
|
|
10265
|
+
|| normalized.includes('twoline')
|
|
10266
|
+
|| normalized.includes('duas linhas')
|
|
10267
|
+
|| normalized.includes('duaslinhas')
|
|
10268
|
+
|| normalized.includes('duas_linhas')
|
|
10269
|
+
|| normalized.includes('segunda linha')) {
|
|
10270
|
+
return 'two_lines';
|
|
10271
|
+
}
|
|
10272
|
+
if (normalized.includes('badge') || normalized.includes('chip') || normalized.includes('etiqueta')) {
|
|
10273
|
+
return 'badge';
|
|
10274
|
+
}
|
|
10275
|
+
if (normalized.includes('icone') || normalized.includes('icon')) {
|
|
10276
|
+
return 'icon';
|
|
10277
|
+
}
|
|
10278
|
+
if (normalized.includes('alinhamento') || normalized.includes('alinhar')) {
|
|
10279
|
+
return 'alignment';
|
|
10280
|
+
}
|
|
10281
|
+
return null;
|
|
10282
|
+
}
|
|
10283
|
+
visualPresentationTargetField(response, request) {
|
|
10284
|
+
const text = this.normalizeLabel([
|
|
10285
|
+
request?.prompt,
|
|
10286
|
+
request?.pendingClarification?.sourcePrompt,
|
|
10287
|
+
request?.pendingClarification?.assistantMessage,
|
|
10288
|
+
response.message,
|
|
10289
|
+
...(response.questions ?? []),
|
|
10290
|
+
].filter(Boolean).join(' '));
|
|
10291
|
+
const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
10292
|
+
.map((column) => this.toRecord(column))
|
|
10293
|
+
.filter((column) => !!column);
|
|
10294
|
+
const matched = columns
|
|
10295
|
+
.map((column) => {
|
|
10296
|
+
const field = this.stringValue(column['field']);
|
|
10297
|
+
if (!field)
|
|
10298
|
+
return null;
|
|
10299
|
+
const header = this.stringValue(column['header']) || field;
|
|
10300
|
+
const score = Math.max(this.visualPresentationTargetFieldScore(text, field), this.visualPresentationTargetFieldScore(text, header));
|
|
10301
|
+
return score > 0 ? { field, score } : null;
|
|
10302
|
+
})
|
|
10303
|
+
.filter((entry) => !!entry)
|
|
10304
|
+
.sort((a, b) => b.score - a.score)[0];
|
|
10305
|
+
if (matched)
|
|
10306
|
+
return matched.field;
|
|
10307
|
+
const visibleColumns = columns.filter((column) => column['visible'] !== false);
|
|
10308
|
+
const lastVisibleField = this.stringValue(visibleColumns[visibleColumns.length - 1]?.['field']);
|
|
10309
|
+
return lastVisibleField || null;
|
|
10310
|
+
}
|
|
10311
|
+
visualPresentationTargetFieldScore(normalizedText, value) {
|
|
10312
|
+
return this.filterFieldMentionVariants(value)
|
|
10313
|
+
.filter((variant) => !!variant && this.normalizedTextContainsApproxPhrase(normalizedText, variant))
|
|
10314
|
+
.reduce((score, variant) => Math.max(score, variant.length), 0);
|
|
10315
|
+
}
|
|
10316
|
+
visualPresentationOptionContextHints(rendererValue, field) {
|
|
10317
|
+
const selected = {
|
|
10318
|
+
selection: {
|
|
10319
|
+
mode: 'renderer',
|
|
10320
|
+
...(field ? { field } : {}),
|
|
10321
|
+
value: rendererValue,
|
|
10322
|
+
},
|
|
10323
|
+
};
|
|
10324
|
+
if (field) {
|
|
10325
|
+
selected['targetField'] = field;
|
|
10326
|
+
}
|
|
10327
|
+
return {
|
|
10328
|
+
optionSelected: selected,
|
|
8880
10329
|
presentation: {
|
|
8881
10330
|
kind: 'guided-option',
|
|
8882
|
-
|
|
8883
|
-
|
|
10331
|
+
ctaLabel: 'Aplicar opção',
|
|
10332
|
+
icon: this.visualPresentationIcon(rendererValue),
|
|
10333
|
+
tone: 'primary',
|
|
8884
10334
|
},
|
|
8885
|
-
}
|
|
10335
|
+
};
|
|
10336
|
+
}
|
|
10337
|
+
visualPresentationIcon(rendererValue) {
|
|
10338
|
+
switch (rendererValue) {
|
|
10339
|
+
case 'badge':
|
|
10340
|
+
return 'sell';
|
|
10341
|
+
case 'icon':
|
|
10342
|
+
return 'stars';
|
|
10343
|
+
case 'alignment':
|
|
10344
|
+
return 'format_align_left';
|
|
10345
|
+
case 'two_lines':
|
|
10346
|
+
return 'format_line_spacing';
|
|
10347
|
+
default:
|
|
10348
|
+
return 'check';
|
|
10349
|
+
}
|
|
8886
10350
|
}
|
|
8887
10351
|
filterFieldCatalogEntryForClarificationLabel(label) {
|
|
8888
10352
|
const normalized = this.normalizeLabel(label);
|