@praxisui/table 8.0.0-beta.84 → 8.0.0-beta.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{praxisui-table-praxisui-table-mlOL4l4b.mjs → praxisui-table-praxisui-table-CpSv6jWv.mjs} +876 -102
- package/fesm2022/praxisui-table-table-agentic-authoring-turn-flow-Dhte2er9.mjs +7251 -0
- package/fesm2022/{praxisui-table-table-ai.adapter-BJA9_YlX.mjs → praxisui-table-table-ai.adapter-BvgPN7wY.mjs} +174 -4
- package/fesm2022/praxisui-table.mjs +1 -1
- package/package.json +10 -10
- package/src/lib/praxis-table.json-api.md +4 -0
- package/types/praxisui-table.d.ts +39 -7
- package/fesm2022/praxisui-table-table-agentic-authoring-turn-flow-DMglW7f1.mjs +0 -3536
|
@@ -1,3536 +0,0 @@
|
|
|
1
|
-
import { firstValueFrom, Observable } from 'rxjs';
|
|
2
|
-
|
|
3
|
-
class TableAgenticAuthoringTurnFlow {
|
|
4
|
-
adapter;
|
|
5
|
-
aiApi;
|
|
6
|
-
mode = 'agentic-authoring';
|
|
7
|
-
filterFieldLabels = new Map();
|
|
8
|
-
filterFieldCatalogEntries = [];
|
|
9
|
-
selectionDerivedFilterCandidateFields = new Set();
|
|
10
|
-
selectedRecordFieldsForTurn = new Set();
|
|
11
|
-
selectedRecordsCountForTurn = 0;
|
|
12
|
-
filterExpressionSupportedForTurn = null;
|
|
13
|
-
contextHintsForTurn = null;
|
|
14
|
-
streamTerminalWatchdogMs = 75_000;
|
|
15
|
-
streamTerminalReconnectLimit = 2;
|
|
16
|
-
selectedRecordStreamTerminalWatchdogMs = 45_000;
|
|
17
|
-
selectedRecordSurfaceStreamTerminalWatchdogMs = 18_000;
|
|
18
|
-
constructor(adapter, aiApi) {
|
|
19
|
-
this.adapter = adapter;
|
|
20
|
-
this.aiApi = aiApi;
|
|
21
|
-
}
|
|
22
|
-
submit(request) {
|
|
23
|
-
const prompt = (request.prompt ?? '').trim();
|
|
24
|
-
if (!prompt) {
|
|
25
|
-
return Promise.resolve({
|
|
26
|
-
state: 'listening',
|
|
27
|
-
phase: 'capture',
|
|
28
|
-
statusText: '',
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
const componentId = this.adapter.componentId || request.componentId || 'praxis-table';
|
|
32
|
-
const componentType = this.adapter.componentType || request.componentType || 'table';
|
|
33
|
-
const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
|
|
34
|
-
const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
|
|
35
|
-
const pendingCompletion = this.completePendingComponentEditClarification(request);
|
|
36
|
-
if (pendingCompletion) {
|
|
37
|
-
return Promise.resolve(this.toTurnResult(this.compileAdapterResponse(pendingCompletion, request), request));
|
|
38
|
-
}
|
|
39
|
-
const run = async () => {
|
|
40
|
-
await this.prepareAuthoringContext();
|
|
41
|
-
const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
|
|
42
|
-
const schemaFields = this.adapter.getSchemaFields?.()
|
|
43
|
-
?.map((field) => this.toAiJsonObject(field))
|
|
44
|
-
.filter((field) => Object.keys(field).length > 0);
|
|
45
|
-
const contextHints = this.mergeJsonObjects(this.mergeJsonObjects(this.optionalJsonObject(this.adapter.getAuthoringContext?.()), this.tableConversationMemoryHints(request)), this.optionalJsonObject(request.action?.contextHints)) ?? null;
|
|
46
|
-
this.filterFieldCatalogEntries = this.extractFilterFieldCatalogEntries(contextHints);
|
|
47
|
-
this.filterFieldLabels = this.extractFilterFieldLabels(this.filterFieldCatalogEntries);
|
|
48
|
-
this.selectionDerivedFilterCandidateFields = this.extractSelectionDerivedFilterCandidateFields(contextHints);
|
|
49
|
-
this.selectedRecordFieldsForTurn = this.extractSelectedRecordFields(contextHints);
|
|
50
|
-
this.selectedRecordsCountForTurn = this.extractSelectedRecordsCount(contextHints);
|
|
51
|
-
this.filterExpressionSupportedForTurn = this.resolveFilterExpressionSupported(contextHints);
|
|
52
|
-
this.contextHintsForTurn = contextHints;
|
|
53
|
-
const messages = this.withCapabilitySystemMessages(this.toChatMessages(request.messages, prompt), contextHints);
|
|
54
|
-
return {
|
|
55
|
-
contextHints,
|
|
56
|
-
patchRequest: {
|
|
57
|
-
componentId,
|
|
58
|
-
componentType,
|
|
59
|
-
userPrompt: prompt,
|
|
60
|
-
sessionId: request.sessionId,
|
|
61
|
-
clientTurnId: request.clientTurnId,
|
|
62
|
-
messages,
|
|
63
|
-
currentState,
|
|
64
|
-
currentStateDigest: this.buildCurrentStateDigest(currentState, dataProfile),
|
|
65
|
-
uiContextRef: {
|
|
66
|
-
componentId,
|
|
67
|
-
componentType,
|
|
68
|
-
},
|
|
69
|
-
...(dataProfile ? { dataProfile } : {}),
|
|
70
|
-
...(runtimeState ? { runtimeState } : {}),
|
|
71
|
-
...(schemaFields?.length ? { schemaFields } : {}),
|
|
72
|
-
...(contextHints ? { contextHints } : {}),
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
const progressPrompt = this.progressPromptFor(request, prompt);
|
|
77
|
-
if (this.canUsePatchStream()) {
|
|
78
|
-
return this.submitViaStream(request, progressPrompt, currentState, componentId, run);
|
|
79
|
-
}
|
|
80
|
-
return this.submitViaSnapshot(request, currentState, run);
|
|
81
|
-
}
|
|
82
|
-
async submitViaSnapshot(request, currentState, buildRequest) {
|
|
83
|
-
const { patchRequest } = await buildRequest();
|
|
84
|
-
const response = await firstValueFrom(this.aiApi.getPatch(patchRequest));
|
|
85
|
-
return this.toTurnResult(this.compileAdapterResponse(this.groundRelativeColumnOrder(response, request, currentState), request), request);
|
|
86
|
-
}
|
|
87
|
-
submitViaStream(request, prompt, currentState, componentId, buildRequest) {
|
|
88
|
-
return new Observable((subscriber) => {
|
|
89
|
-
let closed = false;
|
|
90
|
-
let connection = null;
|
|
91
|
-
let subscription = null;
|
|
92
|
-
let terminalWatchdog = null;
|
|
93
|
-
let streamId = null;
|
|
94
|
-
let streamAccessToken;
|
|
95
|
-
let lastEventId;
|
|
96
|
-
let reconnectAttempts = 0;
|
|
97
|
-
let streamContextHints = null;
|
|
98
|
-
const clearTerminalWatchdog = () => {
|
|
99
|
-
if (terminalWatchdog) {
|
|
100
|
-
clearTimeout(terminalWatchdog);
|
|
101
|
-
terminalWatchdog = null;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
const closeConnection = () => {
|
|
105
|
-
clearTerminalWatchdog();
|
|
106
|
-
subscription?.unsubscribe();
|
|
107
|
-
subscription = null;
|
|
108
|
-
if (connection) {
|
|
109
|
-
connection.close();
|
|
110
|
-
connection = null;
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
const emitProgress = (statusText) => {
|
|
114
|
-
if (closed || !statusText.trim())
|
|
115
|
-
return;
|
|
116
|
-
subscriber.next({
|
|
117
|
-
state: 'processing',
|
|
118
|
-
phase: 'contextualize',
|
|
119
|
-
statusText,
|
|
120
|
-
canApply: false,
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
const completeWithSnapshotFallback = async () => {
|
|
124
|
-
if (closed)
|
|
125
|
-
return;
|
|
126
|
-
let shouldClose = true;
|
|
127
|
-
try {
|
|
128
|
-
emitProgress(this.buildSnapshotFallbackMessage(prompt, componentId));
|
|
129
|
-
const result = await this.submitViaSnapshot(request, currentState, buildRequest);
|
|
130
|
-
if (this.isProcessingTurnResult(result)
|
|
131
|
-
&& streamId
|
|
132
|
-
&& reconnectAttempts < this.streamTerminalReconnectLimitFor(streamContextHints)) {
|
|
133
|
-
subscriber.next(result);
|
|
134
|
-
reconnectAttempts += 1;
|
|
135
|
-
shouldClose = false;
|
|
136
|
-
emitProgress(this.buildStreamReplayMessage(prompt, componentId));
|
|
137
|
-
openConnection();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (!closed) {
|
|
141
|
-
subscriber.next(result);
|
|
142
|
-
subscriber.complete();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
if (!closed)
|
|
147
|
-
subscriber.error(error);
|
|
148
|
-
}
|
|
149
|
-
finally {
|
|
150
|
-
if (shouldClose) {
|
|
151
|
-
closed = true;
|
|
152
|
-
closeConnection();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const armTerminalWatchdog = () => {
|
|
157
|
-
clearTerminalWatchdog();
|
|
158
|
-
const watchdogMs = this.streamTerminalWatchdogDelay(streamContextHints);
|
|
159
|
-
if (watchdogMs <= 0)
|
|
160
|
-
return;
|
|
161
|
-
terminalWatchdog = setTimeout(() => {
|
|
162
|
-
if (closed || !streamId)
|
|
163
|
-
return;
|
|
164
|
-
if (reconnectAttempts < this.streamTerminalReconnectLimitFor(streamContextHints)) {
|
|
165
|
-
reconnectAttempts += 1;
|
|
166
|
-
emitProgress(this.buildStreamReplayMessage(prompt, componentId));
|
|
167
|
-
openConnection();
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
void completeWithSnapshotFallback();
|
|
171
|
-
}, watchdogMs);
|
|
172
|
-
};
|
|
173
|
-
const openConnection = () => {
|
|
174
|
-
if (!streamId || closed)
|
|
175
|
-
return;
|
|
176
|
-
closeConnection();
|
|
177
|
-
connection = this.aiApi.connectPatchStream(streamId, lastEventId, streamAccessToken);
|
|
178
|
-
subscription = connection.events$.subscribe({
|
|
179
|
-
next: (event) => {
|
|
180
|
-
if (closed)
|
|
181
|
-
return;
|
|
182
|
-
if (event?.eventId) {
|
|
183
|
-
lastEventId = event.eventId;
|
|
184
|
-
}
|
|
185
|
-
const terminal = this.toTerminalStreamResult(event, request, currentState);
|
|
186
|
-
if (terminal) {
|
|
187
|
-
subscriber.next(terminal);
|
|
188
|
-
subscriber.complete();
|
|
189
|
-
closed = true;
|
|
190
|
-
closeConnection();
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
if (event.type !== 'heartbeat') {
|
|
194
|
-
armTerminalWatchdog();
|
|
195
|
-
}
|
|
196
|
-
const progress = this.buildStreamProgressMessage(event.type, this.toRecord(event.payload) ?? {}, prompt, componentId);
|
|
197
|
-
emitProgress(progress);
|
|
198
|
-
},
|
|
199
|
-
error: async () => {
|
|
200
|
-
if (closed)
|
|
201
|
-
return;
|
|
202
|
-
if (streamId && reconnectAttempts < this.streamTerminalReconnectLimitFor(streamContextHints)) {
|
|
203
|
-
reconnectAttempts += 1;
|
|
204
|
-
emitProgress(this.buildStreamReplayMessage(prompt, componentId));
|
|
205
|
-
openConnection();
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
await completeWithSnapshotFallback();
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
armTerminalWatchdog();
|
|
212
|
-
};
|
|
213
|
-
void (async () => {
|
|
214
|
-
try {
|
|
215
|
-
emitProgress(this.buildInitialProgressMessage(prompt, componentId));
|
|
216
|
-
const { patchRequest, contextHints } = await buildRequest();
|
|
217
|
-
streamContextHints = contextHints ?? null;
|
|
218
|
-
if (this.shouldUseSelectedRecordSurfaceSnapshotFallback(streamContextHints)) {
|
|
219
|
-
const result = await this.submitViaSnapshot(request, currentState, buildRequest);
|
|
220
|
-
if (!closed) {
|
|
221
|
-
subscriber.next(result);
|
|
222
|
-
subscriber.complete();
|
|
223
|
-
}
|
|
224
|
-
closed = true;
|
|
225
|
-
closeConnection();
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
const start = await this.awaitPatchStreamStart(patchRequest, streamContextHints);
|
|
229
|
-
streamId = start.streamId;
|
|
230
|
-
streamAccessToken = start.streamAccessToken ?? undefined;
|
|
231
|
-
if (start.observationId) {
|
|
232
|
-
emitProgress(this.buildStreamProgressMessage('status', { message: 'Stream iniciado.', phase: 'request' }, prompt, componentId));
|
|
233
|
-
}
|
|
234
|
-
openConnection();
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
if (closed)
|
|
238
|
-
return;
|
|
239
|
-
await completeWithSnapshotFallback();
|
|
240
|
-
}
|
|
241
|
-
})();
|
|
242
|
-
return () => {
|
|
243
|
-
closed = true;
|
|
244
|
-
closeConnection();
|
|
245
|
-
};
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
streamTerminalWatchdogDelay(contextHints) {
|
|
249
|
-
if (this.shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints)) {
|
|
250
|
-
return Math.min(this.streamTerminalWatchdogMs, this.selectedRecordSurfaceStreamTerminalWatchdogMs);
|
|
251
|
-
}
|
|
252
|
-
if (this.shouldUseSelectedRecordSnapshotFallback(contextHints)) {
|
|
253
|
-
return Math.min(this.streamTerminalWatchdogMs, this.selectedRecordStreamTerminalWatchdogMs);
|
|
254
|
-
}
|
|
255
|
-
return this.streamTerminalWatchdogMs;
|
|
256
|
-
}
|
|
257
|
-
streamTerminalReconnectLimitFor(contextHints) {
|
|
258
|
-
if (this.shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints)) {
|
|
259
|
-
return 0;
|
|
260
|
-
}
|
|
261
|
-
return this.streamTerminalReconnectLimit;
|
|
262
|
-
}
|
|
263
|
-
shouldUseSelectedRecordSnapshotFallback(contextHints) {
|
|
264
|
-
return this.selectedRecordsCountForTurn > 0
|
|
265
|
-
&& (this.selectedRecordFilterCandidates(contextHints).length > 0
|
|
266
|
-
|| this.shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints));
|
|
267
|
-
}
|
|
268
|
-
shouldUseSelectedRecordSurfaceSnapshotFallback(contextHints) {
|
|
269
|
-
return this.selectedRecordSurfaces(contextHints).length > 0;
|
|
270
|
-
}
|
|
271
|
-
selectedRecordSurfaces(contextHints) {
|
|
272
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
273
|
-
const consultativeContext = this.toRecord(authoringContract?.['consultativeContext']);
|
|
274
|
-
const recordSurfaces = this.toRecord(consultativeContext?.['recordSurfaces']);
|
|
275
|
-
const surfaces = Array.isArray(recordSurfaces?.['surfaces']) ? recordSurfaces['surfaces'] : [];
|
|
276
|
-
return surfaces
|
|
277
|
-
.map((surface) => this.toRecord(surface))
|
|
278
|
-
.filter((surface) => !!surface);
|
|
279
|
-
}
|
|
280
|
-
isProcessingTurnResult(result) {
|
|
281
|
-
return result?.state === 'processing';
|
|
282
|
-
}
|
|
283
|
-
awaitPatchStreamStart(patchRequest, contextHints) {
|
|
284
|
-
const start = firstValueFrom(this.aiApi.startPatchStream(patchRequest));
|
|
285
|
-
if (!this.shouldUseSelectedRecordSnapshotFallback(contextHints)) {
|
|
286
|
-
return start;
|
|
287
|
-
}
|
|
288
|
-
return this.withTimeout(start, this.streamTerminalWatchdogDelay(contextHints), 'selected-record-stream-start-timeout');
|
|
289
|
-
}
|
|
290
|
-
withTimeout(promise, timeoutMs, message) {
|
|
291
|
-
if (timeoutMs <= 0)
|
|
292
|
-
return promise;
|
|
293
|
-
return new Promise((resolve, reject) => {
|
|
294
|
-
const timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
295
|
-
promise.then((value) => {
|
|
296
|
-
clearTimeout(timeout);
|
|
297
|
-
resolve(value);
|
|
298
|
-
}, (error) => {
|
|
299
|
-
clearTimeout(timeout);
|
|
300
|
-
reject(error);
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
canUsePatchStream() {
|
|
305
|
-
return typeof this.aiApi.startPatchStream === 'function'
|
|
306
|
-
&& typeof this.aiApi.connectPatchStream === 'function';
|
|
307
|
-
}
|
|
308
|
-
toTerminalStreamResult(event, request, currentState) {
|
|
309
|
-
if (!event)
|
|
310
|
-
return null;
|
|
311
|
-
const payload = this.toRecord(event.payload) ?? {};
|
|
312
|
-
if (event.type === 'result') {
|
|
313
|
-
const response = (this.toRecord(payload['response']) ?? payload);
|
|
314
|
-
return this.toTurnResult(this.compileAdapterResponse(this.groundRelativeColumnOrder(response, request, currentState), request), request);
|
|
315
|
-
}
|
|
316
|
-
if (event.type === 'error' || event.type === 'cancelled') {
|
|
317
|
-
const message = this.stringValue(payload['assistantMessage'])
|
|
318
|
-
|| this.stringValue(payload['message'])
|
|
319
|
-
|| 'Nao foi possivel concluir o pedido da tabela.';
|
|
320
|
-
return {
|
|
321
|
-
state: 'error',
|
|
322
|
-
phase: 'capture',
|
|
323
|
-
assistantMessage: message,
|
|
324
|
-
errorText: message,
|
|
325
|
-
canApply: false,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
buildInitialProgressMessage(prompt, componentId) {
|
|
331
|
-
return `Vou acompanhar este pedido na ${this.componentProgressLabel(componentId)}: "${this.promptPreview(prompt)}".`;
|
|
332
|
-
}
|
|
333
|
-
buildSnapshotFallbackMessage(prompt, componentId) {
|
|
334
|
-
return `Vou continuar pela resposta direta para este pedido na ${this.componentProgressLabel(componentId)}: "${this.promptPreview(prompt)}".`;
|
|
335
|
-
}
|
|
336
|
-
buildStreamReplayMessage(prompt, componentId) {
|
|
337
|
-
return `Ainda estou aguardando a conclusão na ${this.componentProgressLabel(componentId)}; vou sincronizar novamente este pedido: "${this.promptPreview(prompt)}".`;
|
|
338
|
-
}
|
|
339
|
-
buildStreamProgressMessage(eventType, payload, prompt, componentId) {
|
|
340
|
-
const phase = this.stringValue(payload['phase']);
|
|
341
|
-
const title = this.stringValue(payload['title']);
|
|
342
|
-
const detail = this.stringValue(payload['detail']);
|
|
343
|
-
const message = this.stringValue(payload['message']);
|
|
344
|
-
const summary = this.stringValue(payload['summary']);
|
|
345
|
-
const base = detail || summary || message || title || this.progressFallbackForPhase(phase, eventType);
|
|
346
|
-
const target = this.componentProgressLabel(componentId);
|
|
347
|
-
const preview = this.promptPreview(prompt);
|
|
348
|
-
if (phase === 'request' || eventType === 'status' && payload['state'] === 'started') {
|
|
349
|
-
return `Pedido recebido na ${target}: "${preview}".`;
|
|
350
|
-
}
|
|
351
|
-
if (phase === 'proposal') {
|
|
352
|
-
return `${base} Pedido em foco: "${preview}".`;
|
|
353
|
-
}
|
|
354
|
-
if (phase === 'analysis') {
|
|
355
|
-
return `Analisando contexto e capacidades da ${target} para: "${preview}".`;
|
|
356
|
-
}
|
|
357
|
-
return `${base} Pedido: "${preview}".`;
|
|
358
|
-
}
|
|
359
|
-
progressFallbackForPhase(phase, eventType) {
|
|
360
|
-
if (phase === 'analysis')
|
|
361
|
-
return 'Analisando contexto e capacidades disponiveis.';
|
|
362
|
-
if (phase === 'proposal')
|
|
363
|
-
return 'Preparando uma resposta aplicavel.';
|
|
364
|
-
if (eventType === 'heartbeat')
|
|
365
|
-
return 'Ainda processando o pedido.';
|
|
366
|
-
return 'Processando o pedido.';
|
|
367
|
-
}
|
|
368
|
-
componentProgressLabel(componentId) {
|
|
369
|
-
return componentId === 'praxis-table' ? 'tabela dinamica' : 'superficie selecionada';
|
|
370
|
-
}
|
|
371
|
-
promptPreview(prompt) {
|
|
372
|
-
const normalized = prompt.replace(/\s+/g, ' ').trim();
|
|
373
|
-
return normalized.length > 90 ? `${normalized.slice(0, 87)}...` : normalized;
|
|
374
|
-
}
|
|
375
|
-
progressPromptFor(request, fallback) {
|
|
376
|
-
const displayPrompt = typeof request.action?.displayPrompt === 'string'
|
|
377
|
-
? request.action.displayPrompt.trim()
|
|
378
|
-
: '';
|
|
379
|
-
return displayPrompt || fallback;
|
|
380
|
-
}
|
|
381
|
-
normalizePrompt(prompt) {
|
|
382
|
-
return prompt
|
|
383
|
-
.normalize('NFD')
|
|
384
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
385
|
-
.toLowerCase()
|
|
386
|
-
.trim();
|
|
387
|
-
}
|
|
388
|
-
extractResourcePath(source) {
|
|
389
|
-
if (!source)
|
|
390
|
-
return null;
|
|
391
|
-
const direct = this.nonEmptyStringValue(source['resourcePath']);
|
|
392
|
-
if (direct)
|
|
393
|
-
return direct;
|
|
394
|
-
const consultativeContext = this.toRecord(source['consultativeContext']);
|
|
395
|
-
const fromConsultative = this.nonEmptyStringValue(consultativeContext?.['resourcePath']);
|
|
396
|
-
if (fromConsultative)
|
|
397
|
-
return fromConsultative;
|
|
398
|
-
const authoringContract = this.toRecord(source['authoringContract']);
|
|
399
|
-
if (authoringContract && authoringContract !== source) {
|
|
400
|
-
return this.extractResourcePath(authoringContract);
|
|
401
|
-
}
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
nonEmptyStringValue(value) {
|
|
405
|
-
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
406
|
-
}
|
|
407
|
-
async apply(request) {
|
|
408
|
-
const patch = this.toRecord(request.pendingPatch);
|
|
409
|
-
if (!patch) {
|
|
410
|
-
return {
|
|
411
|
-
state: 'error',
|
|
412
|
-
phase: 'apply',
|
|
413
|
-
assistantMessage: 'Não há alteração de tabela pronta para aplicar.',
|
|
414
|
-
errorText: 'Não há alteração de tabela pronta para aplicar.',
|
|
415
|
-
canApply: false,
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
const result = await this.adapter.applyPatch(patch, request.prompt);
|
|
419
|
-
if (!result.success) {
|
|
420
|
-
return {
|
|
421
|
-
state: 'error',
|
|
422
|
-
phase: 'apply',
|
|
423
|
-
assistantMessage: result.error || 'Não foi possível aplicar as alterações na tabela.',
|
|
424
|
-
errorText: result.error || 'Não foi possível aplicar as alterações na tabela.',
|
|
425
|
-
canApply: true,
|
|
426
|
-
pendingPatch: patch,
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
return {
|
|
430
|
-
state: 'success',
|
|
431
|
-
phase: 'summarize',
|
|
432
|
-
assistantMessage: 'Alterações aplicadas na tabela.',
|
|
433
|
-
statusText: 'Alterações aplicadas na tabela.',
|
|
434
|
-
canApply: false,
|
|
435
|
-
pendingPatch: null,
|
|
436
|
-
diagnostics: result.warnings?.length ? { warnings: result.warnings } : undefined,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
cancel() {
|
|
440
|
-
return Promise.resolve({
|
|
441
|
-
state: 'listening',
|
|
442
|
-
phase: 'capture',
|
|
443
|
-
assistantMessage: 'Solicitação cancelada.',
|
|
444
|
-
statusText: '',
|
|
445
|
-
canApply: false,
|
|
446
|
-
pendingPatch: null,
|
|
447
|
-
pendingClarification: null,
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
retry(request) {
|
|
451
|
-
const lastPrompt = [...(request.messages ?? [])].reverse()
|
|
452
|
-
.find((message) => message.role === 'user')?.text;
|
|
453
|
-
return this.submit({
|
|
454
|
-
...request,
|
|
455
|
-
prompt: lastPrompt ?? request.prompt,
|
|
456
|
-
action: { kind: 'retry' },
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
toTurnResult(response, request) {
|
|
460
|
-
if (!response) {
|
|
461
|
-
return {
|
|
462
|
-
state: 'error',
|
|
463
|
-
phase: 'capture',
|
|
464
|
-
assistantMessage: 'Resposta vazia da IA.',
|
|
465
|
-
errorText: 'Resposta vazia da IA.',
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
if (response.sessionId && response.sessionId !== request.sessionId) {
|
|
469
|
-
request = { ...request, sessionId: response.sessionId };
|
|
470
|
-
}
|
|
471
|
-
if (response.type === 'clarification') {
|
|
472
|
-
const questions = this.toClarificationQuestions(response, request);
|
|
473
|
-
const diagnostics = this.buildClarificationDiagnostics(response);
|
|
474
|
-
return {
|
|
475
|
-
state: 'clarification',
|
|
476
|
-
phase: 'clarify',
|
|
477
|
-
sessionId: response.sessionId ?? request.sessionId,
|
|
478
|
-
observationId: response.observationId ?? request.observationId ?? null,
|
|
479
|
-
assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
|
|
480
|
-
clarificationQuestions: questions,
|
|
481
|
-
quickReplies: this.toQuickReplies(response, request),
|
|
482
|
-
canApply: false,
|
|
483
|
-
diagnostics,
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
if (response.type === 'info') {
|
|
487
|
-
if (this.isTurnInProgressResponse(response)) {
|
|
488
|
-
const message = response.message || response.explanation || 'Ainda estou processando este pedido.';
|
|
489
|
-
return {
|
|
490
|
-
state: 'processing',
|
|
491
|
-
phase: 'contextualize',
|
|
492
|
-
sessionId: response.sessionId ?? request.sessionId,
|
|
493
|
-
observationId: response.observationId ?? request.observationId ?? null,
|
|
494
|
-
statusText: message,
|
|
495
|
-
canApply: false,
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
const message = response.message || response.explanation || 'Informação gerada.';
|
|
499
|
-
return {
|
|
500
|
-
state: 'success',
|
|
501
|
-
phase: 'summarize',
|
|
502
|
-
sessionId: response.sessionId ?? request.sessionId,
|
|
503
|
-
observationId: response.observationId ?? request.observationId ?? null,
|
|
504
|
-
assistantMessage: message,
|
|
505
|
-
statusText: message,
|
|
506
|
-
quickReplies: this.toQuickReplies(response, request),
|
|
507
|
-
canApply: false,
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
if (response.type === 'error') {
|
|
511
|
-
const message = response.message || 'Falha ao gerar alteração de tabela.';
|
|
512
|
-
return {
|
|
513
|
-
state: 'error',
|
|
514
|
-
phase: 'capture',
|
|
515
|
-
sessionId: response.sessionId ?? request.sessionId,
|
|
516
|
-
observationId: response.observationId ?? request.observationId ?? null,
|
|
517
|
-
assistantMessage: message,
|
|
518
|
-
errorText: message,
|
|
519
|
-
diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
523
|
-
const warnings = response.warnings?.filter(Boolean) ?? [];
|
|
524
|
-
const quickReplies = this.toQuickReplies(response, request);
|
|
525
|
-
const diagnostics = this.buildReviewDiagnostics(response, warnings);
|
|
526
|
-
return {
|
|
527
|
-
state: 'review',
|
|
528
|
-
phase: 'review',
|
|
529
|
-
sessionId: response.sessionId ?? request.sessionId,
|
|
530
|
-
observationId: response.observationId ?? request.observationId ?? null,
|
|
531
|
-
assistantMessage: this.toReviewMessage(response),
|
|
532
|
-
statusText: 'Revise a proposta antes de aplicar.',
|
|
533
|
-
quickReplies,
|
|
534
|
-
canApply: true,
|
|
535
|
-
pendingPatch: response.patch,
|
|
536
|
-
preview: {
|
|
537
|
-
kind: 'table-config-patch',
|
|
538
|
-
diff: response.diff ?? [],
|
|
539
|
-
},
|
|
540
|
-
diagnostics,
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
return {
|
|
544
|
-
state: 'success',
|
|
545
|
-
phase: 'summarize',
|
|
546
|
-
sessionId: response.sessionId ?? request.sessionId,
|
|
547
|
-
observationId: response.observationId ?? request.observationId ?? null,
|
|
548
|
-
assistantMessage: response.message || response.explanation || 'Nenhuma alteração necessária.',
|
|
549
|
-
statusText: response.message || response.explanation || 'Nenhuma alteração necessária.',
|
|
550
|
-
canApply: false,
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
isTurnInProgressResponse(response) {
|
|
554
|
-
return this.normalizeLabel(response.code ?? '') === 'turn in progress';
|
|
555
|
-
}
|
|
556
|
-
compileAdapterResponse(response, request) {
|
|
557
|
-
if (response.type === 'clarification' || response.type === 'info' || response.type === 'error') {
|
|
558
|
-
const compiledExecutable = !this.responseCarriesClarificationChoices(response)
|
|
559
|
-
&& this.responseMayContainExecutableEnvelope(response)
|
|
560
|
-
? this.adapter.compileAiResponse?.(response)
|
|
561
|
-
: null;
|
|
562
|
-
if (compiledExecutable?.patch && Object.keys(compiledExecutable.patch).length > 0) {
|
|
563
|
-
const normalizedExecutable = this.normalizeBulkRouteSelectionPayload(compiledExecutable, request);
|
|
564
|
-
const warnings = [
|
|
565
|
-
...(response.warnings ?? []),
|
|
566
|
-
...(normalizedExecutable.warnings ?? []),
|
|
567
|
-
];
|
|
568
|
-
const executableResponse = {
|
|
569
|
-
...response,
|
|
570
|
-
patch: normalizedExecutable.patch,
|
|
571
|
-
warnings: warnings.length ? warnings : undefined,
|
|
572
|
-
};
|
|
573
|
-
delete executableResponse.type;
|
|
574
|
-
return executableResponse;
|
|
575
|
-
}
|
|
576
|
-
if (compiledExecutable?.type === 'error') {
|
|
577
|
-
const continuedInvalidExecutable = this.selectedRecordSurfaceRuntimeOperationForInvalidExecutable(response, request, compiledExecutable.warnings);
|
|
578
|
-
if (continuedInvalidExecutable) {
|
|
579
|
-
return continuedInvalidExecutable;
|
|
580
|
-
}
|
|
581
|
-
const continuedBulkRouteAction = this.bulkRouteActionPlanForInvalidExecutable(response, request, compiledExecutable.warnings);
|
|
582
|
-
if (continuedBulkRouteAction) {
|
|
583
|
-
return this.compileAdapterResponse(continuedBulkRouteAction, request);
|
|
584
|
-
}
|
|
585
|
-
return {
|
|
586
|
-
type: 'error',
|
|
587
|
-
message: compiledExecutable.message || 'O componentEditPlan da tabela nao passou na validacao de capacidades.',
|
|
588
|
-
warnings: compiledExecutable.warnings,
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
const continuedSurfaceRowActionFromInfo = this.selectedRecordSurfaceRowActionPlanForInfo(response, request);
|
|
592
|
-
if (continuedSurfaceRowActionFromInfo) {
|
|
593
|
-
return this.compileAdapterResponse(continuedSurfaceRowActionFromInfo, request);
|
|
594
|
-
}
|
|
595
|
-
const continuedSurfaceRuntimeOperation = this.selectedRecordSurfaceRuntimeOperationForInfo(response, request);
|
|
596
|
-
if (continuedSurfaceRuntimeOperation) {
|
|
597
|
-
return continuedSurfaceRuntimeOperation;
|
|
598
|
-
}
|
|
599
|
-
const continuedBulkRouteNarrative = this.bulkRouteActionPlanForNonExecutableNarrative(response, request);
|
|
600
|
-
if (continuedBulkRouteNarrative) {
|
|
601
|
-
return this.compileAdapterResponse(continuedBulkRouteNarrative, request);
|
|
602
|
-
}
|
|
603
|
-
const continuedBulkRouteAction = this.bulkRouteActionPlanForClarification(response, request);
|
|
604
|
-
if (continuedBulkRouteAction) {
|
|
605
|
-
return this.compileAdapterResponse(continuedBulkRouteAction, request);
|
|
606
|
-
}
|
|
607
|
-
const continuedSurfaceRowAction = this.selectedRecordSurfaceRowActionPlanForClarification(response, request);
|
|
608
|
-
if (continuedSurfaceRowAction) {
|
|
609
|
-
return this.compileAdapterResponse(continuedSurfaceRowAction, request);
|
|
610
|
-
}
|
|
611
|
-
return response;
|
|
612
|
-
}
|
|
613
|
-
const rowActionGuidance = this.recordSurfaceRowActionGuidanceForConsult(response, request);
|
|
614
|
-
if (rowActionGuidance) {
|
|
615
|
-
return rowActionGuidance;
|
|
616
|
-
}
|
|
617
|
-
const compiled = this.adapter.compileAiResponse?.(response);
|
|
618
|
-
if (!compiled && response.patch && Object.keys(response.patch).length > 0) {
|
|
619
|
-
return {
|
|
620
|
-
type: 'error',
|
|
621
|
-
message: 'A tabela exige componentEditPlan validado pelo manifesto antes de gerar patch local.',
|
|
622
|
-
warnings: [
|
|
623
|
-
'free-table-patch-rejected',
|
|
624
|
-
'Use componentEditPlan validado contra PRAXIS_TABLE_AUTHORING_MANIFEST.',
|
|
625
|
-
],
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
if (!compiled) {
|
|
629
|
-
const continuedBulkRouteNarrative = this.bulkRouteActionPlanForNonExecutableNarrative(response, request);
|
|
630
|
-
if (continuedBulkRouteNarrative) {
|
|
631
|
-
return this.compileAdapterResponse(continuedBulkRouteNarrative, request);
|
|
632
|
-
}
|
|
633
|
-
return response;
|
|
634
|
-
}
|
|
635
|
-
if (compiled.type === 'error') {
|
|
636
|
-
const continuedBulkRouteAction = this.bulkRouteActionPlanForInvalidExecutable(response, request, compiled.warnings);
|
|
637
|
-
if (continuedBulkRouteAction) {
|
|
638
|
-
return this.compileAdapterResponse(continuedBulkRouteAction, request);
|
|
639
|
-
}
|
|
640
|
-
const continuedInvalidExecutable = this.selectedRecordSurfaceRuntimeOperationForInvalidExecutable(response, request, compiled.warnings);
|
|
641
|
-
if (continuedInvalidExecutable) {
|
|
642
|
-
return continuedInvalidExecutable.componentEditPlan
|
|
643
|
-
? this.compileAdapterResponse(continuedInvalidExecutable, request)
|
|
644
|
-
: continuedInvalidExecutable;
|
|
645
|
-
}
|
|
646
|
-
return {
|
|
647
|
-
type: 'error',
|
|
648
|
-
message: compiled.message || 'O componentEditPlan da tabela nao passou na validacao de capacidades.',
|
|
649
|
-
warnings: compiled.warnings,
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
const governedSelectedRecordFilter = this.isGovernedSelectedRecordFilterResponse(response);
|
|
653
|
-
if (!governedSelectedRecordFilter && this.shouldBlockUnsupportedFilterExpressionRuntimePatch(compiled.patch, request)) {
|
|
654
|
-
const questions = this.unsupportedFilterExpressionClarificationQuestions(compiled.patch);
|
|
655
|
-
return {
|
|
656
|
-
type: 'clarification',
|
|
657
|
-
message: [
|
|
658
|
-
'Esse pedido combina alternativas entre campos diferentes, mas o contrato atual da tabela so materializa filtros simples.',
|
|
659
|
-
'Posso aplicar uma unica escolha de filtro simples por vez, derivada dos campos suportados desta tabela, ou manter a tabela sem alteracao enquanto a intencao e refinada.',
|
|
660
|
-
].join(' '),
|
|
661
|
-
questions,
|
|
662
|
-
warnings: [
|
|
663
|
-
'unsupported-filter-expression-materialization-blocked',
|
|
664
|
-
'Residual textual boolean-operator guard acted only after LLM proposed a runtime table.filter.apply operation while filterExpression is unsupported.',
|
|
665
|
-
],
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
const implicitSelectedFilterClarification = governedSelectedRecordFilter ? null : this.implicitSelectedRecordFilterClarification(compiled.patch, request);
|
|
669
|
-
if (implicitSelectedFilterClarification) {
|
|
670
|
-
return implicitSelectedFilterClarification;
|
|
671
|
-
}
|
|
672
|
-
const normalizedCompiled = this.normalizeBulkRouteSelectionPayload(compiled, request);
|
|
673
|
-
const warnings = [
|
|
674
|
-
...(response.warnings ?? []),
|
|
675
|
-
...(normalizedCompiled.warnings ?? []),
|
|
676
|
-
];
|
|
677
|
-
return {
|
|
678
|
-
...response,
|
|
679
|
-
...normalizedCompiled,
|
|
680
|
-
patch: normalizedCompiled.patch,
|
|
681
|
-
warnings: warnings.length ? warnings : undefined,
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
normalizeBulkRouteSelectionPayload(compiled, request) {
|
|
685
|
-
if (!compiled?.patch || !request)
|
|
686
|
-
return compiled;
|
|
687
|
-
const text = this.normalizeLabel([
|
|
688
|
-
...(request.messages ?? [])
|
|
689
|
-
.filter((message) => message?.role === 'user')
|
|
690
|
-
.slice(-2)
|
|
691
|
-
.map((message) => message?.text ?? ''),
|
|
692
|
-
request.prompt ?? '',
|
|
693
|
-
].join(' '));
|
|
694
|
-
if (!this.textMentionsBulkActionRequest(text))
|
|
695
|
-
return compiled;
|
|
696
|
-
const routePath = this.extractRoutePath(text);
|
|
697
|
-
const patch = this.cloneJson(compiled.patch);
|
|
698
|
-
const componentEditPlan = compiled.componentEditPlan
|
|
699
|
-
? this.cloneJson(compiled.componentEditPlan)
|
|
700
|
-
: undefined;
|
|
701
|
-
const bulkActions = patch?.actions?.bulk?.actions;
|
|
702
|
-
let changed = false;
|
|
703
|
-
if (Array.isArray(bulkActions)) {
|
|
704
|
-
for (const action of bulkActions) {
|
|
705
|
-
if (!action || typeof action !== 'object')
|
|
706
|
-
continue;
|
|
707
|
-
if (this.ensureBulkRouteActionSelectionQuery(action, routePath)) {
|
|
708
|
-
changed = true;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
if (componentEditPlan && this.ensureBulkRouteActionSelectionQueryOnPlan(componentEditPlan, routePath)) {
|
|
713
|
-
changed = true;
|
|
714
|
-
}
|
|
715
|
-
if (!Array.isArray(bulkActions) && !componentEditPlan)
|
|
716
|
-
return compiled;
|
|
717
|
-
if (!changed)
|
|
718
|
-
return compiled;
|
|
719
|
-
return {
|
|
720
|
-
...compiled,
|
|
721
|
-
...(componentEditPlan ? { componentEditPlan } : {}),
|
|
722
|
-
patch,
|
|
723
|
-
warnings: [
|
|
724
|
-
...(compiled.warnings ?? []),
|
|
725
|
-
'bulk-route-action-selection-query-normalized',
|
|
726
|
-
],
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
ensureBulkRouteActionSelectionQueryOnPlan(plan, routePath) {
|
|
730
|
-
if (!plan || typeof plan !== 'object')
|
|
731
|
-
return false;
|
|
732
|
-
let changed = false;
|
|
733
|
-
for (const key of ['input', 'value']) {
|
|
734
|
-
if (this.ensureBulkRouteActionSelectionQuery(plan[key], routePath)) {
|
|
735
|
-
changed = true;
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
if (Array.isArray(plan.operations)) {
|
|
739
|
-
for (const operation of plan.operations) {
|
|
740
|
-
if (!operation || typeof operation !== 'object')
|
|
741
|
-
continue;
|
|
742
|
-
for (const key of ['input', 'value']) {
|
|
743
|
-
if (this.ensureBulkRouteActionSelectionQuery(operation[key], routePath)) {
|
|
744
|
-
changed = true;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
return changed;
|
|
750
|
-
}
|
|
751
|
-
ensureBulkRouteActionSelectionQuery(action, routePath) {
|
|
752
|
-
if (!action || typeof action !== 'object')
|
|
753
|
-
return false;
|
|
754
|
-
let changed = false;
|
|
755
|
-
if (this.ensureBulkRouteActionGlobalRouteRef(action, routePath)) {
|
|
756
|
-
changed = true;
|
|
757
|
-
}
|
|
758
|
-
if (this.ensureGlobalRouteActionSelectionQuery(action.globalAction)) {
|
|
759
|
-
changed = true;
|
|
760
|
-
}
|
|
761
|
-
if (Array.isArray(action.effects)) {
|
|
762
|
-
for (const effect of action.effects) {
|
|
763
|
-
if (effect?.kind === 'global-action' && this.ensureGlobalRouteActionSelectionQuery(effect.globalAction)) {
|
|
764
|
-
changed = true;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return changed;
|
|
769
|
-
}
|
|
770
|
-
ensureBulkRouteActionGlobalRouteRef(action, routePath) {
|
|
771
|
-
if (!routePath)
|
|
772
|
-
return false;
|
|
773
|
-
if (this.hasGlobalRouteActionRef(action))
|
|
774
|
-
return false;
|
|
775
|
-
const configuredAction = this.stringValue(action.action);
|
|
776
|
-
const looksLikeRouteAction = configuredAction === 'navigation.openRoute'
|
|
777
|
-
|| configuredAction === 'navigation.open route'
|
|
778
|
-
|| this.stringValue(action.globalAction?.actionId) === 'navigation.openRoute';
|
|
779
|
-
const hasRoutePayload = this.stringValue(action.payload?.path)
|
|
780
|
-
|| this.stringValue(action.globalAction?.payload?.path)
|
|
781
|
-
|| routePath;
|
|
782
|
-
if (!looksLikeRouteAction && !hasRoutePayload)
|
|
783
|
-
return false;
|
|
784
|
-
const globalAction = {
|
|
785
|
-
actionId: 'navigation.openRoute',
|
|
786
|
-
payload: {
|
|
787
|
-
path: this.stringValue(action.payload?.path)
|
|
788
|
-
|| this.stringValue(action.globalAction?.payload?.path)
|
|
789
|
-
|| routePath,
|
|
790
|
-
query: { ids: '${runtime.selectedIds}' },
|
|
791
|
-
},
|
|
792
|
-
};
|
|
793
|
-
action.globalAction = globalAction;
|
|
794
|
-
action.effects = [{ kind: 'global-action', globalAction }];
|
|
795
|
-
if (!this.stringValue(action.action)) {
|
|
796
|
-
action.action = 'navigation.openRoute';
|
|
797
|
-
}
|
|
798
|
-
return true;
|
|
799
|
-
}
|
|
800
|
-
hasGlobalRouteActionRef(action) {
|
|
801
|
-
if (this.stringValue(action?.globalAction?.actionId) === 'navigation.openRoute') {
|
|
802
|
-
return true;
|
|
803
|
-
}
|
|
804
|
-
if (!Array.isArray(action?.effects))
|
|
805
|
-
return false;
|
|
806
|
-
return action.effects.some((effect) => effect?.kind === 'global-action'
|
|
807
|
-
&& this.stringValue(effect.globalAction?.actionId) === 'navigation.openRoute');
|
|
808
|
-
}
|
|
809
|
-
ensureGlobalRouteActionSelectionQuery(globalAction) {
|
|
810
|
-
if (!globalAction || typeof globalAction !== 'object')
|
|
811
|
-
return false;
|
|
812
|
-
if (globalAction.actionId !== 'navigation.openRoute')
|
|
813
|
-
return false;
|
|
814
|
-
const payload = globalAction.payload;
|
|
815
|
-
if (!payload || typeof payload !== 'object')
|
|
816
|
-
return false;
|
|
817
|
-
if (!this.stringValue(payload.path))
|
|
818
|
-
return false;
|
|
819
|
-
if (!payload.query || typeof payload.query !== 'object' || Array.isArray(payload.query)) {
|
|
820
|
-
payload.query = {};
|
|
821
|
-
}
|
|
822
|
-
if (payload.query.ids !== undefined && payload.query.ids !== null && payload.query.ids !== '') {
|
|
823
|
-
return false;
|
|
824
|
-
}
|
|
825
|
-
payload.query.ids = '${runtime.selectedIds}';
|
|
826
|
-
return true;
|
|
827
|
-
}
|
|
828
|
-
recordSurfaceRowActionGuidanceForConsult(response, request) {
|
|
829
|
-
if (!request || !this.promptAsksRowActionCapabilities(request))
|
|
830
|
-
return null;
|
|
831
|
-
if (!this.responseCarriesRowActionAddPlan(response))
|
|
832
|
-
return null;
|
|
833
|
-
const surfaces = this.selectedRecordSurfaces(this.contextHintsFor(request));
|
|
834
|
-
if (!surfaces.length)
|
|
835
|
-
return null;
|
|
836
|
-
const labels = [...new Set(surfaces
|
|
837
|
-
.map((surface) => {
|
|
838
|
-
const resourceSurface = this.toRecord(surface['resourceSurface']);
|
|
839
|
-
return this.stringValue(surface['label'])
|
|
840
|
-
|| this.stringValue(resourceSurface?.['title'])
|
|
841
|
-
|| this.stringValue(surface['id']);
|
|
842
|
-
})
|
|
843
|
-
.filter((label) => !!label))];
|
|
844
|
-
if (!labels.length)
|
|
845
|
-
return null;
|
|
846
|
-
return {
|
|
847
|
-
type: 'info',
|
|
848
|
-
message: [
|
|
849
|
-
'Você pode adicionar botões de linha para superfícies relacionadas declaradas para este recurso.',
|
|
850
|
-
`Opções disponíveis agora: ${labels.map((label) => `**${label}**`).join(', ')}.`,
|
|
851
|
-
'Quando quiser materializar uma delas, peça algo como "adicione um botão na linha para abrir Histórico de folha".',
|
|
852
|
-
].join(' '),
|
|
853
|
-
warnings: [
|
|
854
|
-
'row-action-capability-question-preserved-as-consult',
|
|
855
|
-
'Residual consult guard acted only after the LLM proposed rowAction.add for a row-action capability-discovery question while declared recordSurfaces were available.',
|
|
856
|
-
],
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
promptAsksRowActionCapabilities(request) {
|
|
860
|
-
const currentPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
861
|
-
const recentUserContext = this.normalizeLabel((request.messages ?? [])
|
|
862
|
-
.filter((message) => message?.role === 'user')
|
|
863
|
-
.slice(-2)
|
|
864
|
-
.map((message) => message?.text ?? '')
|
|
865
|
-
.join(' '));
|
|
866
|
-
const text = [recentUserContext, currentPrompt].filter(Boolean).join(' ');
|
|
867
|
-
if (!this.textMentionsRowActionRequest(text))
|
|
868
|
-
return false;
|
|
869
|
-
const asksAvailability = [
|
|
870
|
-
'quais',
|
|
871
|
-
'qual',
|
|
872
|
-
'o que',
|
|
873
|
-
'que botoes',
|
|
874
|
-
'que botões',
|
|
875
|
-
'botoes posso',
|
|
876
|
-
'botões posso',
|
|
877
|
-
'acoes disponiveis',
|
|
878
|
-
'ações disponíveis',
|
|
879
|
-
'acoes estao disponiveis',
|
|
880
|
-
'ações estão disponíveis',
|
|
881
|
-
'posso adicionar',
|
|
882
|
-
'can i add',
|
|
883
|
-
'available',
|
|
884
|
-
'what actions',
|
|
885
|
-
'which actions',
|
|
886
|
-
'which buttons',
|
|
887
|
-
].some((needle) => text.includes(this.normalizeLabel(needle)));
|
|
888
|
-
if (!asksAvailability)
|
|
889
|
-
return false;
|
|
890
|
-
const explicitMaterialization = [
|
|
891
|
-
'adicione',
|
|
892
|
-
'adicionar agora',
|
|
893
|
-
'crie',
|
|
894
|
-
'criar agora',
|
|
895
|
-
'coloque',
|
|
896
|
-
'aplique',
|
|
897
|
-
'materialize',
|
|
898
|
-
'create a',
|
|
899
|
-
'add a',
|
|
900
|
-
].some((needle) => currentPrompt.includes(this.normalizeLabel(needle)));
|
|
901
|
-
return !explicitMaterialization;
|
|
902
|
-
}
|
|
903
|
-
responseCarriesRowActionAddPlan(response) {
|
|
904
|
-
const record = response;
|
|
905
|
-
return [
|
|
906
|
-
this.toRecord(record['componentEditPlan']),
|
|
907
|
-
this.toRecord(this.toRecord(record['patch'])?.['componentEditPlan']),
|
|
908
|
-
this.toRecord(record['tableEditPlan']),
|
|
909
|
-
this.toRecord(record['editPlan']),
|
|
910
|
-
].some((plan) => {
|
|
911
|
-
if (!plan)
|
|
912
|
-
return false;
|
|
913
|
-
if (this.stringValue(plan['operationId']) === 'rowAction.add')
|
|
914
|
-
return true;
|
|
915
|
-
const operations = Array.isArray(plan['operations']) ? plan['operations'] : [];
|
|
916
|
-
return operations.some((operation) => this.stringValue(this.toRecord(operation)?.['operationId']) === 'rowAction.add');
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
responseCarriesClarificationChoices(response) {
|
|
920
|
-
if (response.type !== 'clarification')
|
|
921
|
-
return false;
|
|
922
|
-
const record = response;
|
|
923
|
-
const questions = Array.isArray(response.questions) ? response.questions : [];
|
|
924
|
-
const optionPayloads = Array.isArray(record['optionPayloads']) ? record['optionPayloads'] : [];
|
|
925
|
-
return questions.length > 0 || optionPayloads.length > 0;
|
|
926
|
-
}
|
|
927
|
-
responseMayContainExecutableEnvelope(response) {
|
|
928
|
-
return this.responseMayContainRuntimeOperationEnvelope(response)
|
|
929
|
-
|| this.responseMayContainComponentEditPlanEnvelope(response);
|
|
930
|
-
}
|
|
931
|
-
responseMayContainRuntimeOperationEnvelope(response) {
|
|
932
|
-
const record = response;
|
|
933
|
-
if (record['tableRuntimeOperations'])
|
|
934
|
-
return true;
|
|
935
|
-
const patch = this.toRecord(record['patch']);
|
|
936
|
-
if (patch?.['tableRuntimeOperations'])
|
|
937
|
-
return true;
|
|
938
|
-
return ['message', 'assistantMessage', 'content', 'explanation'].some((key) => {
|
|
939
|
-
const value = this.stringValue(record[key]);
|
|
940
|
-
return value.includes('tableRuntimeOperations') && value.includes('operationId');
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
responseMayContainComponentEditPlanEnvelope(response) {
|
|
944
|
-
const record = response;
|
|
945
|
-
if (record['componentEditPlan'] || record['tableEditPlan'] || record['editPlan'])
|
|
946
|
-
return true;
|
|
947
|
-
return ['message', 'assistantMessage', 'content', 'explanation'].some((key) => {
|
|
948
|
-
const value = this.stringValue(record[key]);
|
|
949
|
-
return value.includes('componentEditPlan') && value.includes('operationId');
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
isGovernedSelectedRecordFilterResponse(response) {
|
|
953
|
-
return (response.warnings ?? []).some((warning) => [
|
|
954
|
-
'selected-record-filter-request-materialized',
|
|
955
|
-
'selected-record-filter-clarification-materialized',
|
|
956
|
-
].includes(warning));
|
|
957
|
-
}
|
|
958
|
-
shouldBlockUnsupportedFilterExpressionRuntimePatch(patch, request) {
|
|
959
|
-
if (this.filterExpressionSupportedForTurn === true)
|
|
960
|
-
return false;
|
|
961
|
-
if (!this.patchHasTableFilterApply(patch))
|
|
962
|
-
return false;
|
|
963
|
-
const prompt = this.normalizeLabel(request?.prompt ?? '');
|
|
964
|
-
// Safety guard only after an LLM-authored executable operation exists. It does
|
|
965
|
-
// not route primary intent; it blocks degraded materialization of explicit
|
|
966
|
-
// boolean alternatives when the canonical contract lacks filterExpression.
|
|
967
|
-
return /\b(ou|or|anyof|oneof|allof)\b/u.test(prompt);
|
|
968
|
-
}
|
|
969
|
-
unsupportedFilterExpressionClarificationQuestions(patch) {
|
|
970
|
-
const labels = this.attemptedRuntimeFilterFieldLabels(patch);
|
|
971
|
-
const fieldOptions = labels
|
|
972
|
-
.slice(0, 2)
|
|
973
|
-
.map((label) => `Aplicar apenas o filtro por ${label}`);
|
|
974
|
-
return [
|
|
975
|
-
...fieldOptions,
|
|
976
|
-
'Nao aplicar filtro e revisar a intencao',
|
|
977
|
-
].slice(0, 3);
|
|
978
|
-
}
|
|
979
|
-
attemptedRuntimeFilterFieldLabels(patch) {
|
|
980
|
-
const envelope = this.toRecord(this.toRecord(patch)?.['tableRuntimeOperations']);
|
|
981
|
-
const operations = Array.isArray(envelope?.['operations']) ? envelope['operations'] : [];
|
|
982
|
-
const labels = new Set();
|
|
983
|
-
for (const operation of operations) {
|
|
984
|
-
const record = this.toRecord(operation);
|
|
985
|
-
if (this.stringValue(record?.['operationId']) !== 'table.filter.apply')
|
|
986
|
-
continue;
|
|
987
|
-
const input = this.toRecord(record?.['input']) ?? {};
|
|
988
|
-
const criteria = this.toRecord(input['criteria']) ?? {};
|
|
989
|
-
for (const field of Object.keys(criteria)) {
|
|
990
|
-
labels.add(this.humanizeFilterField(field));
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
return [...labels];
|
|
994
|
-
}
|
|
995
|
-
patchHasTableFilterApply(patch) {
|
|
996
|
-
const envelope = this.toRecord(this.toRecord(patch)?.['tableRuntimeOperations']);
|
|
997
|
-
const operations = Array.isArray(envelope?.['operations']) ? envelope['operations'] : [];
|
|
998
|
-
return operations
|
|
999
|
-
.map((operation) => this.toRecord(operation))
|
|
1000
|
-
.some((operation) => this.stringValue(operation?.['operationId']) === 'table.filter.apply');
|
|
1001
|
-
}
|
|
1002
|
-
selectedRecordFilterCandidate(contextHints, field) {
|
|
1003
|
-
const normalizedField = field.trim();
|
|
1004
|
-
if (!normalizedField)
|
|
1005
|
-
return null;
|
|
1006
|
-
return this.selectedRecordFilterCandidates(contextHints)
|
|
1007
|
-
.find((candidate) => this.stringValue(candidate['field']) === normalizedField) ?? null;
|
|
1008
|
-
}
|
|
1009
|
-
selectedRecordFilterCandidates(contextHints) {
|
|
1010
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
1011
|
-
const consultativeContext = this.toRecord(authoringContract?.['consultativeContext']);
|
|
1012
|
-
const selectedRecordsContext = this.toRecord(consultativeContext?.['selectedRecordsContext'])
|
|
1013
|
-
?? this.toRecord(contextHints?.['selectedRecordsContext']);
|
|
1014
|
-
const candidates = Array.isArray(selectedRecordsContext?.['filterCandidates'])
|
|
1015
|
-
? selectedRecordsContext['filterCandidates']
|
|
1016
|
-
: [];
|
|
1017
|
-
return candidates
|
|
1018
|
-
.map((candidate) => this.toRecord(candidate))
|
|
1019
|
-
.filter((candidate) => !!candidate);
|
|
1020
|
-
}
|
|
1021
|
-
selectedRecordSampleRows(contextHints) {
|
|
1022
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
1023
|
-
const consultativeContext = this.toRecord(authoringContract?.['consultativeContext']);
|
|
1024
|
-
const selectedRecordsContext = this.toRecord(consultativeContext?.['selectedRecordsContext'])
|
|
1025
|
-
?? this.toRecord(contextHints?.['selectedRecordsContext']);
|
|
1026
|
-
const sampleRows = Array.isArray(selectedRecordsContext?.['sampleRows'])
|
|
1027
|
-
? selectedRecordsContext['sampleRows']
|
|
1028
|
-
: [];
|
|
1029
|
-
return sampleRows
|
|
1030
|
-
.map((row) => this.toRecord(row))
|
|
1031
|
-
.filter((row) => !!row);
|
|
1032
|
-
}
|
|
1033
|
-
selectedRecordFilterClarificationOptionPayload(entry, contextHints) {
|
|
1034
|
-
const description = this.selectedRecordFilterCandidateDescription(entry.name, contextHints);
|
|
1035
|
-
return {
|
|
1036
|
-
value: entry.name,
|
|
1037
|
-
label: `Filtrar por ${entry.label}`,
|
|
1038
|
-
contextHints: {
|
|
1039
|
-
selectedRecordsFilter: {
|
|
1040
|
-
field: entry.name,
|
|
1041
|
-
label: entry.label,
|
|
1042
|
-
source: 'selected-record-clarification',
|
|
1043
|
-
},
|
|
1044
|
-
...(description
|
|
1045
|
-
? {
|
|
1046
|
-
presentation: {
|
|
1047
|
-
kind: 'guided-option',
|
|
1048
|
-
icon: 'check',
|
|
1049
|
-
description,
|
|
1050
|
-
ctaLabel: 'Usar esta opção',
|
|
1051
|
-
},
|
|
1052
|
-
}
|
|
1053
|
-
: {}),
|
|
1054
|
-
},
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
selectedRecordFilterCandidateDescription(field, contextHints) {
|
|
1058
|
-
const candidate = this.selectedRecordFilterCandidate(contextHints, field);
|
|
1059
|
-
const displayValues = Array.isArray(candidate?.['displayValues'])
|
|
1060
|
-
? candidate['displayValues']
|
|
1061
|
-
.map((entry) => this.formatSelectedRecordFilterValue(entry))
|
|
1062
|
-
.filter((entry) => !!entry)
|
|
1063
|
-
: [];
|
|
1064
|
-
if (displayValues.length) {
|
|
1065
|
-
const preview = displayValues.slice(0, 3).join(', ');
|
|
1066
|
-
return `Valores dos selecionados: ${preview}${displayValues.length > 3 ? ', ...' : ''}`;
|
|
1067
|
-
}
|
|
1068
|
-
const criteria = this.toRecord(candidate?.['criteria']);
|
|
1069
|
-
const value = criteria?.[field];
|
|
1070
|
-
if (value === undefined || value === null)
|
|
1071
|
-
return null;
|
|
1072
|
-
const record = this.toRecord(value);
|
|
1073
|
-
if (record) {
|
|
1074
|
-
const start = record['start'] ?? record['startDate'];
|
|
1075
|
-
const end = record['end'] ?? record['endDate'];
|
|
1076
|
-
if (start !== undefined && end !== undefined) {
|
|
1077
|
-
return `Valores dos selecionados: ${this.formatSelectedRecordFilterValue(start)} até ${this.formatSelectedRecordFilterValue(end)}`;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
if (Array.isArray(value) && value.length) {
|
|
1081
|
-
const preview = value.slice(0, 3).map((entry) => this.formatSelectedRecordFilterValue(entry)).join(', ');
|
|
1082
|
-
return `Valores dos selecionados: ${preview}${value.length > 3 ? ', ...' : ''}`;
|
|
1083
|
-
}
|
|
1084
|
-
return null;
|
|
1085
|
-
}
|
|
1086
|
-
formatSelectedRecordFilterValue(value) {
|
|
1087
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
1088
|
-
return new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 2 }).format(value);
|
|
1089
|
-
}
|
|
1090
|
-
if (typeof value === 'string') {
|
|
1091
|
-
const trimmed = value.trim();
|
|
1092
|
-
const dateMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/u);
|
|
1093
|
-
if (dateMatch)
|
|
1094
|
-
return `${dateMatch[3]}/${dateMatch[2]}/${dateMatch[1]}`;
|
|
1095
|
-
return trimmed;
|
|
1096
|
-
}
|
|
1097
|
-
if (typeof value === 'boolean')
|
|
1098
|
-
return value ? 'sim' : 'não';
|
|
1099
|
-
return String(value ?? '');
|
|
1100
|
-
}
|
|
1101
|
-
implicitSelectedRecordFilterClarification(patch, request) {
|
|
1102
|
-
if (this.selectedRecordsCountForTurn <= 0)
|
|
1103
|
-
return null;
|
|
1104
|
-
if (this.filterFieldCatalogEntries.length < 2)
|
|
1105
|
-
return null;
|
|
1106
|
-
const operations = this.tableFilterApplyOperations(patch);
|
|
1107
|
-
if (!operations.length)
|
|
1108
|
-
return null;
|
|
1109
|
-
const prompt = this.normalizeLabel(request?.prompt ?? '');
|
|
1110
|
-
if (!prompt)
|
|
1111
|
-
return null;
|
|
1112
|
-
const groundedSurfaceOperation = this.selectedRecordSurfaceOperationForMisgroundedFilter(prompt, request);
|
|
1113
|
-
if (groundedSurfaceOperation) {
|
|
1114
|
-
return groundedSurfaceOperation;
|
|
1115
|
-
}
|
|
1116
|
-
const implicitFields = operations
|
|
1117
|
-
.flatMap((operation) => Object.keys(this.toRecord(this.toRecord(operation['input'])?.['criteria']) ?? {}))
|
|
1118
|
-
.filter((field) => field && !this.promptMentionsFilterField(prompt, field));
|
|
1119
|
-
if (!implicitFields.length)
|
|
1120
|
-
return null;
|
|
1121
|
-
const contextHints = this.contextHintsFor(request);
|
|
1122
|
-
// This guard runs only after the LLM authored an executable table.filter.apply
|
|
1123
|
-
// operation. It does not route primary intent; it blocks ungrounded
|
|
1124
|
-
// materialization when selected rows offer multiple plausible filter fields.
|
|
1125
|
-
const options = this.selectedRecordFilterClarificationOptionEntries(implicitFields);
|
|
1126
|
-
if (options.length < 2)
|
|
1127
|
-
return null;
|
|
1128
|
-
return {
|
|
1129
|
-
type: 'clarification',
|
|
1130
|
-
message: [
|
|
1131
|
-
`Encontrei ${this.selectedRecordsCountForTurn} registro${this.selectedRecordsCountForTurn === 1 ? '' : 's'} selecionado${this.selectedRecordsCountForTurn === 1 ? '' : 's'}.`,
|
|
1132
|
-
'Para buscar registros parecidos, escolha qual propriedade deve guiar o filtro.',
|
|
1133
|
-
].join(' '),
|
|
1134
|
-
questions: ['Como você quer definir registros parecidos?'],
|
|
1135
|
-
optionPayloads: options.map((entry) => this.selectedRecordFilterClarificationOptionPayload(entry, contextHints)),
|
|
1136
|
-
warnings: [
|
|
1137
|
-
'implicit-selected-record-filter-materialization-blocked',
|
|
1138
|
-
'Residual grounding guard acted only after LLM proposed table.filter.apply from selected-record context with multiple plausible filter fields.',
|
|
1139
|
-
],
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
selectedRecordSurfaceOperationForMisgroundedFilter(normalizedPrompt, request) {
|
|
1143
|
-
if (this.promptRequestsSimilarRecords(normalizedPrompt))
|
|
1144
|
-
return null;
|
|
1145
|
-
const contextHints = this.contextHintsFor(request);
|
|
1146
|
-
const surfaces = this.selectedRecordSurfaces(contextHints);
|
|
1147
|
-
if (!surfaces.length)
|
|
1148
|
-
return null;
|
|
1149
|
-
const ranked = surfaces
|
|
1150
|
-
.map((surface) => ({
|
|
1151
|
-
surface,
|
|
1152
|
-
score: this.selectedRecordSurfacePromptScore(normalizedPrompt, surface),
|
|
1153
|
-
}))
|
|
1154
|
-
.filter((entry) => entry.score >= 2)
|
|
1155
|
-
.sort((a, b) => b.score - a.score);
|
|
1156
|
-
if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
|
|
1157
|
-
return null;
|
|
1158
|
-
}
|
|
1159
|
-
const surface = ranked[0].surface;
|
|
1160
|
-
const surfaceId = this.stringValue(surface['id']);
|
|
1161
|
-
if (!surfaceId)
|
|
1162
|
-
return null;
|
|
1163
|
-
const surfaceLabel = this.stringValue(surface['label'])
|
|
1164
|
-
|| this.stringValue(this.toRecord(surface['resourceSurface'])?.['title'])
|
|
1165
|
-
|| this.humanizeField(surfaceId);
|
|
1166
|
-
// Residual grounding guard: this runs only after the LLM authored a
|
|
1167
|
-
// selected-record table.filter.apply operation. It ranks declared canonical
|
|
1168
|
-
// recordSurfaces to correct an unsafe filter materialization, not to route
|
|
1169
|
-
// primary user intent by command text.
|
|
1170
|
-
return {
|
|
1171
|
-
type: 'patch',
|
|
1172
|
-
patch: {
|
|
1173
|
-
tableRuntimeOperations: {
|
|
1174
|
-
source: 'selected-record-surface-grounding-guard',
|
|
1175
|
-
operations: [{
|
|
1176
|
-
operationId: 'dynamicPage.surface.open',
|
|
1177
|
-
input: {
|
|
1178
|
-
surfaceId,
|
|
1179
|
-
surfaceLabel,
|
|
1180
|
-
source: 'selected-records',
|
|
1181
|
-
},
|
|
1182
|
-
}],
|
|
1183
|
-
},
|
|
1184
|
-
},
|
|
1185
|
-
warnings: [
|
|
1186
|
-
'selected-record-filter-materialization-corrected-to-record-surface',
|
|
1187
|
-
'Residual grounding guard acted only after LLM proposed table.filter.apply while declared recordSurfaces semantically matched the selected-record request.',
|
|
1188
|
-
],
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
selectedRecordSurfaceRowActionPlanForClarification(response, request) {
|
|
1192
|
-
if (!request)
|
|
1193
|
-
return null;
|
|
1194
|
-
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
1195
|
-
const normalizedConversation = this.normalizeLabel([
|
|
1196
|
-
...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
|
|
1197
|
-
request.prompt ?? '',
|
|
1198
|
-
].join(' '));
|
|
1199
|
-
if (!this.textMentionsRowActionRequest(`${normalizedPrompt} ${normalizedConversation}`)) {
|
|
1200
|
-
return null;
|
|
1201
|
-
}
|
|
1202
|
-
const clarificationKind = this.selectedRecordSurfaceRowActionClarificationKind(response);
|
|
1203
|
-
if (!clarificationKind)
|
|
1204
|
-
return null;
|
|
1205
|
-
const contextHints = this.contextHintsFor(request);
|
|
1206
|
-
const surfaces = this.selectedRecordSurfaces(contextHints);
|
|
1207
|
-
if (!surfaces.length)
|
|
1208
|
-
return null;
|
|
1209
|
-
const ranked = surfaces
|
|
1210
|
-
.map((surface) => ({
|
|
1211
|
-
surface,
|
|
1212
|
-
score: this.selectedRecordSurfacePromptScore(normalizedConversation || normalizedPrompt, surface),
|
|
1213
|
-
}))
|
|
1214
|
-
.filter((entry) => entry.score >= 2)
|
|
1215
|
-
.sort((left, right) => right.score - left.score);
|
|
1216
|
-
if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
|
|
1217
|
-
return null;
|
|
1218
|
-
}
|
|
1219
|
-
const hasSelectedRecords = this.selectedRecordsCountForTurn > 0;
|
|
1220
|
-
const source = hasSelectedRecords
|
|
1221
|
-
? 'selected-record-surface-row-action-continued-from-clarification'
|
|
1222
|
-
: 'record-surface-row-action-continued-from-clarification';
|
|
1223
|
-
const warning = hasSelectedRecords
|
|
1224
|
-
? (clarificationKind === 'technical'
|
|
1225
|
-
? 'selected-record-surface-row-action-continued-from-technical-clarification'
|
|
1226
|
-
: 'selected-record-surface-row-action-continued-from-generic-clarification')
|
|
1227
|
-
: 'record-surface-row-action-continued-from-clarification';
|
|
1228
|
-
const warningDetail = clarificationKind === 'technical'
|
|
1229
|
-
? `Residual continuity guard acted only after LLM asked for technical row-action details while declared recordSurfaces${hasSelectedRecords ? ' and conversation history' : ''} already grounded the target surface.`
|
|
1230
|
-
: 'Residual continuity guard acted only after LLM asked a generic dataset/listing clarification while declared recordSurfaces and a row-action request already grounded the target surface.';
|
|
1231
|
-
const rowActionResponse = this.buildSelectedRecordSurfaceRowActionResponse(ranked[0].surface, source, warning, warningDetail);
|
|
1232
|
-
if (!rowActionResponse)
|
|
1233
|
-
return null;
|
|
1234
|
-
// Residual continuity guard: this runs only after the LLM returned a
|
|
1235
|
-
// clarification/info response for a row-button request. The target surface
|
|
1236
|
-
// is ranked from declared canonical recordSurfaces and recent conversation
|
|
1237
|
-
// context, so the guard repairs materialization without becoming primary
|
|
1238
|
-
// intent routing by local command text.
|
|
1239
|
-
return rowActionResponse;
|
|
1240
|
-
}
|
|
1241
|
-
selectedRecordSurfaceRowActionPlanForInfo(response, request) {
|
|
1242
|
-
if (response.type !== 'info' || !request)
|
|
1243
|
-
return null;
|
|
1244
|
-
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
1245
|
-
const normalizedConversation = this.normalizeLabel([
|
|
1246
|
-
...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
|
|
1247
|
-
request.prompt ?? '',
|
|
1248
|
-
].join(' '));
|
|
1249
|
-
if (!this.textMentionsRowActionRequest(`${normalizedPrompt} ${normalizedConversation}`)) {
|
|
1250
|
-
return null;
|
|
1251
|
-
}
|
|
1252
|
-
const responseText = this.normalizeLabel([
|
|
1253
|
-
response.message ?? '',
|
|
1254
|
-
response.explanation ?? '',
|
|
1255
|
-
].join(' '));
|
|
1256
|
-
if (!this.textMentionsSurfaceOpenCommitment(responseText) && !this.textMentionsRowActionRequest(responseText)) {
|
|
1257
|
-
return null;
|
|
1258
|
-
}
|
|
1259
|
-
const contextHints = this.contextHintsFor(request);
|
|
1260
|
-
const surfaces = this.selectedRecordSurfaces(contextHints);
|
|
1261
|
-
if (!surfaces.length)
|
|
1262
|
-
return null;
|
|
1263
|
-
const ranked = surfaces
|
|
1264
|
-
.map((surface) => ({
|
|
1265
|
-
surface,
|
|
1266
|
-
score: this.selectedRecordSurfacePromptScore(`${normalizedConversation || normalizedPrompt} ${responseText}`, surface),
|
|
1267
|
-
}))
|
|
1268
|
-
.filter((entry) => entry.score >= 2)
|
|
1269
|
-
.sort((left, right) => right.score - left.score);
|
|
1270
|
-
if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
|
|
1271
|
-
return null;
|
|
1272
|
-
}
|
|
1273
|
-
// Residual continuity guard: this runs only after the LLM has already
|
|
1274
|
-
// answered an info turn for a row-button request but failed to emit the
|
|
1275
|
-
// manifest-backed rowAction.add envelope. The target is ranked from the
|
|
1276
|
-
// governed recordSurfaces catalog plus LLM-authored evidence.
|
|
1277
|
-
return this.buildSelectedRecordSurfaceRowActionResponse(ranked[0].surface, 'selected-record-surface-row-action-info-continuity-guard', 'selected-record-surface-row-action-continued-from-info', 'Residual continuity guard acted only after LLM resolved a declared recordSurface in an info answer for a row-action request without emitting rowAction.add.');
|
|
1278
|
-
}
|
|
1279
|
-
selectedRecordSurfaceRuntimeOperationForInfo(response, request) {
|
|
1280
|
-
if (response.type !== 'info' || !request)
|
|
1281
|
-
return null;
|
|
1282
|
-
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
1283
|
-
if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
|
|
1284
|
-
return null;
|
|
1285
|
-
const responseText = this.normalizeLabel([
|
|
1286
|
-
response.message ?? '',
|
|
1287
|
-
response.explanation ?? '',
|
|
1288
|
-
].join(' '));
|
|
1289
|
-
if (!this.textMentionsSurfaceOpenCommitment(responseText))
|
|
1290
|
-
return null;
|
|
1291
|
-
const contextHints = this.contextHintsFor(request);
|
|
1292
|
-
const surfaces = this.selectedRecordSurfaces(contextHints);
|
|
1293
|
-
if (!surfaces.length)
|
|
1294
|
-
return null;
|
|
1295
|
-
const ranked = surfaces
|
|
1296
|
-
.map((surface) => ({
|
|
1297
|
-
surface,
|
|
1298
|
-
score: this.selectedRecordSurfacePromptScore(`${normalizedPrompt} ${responseText}`, surface),
|
|
1299
|
-
}))
|
|
1300
|
-
.filter((entry) => entry.score >= 2)
|
|
1301
|
-
.sort((left, right) => right.score - left.score);
|
|
1302
|
-
if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
|
|
1303
|
-
return null;
|
|
1304
|
-
}
|
|
1305
|
-
const surface = ranked[0].surface;
|
|
1306
|
-
const surfaceId = this.stringValue(surface['id']);
|
|
1307
|
-
if (!surfaceId)
|
|
1308
|
-
return null;
|
|
1309
|
-
const resourceSurface = this.toRecord(surface['resourceSurface']);
|
|
1310
|
-
const surfaceLabel = this.stringValue(surface['label'])
|
|
1311
|
-
|| this.stringValue(resourceSurface?.['title'])
|
|
1312
|
-
|| this.humanizeField(surfaceId);
|
|
1313
|
-
// Residual continuity guard: this runs only after the LLM already resolved
|
|
1314
|
-
// a declared recordSurface in an info answer but failed to emit the runtime
|
|
1315
|
-
// operation required by the user request. Ranking uses the governed
|
|
1316
|
-
// recordSurfaces catalog plus the LLM-authored answer, not primary local
|
|
1317
|
-
// command routing.
|
|
1318
|
-
return {
|
|
1319
|
-
type: 'patch',
|
|
1320
|
-
patch: {
|
|
1321
|
-
tableRuntimeOperations: {
|
|
1322
|
-
source: 'selected-record-surface-info-continuity-guard',
|
|
1323
|
-
operations: [{
|
|
1324
|
-
operationId: 'dynamicPage.surface.open',
|
|
1325
|
-
input: {
|
|
1326
|
-
surfaceId,
|
|
1327
|
-
surfaceLabel,
|
|
1328
|
-
source: 'selected-records',
|
|
1329
|
-
},
|
|
1330
|
-
}],
|
|
1331
|
-
},
|
|
1332
|
-
},
|
|
1333
|
-
explanation: `Vou abrir a superfície relacionada ${surfaceLabel}.`,
|
|
1334
|
-
warnings: [
|
|
1335
|
-
'selected-record-surface-info-continued-to-runtime-operation',
|
|
1336
|
-
'Residual continuity guard acted only after LLM resolved a declared recordSurface in an info answer without emitting the required runtime operation.',
|
|
1337
|
-
],
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
selectedRecordSurfaceRuntimeOperationForInvalidExecutable(response, request, warnings = []) {
|
|
1341
|
-
if (!request || !this.responseMayContainExecutableEnvelope(response))
|
|
1342
|
-
return null;
|
|
1343
|
-
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
1344
|
-
const normalizedConversation = this.normalizeLabel([
|
|
1345
|
-
...(request.messages ?? []).slice(-8).map((message) => message?.text ?? ''),
|
|
1346
|
-
request.prompt ?? '',
|
|
1347
|
-
].join(' '));
|
|
1348
|
-
const requestText = `${normalizedPrompt} ${normalizedConversation}`;
|
|
1349
|
-
if (!this.textMentionsRecordSurfaceOpenRequest(requestText))
|
|
1350
|
-
return null;
|
|
1351
|
-
if (this.promptRequestsSimilarRecords(requestText))
|
|
1352
|
-
return null;
|
|
1353
|
-
const contextHints = this.contextHintsFor(request);
|
|
1354
|
-
const surfaces = this.selectedRecordSurfaces(contextHints);
|
|
1355
|
-
if (!surfaces.length)
|
|
1356
|
-
return null;
|
|
1357
|
-
const responseEvidence = this.normalizeLabel([
|
|
1358
|
-
response.message ?? '',
|
|
1359
|
-
response.explanation ?? '',
|
|
1360
|
-
...(response.questions ?? []),
|
|
1361
|
-
...warnings,
|
|
1362
|
-
this.stringifySurfaceEvidence(response),
|
|
1363
|
-
].join(' '));
|
|
1364
|
-
const ranked = surfaces
|
|
1365
|
-
.map((surface) => ({
|
|
1366
|
-
surface,
|
|
1367
|
-
score: this.selectedRecordSurfacePromptScore(`${requestText} ${responseEvidence}`, surface),
|
|
1368
|
-
}))
|
|
1369
|
-
.filter((entry) => entry.score >= 2)
|
|
1370
|
-
.sort((left, right) => right.score - left.score);
|
|
1371
|
-
if (!ranked.length || (ranked.length > 1 && ranked[0].score === ranked[1].score)) {
|
|
1372
|
-
return null;
|
|
1373
|
-
}
|
|
1374
|
-
if (this.textMentionsRowActionRequest(requestText)) {
|
|
1375
|
-
const rowActionResponse = this.buildSelectedRecordSurfaceRowActionResponse(ranked[0].surface, 'selected-record-surface-row-action-invalid-executable-continuity-guard', 'selected-record-surface-row-action-continued-from-invalid-executable', 'Residual continuity guard acted only after LLM emitted an executable envelope that failed table component capability validation for a row-action request while a declared recordSurface uniquely matched the selected-record request.');
|
|
1376
|
-
if (rowActionResponse)
|
|
1377
|
-
return rowActionResponse;
|
|
1378
|
-
}
|
|
1379
|
-
const surface = ranked[0].surface;
|
|
1380
|
-
const surfaceId = this.stringValue(surface['id']);
|
|
1381
|
-
if (!surfaceId)
|
|
1382
|
-
return null;
|
|
1383
|
-
const resourceSurface = this.toRecord(surface['resourceSurface']);
|
|
1384
|
-
const surfaceLabel = this.stringValue(surface['label'])
|
|
1385
|
-
|| this.stringValue(resourceSurface?.['title'])
|
|
1386
|
-
|| this.humanizeField(surfaceId);
|
|
1387
|
-
// Residual executable guard: this runs only after the LLM already authored
|
|
1388
|
-
// an executable envelope that failed component capability validation. The
|
|
1389
|
-
// surface target is ranked from declared recordSurfaces plus LLM-authored
|
|
1390
|
-
// evidence, so this does not become primary command routing by text.
|
|
1391
|
-
return {
|
|
1392
|
-
type: 'patch',
|
|
1393
|
-
patch: {
|
|
1394
|
-
tableRuntimeOperations: {
|
|
1395
|
-
source: 'selected-record-surface-invalid-executable-continuity-guard',
|
|
1396
|
-
operations: [{
|
|
1397
|
-
operationId: 'dynamicPage.surface.open',
|
|
1398
|
-
input: {
|
|
1399
|
-
surfaceId,
|
|
1400
|
-
surfaceLabel,
|
|
1401
|
-
source: 'selected-records',
|
|
1402
|
-
},
|
|
1403
|
-
}],
|
|
1404
|
-
},
|
|
1405
|
-
},
|
|
1406
|
-
explanation: `Vou abrir a superfície relacionada ${surfaceLabel}.`,
|
|
1407
|
-
warnings: [
|
|
1408
|
-
'selected-record-surface-invalid-executable-continued-to-runtime-operation',
|
|
1409
|
-
'Residual continuity guard acted only after LLM emitted an executable envelope that failed table component capability validation while a declared recordSurface uniquely matched the selected-record request.',
|
|
1410
|
-
],
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
buildSelectedRecordSurfaceRowActionResponse(surface, source, warning, warningDetail) {
|
|
1414
|
-
const resourceSurface = this.toRecord(surface['resourceSurface']);
|
|
1415
|
-
if (!this.isResourceSurfaceCatalogDigest(resourceSurface)) {
|
|
1416
|
-
return null;
|
|
1417
|
-
}
|
|
1418
|
-
const surfaceId = this.stringValue(surface['id']) || this.stringValue(resourceSurface['id']);
|
|
1419
|
-
if (!surfaceId)
|
|
1420
|
-
return null;
|
|
1421
|
-
const surfaceLabel = this.stringValue(surface['label'])
|
|
1422
|
-
|| this.stringValue(resourceSurface['title'])
|
|
1423
|
-
|| this.humanizeField(surfaceId);
|
|
1424
|
-
const actionId = `open-${this.slugifyActionId(surfaceId)}`;
|
|
1425
|
-
const rowAction = {
|
|
1426
|
-
id: actionId,
|
|
1427
|
-
label: surfaceLabel,
|
|
1428
|
-
action: 'surface.open',
|
|
1429
|
-
icon: 'open_in_new',
|
|
1430
|
-
recordSurface: this.toAiJsonObject(resourceSurface),
|
|
1431
|
-
};
|
|
1432
|
-
return {
|
|
1433
|
-
type: 'patch',
|
|
1434
|
-
componentEditPlan: {
|
|
1435
|
-
kind: 'praxis.table.component-edit-plan',
|
|
1436
|
-
version: '1.0',
|
|
1437
|
-
componentId: 'praxis-table',
|
|
1438
|
-
operationId: 'rowAction.add',
|
|
1439
|
-
changeKind: 'add_row_action',
|
|
1440
|
-
capabilityPath: 'actions.row.actions[]',
|
|
1441
|
-
input: rowAction,
|
|
1442
|
-
value: rowAction,
|
|
1443
|
-
source,
|
|
1444
|
-
},
|
|
1445
|
-
explanation: `Vou criar um botão em cada linha para abrir ${surfaceLabel}.`,
|
|
1446
|
-
warnings: [warning, warningDetail],
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
bulkRouteActionPlanForClarification(response, request) {
|
|
1450
|
-
if (response.type !== 'clarification' || !request)
|
|
1451
|
-
return null;
|
|
1452
|
-
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
1453
|
-
if (!this.textMentionsBulkActionRequest(normalizedPrompt))
|
|
1454
|
-
return null;
|
|
1455
|
-
if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
|
|
1456
|
-
return null;
|
|
1457
|
-
const routePath = this.extractRoutePath(request.prompt ?? '');
|
|
1458
|
-
if (!routePath)
|
|
1459
|
-
return null;
|
|
1460
|
-
const clarificationKind = this.globalActionClarificationKind(response);
|
|
1461
|
-
if (!clarificationKind)
|
|
1462
|
-
return null;
|
|
1463
|
-
return this.buildBulkRouteActionPlan(routePath, 'bulk-route-action-continued-from-clarification', [
|
|
1464
|
-
'bulk-route-action-continued-from-technical-clarification',
|
|
1465
|
-
'Residual continuity guard acted only after LLM asked for technical bulk route details while a route path and selected-record bulk action intent were already grounded.',
|
|
1466
|
-
]);
|
|
1467
|
-
}
|
|
1468
|
-
bulkRouteActionPlanForInvalidExecutable(response, request, validationWarnings = []) {
|
|
1469
|
-
if (!request)
|
|
1470
|
-
return null;
|
|
1471
|
-
if (!response.componentEditPlan && !response.patch)
|
|
1472
|
-
return null;
|
|
1473
|
-
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
1474
|
-
if (!this.textMentionsBulkActionRequest(normalizedPrompt))
|
|
1475
|
-
return null;
|
|
1476
|
-
if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
|
|
1477
|
-
return null;
|
|
1478
|
-
const routePath = this.extractRoutePath(request.prompt ?? '');
|
|
1479
|
-
if (!routePath)
|
|
1480
|
-
return null;
|
|
1481
|
-
return this.buildBulkRouteActionPlan(routePath, 'bulk-route-action-continued-from-invalid-executable', [
|
|
1482
|
-
...validationWarnings,
|
|
1483
|
-
'bulk-route-action-continued-from-invalid-executable',
|
|
1484
|
-
'Residual continuity guard acted only after LLM proposed an invalid executable table plan while route path and selected-record bulk action intent were already grounded.',
|
|
1485
|
-
]);
|
|
1486
|
-
}
|
|
1487
|
-
bulkRouteActionPlanForNonExecutableNarrative(response, request) {
|
|
1488
|
-
if (!request)
|
|
1489
|
-
return null;
|
|
1490
|
-
const normalizedPrompt = this.normalizeLabel(request.prompt ?? '');
|
|
1491
|
-
if (!this.textMentionsBulkActionRequest(normalizedPrompt))
|
|
1492
|
-
return null;
|
|
1493
|
-
if (!this.textMentionsRecordSurfaceOpenRequest(normalizedPrompt))
|
|
1494
|
-
return null;
|
|
1495
|
-
const routePath = this.extractRoutePath(request.prompt ?? '');
|
|
1496
|
-
if (!routePath)
|
|
1497
|
-
return null;
|
|
1498
|
-
const narrative = this.normalizeLabel([
|
|
1499
|
-
response.message ?? '',
|
|
1500
|
-
response.explanation ?? '',
|
|
1501
|
-
...(response.warnings ?? []),
|
|
1502
|
-
].join(' '));
|
|
1503
|
-
const hasCanonicalDecision = this.normalizedTextIncludesAny(narrative, ['componentEditPlan', 'component edit plan'])
|
|
1504
|
-
&& this.normalizedTextIncludesAny(narrative, ['bulkAction.add', 'bulk action.add'])
|
|
1505
|
-
&& this.normalizedTextIncludesAny(narrative, ['navigation.openRoute', 'navigation.open route'])
|
|
1506
|
-
&& this.normalizedTextIncludesAny(narrative, ['selectedIds', 'selected ids', 'runtime.selectedIds']);
|
|
1507
|
-
if (!hasCanonicalDecision)
|
|
1508
|
-
return null;
|
|
1509
|
-
return this.buildBulkRouteActionPlan(routePath, 'bulk-route-action-continued-from-non-executable-narrative', [
|
|
1510
|
-
'bulk-route-action-continued-from-non-executable-narrative',
|
|
1511
|
-
'Residual continuity guard acted only after LLM described the canonical bulkAction.add/navigation.openRoute decision without returning an executable componentEditPlan envelope.',
|
|
1512
|
-
]);
|
|
1513
|
-
}
|
|
1514
|
-
buildBulkRouteActionPlan(routePath, source, warnings) {
|
|
1515
|
-
const actionId = `open-${this.slugifyActionId(routePath)}-selected`;
|
|
1516
|
-
const label = this.humanizeBulkRouteActionLabel(routePath);
|
|
1517
|
-
const globalAction = {
|
|
1518
|
-
actionId: 'navigation.openRoute',
|
|
1519
|
-
payload: {
|
|
1520
|
-
path: routePath,
|
|
1521
|
-
query: { ids: '${runtime.selectedIds}' },
|
|
1522
|
-
},
|
|
1523
|
-
};
|
|
1524
|
-
const bulkAction = {
|
|
1525
|
-
id: actionId,
|
|
1526
|
-
label,
|
|
1527
|
-
action: 'navigation.openRoute',
|
|
1528
|
-
icon: 'route',
|
|
1529
|
-
globalAction,
|
|
1530
|
-
effects: [
|
|
1531
|
-
{
|
|
1532
|
-
kind: 'global-action',
|
|
1533
|
-
globalAction,
|
|
1534
|
-
},
|
|
1535
|
-
],
|
|
1536
|
-
};
|
|
1537
|
-
// Residual continuity guard: this executes only after the LLM already
|
|
1538
|
-
// resolved the turn as a clarification, while the user-provided route path
|
|
1539
|
-
// and the declared bulkAction.add/globalAction contract are sufficient to
|
|
1540
|
-
// materialize the operation. It grounds payload details in canonical runtime
|
|
1541
|
-
// selection templates instead of using local text as primary intent routing.
|
|
1542
|
-
return {
|
|
1543
|
-
type: 'patch',
|
|
1544
|
-
componentEditPlan: {
|
|
1545
|
-
kind: 'praxis.table.component-edit-plan',
|
|
1546
|
-
version: '1.0',
|
|
1547
|
-
componentId: 'praxis-table',
|
|
1548
|
-
operationId: 'bulkAction.add',
|
|
1549
|
-
changeKind: 'add_bulk_action',
|
|
1550
|
-
capabilityPath: 'actions.bulk.actions[]',
|
|
1551
|
-
input: bulkAction,
|
|
1552
|
-
value: bulkAction,
|
|
1553
|
-
source,
|
|
1554
|
-
},
|
|
1555
|
-
explanation: `Vou adicionar a ação ${label} para os registros selecionados.`,
|
|
1556
|
-
warnings,
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
|
-
selectedRecordSurfaceRowActionClarificationKind(response) {
|
|
1560
|
-
const text = this.normalizeLabel([
|
|
1561
|
-
response.message ?? '',
|
|
1562
|
-
response.explanation ?? '',
|
|
1563
|
-
...(response.questions ?? []),
|
|
1564
|
-
].join(' '));
|
|
1565
|
-
if ([
|
|
1566
|
-
'surface to open',
|
|
1567
|
-
'button label',
|
|
1568
|
-
'placement in row',
|
|
1569
|
-
'mais detalhes sobre surface',
|
|
1570
|
-
'mais detalhes sobre button',
|
|
1571
|
-
'mais detalhes sobre placement',
|
|
1572
|
-
'mais detalhes sobre openmode',
|
|
1573
|
-
'mais detalhes sobre open mode',
|
|
1574
|
-
'mais detalhes sobre iconorstyle',
|
|
1575
|
-
'mais detalhes sobre appliesto',
|
|
1576
|
-
'mais detalhes sobre actionlabel',
|
|
1577
|
-
'mais detalhes sobre openbehavior',
|
|
1578
|
-
'mais detalhes sobre actionbehavior',
|
|
1579
|
-
'mais detalhes sobre destinationsurface',
|
|
1580
|
-
'mais detalhes sobre iconlabel',
|
|
1581
|
-
'qual condicao deve ser usada',
|
|
1582
|
-
'qual condição deve ser usada',
|
|
1583
|
-
'mais detalhes sobre action behavior',
|
|
1584
|
-
'o alvo do ajuste',
|
|
1585
|
-
'nome que voce prefere',
|
|
1586
|
-
'nome que você prefere',
|
|
1587
|
-
'openmode',
|
|
1588
|
-
'open mode',
|
|
1589
|
-
'iconorstyle',
|
|
1590
|
-
'appliesto',
|
|
1591
|
-
'actionlabel',
|
|
1592
|
-
'openbehavior',
|
|
1593
|
-
'actionbehavior',
|
|
1594
|
-
'destinationsurface',
|
|
1595
|
-
'iconlabel',
|
|
1596
|
-
].some((needle) => text.includes(needle))) {
|
|
1597
|
-
return 'technical';
|
|
1598
|
-
}
|
|
1599
|
-
if ([
|
|
1600
|
-
'qual conjunto de dados',
|
|
1601
|
-
'conjunto de dados',
|
|
1602
|
-
'dados voce quer listar',
|
|
1603
|
-
'dados você quer listar',
|
|
1604
|
-
'quer listar',
|
|
1605
|
-
'listar',
|
|
1606
|
-
'dataset',
|
|
1607
|
-
].some((needle) => text.includes(this.normalizeLabel(needle)))) {
|
|
1608
|
-
return 'generic-dataset';
|
|
1609
|
-
}
|
|
1610
|
-
return null;
|
|
1611
|
-
}
|
|
1612
|
-
globalActionClarificationKind(response) {
|
|
1613
|
-
const text = this.normalizeLabel([
|
|
1614
|
-
response.message ?? '',
|
|
1615
|
-
response.explanation ?? '',
|
|
1616
|
-
...(response.questions ?? []),
|
|
1617
|
-
].join(' '));
|
|
1618
|
-
if ([
|
|
1619
|
-
'recordidparamname',
|
|
1620
|
-
'record id param name',
|
|
1621
|
-
'o alvo do ajuste',
|
|
1622
|
-
'qual formato aplicar',
|
|
1623
|
-
'mais detalhes sobre target',
|
|
1624
|
-
'mais detalhes sobre payload',
|
|
1625
|
-
'mais detalhes sobre route',
|
|
1626
|
-
'mais detalhes sobre openmode',
|
|
1627
|
-
'mais detalhes sobre open mode',
|
|
1628
|
-
].some((needle) => text.includes(this.normalizeLabel(needle)))) {
|
|
1629
|
-
return 'technical';
|
|
1630
|
-
}
|
|
1631
|
-
return null;
|
|
1632
|
-
}
|
|
1633
|
-
textMentionsRowActionRequest(normalizedText) {
|
|
1634
|
-
return [
|
|
1635
|
-
'botao',
|
|
1636
|
-
'acao de linha',
|
|
1637
|
-
'acoes de linha',
|
|
1638
|
-
'acao por registro',
|
|
1639
|
-
'acoes por registro',
|
|
1640
|
-
'nas linhas',
|
|
1641
|
-
'por linha',
|
|
1642
|
-
'em cada linha',
|
|
1643
|
-
].some((needle) => normalizedText.includes(needle));
|
|
1644
|
-
}
|
|
1645
|
-
textMentionsBulkActionRequest(normalizedText) {
|
|
1646
|
-
return [
|
|
1647
|
-
'acao em lote',
|
|
1648
|
-
'acoes em lote',
|
|
1649
|
-
'registros selecionados',
|
|
1650
|
-
'linhas selecionadas',
|
|
1651
|
-
'selecionados',
|
|
1652
|
-
'bulk action',
|
|
1653
|
-
].some((needle) => normalizedText.includes(this.normalizeLabel(needle)));
|
|
1654
|
-
}
|
|
1655
|
-
normalizedTextIncludesAny(normalizedText, needles) {
|
|
1656
|
-
return needles.some((needle) => normalizedText.includes(this.normalizeLabel(needle)));
|
|
1657
|
-
}
|
|
1658
|
-
extractRoutePath(text) {
|
|
1659
|
-
const match = String(text || '').match(/(?:^|\s)(\/[A-Za-z0-9._~:/?#[\]@!$&'()*+,;=%-]+)(?=$|\s|[,.])/u);
|
|
1660
|
-
return match?.[1]?.replace(/[,.]$/u, '') ?? '';
|
|
1661
|
-
}
|
|
1662
|
-
humanizeBulkRouteActionLabel(path) {
|
|
1663
|
-
const segments = path
|
|
1664
|
-
.split(/[?#]/u)[0]
|
|
1665
|
-
.split('/')
|
|
1666
|
-
.map((segment) => segment.trim())
|
|
1667
|
-
.filter(Boolean);
|
|
1668
|
-
const lastSegments = segments.slice(-2);
|
|
1669
|
-
const label = lastSegments
|
|
1670
|
-
.map((segment) => this.humanizeField(segment))
|
|
1671
|
-
.join(' - ');
|
|
1672
|
-
return label ? `Abrir ${label}` : 'Abrir rota dos selecionados';
|
|
1673
|
-
}
|
|
1674
|
-
textMentionsRecordSurfaceOpenRequest(normalizedText) {
|
|
1675
|
-
return [
|
|
1676
|
-
'abrir',
|
|
1677
|
-
'abre',
|
|
1678
|
-
'abra',
|
|
1679
|
-
'mostra',
|
|
1680
|
-
'mostrar',
|
|
1681
|
-
'mostre',
|
|
1682
|
-
'exibir',
|
|
1683
|
-
'exiba',
|
|
1684
|
-
'ver',
|
|
1685
|
-
'consulta',
|
|
1686
|
-
'consultar',
|
|
1687
|
-
].some((needle) => normalizedText.includes(needle));
|
|
1688
|
-
}
|
|
1689
|
-
textMentionsSurfaceOpenCommitment(normalizedText) {
|
|
1690
|
-
return [
|
|
1691
|
-
'vou abrir',
|
|
1692
|
-
'abrindo',
|
|
1693
|
-
'abrir a superficie',
|
|
1694
|
-
'abrir a superfície',
|
|
1695
|
-
'superficie a abrir',
|
|
1696
|
-
'superfície a abrir',
|
|
1697
|
-
].some((needle) => normalizedText.includes(this.normalizeLabel(needle)));
|
|
1698
|
-
}
|
|
1699
|
-
isResourceSurfaceCatalogDigest(value) {
|
|
1700
|
-
if (!value)
|
|
1701
|
-
return false;
|
|
1702
|
-
return !!this.stringValue(value['id'])
|
|
1703
|
-
&& !!this.stringValue(value['kind'])
|
|
1704
|
-
&& !!this.stringValue(value['scope'])
|
|
1705
|
-
&& !!this.stringValue(value['path'])
|
|
1706
|
-
&& !!this.stringValue(value['method']);
|
|
1707
|
-
}
|
|
1708
|
-
slugifyActionId(value) {
|
|
1709
|
-
return this.normalizeLabel(value)
|
|
1710
|
-
.replace(/[^a-z0-9]+/gu, '-')
|
|
1711
|
-
.replace(/^-+|-+$/gu, '')
|
|
1712
|
-
|| 'surface';
|
|
1713
|
-
}
|
|
1714
|
-
promptRequestsSimilarRecords(normalizedPrompt) {
|
|
1715
|
-
return [
|
|
1716
|
-
'registro parecido',
|
|
1717
|
-
'registros parecidos',
|
|
1718
|
-
'outro registro',
|
|
1719
|
-
'outros registros',
|
|
1720
|
-
'buscar registros',
|
|
1721
|
-
'procurar registros',
|
|
1722
|
-
'filtrar registros',
|
|
1723
|
-
'filtrar por',
|
|
1724
|
-
].some((needle) => this.normalizedTextContainsApproxPhrase(normalizedPrompt, needle));
|
|
1725
|
-
}
|
|
1726
|
-
selectedRecordSurfacePromptScore(normalizedPrompt, surface) {
|
|
1727
|
-
const resourceSurface = this.toRecord(surface['resourceSurface']);
|
|
1728
|
-
const tags = Array.isArray(resourceSurface?.['tags']) ? resourceSurface['tags'] : [];
|
|
1729
|
-
const haystack = this.normalizeLabel([
|
|
1730
|
-
this.stringValue(surface['id']),
|
|
1731
|
-
this.stringValue(surface['label']),
|
|
1732
|
-
this.stringValue(surface['title']),
|
|
1733
|
-
this.stringValue(surface['description']),
|
|
1734
|
-
this.stringValue(surface['semanticDescription']),
|
|
1735
|
-
this.stringValue(surface['semanticIntent']),
|
|
1736
|
-
this.stringValue(surface['kind']),
|
|
1737
|
-
this.stringValue(surface['scope']),
|
|
1738
|
-
this.stringValue(surface['relation']),
|
|
1739
|
-
this.stringValue(resourceSurface?.['id']),
|
|
1740
|
-
this.stringValue(resourceSurface?.['title']),
|
|
1741
|
-
this.stringValue(resourceSurface?.['description']),
|
|
1742
|
-
this.stringValue(resourceSurface?.['semanticIntent']),
|
|
1743
|
-
this.stringValue(resourceSurface?.['kind']),
|
|
1744
|
-
this.stringValue(resourceSurface?.['scope']),
|
|
1745
|
-
...this.compactStringArray(surface['tags']),
|
|
1746
|
-
...tags.map((tag) => this.stringValue(tag)),
|
|
1747
|
-
].join(' '));
|
|
1748
|
-
const promptTokens = normalizedPrompt
|
|
1749
|
-
.split(/\s+/u)
|
|
1750
|
-
.map((token) => token.trim())
|
|
1751
|
-
.filter((token) => token.length >= 4 && !this.isSelectedRecordSurfaceStopToken(token));
|
|
1752
|
-
return [...new Set(promptTokens)]
|
|
1753
|
-
.reduce((score, token) => score + (haystack.includes(token) ? 1 : 0), 0);
|
|
1754
|
-
}
|
|
1755
|
-
isSelectedRecordSurfaceStopToken(token) {
|
|
1756
|
-
return [
|
|
1757
|
-
'quero',
|
|
1758
|
-
'mostra',
|
|
1759
|
-
'mostrar',
|
|
1760
|
-
'mostre',
|
|
1761
|
-
'abrir',
|
|
1762
|
-
'abre',
|
|
1763
|
-
'deste',
|
|
1764
|
-
'desse',
|
|
1765
|
-
'dele',
|
|
1766
|
-
'dela',
|
|
1767
|
-
'registro',
|
|
1768
|
-
'selecionado',
|
|
1769
|
-
'selecionada',
|
|
1770
|
-
].includes(token);
|
|
1771
|
-
}
|
|
1772
|
-
compactStringArray(value) {
|
|
1773
|
-
return Array.isArray(value)
|
|
1774
|
-
? value
|
|
1775
|
-
.map((entry) => this.stringValue(entry))
|
|
1776
|
-
.filter((entry) => !!entry)
|
|
1777
|
-
: [];
|
|
1778
|
-
}
|
|
1779
|
-
contextHintsFor(request) {
|
|
1780
|
-
return this.toRecord(request?.contextHints) ?? this.contextHintsForTurn;
|
|
1781
|
-
}
|
|
1782
|
-
tableFilterApplyOperations(patch) {
|
|
1783
|
-
const envelope = this.toRecord(this.toRecord(patch)?.['tableRuntimeOperations']);
|
|
1784
|
-
const operations = Array.isArray(envelope?.['operations']) ? envelope['operations'] : [];
|
|
1785
|
-
return operations
|
|
1786
|
-
.map((operation) => this.toRecord(operation))
|
|
1787
|
-
.filter((operation) => !!operation && this.stringValue(operation['operationId']) === 'table.filter.apply');
|
|
1788
|
-
}
|
|
1789
|
-
promptMentionsFilterField(prompt, fieldName) {
|
|
1790
|
-
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
1791
|
-
if (!normalizedPrompt)
|
|
1792
|
-
return false;
|
|
1793
|
-
const entry = this.filterFieldCatalogEntries.find((candidate) => candidate.name === fieldName);
|
|
1794
|
-
const rawCandidates = [
|
|
1795
|
-
fieldName,
|
|
1796
|
-
this.humanizeFilterField(fieldName),
|
|
1797
|
-
entry?.label,
|
|
1798
|
-
...(entry?.aliases ?? []),
|
|
1799
|
-
...(entry?.relatedColumnFields ?? []),
|
|
1800
|
-
...(entry?.relatedColumnLabels ?? []),
|
|
1801
|
-
];
|
|
1802
|
-
return rawCandidates
|
|
1803
|
-
.flatMap((candidate) => this.filterFieldMentionVariants(candidate ?? ''))
|
|
1804
|
-
.some((candidate) => candidate && this.normalizedTextContainsApproxPhrase(normalizedPrompt, candidate));
|
|
1805
|
-
}
|
|
1806
|
-
selectedRecordFilterPromptGroundingScore(prompt, candidate) {
|
|
1807
|
-
const fieldName = this.stringValue(candidate['field']);
|
|
1808
|
-
if (!fieldName)
|
|
1809
|
-
return 0;
|
|
1810
|
-
const normalizedPrompt = this.normalizeLabel(prompt);
|
|
1811
|
-
if (!normalizedPrompt)
|
|
1812
|
-
return 0;
|
|
1813
|
-
const entry = this.filterFieldCatalogEntries.find((candidateEntry) => candidateEntry.name === fieldName);
|
|
1814
|
-
const candidateLabel = this.stringValue(candidate['label']);
|
|
1815
|
-
const candidateAliases = this.stringArrayValue(candidate['aliases']);
|
|
1816
|
-
const candidateRelatedColumnFields = this.stringArrayValue(candidate['relatedColumnFields']);
|
|
1817
|
-
const candidateRelatedColumnLabels = this.stringArrayValue(candidate['relatedColumnLabels']);
|
|
1818
|
-
const criterionKind = this.normalizeLabel(this.stringValue(candidate['criterionKind']) || entry?.criterionKind || '');
|
|
1819
|
-
let score = 0;
|
|
1820
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, candidateLabel || entry?.label || this.humanizeFilterField(fieldName)) ? 180 : 0;
|
|
1821
|
-
score += candidateLabel && entry?.label && candidateLabel !== entry.label && this.promptContainsAnyVariant(normalizedPrompt, entry.label) ? 160 : 0;
|
|
1822
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, this.humanizeFilterField(fieldName)) ? 120 : 0;
|
|
1823
|
-
for (const alias of candidateAliases) {
|
|
1824
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, alias) ? 100 : 0;
|
|
1825
|
-
}
|
|
1826
|
-
for (const alias of entry?.aliases ?? []) {
|
|
1827
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, alias) ? 90 : 0;
|
|
1828
|
-
}
|
|
1829
|
-
for (const label of candidateRelatedColumnLabels) {
|
|
1830
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, label) ? 50 : 0;
|
|
1831
|
-
}
|
|
1832
|
-
for (const label of entry?.relatedColumnLabels ?? []) {
|
|
1833
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, label) ? 40 : 0;
|
|
1834
|
-
}
|
|
1835
|
-
for (const field of candidateRelatedColumnFields) {
|
|
1836
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, field) ? 35 : 0;
|
|
1837
|
-
}
|
|
1838
|
-
for (const field of entry?.relatedColumnFields ?? []) {
|
|
1839
|
-
score += this.promptContainsAnyVariant(normalizedPrompt, field) ? 30 : 0;
|
|
1840
|
-
}
|
|
1841
|
-
const hasRangeIntent = ['faixa', 'banda', 'entre'].some((token) => this.normalizedTextContainsApproxToken(normalizedPrompt, token));
|
|
1842
|
-
const hasDateIntent = ['data', 'periodo', 'admissao', 'entrou', 'entrada', 'epoca'].some((token) => this.normalizedTextContainsApproxToken(normalizedPrompt, token));
|
|
1843
|
-
const hasPeriodIntent = ['periodo', 'intervalo', 'entre'].some((token) => this.normalizedTextContainsApproxToken(normalizedPrompt, token));
|
|
1844
|
-
const hasRecentIntent = ['recente', 'recentes', 'ultimo', 'ultimos'].some((token) => this.normalizedTextContainsApproxToken(normalizedPrompt, token));
|
|
1845
|
-
if (criterionKind.includes('date range')) {
|
|
1846
|
-
score += hasDateIntent ? 70 : 0;
|
|
1847
|
-
score += hasPeriodIntent ? 45 : 0;
|
|
1848
|
-
}
|
|
1849
|
-
else if (criterionKind.includes('range')) {
|
|
1850
|
-
score += hasRangeIntent ? 70 : 0;
|
|
1851
|
-
score += hasPeriodIntent ? 25 : 0;
|
|
1852
|
-
}
|
|
1853
|
-
if (fieldName.endsWith('LastDays')) {
|
|
1854
|
-
score += hasRecentIntent ? 35 : 0;
|
|
1855
|
-
score -= hasPeriodIntent ? 25 : 0;
|
|
1856
|
-
}
|
|
1857
|
-
return score;
|
|
1858
|
-
}
|
|
1859
|
-
promptContainsAnyVariant(normalizedPrompt, value) {
|
|
1860
|
-
return this.filterFieldMentionVariants(value)
|
|
1861
|
-
.some((variant) => !!variant && this.normalizedTextContainsApproxPhrase(normalizedPrompt, variant));
|
|
1862
|
-
}
|
|
1863
|
-
filterFieldMentionVariants(value) {
|
|
1864
|
-
const normalized = this.normalizeLabel(value);
|
|
1865
|
-
if (!normalized)
|
|
1866
|
-
return [];
|
|
1867
|
-
const variants = new Set([normalized]);
|
|
1868
|
-
if (normalized.endsWith('s') && normalized.length > 3) {
|
|
1869
|
-
variants.add(normalized.slice(0, -1));
|
|
1870
|
-
}
|
|
1871
|
-
else {
|
|
1872
|
-
variants.add(`${normalized}s`);
|
|
1873
|
-
}
|
|
1874
|
-
for (const token of normalized.split(/\s+/u)) {
|
|
1875
|
-
if (token.length >= 4) {
|
|
1876
|
-
variants.add(token);
|
|
1877
|
-
if (token.endsWith('s') && token.length > 4) {
|
|
1878
|
-
variants.add(token.slice(0, -1));
|
|
1879
|
-
}
|
|
1880
|
-
else {
|
|
1881
|
-
variants.add(`${token}s`);
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
return [...variants];
|
|
1886
|
-
}
|
|
1887
|
-
normalizedTextContainsApproxPhrase(normalizedText, normalizedPhrase) {
|
|
1888
|
-
if (!normalizedText || !normalizedPhrase)
|
|
1889
|
-
return false;
|
|
1890
|
-
if (normalizedText.includes(normalizedPhrase))
|
|
1891
|
-
return true;
|
|
1892
|
-
const phraseTokens = normalizedPhrase
|
|
1893
|
-
.split(/\s+/u)
|
|
1894
|
-
.filter((token) => token.length > 2);
|
|
1895
|
-
if (!phraseTokens.length)
|
|
1896
|
-
return false;
|
|
1897
|
-
return phraseTokens.every((token) => this.normalizedTextContainsApproxToken(normalizedText, token));
|
|
1898
|
-
}
|
|
1899
|
-
normalizedTextContainsApproxToken(normalizedText, rawToken) {
|
|
1900
|
-
const token = this.normalizeLabel(rawToken);
|
|
1901
|
-
if (!normalizedText || !token)
|
|
1902
|
-
return false;
|
|
1903
|
-
if (normalizedText.includes(token))
|
|
1904
|
-
return true;
|
|
1905
|
-
if (token.length < 5)
|
|
1906
|
-
return false;
|
|
1907
|
-
const maxDistance = token.length >= 9 ? 2 : 1;
|
|
1908
|
-
return normalizedText
|
|
1909
|
-
.split(/\s+/u)
|
|
1910
|
-
.some((word) => word.length >= 4 && !this.isApproximateMatchingStopword(word) && ((word[0] === token[0] && this.boundedEditDistance(word, token, maxDistance) <= maxDistance)
|
|
1911
|
-
|| this.normalizedConsonantSignature(word) === this.normalizedConsonantSignature(token)));
|
|
1912
|
-
}
|
|
1913
|
-
isApproximateMatchingStopword(word) {
|
|
1914
|
-
return new Set([
|
|
1915
|
-
'estao',
|
|
1916
|
-
'esta',
|
|
1917
|
-
'sao',
|
|
1918
|
-
'tem',
|
|
1919
|
-
'têm',
|
|
1920
|
-
'esses',
|
|
1921
|
-
'essas',
|
|
1922
|
-
'eles',
|
|
1923
|
-
'elas',
|
|
1924
|
-
'entrou',
|
|
1925
|
-
'entrada',
|
|
1926
|
-
]).has(word);
|
|
1927
|
-
}
|
|
1928
|
-
normalizedConsonantSignature(value) {
|
|
1929
|
-
return this.normalizeLabel(value).replace(/[aeiou]/gu, '');
|
|
1930
|
-
}
|
|
1931
|
-
stringArrayValue(value) {
|
|
1932
|
-
return Array.isArray(value)
|
|
1933
|
-
? value.map((entry) => this.stringValue(entry)).filter((entry) => !!entry)
|
|
1934
|
-
: [];
|
|
1935
|
-
}
|
|
1936
|
-
boundedEditDistance(left, right, maxDistance) {
|
|
1937
|
-
if (Math.abs(left.length - right.length) > maxDistance)
|
|
1938
|
-
return maxDistance + 1;
|
|
1939
|
-
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
1940
|
-
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
1941
|
-
const current = [leftIndex];
|
|
1942
|
-
let rowMin = current[0];
|
|
1943
|
-
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
1944
|
-
const cost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
1945
|
-
const value = Math.min(previous[rightIndex] + 1, current[rightIndex - 1] + 1, previous[rightIndex - 1] + cost);
|
|
1946
|
-
current[rightIndex] = value;
|
|
1947
|
-
rowMin = Math.min(rowMin, value);
|
|
1948
|
-
}
|
|
1949
|
-
if (rowMin > maxDistance)
|
|
1950
|
-
return maxDistance + 1;
|
|
1951
|
-
previous = current;
|
|
1952
|
-
}
|
|
1953
|
-
return previous[right.length];
|
|
1954
|
-
}
|
|
1955
|
-
selectedRecordFilterClarificationOptionEntries(preferredFields) {
|
|
1956
|
-
const preferred = new Set(preferredFields);
|
|
1957
|
-
const entries = [...this.filterFieldCatalogEntries]
|
|
1958
|
-
.sort((left, right) => this.selectedRecordFilterOptionScore(right, preferred)
|
|
1959
|
-
- this.selectedRecordFilterOptionScore(left, preferred));
|
|
1960
|
-
const seen = new Set();
|
|
1961
|
-
const options = [];
|
|
1962
|
-
for (const entry of entries) {
|
|
1963
|
-
const key = this.normalizeLabel(entry.label);
|
|
1964
|
-
if (!key || seen.has(key))
|
|
1965
|
-
continue;
|
|
1966
|
-
seen.add(key);
|
|
1967
|
-
options.push(entry);
|
|
1968
|
-
if (options.length >= 4)
|
|
1969
|
-
break;
|
|
1970
|
-
}
|
|
1971
|
-
return options;
|
|
1972
|
-
}
|
|
1973
|
-
selectedRecordFilterOptionScore(entry, preferred) {
|
|
1974
|
-
let score = preferred.has(entry.name) ? 40 : 0;
|
|
1975
|
-
const name = this.normalizeLabel(entry.name);
|
|
1976
|
-
const label = this.normalizeLabel(entry.label);
|
|
1977
|
-
const controlType = this.normalizeLabel(entry.controlType ?? '');
|
|
1978
|
-
const type = this.normalizeLabel(entry.type ?? '');
|
|
1979
|
-
const criterionKind = this.normalizeLabel(entry.criterionKind ?? '');
|
|
1980
|
-
if (this.selectionDerivedFilterCandidateFields.has(entry.name))
|
|
1981
|
-
score += 120;
|
|
1982
|
-
if (/IdsIn$/u.test(entry.name))
|
|
1983
|
-
score += 40;
|
|
1984
|
-
if (criterionKind.includes('range') || controlType.includes('range'))
|
|
1985
|
-
score += 120;
|
|
1986
|
-
if (this.filterEntryHasSelectedRecordSourceField(entry))
|
|
1987
|
-
score += 45;
|
|
1988
|
-
if (criterionKind === 'date range' || type.includes('date') || type.includes('time'))
|
|
1989
|
-
score += 5;
|
|
1990
|
-
if (controlType.includes('select') || controlType.includes('lookup'))
|
|
1991
|
-
score += 25;
|
|
1992
|
-
if (type === 'boolean' || controlType.includes('switch'))
|
|
1993
|
-
score += 15;
|
|
1994
|
-
if (type === 'string' || controlType.includes('text') || controlType.includes('input'))
|
|
1995
|
-
score -= 10;
|
|
1996
|
-
if (name.includes('nome') || label.includes('nome'))
|
|
1997
|
-
score -= 15;
|
|
1998
|
-
return score;
|
|
1999
|
-
}
|
|
2000
|
-
groundRelativeColumnOrder(response, request, currentState) {
|
|
2001
|
-
const prompt = this.normalizeLabel(request.prompt ?? '');
|
|
2002
|
-
const position = this.relativeOrderPosition(prompt);
|
|
2003
|
-
if (!position)
|
|
2004
|
-
return response;
|
|
2005
|
-
const plan = this.toRecord(response.componentEditPlan);
|
|
2006
|
-
if (!plan)
|
|
2007
|
-
return response;
|
|
2008
|
-
const columns = Array.isArray(currentState['columns'])
|
|
2009
|
-
? currentState['columns']
|
|
2010
|
-
.map((column) => this.toRecord(column))
|
|
2011
|
-
.filter((column) => !!column && !!this.stringValue(column['field']))
|
|
2012
|
-
: [];
|
|
2013
|
-
if (columns.length < 2)
|
|
2014
|
-
return response;
|
|
2015
|
-
const operations = this.componentEditOperations(plan);
|
|
2016
|
-
if (!operations.some((operation) => this.isColumnOrderOperation(operation))) {
|
|
2017
|
-
return response;
|
|
2018
|
-
}
|
|
2019
|
-
const groundOperation = (operation) => {
|
|
2020
|
-
if (!this.isColumnOrderOperation(operation))
|
|
2021
|
-
return operation;
|
|
2022
|
-
const targetField = this.resolveOperationTargetField(operation, columns);
|
|
2023
|
-
if (!targetField)
|
|
2024
|
-
return operation;
|
|
2025
|
-
const reference = this.findRelativeOrderReferenceColumn(prompt, columns, targetField);
|
|
2026
|
-
if (!reference)
|
|
2027
|
-
return operation;
|
|
2028
|
-
const reorderedBase = columns.filter((column) => this.stringValue(column['field']) !== targetField);
|
|
2029
|
-
const referenceIndex = reorderedBase.findIndex((column) => this.stringValue(column['field']) === reference);
|
|
2030
|
-
if (referenceIndex < 0)
|
|
2031
|
-
return operation;
|
|
2032
|
-
const order = position === 'after' ? referenceIndex + 1 : referenceIndex;
|
|
2033
|
-
const input = {
|
|
2034
|
-
...(this.toRecord(operation['input']) ?? {}),
|
|
2035
|
-
order,
|
|
2036
|
-
};
|
|
2037
|
-
return { ...operation, input };
|
|
2038
|
-
};
|
|
2039
|
-
if (Array.isArray(plan['operations'])) {
|
|
2040
|
-
return {
|
|
2041
|
-
...response,
|
|
2042
|
-
componentEditPlan: {
|
|
2043
|
-
...plan,
|
|
2044
|
-
operations: operations.map((operation) => groundOperation(operation)),
|
|
2045
|
-
},
|
|
2046
|
-
};
|
|
2047
|
-
}
|
|
2048
|
-
return {
|
|
2049
|
-
...response,
|
|
2050
|
-
componentEditPlan: groundOperation(plan),
|
|
2051
|
-
};
|
|
2052
|
-
}
|
|
2053
|
-
relativeOrderPosition(prompt) {
|
|
2054
|
-
if (prompt.includes('antes'))
|
|
2055
|
-
return 'before';
|
|
2056
|
-
if (prompt.includes('depois') || prompt.includes('apos'))
|
|
2057
|
-
return 'after';
|
|
2058
|
-
return null;
|
|
2059
|
-
}
|
|
2060
|
-
isColumnOrderOperation(operation) {
|
|
2061
|
-
return this.stringValue(operation['operationId']) === 'column.order.set'
|
|
2062
|
-
|| this.stringValue(operation['changeKind']) === 'set_column_order';
|
|
2063
|
-
}
|
|
2064
|
-
resolveOperationTargetField(operation, columns) {
|
|
2065
|
-
const target = this.toRecord(operation['target']);
|
|
2066
|
-
const input = this.toRecord(operation['input']);
|
|
2067
|
-
const rawTarget = this.stringValue(target?.['field'])
|
|
2068
|
-
|| this.stringValue(operation['field'])
|
|
2069
|
-
|| this.stringValue(input?.['field']);
|
|
2070
|
-
if (!rawTarget)
|
|
2071
|
-
return null;
|
|
2072
|
-
const normalizedTarget = this.normalizeLabel(rawTarget);
|
|
2073
|
-
const match = columns.find((column) => this.normalizeLabel(this.stringValue(column['field'])) === normalizedTarget
|
|
2074
|
-
|| this.normalizeLabel(this.stringValue(column['header'])) === normalizedTarget);
|
|
2075
|
-
return this.stringValue(match?.['field']) || rawTarget;
|
|
2076
|
-
}
|
|
2077
|
-
findRelativeOrderReferenceColumn(prompt, columns, targetField) {
|
|
2078
|
-
const candidates = columns
|
|
2079
|
-
.filter((column) => this.stringValue(column['field']) !== targetField)
|
|
2080
|
-
.map((column) => {
|
|
2081
|
-
const field = this.stringValue(column['field']);
|
|
2082
|
-
const header = this.stringValue(column['header']) || this.humanizeField(field);
|
|
2083
|
-
const fieldKey = this.normalizeLabel(field);
|
|
2084
|
-
const headerKey = this.normalizeLabel(header);
|
|
2085
|
-
let score = 0;
|
|
2086
|
-
if (headerKey && prompt.includes(headerKey))
|
|
2087
|
-
score += 4;
|
|
2088
|
-
if (fieldKey && prompt.includes(fieldKey))
|
|
2089
|
-
score += 3;
|
|
2090
|
-
return { field, score };
|
|
2091
|
-
})
|
|
2092
|
-
.filter((candidate) => candidate.field && candidate.score > 0)
|
|
2093
|
-
.sort((left, right) => right.score - left.score);
|
|
2094
|
-
return candidates[0]?.field ?? null;
|
|
2095
|
-
}
|
|
2096
|
-
toReviewMessage(response) {
|
|
2097
|
-
const runtimeSummaries = this.describeTableRuntimeOperations(response.patch);
|
|
2098
|
-
if (runtimeSummaries.length) {
|
|
2099
|
-
return [
|
|
2100
|
-
'Preparei esta operação para revisão:',
|
|
2101
|
-
'',
|
|
2102
|
-
...runtimeSummaries.map((summary) => `- ${summary}`),
|
|
2103
|
-
].join('\n');
|
|
2104
|
-
}
|
|
2105
|
-
const planSummaries = this.describeComponentEditPlan(response.componentEditPlan);
|
|
2106
|
-
if (planSummaries.length) {
|
|
2107
|
-
return [
|
|
2108
|
-
'Preparei este ajuste para revisão:',
|
|
2109
|
-
'',
|
|
2110
|
-
...planSummaries.map((summary) => `- ${summary}`),
|
|
2111
|
-
].join('\n');
|
|
2112
|
-
}
|
|
2113
|
-
return response.explanation || 'Proposta de alteração pronta para revisar.';
|
|
2114
|
-
}
|
|
2115
|
-
describeTableRuntimeOperations(patch) {
|
|
2116
|
-
const envelope = this.toRecord(this.toRecord(patch)?.['tableRuntimeOperations']);
|
|
2117
|
-
const operations = Array.isArray(envelope?.['operations']) ? envelope['operations'] : [];
|
|
2118
|
-
return operations
|
|
2119
|
-
.map((operation) => this.toRecord(operation))
|
|
2120
|
-
.filter((operation) => !!operation)
|
|
2121
|
-
.map((operation) => this.describeTableRuntimeOperation(operation))
|
|
2122
|
-
.filter((summary) => !!summary);
|
|
2123
|
-
}
|
|
2124
|
-
describeTableRuntimeOperation(operation) {
|
|
2125
|
-
const operationId = this.stringValue(operation['operationId']);
|
|
2126
|
-
const input = this.toRecord(operation['input']) ?? {};
|
|
2127
|
-
if (operationId === 'table.filter.apply') {
|
|
2128
|
-
const criteria = this.toRecord(input['criteria']) ?? {};
|
|
2129
|
-
const fields = Object.keys(criteria);
|
|
2130
|
-
if (!fields.length)
|
|
2131
|
-
return 'Vou aplicar filtros na tabela.';
|
|
2132
|
-
return `Vou aplicar filtros por ${fields.map((field) => `**${this.humanizeFilterField(field)}**`).join(', ')}.`;
|
|
2133
|
-
}
|
|
2134
|
-
if (operationId === 'table.export.run') {
|
|
2135
|
-
const format = this.stringValue(input['format']).toUpperCase();
|
|
2136
|
-
const scope = this.describeExportScope(this.stringValue(input['scope']));
|
|
2137
|
-
return `Vou exportar ${scope}${format ? ` em **${format}**` : ''}.`;
|
|
2138
|
-
}
|
|
2139
|
-
if (operationId === 'dynamicPage.surface.open') {
|
|
2140
|
-
const surfaceId = this.stringValue(input['surfaceId'] ?? input['id']);
|
|
2141
|
-
const surfaceLabel = this.stringValue(input['surfaceLabel']);
|
|
2142
|
-
return surfaceId
|
|
2143
|
-
? `Vou abrir a superfície relacionada **${surfaceLabel || this.humanizeField(surfaceId)}**.`
|
|
2144
|
-
: 'Vou abrir a superfície relacionada solicitada.';
|
|
2145
|
-
}
|
|
2146
|
-
return null;
|
|
2147
|
-
}
|
|
2148
|
-
describeExportScope(scope) {
|
|
2149
|
-
switch (scope) {
|
|
2150
|
-
case 'selected':
|
|
2151
|
-
return 'as linhas selecionadas';
|
|
2152
|
-
case 'filtered':
|
|
2153
|
-
return 'o resultado filtrado';
|
|
2154
|
-
case 'currentPage':
|
|
2155
|
-
return 'a pagina atual';
|
|
2156
|
-
case 'all':
|
|
2157
|
-
return 'todos os registros';
|
|
2158
|
-
default:
|
|
2159
|
-
return 'os dados da tabela';
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
describeComponentEditPlan(componentEditPlan) {
|
|
2163
|
-
const operations = this.componentEditOperations(componentEditPlan);
|
|
2164
|
-
const booleanStateSummary = this.describeBooleanStateRenderers(operations);
|
|
2165
|
-
if (booleanStateSummary)
|
|
2166
|
-
return [booleanStateSummary];
|
|
2167
|
-
const categoricalRendererSummary = this.describeCategoricalRenderers(operations);
|
|
2168
|
-
if (categoricalRendererSummary)
|
|
2169
|
-
return [categoricalRendererSummary];
|
|
2170
|
-
const seen = new Set();
|
|
2171
|
-
return operations
|
|
2172
|
-
.map((operation) => this.describeComponentEditOperation(operation))
|
|
2173
|
-
.filter((summary) => {
|
|
2174
|
-
if (!summary || seen.has(summary))
|
|
2175
|
-
return false;
|
|
2176
|
-
seen.add(summary);
|
|
2177
|
-
return true;
|
|
2178
|
-
});
|
|
2179
|
-
}
|
|
2180
|
-
componentEditOperations(componentEditPlan) {
|
|
2181
|
-
const plan = this.toRecord(componentEditPlan);
|
|
2182
|
-
if (!plan)
|
|
2183
|
-
return [];
|
|
2184
|
-
const operations = plan['operations'];
|
|
2185
|
-
if (Array.isArray(operations)) {
|
|
2186
|
-
return operations
|
|
2187
|
-
.map((operation) => this.toRecord(operation))
|
|
2188
|
-
.filter((operation) => !!operation);
|
|
2189
|
-
}
|
|
2190
|
-
return plan['operationId'] || plan['changeKind'] || plan['capabilityPath']
|
|
2191
|
-
? [plan]
|
|
2192
|
-
: [];
|
|
2193
|
-
}
|
|
2194
|
-
buildClarificationDiagnostics(response) {
|
|
2195
|
-
const continuation = this.extractPendingComponentEditContinuation(response);
|
|
2196
|
-
return continuation ? { tableComponentEditContinuation: continuation } : undefined;
|
|
2197
|
-
}
|
|
2198
|
-
buildReviewDiagnostics(response, warnings) {
|
|
2199
|
-
const memory = this.extractComponentEditDecisionMemory(response);
|
|
2200
|
-
if (!memory && !warnings.length)
|
|
2201
|
-
return undefined;
|
|
2202
|
-
return {
|
|
2203
|
-
...(warnings.length ? { warnings: [...warnings] } : {}),
|
|
2204
|
-
...(memory ? { tableComponentEditDecision: memory } : {}),
|
|
2205
|
-
};
|
|
2206
|
-
}
|
|
2207
|
-
tableConversationMemoryHints(request) {
|
|
2208
|
-
const diagnostics = this.toRecord(request.diagnostics);
|
|
2209
|
-
const decision = this.toRecord(diagnostics?.['tableComponentEditDecision']);
|
|
2210
|
-
if (!decision)
|
|
2211
|
-
return undefined;
|
|
2212
|
-
return {
|
|
2213
|
-
tableConversationMemory: {
|
|
2214
|
-
lastComponentEditDecision: this.toAiJsonObject(decision),
|
|
2215
|
-
},
|
|
2216
|
-
};
|
|
2217
|
-
}
|
|
2218
|
-
extractComponentEditDecisionMemory(response) {
|
|
2219
|
-
const operations = this.componentEditOperations(response.componentEditPlan);
|
|
2220
|
-
if (!operations.length)
|
|
2221
|
-
return null;
|
|
2222
|
-
const mappedOperations = operations
|
|
2223
|
-
.map((operation) => this.extractComponentEditOperationMemory(operation))
|
|
2224
|
-
.filter((operation) => !!operation);
|
|
2225
|
-
if (!mappedOperations.length)
|
|
2226
|
-
return null;
|
|
2227
|
-
return {
|
|
2228
|
-
kind: 'praxis.table.component-edit-decision-memory',
|
|
2229
|
-
operations: mappedOperations,
|
|
2230
|
-
lastTarget: mappedOperations.at(-1)?.['target'] ?? null,
|
|
2231
|
-
lastOperationId: this.stringValue(mappedOperations.at(-1)?.['operationId']),
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
extractComponentEditOperationMemory(operation) {
|
|
2235
|
-
const operationId = this.stringValue(operation['operationId']) || this.stringValue(operation['changeKind']);
|
|
2236
|
-
const target = this.toRecord(operation['target']) ?? {};
|
|
2237
|
-
const input = this.toRecord(operation['input']) ?? this.toRecord(operation['params']) ?? {};
|
|
2238
|
-
const field = this.stringValue(target['field'])
|
|
2239
|
-
|| this.stringValue(operation['field'])
|
|
2240
|
-
|| this.stringValue(input['field']);
|
|
2241
|
-
if (!operationId && !field)
|
|
2242
|
-
return null;
|
|
2243
|
-
return {
|
|
2244
|
-
...(operationId ? { operationId } : {}),
|
|
2245
|
-
...(field ? { target: { ...target, kind: this.stringValue(target['kind']) || 'column', field } } : {}),
|
|
2246
|
-
...(Object.keys(input).length ? { input } : {}),
|
|
2247
|
-
};
|
|
2248
|
-
}
|
|
2249
|
-
extractPendingComponentEditContinuation(response) {
|
|
2250
|
-
const operation = this.componentEditOperations(response.componentEditPlan)[0];
|
|
2251
|
-
if (!operation)
|
|
2252
|
-
return null;
|
|
2253
|
-
const operationId = this.stringValue(operation['operationId']) || this.stringValue(operation['changeKind']);
|
|
2254
|
-
const target = this.toRecord(operation['target']) ?? {};
|
|
2255
|
-
const input = this.toRecord(operation['input']) ?? this.toRecord(operation['params']) ?? {};
|
|
2256
|
-
const field = this.stringValue(target['field'])
|
|
2257
|
-
|| this.stringValue(operation['field'])
|
|
2258
|
-
|| this.stringValue(input['field']);
|
|
2259
|
-
if (!operationId || !field)
|
|
2260
|
-
return null;
|
|
2261
|
-
const missingInputKey = this.pendingComponentEditInputKey(operationId, input);
|
|
2262
|
-
if (!missingInputKey)
|
|
2263
|
-
return null;
|
|
2264
|
-
return {
|
|
2265
|
-
operationId,
|
|
2266
|
-
target: { ...target, field },
|
|
2267
|
-
input: { ...input },
|
|
2268
|
-
missingInputKey,
|
|
2269
|
-
};
|
|
2270
|
-
}
|
|
2271
|
-
pendingComponentEditInputKey(operationId, input) {
|
|
2272
|
-
if (operationId === 'column.width.set' && !this.stringValue(input['width'] ?? input['value'])) {
|
|
2273
|
-
return 'width';
|
|
2274
|
-
}
|
|
2275
|
-
return null;
|
|
2276
|
-
}
|
|
2277
|
-
completePendingComponentEditClarification(request) {
|
|
2278
|
-
if (request.action?.kind !== 'clarify')
|
|
2279
|
-
return null;
|
|
2280
|
-
const diagnostics = this.toRecord(request.pendingClarification?.diagnostics);
|
|
2281
|
-
const continuation = this.toRecord(diagnostics?.['tableComponentEditContinuation']);
|
|
2282
|
-
if (!continuation)
|
|
2283
|
-
return null;
|
|
2284
|
-
const operationId = this.stringValue(continuation['operationId']);
|
|
2285
|
-
const missingInputKey = this.stringValue(continuation['missingInputKey']);
|
|
2286
|
-
const target = this.toRecord(continuation['target']);
|
|
2287
|
-
const input = this.toRecord(continuation['input']) ?? {};
|
|
2288
|
-
const value = (request.prompt ?? '').trim();
|
|
2289
|
-
if (!operationId || !missingInputKey || !target || !value)
|
|
2290
|
-
return null;
|
|
2291
|
-
return {
|
|
2292
|
-
type: 'patch',
|
|
2293
|
-
sessionId: request.sessionId,
|
|
2294
|
-
componentEditPlan: {
|
|
2295
|
-
kind: 'praxis.table.component-edit-plan',
|
|
2296
|
-
version: '1.0',
|
|
2297
|
-
componentId: this.adapter.componentId || request.componentId || 'praxis-table',
|
|
2298
|
-
operationId,
|
|
2299
|
-
target,
|
|
2300
|
-
input: {
|
|
2301
|
-
...input,
|
|
2302
|
-
[missingInputKey]: value,
|
|
2303
|
-
},
|
|
2304
|
-
},
|
|
2305
|
-
explanation: 'Clarificacao aplicada ao ajuste pendente.',
|
|
2306
|
-
};
|
|
2307
|
-
}
|
|
2308
|
-
describeBooleanStateRenderers(operations) {
|
|
2309
|
-
if (operations.length < 2)
|
|
2310
|
-
return null;
|
|
2311
|
-
const rendererOperations = operations
|
|
2312
|
-
.filter((operation) => this.stringValue(operation['operationId']) === 'column.conditionalRenderer.add')
|
|
2313
|
-
.map((operation) => {
|
|
2314
|
-
const target = this.toRecord(operation['target']);
|
|
2315
|
-
const input = this.toRecord(operation['input']) ?? this.toRecord(operation['params']) ?? {};
|
|
2316
|
-
const renderer = this.toRecord(input['renderer']);
|
|
2317
|
-
const rendererType = this.stringValue(renderer?.['type']) || 'badge';
|
|
2318
|
-
const visual = this.toRecord(renderer?.[rendererType]) ?? {};
|
|
2319
|
-
return {
|
|
2320
|
-
field: this.stringValue(target?.['field']) || this.stringValue(operation['field']) || this.stringValue(input['field']),
|
|
2321
|
-
trueLabel: this.booleanConditionValue(input['condition']) === true ? this.stringValue(visual['text']) : '',
|
|
2322
|
-
falseLabel: this.booleanConditionValue(input['condition']) === false ? this.stringValue(visual['text']) : '',
|
|
2323
|
-
rendererType,
|
|
2324
|
-
details: this.booleanRendererBranchDetails(input, visual),
|
|
2325
|
-
};
|
|
2326
|
-
})
|
|
2327
|
-
.filter((item) => item.field && (item.trueLabel || item.falseLabel));
|
|
2328
|
-
if (rendererOperations.length < 2)
|
|
2329
|
-
return null;
|
|
2330
|
-
const field = rendererOperations[0].field;
|
|
2331
|
-
if (!rendererOperations.every((item) => item.field === field))
|
|
2332
|
-
return null;
|
|
2333
|
-
const trueLabel = rendererOperations.find((item) => item.trueLabel)?.trueLabel;
|
|
2334
|
-
const falseLabel = rendererOperations.find((item) => item.falseLabel)?.falseLabel;
|
|
2335
|
-
if (!trueLabel || !falseLabel)
|
|
2336
|
-
return null;
|
|
2337
|
-
const rendererType = rendererOperations.find((item) => item.rendererType)?.rendererType || 'badge';
|
|
2338
|
-
const trueDetails = rendererOperations.find((item) => item.trueLabel)?.details ?? [];
|
|
2339
|
-
const falseDetails = rendererOperations.find((item) => item.falseLabel)?.details ?? [];
|
|
2340
|
-
const trueSuffix = trueDetails.length ? ` (${trueDetails.join(', ')})` : '';
|
|
2341
|
-
const falseSuffix = falseDetails.length ? ` (${falseDetails.join(', ')})` : '';
|
|
2342
|
-
return `Vou mostrar a coluna **${this.humanizeField(field)}** como ${this.rendererLabel(rendererType)}: **${trueLabel}** para verdadeiro${trueSuffix} e **${falseLabel}** para falso${falseSuffix}.`;
|
|
2343
|
-
}
|
|
2344
|
-
booleanRendererBranchDetails(input, visual) {
|
|
2345
|
-
const details = [];
|
|
2346
|
-
const variant = this.stringValue(visual['variant']);
|
|
2347
|
-
const color = this.stringValue(visual['color']);
|
|
2348
|
-
const icon = this.stringValue(visual['icon']);
|
|
2349
|
-
const tooltip = this.tooltipLabel(input['tooltip']);
|
|
2350
|
-
if (variant)
|
|
2351
|
-
details.push(`visual ${this.variantLabel(variant)}`);
|
|
2352
|
-
if (color)
|
|
2353
|
-
details.push(`cor ${this.colorLabel(color)}`);
|
|
2354
|
-
if (icon)
|
|
2355
|
-
details.push(`icone ${icon}`);
|
|
2356
|
-
if (tooltip)
|
|
2357
|
-
details.push(`dica "${tooltip}"`);
|
|
2358
|
-
return details;
|
|
2359
|
-
}
|
|
2360
|
-
describeCategoricalRenderers(operations) {
|
|
2361
|
-
const rendererOperations = operations
|
|
2362
|
-
.filter((operation) => this.stringValue(operation['operationId']) === 'column.conditionalRenderer.add')
|
|
2363
|
-
.map((operation) => {
|
|
2364
|
-
const target = this.toRecord(operation['target']);
|
|
2365
|
-
const input = this.toRecord(operation['input']) ?? this.toRecord(operation['params']) ?? {};
|
|
2366
|
-
const field = this.stringValue(target?.['field'])
|
|
2367
|
-
|| this.stringValue(operation['field'])
|
|
2368
|
-
|| this.stringValue(input['field']);
|
|
2369
|
-
const equality = this.literalEqualityCondition(input['condition']);
|
|
2370
|
-
const renderer = this.toRecord(input['renderer']);
|
|
2371
|
-
const rendererType = this.stringValue(renderer?.['type']) || 'badge';
|
|
2372
|
-
const visual = this.toRecord(renderer?.[rendererType]) ?? {};
|
|
2373
|
-
const label = this.stringValue(visual['text']) || equality?.valueLabel || '';
|
|
2374
|
-
return {
|
|
2375
|
-
field,
|
|
2376
|
-
conditionField: equality?.field,
|
|
2377
|
-
valueLabel: equality?.valueLabel,
|
|
2378
|
-
label,
|
|
2379
|
-
rendererType,
|
|
2380
|
-
details: this.categoricalRendererDetails(input, visual),
|
|
2381
|
-
};
|
|
2382
|
-
})
|
|
2383
|
-
.filter((item) => item.field && item.conditionField && item.valueLabel);
|
|
2384
|
-
if (rendererOperations.length < 2)
|
|
2385
|
-
return null;
|
|
2386
|
-
const field = rendererOperations[0].field;
|
|
2387
|
-
if (!rendererOperations.every((item) => item.field === field && item.conditionField === field))
|
|
2388
|
-
return null;
|
|
2389
|
-
const rendererType = rendererOperations.find((item) => item.rendererType)?.rendererType || 'badge';
|
|
2390
|
-
const uniqueOperations = this.uniqueCategoricalRendererOperations(rendererOperations);
|
|
2391
|
-
const options = uniqueOperations
|
|
2392
|
-
.slice(0, 6)
|
|
2393
|
-
.map((item) => {
|
|
2394
|
-
const details = item.details.length ? ` (${item.details.join(', ')})` : '';
|
|
2395
|
-
return `**${this.enumValueLabel(item.label || item.valueLabel || '')}**${details}`;
|
|
2396
|
-
});
|
|
2397
|
-
const suffix = uniqueOperations.length > options.length
|
|
2398
|
-
? ` e mais ${uniqueOperations.length - options.length} valores`
|
|
2399
|
-
: '';
|
|
2400
|
-
return `Vou mostrar a coluna **${this.humanizeField(field)}** como ${this.rendererPluralLabel(rendererType)} coloridos para ${options.join(', ')}${suffix}.`;
|
|
2401
|
-
}
|
|
2402
|
-
categoricalRendererDetails(input, visual) {
|
|
2403
|
-
const details = this.rendererVisualDetails(visual);
|
|
2404
|
-
const tooltip = this.tooltipLabel(input['tooltip']);
|
|
2405
|
-
if (tooltip)
|
|
2406
|
-
details.push(`dica "${tooltip}"`);
|
|
2407
|
-
return details;
|
|
2408
|
-
}
|
|
2409
|
-
uniqueCategoricalRendererOperations(operations) {
|
|
2410
|
-
const seen = new Set();
|
|
2411
|
-
const unique = [];
|
|
2412
|
-
for (const operation of operations) {
|
|
2413
|
-
const key = this.normalizeComparableLabel(operation.valueLabel || '');
|
|
2414
|
-
if (!key || seen.has(key))
|
|
2415
|
-
continue;
|
|
2416
|
-
seen.add(key);
|
|
2417
|
-
unique.push(operation);
|
|
2418
|
-
}
|
|
2419
|
-
return unique;
|
|
2420
|
-
}
|
|
2421
|
-
describeComponentEditOperation(operation) {
|
|
2422
|
-
const operationId = this.stringValue(operation['operationId']) || this.stringValue(operation['changeKind']);
|
|
2423
|
-
const target = this.toRecord(operation['target']);
|
|
2424
|
-
const input = this.toRecord(operation['input']) ?? this.toRecord(operation['params']) ?? {};
|
|
2425
|
-
const field = this.stringValue(target?.['field'])
|
|
2426
|
-
|| this.stringValue(operation['field'])
|
|
2427
|
-
|| this.stringValue(input['field']);
|
|
2428
|
-
const label = field ? this.humanizeField(field) : 'a tabela';
|
|
2429
|
-
switch (operationId) {
|
|
2430
|
-
case 'column.add':
|
|
2431
|
-
case 'add_column':
|
|
2432
|
-
return `Vou adicionar a coluna **${label}** na tabela.`;
|
|
2433
|
-
case 'column.format.set':
|
|
2434
|
-
case 'set_column_format':
|
|
2435
|
-
return `Vou formatar a coluna **${label}** como **${this.formatLabel(input['format'] ?? operation['value'])}**.`;
|
|
2436
|
-
case 'column.valueMapping.set':
|
|
2437
|
-
case 'set_column_value_mapping':
|
|
2438
|
-
return this.describeValueMapping(label, input['valueMapping'] ?? operation['valueMapping'] ?? operation['value']);
|
|
2439
|
-
case 'column.header.set':
|
|
2440
|
-
case 'set_column_header':
|
|
2441
|
-
return `Vou renomear a coluna **${label}** para **${this.stringValue(input['header'] ?? operation['value']) || label}**.`;
|
|
2442
|
-
case 'column.visibility.set':
|
|
2443
|
-
case 'set_column_visibility':
|
|
2444
|
-
return `${this.booleanInput(input['visible'] ?? operation['value']) === false ? 'Vou ocultar' : 'Vou exibir'} a coluna **${label}**.`;
|
|
2445
|
-
case 'column.sticky.set':
|
|
2446
|
-
return this.describeStickyColumn(label, input['sticky'] ?? input['value'] ?? operation['value']);
|
|
2447
|
-
case 'column.width.set':
|
|
2448
|
-
return `Vou ajustar a largura da coluna **${label}**.`;
|
|
2449
|
-
case 'column.order.set':
|
|
2450
|
-
return `Vou reposicionar a coluna **${label}** na tabela.`;
|
|
2451
|
-
case 'column.align.set':
|
|
2452
|
-
return `Vou alinhar a coluna **${label}** ${this.alignmentLabel(input['align'] ?? input['value'] ?? operation['value'])}.`;
|
|
2453
|
-
case 'column.style.set':
|
|
2454
|
-
return this.describeColumnStyle(label, input['style'] ?? input['value'] ?? operation['value'], 'coluna');
|
|
2455
|
-
case 'column.headerStyle.set':
|
|
2456
|
-
return this.describeColumnStyle(label, input['headerStyle'] ?? input['value'] ?? operation['value'], 'cabecalho');
|
|
2457
|
-
case 'column.remove':
|
|
2458
|
-
return `Vou remover a coluna **${label}** da tabela.`;
|
|
2459
|
-
case 'column.renderer.set':
|
|
2460
|
-
case 'set_column_renderer':
|
|
2461
|
-
return `Vou ajustar a apresentacao visual da coluna **${label}**.`;
|
|
2462
|
-
case 'column.conditionalRenderer.add':
|
|
2463
|
-
case 'set_column_conditional_badge_renderers':
|
|
2464
|
-
return this.describeConditionalRenderer(label, input);
|
|
2465
|
-
case 'column.conditionalStyle.add':
|
|
2466
|
-
case 'set_column_conditional_style':
|
|
2467
|
-
return this.describeConditionalStyle(label, this.toRecord(operation['value']) ?? input);
|
|
2468
|
-
case 'column.computed.add':
|
|
2469
|
-
case 'column.computed.set':
|
|
2470
|
-
case 'add_computed_column':
|
|
2471
|
-
return `Vou criar ou atualizar a coluna calculada **${label}**.`;
|
|
2472
|
-
case 'behavior.filtering.configure':
|
|
2473
|
-
return 'Vou atualizar os filtros da tabela.';
|
|
2474
|
-
case 'filter.advanced.configure':
|
|
2475
|
-
case 'configure_advanced_filters':
|
|
2476
|
-
return this.describeAdvancedFiltersConfigure(input);
|
|
2477
|
-
case 'filter.advanced.fields.add':
|
|
2478
|
-
return this.describeAdvancedFilterFields(input, 'add');
|
|
2479
|
-
case 'filter.advanced.fields.remove':
|
|
2480
|
-
return this.describeAdvancedFilterFields(input, 'remove');
|
|
2481
|
-
case 'behavior.pagination.configure':
|
|
2482
|
-
return 'Vou atualizar a paginacao da tabela.';
|
|
2483
|
-
case 'behavior.selection.configure':
|
|
2484
|
-
return 'Vou atualizar a selecao de linhas.';
|
|
2485
|
-
case 'toolbar.configure':
|
|
2486
|
-
return 'Vou ajustar a barra de acoes da tabela.';
|
|
2487
|
-
case 'toolbar.action.add':
|
|
2488
|
-
case 'add_toolbar_action':
|
|
2489
|
-
return `Vou adicionar a acao **${this.stringValue(input['label']) || this.stringValue(input['id']) || 'solicitada'}** na barra da tabela.`;
|
|
2490
|
-
case 'rowAction.add':
|
|
2491
|
-
case 'add_row_action':
|
|
2492
|
-
return `Vou criar um botao em cada linha para abrir **${this.stringValue(input['label']) || this.stringValue(input['id']) || 'a opcao solicitada'}**.`;
|
|
2493
|
-
case 'bulkAction.add':
|
|
2494
|
-
case 'add_bulk_action':
|
|
2495
|
-
return `Vou adicionar a acao **${this.stringValue(input['label']) || this.stringValue(input['id']) || 'solicitada'}** para os registros selecionados.`;
|
|
2496
|
-
case 'export.configure':
|
|
2497
|
-
return this.describeExportConfigure(input);
|
|
2498
|
-
case 'appearance.density.set':
|
|
2499
|
-
return `Vou ajustar a densidade da tabela para **${this.stringValue(input['density'] ?? operation['value']) || 'o valor escolhido'}**.`;
|
|
2500
|
-
default:
|
|
2501
|
-
if (field && (input['format'] != null || operation['format'] != null)) {
|
|
2502
|
-
return `Vou formatar a coluna **${label}** como **${this.formatLabel(input['format'] ?? operation['format'])}**.`;
|
|
2503
|
-
}
|
|
2504
|
-
return field
|
|
2505
|
-
? `Vou atualizar a coluna **${label}**.`
|
|
2506
|
-
: 'Vou atualizar a configuracao da tabela.';
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
describeExportConfigure(input) {
|
|
2510
|
-
const scope = this.stringValue(input['scope'] ?? input['exportScope']);
|
|
2511
|
-
const serialized = JSON.stringify(input).toLowerCase();
|
|
2512
|
-
const selectedOnly = scope === 'selected'
|
|
2513
|
-
|| scope === 'selection'
|
|
2514
|
-
|| serialized.includes('selected')
|
|
2515
|
-
|| serialized.includes('selection');
|
|
2516
|
-
if (selectedOnly) {
|
|
2517
|
-
return 'Vou habilitar a exportacao das linhas selecionadas.';
|
|
2518
|
-
}
|
|
2519
|
-
return 'Vou habilitar a exportacao da tabela.';
|
|
2520
|
-
}
|
|
2521
|
-
describeValueMapping(label, mappingValue) {
|
|
2522
|
-
const mapping = this.toRecord(mappingValue);
|
|
2523
|
-
if (!mapping) {
|
|
2524
|
-
return `Vou mapear os valores da coluna **${label}** para rotulos legiveis.`;
|
|
2525
|
-
}
|
|
2526
|
-
const trueLabel = this.stringValue(mapping['true'] ?? mapping['TRUE'] ?? mapping['1']);
|
|
2527
|
-
const falseLabel = this.stringValue(mapping['false'] ?? mapping['FALSE'] ?? mapping['0']);
|
|
2528
|
-
if (trueLabel && falseLabel) {
|
|
2529
|
-
return `Vou mostrar a coluna **${label}** como **${trueLabel}/${falseLabel}**.`;
|
|
2530
|
-
}
|
|
2531
|
-
const entries = Object.entries(mapping)
|
|
2532
|
-
.map(([key, value]) => {
|
|
2533
|
-
const text = this.stringValue(value);
|
|
2534
|
-
return key && text ? `**${key}** como **${text}**` : null;
|
|
2535
|
-
})
|
|
2536
|
-
.filter((item) => !!item)
|
|
2537
|
-
.slice(0, 3);
|
|
2538
|
-
if (entries.length > 0) {
|
|
2539
|
-
return `Vou mapear ${entries.join(', ')} na coluna **${label}**.`;
|
|
2540
|
-
}
|
|
2541
|
-
return `Vou mapear os valores da coluna **${label}** para rotulos legiveis.`;
|
|
2542
|
-
}
|
|
2543
|
-
describeStickyColumn(label, value) {
|
|
2544
|
-
const sticky = this.stringValue(value).toLowerCase();
|
|
2545
|
-
if (sticky === 'false' || sticky === 'none') {
|
|
2546
|
-
return `Vou desafixar a coluna **${label}**.`;
|
|
2547
|
-
}
|
|
2548
|
-
if (sticky === 'end' || sticky === 'right') {
|
|
2549
|
-
return `Vou fixar a coluna **${label}** no fim da tabela.`;
|
|
2550
|
-
}
|
|
2551
|
-
return `Vou fixar a coluna **${label}** no inicio da tabela.`;
|
|
2552
|
-
}
|
|
2553
|
-
alignmentLabel(value) {
|
|
2554
|
-
switch (this.stringValue(value).toLowerCase()) {
|
|
2555
|
-
case 'left':
|
|
2556
|
-
return 'a esquerda';
|
|
2557
|
-
case 'right':
|
|
2558
|
-
return 'a direita';
|
|
2559
|
-
case 'center':
|
|
2560
|
-
return 'ao centro';
|
|
2561
|
-
default:
|
|
2562
|
-
return 'como solicitado';
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
describeAdvancedFiltersConfigure(input) {
|
|
2566
|
-
const enabled = this.booleanInput(input['enabled']);
|
|
2567
|
-
if (enabled === false) {
|
|
2568
|
-
return 'Vou desativar os filtros avancados da tabela.';
|
|
2569
|
-
}
|
|
2570
|
-
const settings = this.toRecord(input['settings']) ?? {};
|
|
2571
|
-
const fields = this.arrayOfStrings(settings['alwaysVisibleFields']);
|
|
2572
|
-
if (fields.length) {
|
|
2573
|
-
return `Vou ativar os filtros avancados e deixar **${fields.map((field) => this.humanizeFilterField(field)).join(', ')}** sempre visiveis.`;
|
|
2574
|
-
}
|
|
2575
|
-
return 'Vou ativar os filtros avancados da tabela.';
|
|
2576
|
-
}
|
|
2577
|
-
describeAdvancedFilterFields(input, action) {
|
|
2578
|
-
const fields = this.arrayOfStrings(input['fields']);
|
|
2579
|
-
if (!fields.length) {
|
|
2580
|
-
return action === 'add'
|
|
2581
|
-
? 'Vou incluir novos campos nos filtros avancados.'
|
|
2582
|
-
: 'Vou remover campos dos filtros avancados.';
|
|
2583
|
-
}
|
|
2584
|
-
const labels = fields.map((field) => this.humanizeFilterField(field)).join(', ');
|
|
2585
|
-
if (action === 'remove') {
|
|
2586
|
-
return `Vou remover **${labels}** dos filtros avancados.`;
|
|
2587
|
-
}
|
|
2588
|
-
const alwaysVisible = this.booleanInput(input['alwaysVisible']);
|
|
2589
|
-
if (alwaysVisible === true) {
|
|
2590
|
-
return `Vou incluir **${labels}** nos filtros avancados e deixar disponivel na area principal.`;
|
|
2591
|
-
}
|
|
2592
|
-
return `Vou incluir **${labels}** nos filtros avancados.`;
|
|
2593
|
-
}
|
|
2594
|
-
describeConditionalRenderer(label, input) {
|
|
2595
|
-
const renderer = this.toRecord(input['renderer']);
|
|
2596
|
-
const rendererType = this.stringValue(renderer?.['type']) || 'badge';
|
|
2597
|
-
const visual = this.toRecord(renderer?.[rendererType]) ?? {};
|
|
2598
|
-
const text = this.stringValue(visual['text']);
|
|
2599
|
-
const visualDetails = this.rendererVisualDetails(visual);
|
|
2600
|
-
const visualSuffix = visualDetails.length ? `, ${visualDetails.join(', ')}` : '';
|
|
2601
|
-
const description = this.stringValue(input['description']);
|
|
2602
|
-
const condition = this.conditionPhrase(input['condition'], label);
|
|
2603
|
-
const conditionSuffix = condition ? ` quando **${condition}**` : '';
|
|
2604
|
-
const tooltip = this.tooltipLabel(input['tooltip']) ?? this.semanticDescriptionLabel(input['description']);
|
|
2605
|
-
if (tooltip && !text) {
|
|
2606
|
-
return `Vou adicionar uma dica na coluna **${label}**${conditionSuffix}: **${tooltip}**.`;
|
|
2607
|
-
}
|
|
2608
|
-
if (text) {
|
|
2609
|
-
return `Vou destacar a coluna **${label}** com ${this.rendererLabel(rendererType)} **${text}**${visualSuffix}${conditionSuffix}.`;
|
|
2610
|
-
}
|
|
2611
|
-
if (description) {
|
|
2612
|
-
return `Vou destacar a coluna **${label}**${visualSuffix} quando **${description}**.`;
|
|
2613
|
-
}
|
|
2614
|
-
return `Vou destacar a coluna **${label}**${visualSuffix}${conditionSuffix}.`;
|
|
2615
|
-
}
|
|
2616
|
-
rendererVisualDetails(visual) {
|
|
2617
|
-
const details = [];
|
|
2618
|
-
const color = this.stringValue(visual['color']);
|
|
2619
|
-
const variant = this.stringValue(visual['variant']);
|
|
2620
|
-
const icon = this.stringValue(visual['icon']);
|
|
2621
|
-
if (color)
|
|
2622
|
-
details.push(`cor ${this.colorLabel(color)}`);
|
|
2623
|
-
if (variant)
|
|
2624
|
-
details.push(`estilo ${this.variantLabel(variant)}`);
|
|
2625
|
-
if (icon)
|
|
2626
|
-
details.push(`icone ${icon}`);
|
|
2627
|
-
return details;
|
|
2628
|
-
}
|
|
2629
|
-
describeConditionalStyle(label, input) {
|
|
2630
|
-
const parts = [`Vou destacar a coluna **${label}**`];
|
|
2631
|
-
const condition = this.conditionPhrase(input['condition'], label);
|
|
2632
|
-
const style = this.styleLabel(this.toRecord(input['style']));
|
|
2633
|
-
const tooltip = this.tooltipLabel(input['tooltip']) ?? this.semanticDescriptionLabel(input['description']);
|
|
2634
|
-
if (!condition && !style && tooltip) {
|
|
2635
|
-
return `Vou adicionar uma dica na coluna **${label}**: **${tooltip}**.`;
|
|
2636
|
-
}
|
|
2637
|
-
if (condition && !style && tooltip) {
|
|
2638
|
-
return `Vou adicionar uma dica na coluna **${label}** quando **${condition}**: **${tooltip}**.`;
|
|
2639
|
-
}
|
|
2640
|
-
if (condition) {
|
|
2641
|
-
parts.push(`quando **${condition}**`);
|
|
2642
|
-
}
|
|
2643
|
-
if (style) {
|
|
2644
|
-
parts.push(`usando ${style}`);
|
|
2645
|
-
}
|
|
2646
|
-
if (tooltip) {
|
|
2647
|
-
parts.push(`com dica **${tooltip}**`);
|
|
2648
|
-
}
|
|
2649
|
-
return `${parts.join(' ')}.`;
|
|
2650
|
-
}
|
|
2651
|
-
variantLabel(value) {
|
|
2652
|
-
const normalized = value.toLowerCase();
|
|
2653
|
-
switch (normalized) {
|
|
2654
|
-
case 'filled':
|
|
2655
|
-
case 'solid':
|
|
2656
|
-
return 'preenchido';
|
|
2657
|
-
case 'outlined':
|
|
2658
|
-
case 'outline':
|
|
2659
|
-
return 'com contorno';
|
|
2660
|
-
case 'soft':
|
|
2661
|
-
return 'suave';
|
|
2662
|
-
default:
|
|
2663
|
-
return value;
|
|
2664
|
-
}
|
|
2665
|
-
}
|
|
2666
|
-
conditionPhrase(condition, currentLabel) {
|
|
2667
|
-
const record = this.toRecord(condition);
|
|
2668
|
-
if (!record)
|
|
2669
|
-
return null;
|
|
2670
|
-
if (this.isTautologyCondition(record))
|
|
2671
|
-
return null;
|
|
2672
|
-
for (const operator of ['===', '==']) {
|
|
2673
|
-
const operands = record[operator];
|
|
2674
|
-
if (!Array.isArray(operands) || operands.length < 2)
|
|
2675
|
-
continue;
|
|
2676
|
-
const booleanLabel = this.booleanEqualityConditionLabel(operands[0], operands[1], currentLabel)
|
|
2677
|
-
?? this.booleanEqualityConditionLabel(operands[1], operands[0], currentLabel);
|
|
2678
|
-
if (booleanLabel)
|
|
2679
|
-
return booleanLabel;
|
|
2680
|
-
const literalLabel = this.literalEqualityConditionLabel(operands[0], operands[1], currentLabel)
|
|
2681
|
-
?? this.literalEqualityConditionLabel(operands[1], operands[0], currentLabel);
|
|
2682
|
-
if (literalLabel)
|
|
2683
|
-
return literalLabel;
|
|
2684
|
-
}
|
|
2685
|
-
return this.conditionLabel(condition);
|
|
2686
|
-
}
|
|
2687
|
-
literalEqualityCondition(condition) {
|
|
2688
|
-
const record = this.toRecord(condition);
|
|
2689
|
-
if (!record)
|
|
2690
|
-
return null;
|
|
2691
|
-
for (const operator of ['===', '==']) {
|
|
2692
|
-
const operands = record[operator];
|
|
2693
|
-
if (!Array.isArray(operands) || operands.length < 2)
|
|
2694
|
-
continue;
|
|
2695
|
-
const direct = this.literalEqualityOperands(operands[0], operands[1])
|
|
2696
|
-
?? this.literalEqualityOperands(operands[1], operands[0]);
|
|
2697
|
-
if (direct)
|
|
2698
|
-
return direct;
|
|
2699
|
-
}
|
|
2700
|
-
return null;
|
|
2701
|
-
}
|
|
2702
|
-
literalEqualityOperands(fieldOperand, valueOperand) {
|
|
2703
|
-
const field = this.stringValue(this.toRecord(fieldOperand)?.['var']);
|
|
2704
|
-
if (!field)
|
|
2705
|
-
return null;
|
|
2706
|
-
if (typeof valueOperand !== 'string' && typeof valueOperand !== 'number')
|
|
2707
|
-
return null;
|
|
2708
|
-
return { field, valueLabel: String(valueOperand) };
|
|
2709
|
-
}
|
|
2710
|
-
literalEqualityConditionLabel(fieldOperand, valueOperand, currentLabel) {
|
|
2711
|
-
const equality = this.literalEqualityOperands(fieldOperand, valueOperand);
|
|
2712
|
-
if (!equality)
|
|
2713
|
-
return null;
|
|
2714
|
-
const fieldLabel = this.humanizeField(equality.field);
|
|
2715
|
-
const valueLabel = this.enumValueLabel(equality.valueLabel);
|
|
2716
|
-
const normalizedCurrent = currentLabel ? this.normalizeComparableLabel(currentLabel) : '';
|
|
2717
|
-
const normalizedField = this.normalizeComparableLabel(fieldLabel);
|
|
2718
|
-
if (normalizedCurrent && normalizedCurrent === normalizedField) {
|
|
2719
|
-
return `for ${valueLabel}`;
|
|
2720
|
-
}
|
|
2721
|
-
return `${fieldLabel} for ${valueLabel}`;
|
|
2722
|
-
}
|
|
2723
|
-
isTautologyCondition(condition) {
|
|
2724
|
-
for (const operator of ['===', '==']) {
|
|
2725
|
-
const operands = condition[operator];
|
|
2726
|
-
if (!Array.isArray(operands) || operands.length < 2)
|
|
2727
|
-
continue;
|
|
2728
|
-
if (this.samePrimitiveOperand(operands[0], operands[1]))
|
|
2729
|
-
return true;
|
|
2730
|
-
}
|
|
2731
|
-
return false;
|
|
2732
|
-
}
|
|
2733
|
-
samePrimitiveOperand(left, right) {
|
|
2734
|
-
const leftType = typeof left;
|
|
2735
|
-
return (leftType === 'string' || leftType === 'number' || leftType === 'boolean')
|
|
2736
|
-
&& leftType === typeof right
|
|
2737
|
-
&& left === right;
|
|
2738
|
-
}
|
|
2739
|
-
booleanEqualityConditionLabel(fieldOperand, valueOperand, currentLabel) {
|
|
2740
|
-
if (typeof valueOperand !== 'boolean')
|
|
2741
|
-
return null;
|
|
2742
|
-
const field = this.stringValue(this.toRecord(fieldOperand)?.['var']);
|
|
2743
|
-
if (!field)
|
|
2744
|
-
return null;
|
|
2745
|
-
const fieldLabel = this.humanizeField(field);
|
|
2746
|
-
const normalizedCurrent = currentLabel ? this.normalizeComparableLabel(currentLabel) : '';
|
|
2747
|
-
const normalizedField = this.normalizeComparableLabel(fieldLabel);
|
|
2748
|
-
const state = valueOperand ? 'ativo' : 'inativo';
|
|
2749
|
-
if (normalizedCurrent && normalizedCurrent === normalizedField) {
|
|
2750
|
-
return `estiver ${state}`;
|
|
2751
|
-
}
|
|
2752
|
-
return `${fieldLabel} estiver ${state}`;
|
|
2753
|
-
}
|
|
2754
|
-
normalizeComparableLabel(value) {
|
|
2755
|
-
return value
|
|
2756
|
-
.normalize('NFD')
|
|
2757
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
2758
|
-
.toLowerCase()
|
|
2759
|
-
.replace(/[^a-z0-9]+/g, '');
|
|
2760
|
-
}
|
|
2761
|
-
conditionLabel(condition) {
|
|
2762
|
-
const record = this.toRecord(condition);
|
|
2763
|
-
if (!record)
|
|
2764
|
-
return null;
|
|
2765
|
-
const startsWith = record['startsWith'];
|
|
2766
|
-
if (Array.isArray(startsWith) && startsWith.length >= 2) {
|
|
2767
|
-
const field = this.jsonLogicOperandLabel(startsWith[0]);
|
|
2768
|
-
const value = this.jsonLogicOperandLabel(startsWith[1]);
|
|
2769
|
-
if (field && value) {
|
|
2770
|
-
return `${field} comeca com ${value}`;
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
for (const operator of ['>=', '<=', '>', '<', '===', '==']) {
|
|
2774
|
-
const operands = record[operator];
|
|
2775
|
-
if (!Array.isArray(operands) || operands.length < 2)
|
|
2776
|
-
continue;
|
|
2777
|
-
const field = this.jsonLogicOperandLabel(operands[0]);
|
|
2778
|
-
const value = this.jsonLogicOperandLabel(operands[1]);
|
|
2779
|
-
if (field && value) {
|
|
2780
|
-
return `${field} ${operator} ${value}`;
|
|
2781
|
-
}
|
|
2782
|
-
}
|
|
2783
|
-
return null;
|
|
2784
|
-
}
|
|
2785
|
-
jsonLogicOperandLabel(operand) {
|
|
2786
|
-
const record = this.toRecord(operand);
|
|
2787
|
-
const field = this.stringValue(record?.['var']);
|
|
2788
|
-
if (field)
|
|
2789
|
-
return this.humanizeField(field);
|
|
2790
|
-
if (typeof operand === 'string')
|
|
2791
|
-
return operand;
|
|
2792
|
-
if (typeof operand === 'number' || typeof operand === 'boolean')
|
|
2793
|
-
return String(operand);
|
|
2794
|
-
return null;
|
|
2795
|
-
}
|
|
2796
|
-
styleLabel(style) {
|
|
2797
|
-
if (!style)
|
|
2798
|
-
return null;
|
|
2799
|
-
const labels = [];
|
|
2800
|
-
const background = this.stringValue(style['backgroundColor'] ?? style['background-color']);
|
|
2801
|
-
const color = this.stringValue(style['color']);
|
|
2802
|
-
const fontWeight = this.stringValue(style['fontWeight'] ?? style['font-weight']);
|
|
2803
|
-
const opacity = this.stringValue(style['opacity']);
|
|
2804
|
-
const border = this.stringValue(style['border']
|
|
2805
|
-
?? style['borderLeft']
|
|
2806
|
-
?? style['border-left']
|
|
2807
|
-
?? style['borderRight']
|
|
2808
|
-
?? style['border-right']
|
|
2809
|
-
?? style['borderTop']
|
|
2810
|
-
?? style['border-top']
|
|
2811
|
-
?? style['borderBottom']
|
|
2812
|
-
?? style['border-bottom']);
|
|
2813
|
-
if (background)
|
|
2814
|
-
labels.push(`fundo ${this.colorLabel(background)}`);
|
|
2815
|
-
if (color)
|
|
2816
|
-
labels.push(`texto ${this.colorLabel(color)}`);
|
|
2817
|
-
if (fontWeight)
|
|
2818
|
-
labels.push('texto em destaque');
|
|
2819
|
-
if (opacity)
|
|
2820
|
-
labels.push(`opacidade ${opacity}`);
|
|
2821
|
-
if (border)
|
|
2822
|
-
labels.push('borda discreta');
|
|
2823
|
-
return labels.length ? labels.join(', ') : null;
|
|
2824
|
-
}
|
|
2825
|
-
describeColumnStyle(label, value, target) {
|
|
2826
|
-
const details = this.styleLabel(this.styleRecord(value));
|
|
2827
|
-
const suffix = details ? ` com ${details}` : '';
|
|
2828
|
-
if (target === 'cabecalho') {
|
|
2829
|
-
return `Vou ajustar o estilo do cabecalho da coluna **${label}**${suffix}.`;
|
|
2830
|
-
}
|
|
2831
|
-
return `Vou ajustar o estilo visual da coluna **${label}**${suffix}.`;
|
|
2832
|
-
}
|
|
2833
|
-
styleRecord(value) {
|
|
2834
|
-
const record = this.toRecord(value);
|
|
2835
|
-
if (record)
|
|
2836
|
-
return record;
|
|
2837
|
-
const style = this.stringValue(value);
|
|
2838
|
-
if (!style)
|
|
2839
|
-
return null;
|
|
2840
|
-
const parsed = {};
|
|
2841
|
-
for (const declaration of style.split(';')) {
|
|
2842
|
-
const separatorIndex = declaration.indexOf(':');
|
|
2843
|
-
if (separatorIndex <= 0)
|
|
2844
|
-
continue;
|
|
2845
|
-
const property = declaration.slice(0, separatorIndex).trim();
|
|
2846
|
-
const cssValue = declaration.slice(separatorIndex + 1).trim();
|
|
2847
|
-
if (property && cssValue)
|
|
2848
|
-
parsed[property] = cssValue;
|
|
2849
|
-
}
|
|
2850
|
-
return Object.keys(parsed).length ? parsed : null;
|
|
2851
|
-
}
|
|
2852
|
-
colorLabel(value) {
|
|
2853
|
-
const normalized = value.toLowerCase();
|
|
2854
|
-
if (normalized === 'warn' || normalized === 'warning') {
|
|
2855
|
-
return 'de alerta';
|
|
2856
|
-
}
|
|
2857
|
-
if (normalized === 'accent') {
|
|
2858
|
-
return 'de destaque';
|
|
2859
|
-
}
|
|
2860
|
-
if (normalized === 'info') {
|
|
2861
|
-
return 'informativa';
|
|
2862
|
-
}
|
|
2863
|
-
if (normalized === 'basic' || normalized === 'neutral' || normalized === 'default') {
|
|
2864
|
-
return 'neutra';
|
|
2865
|
-
}
|
|
2866
|
-
if (normalized === 'success' || normalized.includes('46, 125, 50') || normalized.includes('27, 94, 32') || normalized.includes('green')
|
|
2867
|
-
|| normalized.includes('#e8f5e9') || normalized.includes('#1b5e20') || normalized.includes('#a5d6a7')
|
|
2868
|
-
|| normalized.includes('#2e7d32')) {
|
|
2869
|
-
return 'verde suave';
|
|
2870
|
-
}
|
|
2871
|
-
if (normalized.includes('255, 152, 0') || normalized.includes('255, 243, 224') || normalized.includes('138, 75, 0') || normalized.includes('orange')
|
|
2872
|
-
|| normalized.includes('#fff3e0') || normalized.includes('#ff9800') || normalized.includes('#ffa500') || normalized.includes('#8a4b00')) {
|
|
2873
|
-
return 'laranja suave';
|
|
2874
|
-
}
|
|
2875
|
-
if (normalized.includes('244, 67, 54') || normalized.includes('183, 28, 28') || normalized.includes('red')
|
|
2876
|
-
|| normalized.includes('#ffebee') || normalized.includes('#b71c1c')) {
|
|
2877
|
-
return 'vermelho suave';
|
|
2878
|
-
}
|
|
2879
|
-
if (normalized === 'primary' || normalized.includes('blue') || normalized.includes('#1976d2')
|
|
2880
|
-
|| normalized.includes('#2196f3')) {
|
|
2881
|
-
return 'azul';
|
|
2882
|
-
}
|
|
2883
|
-
return value;
|
|
2884
|
-
}
|
|
2885
|
-
enumValueLabel(value) {
|
|
2886
|
-
const trimmed = value.trim();
|
|
2887
|
-
if (!trimmed)
|
|
2888
|
-
return value;
|
|
2889
|
-
return trimmed
|
|
2890
|
-
.toLowerCase()
|
|
2891
|
-
.split(/[_\s-]+/u)
|
|
2892
|
-
.filter((part) => part.length > 0)
|
|
2893
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
2894
|
-
.join(' ');
|
|
2895
|
-
}
|
|
2896
|
-
tooltipLabel(value) {
|
|
2897
|
-
const tooltip = this.toRecord(value);
|
|
2898
|
-
const text = this.stringValue(tooltip?.['text']) || this.stringValue(value);
|
|
2899
|
-
return this.cleanReviewTooltipText(text);
|
|
2900
|
-
}
|
|
2901
|
-
semanticDescriptionLabel(value) {
|
|
2902
|
-
const text = this.cleanReviewTooltipText(this.stringValue(value));
|
|
2903
|
-
if (!text)
|
|
2904
|
-
return null;
|
|
2905
|
-
if (/^(adicionar|ajustar|alterar|destacar|aplicar|criar)\b/i.test(text))
|
|
2906
|
-
return null;
|
|
2907
|
-
return text;
|
|
2908
|
-
}
|
|
2909
|
-
cleanReviewTooltipText(value) {
|
|
2910
|
-
const cleaned = value
|
|
2911
|
-
.trim()
|
|
2912
|
-
.replace(/\bcondicao\b/gi, 'condição')
|
|
2913
|
-
.replace(/^adicionar\s+(?:tooltip|dica)\s*(?:dizendo|com\s+texto)?\s*/i, '')
|
|
2914
|
-
.replace(/^adiciona\s+/i, '')
|
|
2915
|
-
.replace(/^aplica(?:r)?\s+/i, '')
|
|
2916
|
-
.replace(/^com\s+tooltip\s*/i, '')
|
|
2917
|
-
.replace(/^tooltip\s*/i, '')
|
|
2918
|
-
.trim();
|
|
2919
|
-
return cleaned || null;
|
|
2920
|
-
}
|
|
2921
|
-
toChatMessages(messages, prompt) {
|
|
2922
|
-
const supported = (messages ?? [])
|
|
2923
|
-
.filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
|
|
2924
|
-
.map((message) => ({
|
|
2925
|
-
role: message.role,
|
|
2926
|
-
content: message.text,
|
|
2927
|
-
}))
|
|
2928
|
-
.filter((message) => message.content.trim().length > 0);
|
|
2929
|
-
return supported.length ? supported : [{ role: 'user', content: prompt }];
|
|
2930
|
-
}
|
|
2931
|
-
withCapabilitySystemMessages(messages, contextHints) {
|
|
2932
|
-
const policy = this.buildFilterExpressionSystemPolicy(contextHints);
|
|
2933
|
-
return policy ? [policy, ...messages] : messages;
|
|
2934
|
-
}
|
|
2935
|
-
buildFilterExpressionSystemPolicy(contextHints) {
|
|
2936
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
2937
|
-
const runtimeOperations = this.toRecord(authoringContract?.['runtimeOperations']);
|
|
2938
|
-
const filterExpression = this.toRecord(runtimeOperations?.['filterExpression']);
|
|
2939
|
-
if (filterExpression?.['supported'] === true) {
|
|
2940
|
-
return null;
|
|
2941
|
-
}
|
|
2942
|
-
const resourceCapabilities = this.toRecord(this.toRecord(authoringContract?.['consultativeContext'])?.['resourceCapabilities']);
|
|
2943
|
-
const canonicalOperations = this.toRecord(resourceCapabilities?.['canonicalOperations']);
|
|
2944
|
-
if (canonicalOperations?.['filter'] !== true && filterExpression?.['supported'] !== false) {
|
|
2945
|
-
return null;
|
|
2946
|
-
}
|
|
2947
|
-
return {
|
|
2948
|
-
role: 'system',
|
|
2949
|
-
content: [
|
|
2950
|
-
'Praxis table filter capability policy:',
|
|
2951
|
-
'- The current table runtime can materialize table.filter.apply only as simple conjunction criteria over declared filter fields.',
|
|
2952
|
-
'- Resource capability filter=true means the flat /filter DTO is available; it does not mean cross-field OR or nested boolean filter expressions are supported.',
|
|
2953
|
-
'- runtimeOperations.filterExpression.supported is not true for this request.',
|
|
2954
|
-
'- If the user asks for alternatives such as A OR B, cross-field OR, anyOf, oneOf, allOf, or nested boolean groups, do not say it can be applied and do not offer to apply it as a runtime filter.',
|
|
2955
|
-
'- Instead, explain that this resource/runtime contract cannot materialize that compound filter yet and ask the user to choose one simple field/value filter or another supported action such as export of the current selection.',
|
|
2956
|
-
].join('\n'),
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
resolveFilterExpressionSupported(contextHints) {
|
|
2960
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
2961
|
-
const runtimeOperations = this.toRecord(authoringContract?.['runtimeOperations']);
|
|
2962
|
-
const filterExpression = this.toRecord(runtimeOperations?.['filterExpression']);
|
|
2963
|
-
if (filterExpression && typeof filterExpression['supported'] === 'boolean') {
|
|
2964
|
-
return filterExpression['supported'];
|
|
2965
|
-
}
|
|
2966
|
-
const resourceCapabilities = this.toRecord(this.toRecord(authoringContract?.['consultativeContext'])?.['resourceCapabilities']);
|
|
2967
|
-
if (resourceCapabilities && typeof resourceCapabilities['filterExpressionSupported'] === 'boolean') {
|
|
2968
|
-
return resourceCapabilities['filterExpressionSupported'];
|
|
2969
|
-
}
|
|
2970
|
-
return null;
|
|
2971
|
-
}
|
|
2972
|
-
toClarificationQuestions(response, request) {
|
|
2973
|
-
const labels = response.questions?.length
|
|
2974
|
-
? response.questions
|
|
2975
|
-
: response.message
|
|
2976
|
-
? [response.message]
|
|
2977
|
-
: ['Qual ajuste você quer aplicar na tabela?'];
|
|
2978
|
-
const options = this.toQuickReplies(response, request).map((reply) => ({
|
|
2979
|
-
id: reply.id,
|
|
2980
|
-
label: reply.label,
|
|
2981
|
-
value: typeof reply.value === 'string' && reply.value.trim() ? reply.value.trim() : reply.prompt,
|
|
2982
|
-
displayPrompt: reply.label,
|
|
2983
|
-
description: reply.description ?? undefined,
|
|
2984
|
-
contextHints: reply.contextHints ? { ...reply.contextHints } : undefined,
|
|
2985
|
-
}));
|
|
2986
|
-
return labels.map((label, index) => ({
|
|
2987
|
-
id: `table-clarification-${index + 1}`,
|
|
2988
|
-
type: options.length ? 'single-choice' : 'text',
|
|
2989
|
-
label,
|
|
2990
|
-
allowCustom: true,
|
|
2991
|
-
options,
|
|
2992
|
-
}));
|
|
2993
|
-
}
|
|
2994
|
-
toQuickReplies(response, request) {
|
|
2995
|
-
const payloads = response.optionPayloads ?? [];
|
|
2996
|
-
if (payloads.length) {
|
|
2997
|
-
return payloads
|
|
2998
|
-
.map((option, index) => {
|
|
2999
|
-
const rawLabel = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
3000
|
-
const label = this.humanizeClarificationOptionLabel(rawLabel);
|
|
3001
|
-
const canonicalValue = option.value?.trim() || option.example?.trim() || label;
|
|
3002
|
-
return {
|
|
3003
|
-
id: `option-${index + 1}`,
|
|
3004
|
-
label,
|
|
3005
|
-
prompt: label,
|
|
3006
|
-
value: canonicalValue,
|
|
3007
|
-
kind: 'clarification-option',
|
|
3008
|
-
description: this.optionDescription(option),
|
|
3009
|
-
icon: this.optionIcon(option),
|
|
3010
|
-
tone: this.optionTone(option),
|
|
3011
|
-
presentation: this.optionPresentation(option) ?? this.defaultGuidedOptionPresentation(option),
|
|
3012
|
-
contextHints: this.optionContextHints(option),
|
|
3013
|
-
};
|
|
3014
|
-
});
|
|
3015
|
-
}
|
|
3016
|
-
if (response.type && response.type !== 'clarification') {
|
|
3017
|
-
return [];
|
|
3018
|
-
}
|
|
3019
|
-
return this.enhanceColumnClarificationOptions(response.options ?? [], response, request)
|
|
3020
|
-
.filter((option) => !!option?.trim())
|
|
3021
|
-
.map((option, index) => ({
|
|
3022
|
-
id: `option-${index + 1}`,
|
|
3023
|
-
label: this.humanizeClarificationOptionLabel(option.trim()),
|
|
3024
|
-
prompt: option.trim(),
|
|
3025
|
-
kind: 'clarification-option',
|
|
3026
|
-
presentation: {
|
|
3027
|
-
kind: 'guided-option',
|
|
3028
|
-
icon: 'check',
|
|
3029
|
-
ctaLabel: 'Usar esta opção',
|
|
3030
|
-
},
|
|
3031
|
-
}));
|
|
3032
|
-
}
|
|
3033
|
-
enhanceColumnClarificationOptions(options, response, request) {
|
|
3034
|
-
const normalizedOptions = new Set(options
|
|
3035
|
-
.map((option) => this.normalizeLabel(option))
|
|
3036
|
-
.filter((option) => option.length > 0));
|
|
3037
|
-
const enhanced = [...options];
|
|
3038
|
-
if (normalizedOptions.size && this.optionsRepresentCurrentColumns(normalizedOptions)) {
|
|
3039
|
-
this.appendMissingSchemaFields(enhanced, normalizedOptions);
|
|
3040
|
-
return enhanced;
|
|
3041
|
-
}
|
|
3042
|
-
if (this.isFieldClarification(response)) {
|
|
3043
|
-
this.appendRankedSchemaFieldCandidates(enhanced, normalizedOptions, request, response);
|
|
3044
|
-
}
|
|
3045
|
-
return enhanced;
|
|
3046
|
-
}
|
|
3047
|
-
appendMissingSchemaFields(enhanced, normalizedOptions) {
|
|
3048
|
-
const schemaFields = this.adapter.getSchemaFields?.() ?? [];
|
|
3049
|
-
for (const field of schemaFields) {
|
|
3050
|
-
const record = this.toRecord(field);
|
|
3051
|
-
const name = this.stringValue(record?.['name']);
|
|
3052
|
-
if (!name)
|
|
3053
|
-
continue;
|
|
3054
|
-
const label = this.stringValue(record?.['label']) || this.humanizeField(name);
|
|
3055
|
-
const canonical = this.normalizeLabel(label);
|
|
3056
|
-
const technical = this.normalizeLabel(name);
|
|
3057
|
-
if (normalizedOptions.has(canonical) || normalizedOptions.has(technical))
|
|
3058
|
-
continue;
|
|
3059
|
-
normalizedOptions.add(canonical);
|
|
3060
|
-
normalizedOptions.add(technical);
|
|
3061
|
-
enhanced.push(label);
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
appendRankedSchemaFieldCandidates(enhanced, normalizedOptions, request, response) {
|
|
3065
|
-
const text = this.normalizeLabel([
|
|
3066
|
-
request?.prompt,
|
|
3067
|
-
response?.message,
|
|
3068
|
-
...(response?.questions ?? []),
|
|
3069
|
-
...(response?.options ?? []),
|
|
3070
|
-
].filter(Boolean).join(' '));
|
|
3071
|
-
if (!text)
|
|
3072
|
-
return;
|
|
3073
|
-
const candidates = (this.adapter.getSchemaFields?.() ?? [])
|
|
3074
|
-
.map((field) => this.toRecord(field))
|
|
3075
|
-
.filter((field) => !!field)
|
|
3076
|
-
.map((field) => {
|
|
3077
|
-
const name = this.stringValue(field['name']);
|
|
3078
|
-
const label = this.stringValue(field['label']) || this.humanizeField(name);
|
|
3079
|
-
return { name, label, score: this.schemaFieldCandidateScore(name, label, text) };
|
|
3080
|
-
})
|
|
3081
|
-
.filter((field) => field.name && field.score > 0)
|
|
3082
|
-
.sort((left, right) => right.score - left.score || left.label.localeCompare(right.label))
|
|
3083
|
-
.slice(0, 4);
|
|
3084
|
-
for (const candidate of candidates) {
|
|
3085
|
-
const canonical = this.normalizeLabel(candidate.label);
|
|
3086
|
-
const technical = this.normalizeLabel(candidate.name);
|
|
3087
|
-
if (normalizedOptions.has(canonical) || normalizedOptions.has(technical))
|
|
3088
|
-
continue;
|
|
3089
|
-
normalizedOptions.add(canonical);
|
|
3090
|
-
normalizedOptions.add(technical);
|
|
3091
|
-
enhanced.push(candidate.label);
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
isFieldClarification(response) {
|
|
3095
|
-
const text = this.normalizeLabel([
|
|
3096
|
-
response?.message,
|
|
3097
|
-
...(response?.questions ?? []),
|
|
3098
|
-
...(response?.options ?? []),
|
|
3099
|
-
].filter(Boolean).join(' '));
|
|
3100
|
-
return text.includes('campo') || text.includes('coluna') || text.includes('field');
|
|
3101
|
-
}
|
|
3102
|
-
schemaFieldCandidateScore(name, label, normalizedText) {
|
|
3103
|
-
let score = 0;
|
|
3104
|
-
const technical = this.normalizeLabel(name);
|
|
3105
|
-
const canonical = this.normalizeLabel(label);
|
|
3106
|
-
if (technical && normalizedText.includes(technical))
|
|
3107
|
-
score += 3;
|
|
3108
|
-
if (canonical && normalizedText.includes(canonical))
|
|
3109
|
-
score += 4;
|
|
3110
|
-
if (technical && canonical && technical !== canonical && canonical.includes(technical))
|
|
3111
|
-
score += 1;
|
|
3112
|
-
return score;
|
|
3113
|
-
}
|
|
3114
|
-
optionsRepresentCurrentColumns(normalizedOptions) {
|
|
3115
|
-
const columns = this.toArray(this.adapter.getCurrentConfig()?.['columns'])
|
|
3116
|
-
.map((column) => this.toRecord(column))
|
|
3117
|
-
.filter((column) => !!column);
|
|
3118
|
-
if (!columns.length)
|
|
3119
|
-
return false;
|
|
3120
|
-
const columnKeys = new Set();
|
|
3121
|
-
for (const column of columns) {
|
|
3122
|
-
const field = this.stringValue(column['field']);
|
|
3123
|
-
const header = this.stringValue(column['header']);
|
|
3124
|
-
if (field)
|
|
3125
|
-
columnKeys.add(this.normalizeLabel(field));
|
|
3126
|
-
if (header)
|
|
3127
|
-
columnKeys.add(this.normalizeLabel(header));
|
|
3128
|
-
}
|
|
3129
|
-
let matched = 0;
|
|
3130
|
-
for (const option of normalizedOptions) {
|
|
3131
|
-
if (columnKeys.has(option))
|
|
3132
|
-
matched += 1;
|
|
3133
|
-
}
|
|
3134
|
-
return matched > 0 && matched === normalizedOptions.size;
|
|
3135
|
-
}
|
|
3136
|
-
humanizeClarificationOptionLabel(label) {
|
|
3137
|
-
return label
|
|
3138
|
-
.replace(/\s*\((?:column|filter|behavior|toolbar|export|appearance)\.[^)]+\)\s*$/u, '')
|
|
3139
|
-
.replace(/\s*—\s*ocultar\s*$/u, ' — ocultar coluna')
|
|
3140
|
-
.replace(/\s*—\s*remover completamente\s*$/u, ' — remover coluna')
|
|
3141
|
-
.replace(/\s*—\s*exibir\s*$/u, ' — exibir coluna')
|
|
3142
|
-
.trim();
|
|
3143
|
-
}
|
|
3144
|
-
defaultGuidedOptionPresentation(option) {
|
|
3145
|
-
const description = this.optionDescription(option);
|
|
3146
|
-
const icon = this.optionIcon(option) || 'check';
|
|
3147
|
-
const tone = this.optionTone(option);
|
|
3148
|
-
return {
|
|
3149
|
-
kind: 'guided-option',
|
|
3150
|
-
icon,
|
|
3151
|
-
tone,
|
|
3152
|
-
description,
|
|
3153
|
-
ctaLabel: 'Usar esta opção',
|
|
3154
|
-
};
|
|
3155
|
-
}
|
|
3156
|
-
optionContextHints(option) {
|
|
3157
|
-
return this.toRecord(option.contextHints);
|
|
3158
|
-
}
|
|
3159
|
-
optionPresentation(option) {
|
|
3160
|
-
const hints = this.optionContextHints(option);
|
|
3161
|
-
return this.toRecord(hints?.['presentation']);
|
|
3162
|
-
}
|
|
3163
|
-
optionDescription(option) {
|
|
3164
|
-
const presentation = this.optionPresentation(option);
|
|
3165
|
-
const value = presentation?.['description'];
|
|
3166
|
-
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
3167
|
-
}
|
|
3168
|
-
optionIcon(option) {
|
|
3169
|
-
const presentation = this.optionPresentation(option);
|
|
3170
|
-
const value = presentation?.['icon'];
|
|
3171
|
-
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
3172
|
-
}
|
|
3173
|
-
optionTone(option) {
|
|
3174
|
-
const presentation = this.optionPresentation(option);
|
|
3175
|
-
const value = presentation?.['tone'];
|
|
3176
|
-
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
3177
|
-
}
|
|
3178
|
-
booleanConditionValue(condition) {
|
|
3179
|
-
const record = this.toRecord(condition);
|
|
3180
|
-
if (!record)
|
|
3181
|
-
return null;
|
|
3182
|
-
for (const operator of ['==', '===']) {
|
|
3183
|
-
const operands = record[operator];
|
|
3184
|
-
if (!Array.isArray(operands))
|
|
3185
|
-
continue;
|
|
3186
|
-
const literal = operands.find((operand) => typeof operand === 'boolean');
|
|
3187
|
-
if (typeof literal === 'boolean')
|
|
3188
|
-
return literal;
|
|
3189
|
-
}
|
|
3190
|
-
return null;
|
|
3191
|
-
}
|
|
3192
|
-
booleanInput(value) {
|
|
3193
|
-
return typeof value === 'boolean' ? value : null;
|
|
3194
|
-
}
|
|
3195
|
-
arrayOfStrings(value) {
|
|
3196
|
-
return Array.isArray(value)
|
|
3197
|
-
? value.filter((item) => typeof item === 'string' && item.trim().length > 0)
|
|
3198
|
-
: [];
|
|
3199
|
-
}
|
|
3200
|
-
toArray(value) {
|
|
3201
|
-
return Array.isArray(value) ? value : [];
|
|
3202
|
-
}
|
|
3203
|
-
formatLabel(value) {
|
|
3204
|
-
const format = this.stringValue(value);
|
|
3205
|
-
if (!format)
|
|
3206
|
-
return 'o formato solicitado';
|
|
3207
|
-
if (format.startsWith('custom|')) {
|
|
3208
|
-
const parts = format.split('|');
|
|
3209
|
-
if (parts.length >= 3 && parts[1] && parts[2])
|
|
3210
|
-
return `${parts[1]}/${parts[2]}`;
|
|
3211
|
-
return 'formato personalizado';
|
|
3212
|
-
}
|
|
3213
|
-
if (format === '000.000.000-00')
|
|
3214
|
-
return 'CPF brasileiro';
|
|
3215
|
-
if (/^BRL\b/u.test(format))
|
|
3216
|
-
return 'moeda brasileira';
|
|
3217
|
-
switch (format) {
|
|
3218
|
-
case 'active-inactive':
|
|
3219
|
-
return 'Ativo/Inativo';
|
|
3220
|
-
case 'yes-no':
|
|
3221
|
-
return 'Sim/Não';
|
|
3222
|
-
case 'true-false':
|
|
3223
|
-
return 'Verdadeiro/Falso';
|
|
3224
|
-
case 'on-off':
|
|
3225
|
-
case 'onoff':
|
|
3226
|
-
return 'Ligado/Desligado';
|
|
3227
|
-
case 'shortDate':
|
|
3228
|
-
return 'data curta';
|
|
3229
|
-
case 'mediumDate':
|
|
3230
|
-
return 'data com mes abreviado';
|
|
3231
|
-
case 'longDate':
|
|
3232
|
-
return 'data por extenso';
|
|
3233
|
-
case 'fullDate':
|
|
3234
|
-
return 'data completa com dia da semana';
|
|
3235
|
-
case 'MMM/yyyy':
|
|
3236
|
-
return 'mes e ano';
|
|
3237
|
-
case 'dd/MM/yyyy':
|
|
3238
|
-
return 'data no padrao brasileiro';
|
|
3239
|
-
case 'yyyy-MM-dd':
|
|
3240
|
-
return 'data ISO';
|
|
3241
|
-
case 'yyyy-MM-dd HH:mm':
|
|
3242
|
-
return 'data e hora';
|
|
3243
|
-
case 'shortTime':
|
|
3244
|
-
return 'hora curta';
|
|
3245
|
-
case 'short':
|
|
3246
|
-
return 'data e hora curtas';
|
|
3247
|
-
}
|
|
3248
|
-
return format;
|
|
3249
|
-
}
|
|
3250
|
-
rendererLabel(value) {
|
|
3251
|
-
switch (value.trim().toLowerCase()) {
|
|
3252
|
-
case 'chip':
|
|
3253
|
-
return 'chip';
|
|
3254
|
-
case 'avatar':
|
|
3255
|
-
return 'avatar';
|
|
3256
|
-
case 'badge':
|
|
3257
|
-
return 'badge';
|
|
3258
|
-
default:
|
|
3259
|
-
return 'indicador visual';
|
|
3260
|
-
}
|
|
3261
|
-
}
|
|
3262
|
-
rendererPluralLabel(value) {
|
|
3263
|
-
switch (value.trim().toLowerCase()) {
|
|
3264
|
-
case 'chip':
|
|
3265
|
-
return 'chips';
|
|
3266
|
-
case 'badge':
|
|
3267
|
-
return 'badges';
|
|
3268
|
-
case 'avatar':
|
|
3269
|
-
return 'avatares';
|
|
3270
|
-
default:
|
|
3271
|
-
return `${this.rendererLabel(value)}s`;
|
|
3272
|
-
}
|
|
3273
|
-
}
|
|
3274
|
-
humanizeField(field) {
|
|
3275
|
-
if (field.trim().toLowerCase() === 'cpf')
|
|
3276
|
-
return 'CPF';
|
|
3277
|
-
return field
|
|
3278
|
-
.replace(/[_-]+/gu, ' ')
|
|
3279
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
3280
|
-
.trim()
|
|
3281
|
-
.replace(/^./u, (char) => char.toLocaleUpperCase('pt-BR'));
|
|
3282
|
-
}
|
|
3283
|
-
humanizeFilterField(field) {
|
|
3284
|
-
const normalized = field.trim();
|
|
3285
|
-
if (!normalized)
|
|
3286
|
-
return 'Campo';
|
|
3287
|
-
const catalogLabel = this.filterFieldLabels.get(normalized);
|
|
3288
|
-
if (catalogLabel)
|
|
3289
|
-
return catalogLabel;
|
|
3290
|
-
if (normalized.toLowerCase() === 'cpf')
|
|
3291
|
-
return 'CPF';
|
|
3292
|
-
if (/between$/iu.test(normalized)) {
|
|
3293
|
-
const base = normalized.replace(/Between$/iu, '');
|
|
3294
|
-
return `Faixa de ${this.humanizeField(base).toLocaleLowerCase('pt-BR')}`;
|
|
3295
|
-
}
|
|
3296
|
-
if (/range$/iu.test(normalized)) {
|
|
3297
|
-
const base = normalized.replace(/Range$/iu, '');
|
|
3298
|
-
return `Periodo de ${this.humanizeFilterBase(base).toLocaleLowerCase('pt-BR')}`;
|
|
3299
|
-
}
|
|
3300
|
-
if (/lastDays$/iu.test(normalized)) {
|
|
3301
|
-
const base = normalized.replace(/LastDays$/iu, '');
|
|
3302
|
-
if (this.normalizeLabel(base) === 'data admissao') {
|
|
3303
|
-
return 'Admissões recentes';
|
|
3304
|
-
}
|
|
3305
|
-
return `${this.humanizeFilterBase(base)} recentes`;
|
|
3306
|
-
}
|
|
3307
|
-
if (/IdsIn$/iu.test(normalized)) {
|
|
3308
|
-
return this.pluralizeFilterLabel(this.humanizeField(normalized.replace(/IdsIn$/iu, '')));
|
|
3309
|
-
}
|
|
3310
|
-
return this.humanizeField(normalized);
|
|
3311
|
-
}
|
|
3312
|
-
pluralizeFilterLabel(label) {
|
|
3313
|
-
const normalized = label.trim();
|
|
3314
|
-
if (!normalized)
|
|
3315
|
-
return label;
|
|
3316
|
-
if (/s$/iu.test(normalized))
|
|
3317
|
-
return normalized;
|
|
3318
|
-
if (/ão$/iu.test(normalized))
|
|
3319
|
-
return normalized.replace(/ão$/iu, 'ões');
|
|
3320
|
-
if (/m$/iu.test(normalized))
|
|
3321
|
-
return `${normalized.slice(0, -1)}ns`;
|
|
3322
|
-
if (/l$/iu.test(normalized))
|
|
3323
|
-
return `${normalized.slice(0, -1)}is`;
|
|
3324
|
-
if (/[aeo]$/iu.test(normalized))
|
|
3325
|
-
return `${normalized}s`;
|
|
3326
|
-
return normalized;
|
|
3327
|
-
}
|
|
3328
|
-
extractFilterFieldLabels(entries) {
|
|
3329
|
-
const labels = new Map();
|
|
3330
|
-
for (const field of entries) {
|
|
3331
|
-
labels.set(field.name, field.label);
|
|
3332
|
-
}
|
|
3333
|
-
return labels;
|
|
3334
|
-
}
|
|
3335
|
-
extractFilterFieldCatalogEntries(contextHints) {
|
|
3336
|
-
return this.filterFieldCatalogFields(contextHints)
|
|
3337
|
-
.map((field) => {
|
|
3338
|
-
const record = this.toRecord(field);
|
|
3339
|
-
const name = this.stringValue(record?.['name']);
|
|
3340
|
-
if (!name)
|
|
3341
|
-
return null;
|
|
3342
|
-
const label = this.stringValue(record?.['label']) || this.humanizeFilterField(name);
|
|
3343
|
-
const aliases = Array.isArray(record?.['aliases'])
|
|
3344
|
-
? record['aliases'].map((alias) => this.stringValue(alias)).filter((alias) => !!alias)
|
|
3345
|
-
: [];
|
|
3346
|
-
const relatedColumnFields = Array.isArray(record?.['relatedColumnFields'])
|
|
3347
|
-
? record['relatedColumnFields'].map((field) => this.stringValue(field)).filter((field) => !!field)
|
|
3348
|
-
: [];
|
|
3349
|
-
const relatedColumnLabels = Array.isArray(record?.['relatedColumnLabels'])
|
|
3350
|
-
? record['relatedColumnLabels'].map((label) => this.stringValue(label)).filter((label) => !!label)
|
|
3351
|
-
: [];
|
|
3352
|
-
const controlType = this.stringValue(record?.['controlType']) || undefined;
|
|
3353
|
-
const type = this.stringValue(record?.['type']) || undefined;
|
|
3354
|
-
const criterionKind = this.stringValue(record?.['criterionKind']) || undefined;
|
|
3355
|
-
const criterionValueShape = this.stringValue(record?.['criterionValueShape']) || undefined;
|
|
3356
|
-
return {
|
|
3357
|
-
name,
|
|
3358
|
-
label,
|
|
3359
|
-
aliases,
|
|
3360
|
-
relatedColumnFields,
|
|
3361
|
-
relatedColumnLabels,
|
|
3362
|
-
...(controlType ? { controlType } : {}),
|
|
3363
|
-
...(type ? { type } : {}),
|
|
3364
|
-
...(criterionKind ? { criterionKind } : {}),
|
|
3365
|
-
...(criterionValueShape ? { criterionValueShape } : {}),
|
|
3366
|
-
};
|
|
3367
|
-
})
|
|
3368
|
-
.filter((entry) => !!entry);
|
|
3369
|
-
}
|
|
3370
|
-
filterFieldCatalogFields(contextHints) {
|
|
3371
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
3372
|
-
const componentEditPlan = this.toRecord(authoringContract?.['componentEditPlan']);
|
|
3373
|
-
const nestedCatalog = this.toRecord(componentEditPlan?.['filterFieldCatalog']);
|
|
3374
|
-
const nestedFields = nestedCatalog?.['fields'];
|
|
3375
|
-
if (Array.isArray(nestedFields))
|
|
3376
|
-
return nestedFields;
|
|
3377
|
-
const directCatalog = this.toRecord(authoringContract?.['filterFieldCatalog'])
|
|
3378
|
-
?? this.toRecord(contextHints?.['filterFieldCatalog']);
|
|
3379
|
-
const directFields = directCatalog?.['fields'];
|
|
3380
|
-
return Array.isArray(directFields) ? directFields : [];
|
|
3381
|
-
}
|
|
3382
|
-
extractSelectedRecordsCount(contextHints) {
|
|
3383
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
3384
|
-
const consultativeContext = this.toRecord(authoringContract?.['consultativeContext']);
|
|
3385
|
-
const selectedRecordsContext = this.toRecord(consultativeContext?.['selectedRecordsContext'])
|
|
3386
|
-
?? this.toRecord(contextHints?.['selectedRecordsContext']);
|
|
3387
|
-
const selectedCount = selectedRecordsContext?.['selectedCount'];
|
|
3388
|
-
return typeof selectedCount === 'number' && Number.isFinite(selectedCount)
|
|
3389
|
-
? Math.max(0, selectedCount)
|
|
3390
|
-
: 0;
|
|
3391
|
-
}
|
|
3392
|
-
extractSelectionDerivedFilterCandidateFields(contextHints) {
|
|
3393
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
3394
|
-
const consultativeContext = this.toRecord(authoringContract?.['consultativeContext']);
|
|
3395
|
-
const selectedRecordsContext = this.toRecord(consultativeContext?.['selectedRecordsContext'])
|
|
3396
|
-
?? this.toRecord(contextHints?.['selectedRecordsContext']);
|
|
3397
|
-
const candidates = Array.isArray(selectedRecordsContext?.['filterCandidates'])
|
|
3398
|
-
? selectedRecordsContext['filterCandidates']
|
|
3399
|
-
: [];
|
|
3400
|
-
return new Set(candidates
|
|
3401
|
-
.map((candidate) => this.stringValue(this.toRecord(candidate)?.['field']))
|
|
3402
|
-
.filter((field) => !!field));
|
|
3403
|
-
}
|
|
3404
|
-
extractSelectedRecordFields(contextHints) {
|
|
3405
|
-
const authoringContract = this.toRecord(contextHints?.['authoringContract']);
|
|
3406
|
-
const consultativeContext = this.toRecord(authoringContract?.['consultativeContext']);
|
|
3407
|
-
const selectedRecordsContext = this.toRecord(consultativeContext?.['selectedRecordsContext'])
|
|
3408
|
-
?? this.toRecord(contextHints?.['selectedRecordsContext']);
|
|
3409
|
-
const fields = Array.isArray(selectedRecordsContext?.['fields'])
|
|
3410
|
-
? selectedRecordsContext['fields'].map((field) => this.stringValue(field)).filter((field) => !!field)
|
|
3411
|
-
: [];
|
|
3412
|
-
const sampleRows = Array.isArray(selectedRecordsContext?.['sampleRows'])
|
|
3413
|
-
? selectedRecordsContext['sampleRows']
|
|
3414
|
-
: [];
|
|
3415
|
-
for (const row of sampleRows) {
|
|
3416
|
-
const record = this.toRecord(row);
|
|
3417
|
-
if (!record)
|
|
3418
|
-
continue;
|
|
3419
|
-
fields.push(...Object.keys(record));
|
|
3420
|
-
}
|
|
3421
|
-
return new Set(fields);
|
|
3422
|
-
}
|
|
3423
|
-
filterEntryHasSelectedRecordSourceField(entry) {
|
|
3424
|
-
const sourceFields = [
|
|
3425
|
-
...entry.relatedColumnFields,
|
|
3426
|
-
...entry.aliases,
|
|
3427
|
-
];
|
|
3428
|
-
return sourceFields.some((field) => this.selectedRecordFieldsForTurn.has(field));
|
|
3429
|
-
}
|
|
3430
|
-
humanizeFilterBase(field) {
|
|
3431
|
-
const label = this.humanizeField(field).replace(/^Data\s+/iu, '');
|
|
3432
|
-
if (this.normalizeLabel(label) === 'admissao')
|
|
3433
|
-
return 'admissão';
|
|
3434
|
-
return label;
|
|
3435
|
-
}
|
|
3436
|
-
normalizeLabel(value) {
|
|
3437
|
-
return value
|
|
3438
|
-
.normalize('NFD')
|
|
3439
|
-
.replace(/\p{Diacritic}/gu, '')
|
|
3440
|
-
.replace(/[_-]+/gu, ' ')
|
|
3441
|
-
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
3442
|
-
.trim()
|
|
3443
|
-
.toLocaleLowerCase('pt-BR')
|
|
3444
|
-
.replace(/\s+/gu, ' ');
|
|
3445
|
-
}
|
|
3446
|
-
stringValue(value) {
|
|
3447
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
3448
|
-
}
|
|
3449
|
-
stringifySurfaceEvidence(value) {
|
|
3450
|
-
try {
|
|
3451
|
-
return JSON.stringify(value)?.slice(0, 4000) ?? '';
|
|
3452
|
-
}
|
|
3453
|
-
catch {
|
|
3454
|
-
return '';
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
buildCurrentStateDigest(currentState, dataProfile) {
|
|
3458
|
-
const currentColumns = Array.isArray(currentState['columns'])
|
|
3459
|
-
? currentState['columns']
|
|
3460
|
-
.map((column) => this.toRecord(column))
|
|
3461
|
-
.filter((column) => !!column)
|
|
3462
|
-
: [];
|
|
3463
|
-
const columns = currentColumns.length
|
|
3464
|
-
? currentColumns
|
|
3465
|
-
.map((column) => column['field'])
|
|
3466
|
-
.filter((field) => typeof field === 'string' && field.length > 0)
|
|
3467
|
-
: undefined;
|
|
3468
|
-
const columnOrder = currentColumns
|
|
3469
|
-
.map((column, index) => {
|
|
3470
|
-
const field = this.stringValue(column['field']);
|
|
3471
|
-
if (!field)
|
|
3472
|
-
return null;
|
|
3473
|
-
return {
|
|
3474
|
-
field,
|
|
3475
|
-
index,
|
|
3476
|
-
...(this.stringValue(column['header']) ? { header: this.stringValue(column['header']) } : {}),
|
|
3477
|
-
...(typeof column['order'] === 'number' ? { order: column['order'] } : {}),
|
|
3478
|
-
};
|
|
3479
|
-
})
|
|
3480
|
-
.filter((column) => !!column);
|
|
3481
|
-
const rowCount = typeof dataProfile?.['rowCount'] === 'number' ? dataProfile['rowCount'] : undefined;
|
|
3482
|
-
return {
|
|
3483
|
-
...(columns?.length ? { columns } : {}),
|
|
3484
|
-
...(columnOrder.length ? { columnOrder } : {}),
|
|
3485
|
-
...(rowCount !== undefined ? { rowCount } : {}),
|
|
3486
|
-
};
|
|
3487
|
-
}
|
|
3488
|
-
optionalJsonObject(value) {
|
|
3489
|
-
if (value === undefined || value === null) {
|
|
3490
|
-
return undefined;
|
|
3491
|
-
}
|
|
3492
|
-
const object = this.toAiJsonObject(value);
|
|
3493
|
-
return Object.keys(object).length ? object : undefined;
|
|
3494
|
-
}
|
|
3495
|
-
async prepareAuthoringContext() {
|
|
3496
|
-
const adapter = this.adapter;
|
|
3497
|
-
await adapter.prepareAuthoringContext?.();
|
|
3498
|
-
}
|
|
3499
|
-
mergeJsonObjects(base, overlay) {
|
|
3500
|
-
if (!base)
|
|
3501
|
-
return overlay;
|
|
3502
|
-
if (!overlay)
|
|
3503
|
-
return base;
|
|
3504
|
-
return {
|
|
3505
|
-
...base,
|
|
3506
|
-
...overlay,
|
|
3507
|
-
};
|
|
3508
|
-
}
|
|
3509
|
-
toAiJsonObject(value) {
|
|
3510
|
-
const record = this.toRecord(value);
|
|
3511
|
-
if (!record) {
|
|
3512
|
-
return {};
|
|
3513
|
-
}
|
|
3514
|
-
try {
|
|
3515
|
-
return JSON.parse(JSON.stringify(record));
|
|
3516
|
-
}
|
|
3517
|
-
catch {
|
|
3518
|
-
return {};
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
cloneJson(value) {
|
|
3522
|
-
try {
|
|
3523
|
-
return JSON.parse(JSON.stringify(value));
|
|
3524
|
-
}
|
|
3525
|
-
catch {
|
|
3526
|
-
return value;
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
toRecord(value) {
|
|
3530
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
3531
|
-
? value
|
|
3532
|
-
: null;
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
|
|
3536
|
-
export { TableAgenticAuthoringTurnFlow };
|