@praxisui/table 8.0.0-beta.30 → 8.0.0-beta.32

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.
@@ -0,0 +1,847 @@
1
+ import { firstValueFrom } from 'rxjs';
2
+
3
+ class TableAgenticAuthoringTurnFlow {
4
+ adapter;
5
+ aiApi;
6
+ mode = 'config';
7
+ filterFieldLabels = new Map();
8
+ constructor(adapter, aiApi) {
9
+ this.adapter = adapter;
10
+ this.aiApi = aiApi;
11
+ }
12
+ async submit(request) {
13
+ const prompt = (request.prompt ?? '').trim();
14
+ if (!prompt) {
15
+ return {
16
+ state: 'listening',
17
+ phase: 'capture',
18
+ statusText: '',
19
+ };
20
+ }
21
+ const componentId = this.adapter.componentId || request.componentId || 'praxis-table';
22
+ const componentType = this.adapter.componentType || request.componentType || 'table';
23
+ const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
24
+ const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
25
+ const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
26
+ const schemaFields = this.adapter.getSchemaFields?.()
27
+ ?.map((field) => this.toAiJsonObject(field))
28
+ .filter((field) => Object.keys(field).length > 0);
29
+ await this.prepareAuthoringContext();
30
+ const contextHints = this.mergeJsonObjects(this.optionalJsonObject(this.adapter.getAuthoringContext?.()), this.optionalJsonObject(request.action?.contextHints));
31
+ this.filterFieldLabels = this.extractFilterFieldLabels(contextHints ?? null);
32
+ const response = await firstValueFrom(this.aiApi.getPatch({
33
+ componentId,
34
+ componentType,
35
+ userPrompt: prompt,
36
+ sessionId: request.sessionId,
37
+ clientTurnId: request.clientTurnId,
38
+ messages: this.toChatMessages(request.messages, prompt),
39
+ currentState,
40
+ currentStateDigest: this.buildCurrentStateDigest(currentState, dataProfile),
41
+ uiContextRef: {
42
+ componentId,
43
+ componentType,
44
+ },
45
+ ...(dataProfile ? { dataProfile } : {}),
46
+ ...(runtimeState ? { runtimeState } : {}),
47
+ ...(schemaFields?.length ? { schemaFields } : {}),
48
+ ...(contextHints ? { contextHints } : {}),
49
+ }));
50
+ return this.toTurnResult(this.compileAdapterResponse(response), request);
51
+ }
52
+ normalizePrompt(prompt) {
53
+ return prompt
54
+ .normalize('NFD')
55
+ .replace(/[\u0300-\u036f]/g, '')
56
+ .toLowerCase()
57
+ .trim();
58
+ }
59
+ extractResourcePath(source) {
60
+ if (!source)
61
+ return null;
62
+ const direct = this.nonEmptyStringValue(source['resourcePath']);
63
+ if (direct)
64
+ return direct;
65
+ const consultativeContext = this.toRecord(source['consultativeContext']);
66
+ const fromConsultative = this.nonEmptyStringValue(consultativeContext?.['resourcePath']);
67
+ if (fromConsultative)
68
+ return fromConsultative;
69
+ const authoringContract = this.toRecord(source['authoringContract']);
70
+ if (authoringContract && authoringContract !== source) {
71
+ return this.extractResourcePath(authoringContract);
72
+ }
73
+ return null;
74
+ }
75
+ nonEmptyStringValue(value) {
76
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
77
+ }
78
+ async apply(request) {
79
+ const patch = this.toRecord(request.pendingPatch);
80
+ if (!patch) {
81
+ return {
82
+ state: 'error',
83
+ phase: 'apply',
84
+ assistantMessage: 'Nao ha alteracao de tabela pronta para aplicar.',
85
+ errorText: 'Nao ha alteracao de tabela pronta para aplicar.',
86
+ canApply: false,
87
+ };
88
+ }
89
+ const result = await this.adapter.applyPatch(patch, request.prompt);
90
+ if (!result.success) {
91
+ return {
92
+ state: 'error',
93
+ phase: 'apply',
94
+ assistantMessage: result.error || 'Nao foi possivel aplicar as alteracoes na tabela.',
95
+ errorText: result.error || 'Nao foi possivel aplicar as alteracoes na tabela.',
96
+ canApply: true,
97
+ pendingPatch: patch,
98
+ };
99
+ }
100
+ return {
101
+ state: 'success',
102
+ phase: 'summarize',
103
+ assistantMessage: 'Alteracoes aplicadas na tabela.',
104
+ statusText: 'Alteracoes aplicadas na tabela.',
105
+ canApply: false,
106
+ pendingPatch: null,
107
+ diagnostics: result.warnings?.length ? { warnings: result.warnings } : undefined,
108
+ };
109
+ }
110
+ cancel() {
111
+ return Promise.resolve({
112
+ state: 'listening',
113
+ phase: 'capture',
114
+ assistantMessage: 'Solicitacao cancelada.',
115
+ statusText: '',
116
+ canApply: false,
117
+ pendingPatch: null,
118
+ pendingClarification: null,
119
+ });
120
+ }
121
+ retry(request) {
122
+ const lastPrompt = [...(request.messages ?? [])].reverse()
123
+ .find((message) => message.role === 'user')?.text;
124
+ return this.submit({
125
+ ...request,
126
+ prompt: lastPrompt ?? request.prompt,
127
+ action: { kind: 'retry' },
128
+ });
129
+ }
130
+ toTurnResult(response, request) {
131
+ if (!response) {
132
+ return {
133
+ state: 'error',
134
+ phase: 'capture',
135
+ assistantMessage: 'Resposta vazia da IA.',
136
+ errorText: 'Resposta vazia da IA.',
137
+ };
138
+ }
139
+ if (response.sessionId && response.sessionId !== request.sessionId) {
140
+ request = { ...request, sessionId: response.sessionId };
141
+ }
142
+ if (response.type === 'clarification') {
143
+ const questions = this.toClarificationQuestions(response);
144
+ return {
145
+ state: 'clarification',
146
+ phase: 'clarify',
147
+ sessionId: response.sessionId ?? request.sessionId,
148
+ assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
149
+ clarificationQuestions: questions,
150
+ quickReplies: this.toQuickReplies(response),
151
+ canApply: false,
152
+ };
153
+ }
154
+ if (response.type === 'info') {
155
+ const message = response.message || response.explanation || 'Informacao gerada.';
156
+ return {
157
+ state: 'success',
158
+ phase: 'summarize',
159
+ sessionId: response.sessionId ?? request.sessionId,
160
+ assistantMessage: message,
161
+ statusText: message,
162
+ quickReplies: this.toQuickReplies(response),
163
+ canApply: false,
164
+ };
165
+ }
166
+ if (response.type === 'error') {
167
+ const message = response.message || 'Falha ao gerar alteracao de tabela.';
168
+ return {
169
+ state: 'error',
170
+ phase: 'capture',
171
+ sessionId: response.sessionId ?? request.sessionId,
172
+ assistantMessage: message,
173
+ errorText: message,
174
+ diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
175
+ };
176
+ }
177
+ if (response.patch && Object.keys(response.patch).length > 0) {
178
+ const warnings = response.warnings?.filter(Boolean) ?? [];
179
+ const quickReplies = this.toQuickReplies(response);
180
+ return {
181
+ state: 'review',
182
+ phase: 'review',
183
+ sessionId: response.sessionId ?? request.sessionId,
184
+ assistantMessage: this.toReviewMessage(response),
185
+ statusText: 'Revise a proposta antes de aplicar.',
186
+ quickReplies,
187
+ canApply: true,
188
+ pendingPatch: response.patch,
189
+ preview: {
190
+ kind: 'table-config-patch',
191
+ diff: response.diff ?? [],
192
+ },
193
+ diagnostics: warnings.length ? { warnings } : undefined,
194
+ };
195
+ }
196
+ return {
197
+ state: 'success',
198
+ phase: 'summarize',
199
+ sessionId: response.sessionId ?? request.sessionId,
200
+ assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
201
+ statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
202
+ canApply: false,
203
+ };
204
+ }
205
+ compileAdapterResponse(response) {
206
+ const compiled = this.adapter.compileAiResponse?.(response);
207
+ if (!compiled && response.patch && Object.keys(response.patch).length > 0) {
208
+ return {
209
+ type: 'error',
210
+ message: 'A tabela exige componentEditPlan validado pelo manifesto antes de gerar patch local.',
211
+ warnings: [
212
+ 'free-table-patch-rejected',
213
+ 'Use componentEditPlan validado contra PRAXIS_TABLE_AUTHORING_MANIFEST.',
214
+ ],
215
+ };
216
+ }
217
+ if (!compiled) {
218
+ return response;
219
+ }
220
+ if (compiled.type === 'error') {
221
+ return {
222
+ type: 'error',
223
+ message: compiled.message || 'O componentEditPlan da tabela nao passou na validacao de capacidades.',
224
+ warnings: compiled.warnings,
225
+ };
226
+ }
227
+ const warnings = [
228
+ ...(response.warnings ?? []),
229
+ ...(compiled.warnings ?? []),
230
+ ];
231
+ return {
232
+ ...response,
233
+ ...compiled,
234
+ patch: compiled.patch,
235
+ warnings: warnings.length ? warnings : undefined,
236
+ };
237
+ }
238
+ toReviewMessage(response) {
239
+ const planSummaries = this.describeComponentEditPlan(response.componentEditPlan);
240
+ if (planSummaries.length) {
241
+ return [
242
+ 'Preparei este ajuste para revisao:',
243
+ '',
244
+ ...planSummaries.map((summary) => `- ${summary}`),
245
+ ].join('\n');
246
+ }
247
+ return response.explanation || 'Proposta de alteracao pronta para revisar.';
248
+ }
249
+ describeComponentEditPlan(componentEditPlan) {
250
+ const operations = this.componentEditOperations(componentEditPlan);
251
+ const booleanStateSummary = this.describeBooleanStateRenderers(operations);
252
+ if (booleanStateSummary)
253
+ return [booleanStateSummary];
254
+ const seen = new Set();
255
+ return operations
256
+ .map((operation) => this.describeComponentEditOperation(operation))
257
+ .filter((summary) => {
258
+ if (!summary || seen.has(summary))
259
+ return false;
260
+ seen.add(summary);
261
+ return true;
262
+ });
263
+ }
264
+ componentEditOperations(componentEditPlan) {
265
+ const plan = this.toRecord(componentEditPlan);
266
+ if (!plan)
267
+ return [];
268
+ const operations = plan['operations'];
269
+ if (Array.isArray(operations)) {
270
+ return operations
271
+ .map((operation) => this.toRecord(operation))
272
+ .filter((operation) => !!operation);
273
+ }
274
+ return plan['operationId'] || plan['changeKind'] || plan['capabilityPath']
275
+ ? [plan]
276
+ : [];
277
+ }
278
+ describeBooleanStateRenderers(operations) {
279
+ if (operations.length < 2)
280
+ return null;
281
+ const rendererOperations = operations
282
+ .filter((operation) => this.stringValue(operation['operationId']) === 'column.conditionalRenderer.add')
283
+ .map((operation) => {
284
+ const target = this.toRecord(operation['target']);
285
+ const input = this.toRecord(operation['input']) ?? this.toRecord(operation['params']) ?? {};
286
+ const renderer = this.toRecord(input['renderer']);
287
+ const rendererType = this.stringValue(renderer?.['type']) || 'badge';
288
+ const visual = this.toRecord(renderer?.[rendererType]) ?? {};
289
+ return {
290
+ field: this.stringValue(target?.['field']) || this.stringValue(operation['field']) || this.stringValue(input['field']),
291
+ trueLabel: this.booleanConditionValue(input['condition']) === true ? this.stringValue(visual['text']) : '',
292
+ falseLabel: this.booleanConditionValue(input['condition']) === false ? this.stringValue(visual['text']) : '',
293
+ rendererType,
294
+ };
295
+ })
296
+ .filter((item) => item.field && (item.trueLabel || item.falseLabel));
297
+ if (rendererOperations.length < 2)
298
+ return null;
299
+ const field = rendererOperations[0].field;
300
+ if (!rendererOperations.every((item) => item.field === field))
301
+ return null;
302
+ const trueLabel = rendererOperations.find((item) => item.trueLabel)?.trueLabel;
303
+ const falseLabel = rendererOperations.find((item) => item.falseLabel)?.falseLabel;
304
+ if (!trueLabel || !falseLabel)
305
+ return null;
306
+ const rendererType = rendererOperations.find((item) => item.rendererType)?.rendererType || 'badge';
307
+ return `Vou mostrar a coluna **${this.humanizeField(field)}** como ${this.rendererLabel(rendererType)}: **${trueLabel}** para verdadeiro e **${falseLabel}** para falso.`;
308
+ }
309
+ describeComponentEditOperation(operation) {
310
+ const operationId = this.stringValue(operation['operationId']) || this.stringValue(operation['changeKind']);
311
+ const target = this.toRecord(operation['target']);
312
+ const input = this.toRecord(operation['input']) ?? this.toRecord(operation['params']) ?? {};
313
+ const field = this.stringValue(target?.['field'])
314
+ || this.stringValue(operation['field'])
315
+ || this.stringValue(input['field']);
316
+ const label = field ? this.humanizeField(field) : 'a tabela';
317
+ switch (operationId) {
318
+ case 'column.format.set':
319
+ case 'set_column_format':
320
+ return `Vou formatar a coluna **${label}** como **${this.formatLabel(input['format'] ?? operation['value'])}**.`;
321
+ case 'column.header.set':
322
+ case 'set_column_header':
323
+ return `Vou renomear a coluna **${label}** para **${this.stringValue(input['header'] ?? operation['value']) || label}**.`;
324
+ case 'column.visibility.set':
325
+ case 'set_column_visibility':
326
+ return `${this.booleanInput(input['visible'] ?? operation['value']) === false ? 'Vou ocultar' : 'Vou exibir'} a coluna **${label}**.`;
327
+ case 'column.sticky.set':
328
+ return this.describeStickyColumn(label, input['sticky'] ?? input['value'] ?? operation['value']);
329
+ case 'column.width.set':
330
+ return `Vou ajustar a largura da coluna **${label}**.`;
331
+ case 'column.align.set':
332
+ return `Vou alinhar a coluna **${label}** ${this.alignmentLabel(input['align'] ?? input['value'] ?? operation['value'])}.`;
333
+ case 'column.remove':
334
+ return `Vou remover a coluna **${label}** da tabela.`;
335
+ case 'column.renderer.set':
336
+ case 'set_column_renderer':
337
+ return `Vou ajustar a apresentacao visual da coluna **${label}**.`;
338
+ case 'column.conditionalRenderer.add':
339
+ case 'set_column_conditional_badge_renderers':
340
+ return this.describeConditionalRenderer(label, input);
341
+ case 'column.conditionalStyle.add':
342
+ case 'set_column_conditional_style':
343
+ return this.describeConditionalStyle(label, this.toRecord(operation['value']) ?? input);
344
+ case 'column.computed.add':
345
+ case 'column.computed.set':
346
+ case 'add_computed_column':
347
+ return `Vou criar ou atualizar a coluna calculada **${label}**.`;
348
+ case 'behavior.filtering.configure':
349
+ return 'Vou atualizar os filtros da tabela.';
350
+ case 'filter.advanced.configure':
351
+ case 'configure_advanced_filters':
352
+ return this.describeAdvancedFiltersConfigure(input);
353
+ case 'filter.advanced.fields.add':
354
+ return this.describeAdvancedFilterFields(input, 'add');
355
+ case 'filter.advanced.fields.remove':
356
+ return this.describeAdvancedFilterFields(input, 'remove');
357
+ case 'behavior.pagination.configure':
358
+ return 'Vou atualizar a paginacao da tabela.';
359
+ case 'behavior.selection.configure':
360
+ return 'Vou atualizar a selecao de linhas.';
361
+ case 'toolbar.configure':
362
+ return 'Vou ajustar a barra de acoes da tabela.';
363
+ case 'toolbar.action.add':
364
+ case 'add_toolbar_action':
365
+ return `Vou adicionar a acao **${this.stringValue(input['label']) || this.stringValue(input['id']) || 'solicitada'}** na barra da tabela.`;
366
+ case 'export.configure':
367
+ return this.describeExportConfigure(input);
368
+ case 'appearance.density.set':
369
+ return `Vou ajustar a densidade da tabela para **${this.stringValue(input['density'] ?? operation['value']) || 'o valor escolhido'}**.`;
370
+ default:
371
+ return field
372
+ ? `Vou atualizar a coluna **${label}**.`
373
+ : 'Vou atualizar a configuracao da tabela.';
374
+ }
375
+ }
376
+ describeExportConfigure(input) {
377
+ const scope = this.stringValue(input['scope'] ?? input['exportScope']);
378
+ const serialized = JSON.stringify(input).toLowerCase();
379
+ const selectedOnly = scope === 'selected'
380
+ || scope === 'selection'
381
+ || serialized.includes('selected')
382
+ || serialized.includes('selection');
383
+ if (selectedOnly) {
384
+ return 'Vou habilitar a exportacao das linhas selecionadas.';
385
+ }
386
+ return 'Vou habilitar a exportacao da tabela.';
387
+ }
388
+ describeStickyColumn(label, value) {
389
+ const sticky = this.stringValue(value).toLowerCase();
390
+ if (sticky === 'false' || sticky === 'none') {
391
+ return `Vou desafixar a coluna **${label}**.`;
392
+ }
393
+ if (sticky === 'end' || sticky === 'right') {
394
+ return `Vou fixar a coluna **${label}** no fim da tabela.`;
395
+ }
396
+ return `Vou fixar a coluna **${label}** no inicio da tabela.`;
397
+ }
398
+ alignmentLabel(value) {
399
+ switch (this.stringValue(value).toLowerCase()) {
400
+ case 'left':
401
+ return 'a esquerda';
402
+ case 'right':
403
+ return 'a direita';
404
+ case 'center':
405
+ return 'ao centro';
406
+ default:
407
+ return 'como solicitado';
408
+ }
409
+ }
410
+ describeAdvancedFiltersConfigure(input) {
411
+ const enabled = this.booleanInput(input['enabled']);
412
+ if (enabled === false) {
413
+ return 'Vou desativar os filtros avancados da tabela.';
414
+ }
415
+ const settings = this.toRecord(input['settings']) ?? {};
416
+ const fields = this.arrayOfStrings(settings['alwaysVisibleFields']);
417
+ if (fields.length) {
418
+ return `Vou ativar os filtros avancados e deixar **${fields.map((field) => this.humanizeFilterField(field)).join(', ')}** sempre visiveis.`;
419
+ }
420
+ return 'Vou ativar os filtros avancados da tabela.';
421
+ }
422
+ describeAdvancedFilterFields(input, action) {
423
+ const fields = this.arrayOfStrings(input['fields']);
424
+ if (!fields.length) {
425
+ return action === 'add'
426
+ ? 'Vou incluir novos campos nos filtros avancados.'
427
+ : 'Vou remover campos dos filtros avancados.';
428
+ }
429
+ const labels = fields.map((field) => this.humanizeFilterField(field)).join(', ');
430
+ if (action === 'remove') {
431
+ return `Vou remover **${labels}** dos filtros avancados.`;
432
+ }
433
+ const alwaysVisible = this.booleanInput(input['alwaysVisible']);
434
+ if (alwaysVisible === true) {
435
+ return `Vou incluir **${labels}** nos filtros avancados e deixar disponivel na area principal.`;
436
+ }
437
+ return `Vou incluir **${labels}** nos filtros avancados.`;
438
+ }
439
+ describeConditionalRenderer(label, input) {
440
+ const renderer = this.toRecord(input['renderer']);
441
+ const rendererType = this.stringValue(renderer?.['type']) || 'badge';
442
+ const visual = this.toRecord(renderer?.[rendererType]) ?? {};
443
+ const text = this.stringValue(visual['text']);
444
+ const description = this.stringValue(input['description']);
445
+ if (text) {
446
+ return `Vou destacar a coluna **${label}** com ${this.rendererLabel(rendererType)} **${text}** quando a condicao for atendida.`;
447
+ }
448
+ if (description) {
449
+ return `Vou destacar a coluna **${label}** quando **${description}**.`;
450
+ }
451
+ return `Vou destacar a coluna **${label}** quando a condicao for atendida.`;
452
+ }
453
+ describeConditionalStyle(label, input) {
454
+ const parts = [`Vou destacar a coluna **${label}**`];
455
+ const condition = this.conditionLabel(input['condition']);
456
+ if (condition) {
457
+ parts.push(`quando **${condition}**`);
458
+ }
459
+ else {
460
+ parts.push('quando a condicao for atendida');
461
+ }
462
+ const style = this.styleLabel(this.toRecord(input['style']));
463
+ if (style) {
464
+ parts.push(`usando ${style}`);
465
+ }
466
+ const tooltip = this.tooltipLabel(input['tooltip'] ?? input['description']);
467
+ if (tooltip) {
468
+ parts.push(`com tooltip **${tooltip}**`);
469
+ }
470
+ return `${parts.join(' ')}.`;
471
+ }
472
+ conditionLabel(condition) {
473
+ const record = this.toRecord(condition);
474
+ if (!record)
475
+ return null;
476
+ for (const operator of ['>=', '<=', '>', '<', '===', '==']) {
477
+ const operands = record[operator];
478
+ if (!Array.isArray(operands) || operands.length < 2)
479
+ continue;
480
+ const field = this.jsonLogicOperandLabel(operands[0]);
481
+ const value = this.jsonLogicOperandLabel(operands[1]);
482
+ if (field && value) {
483
+ return `${field} ${operator} ${value}`;
484
+ }
485
+ }
486
+ return null;
487
+ }
488
+ jsonLogicOperandLabel(operand) {
489
+ const record = this.toRecord(operand);
490
+ const field = this.stringValue(record?.['var']);
491
+ if (field)
492
+ return this.humanizeField(field);
493
+ if (typeof operand === 'string')
494
+ return operand;
495
+ if (typeof operand === 'number' || typeof operand === 'boolean')
496
+ return String(operand);
497
+ return null;
498
+ }
499
+ styleLabel(style) {
500
+ if (!style)
501
+ return null;
502
+ const labels = [];
503
+ const background = this.stringValue(style['backgroundColor'] ?? style['background-color']);
504
+ const color = this.stringValue(style['color']);
505
+ const fontWeight = this.stringValue(style['fontWeight'] ?? style['font-weight']);
506
+ const opacity = this.stringValue(style['opacity']);
507
+ const border = this.stringValue(style['border']);
508
+ if (background)
509
+ labels.push(`fundo ${this.colorLabel(background)}`);
510
+ if (color)
511
+ labels.push(`texto ${this.colorLabel(color)}`);
512
+ if (fontWeight)
513
+ labels.push('texto em destaque');
514
+ if (opacity)
515
+ labels.push(`opacidade ${opacity}`);
516
+ if (border)
517
+ labels.push('borda discreta');
518
+ return labels.length ? labels.join(', ') : null;
519
+ }
520
+ colorLabel(value) {
521
+ const normalized = value.toLowerCase();
522
+ if (normalized.includes('46, 125, 50') || normalized.includes('27, 94, 32') || normalized.includes('green')
523
+ || normalized.includes('#e8f5e9') || normalized.includes('#1b5e20')) {
524
+ return 'verde suave';
525
+ }
526
+ if (normalized.includes('255, 152, 0') || normalized.includes('255, 243, 224') || normalized.includes('138, 75, 0') || normalized.includes('orange')
527
+ || normalized.includes('#fff3e0') || normalized.includes('#ff9800') || normalized.includes('#8a4b00')) {
528
+ return 'laranja suave';
529
+ }
530
+ if (normalized.includes('244, 67, 54') || normalized.includes('183, 28, 28') || normalized.includes('red')
531
+ || normalized.includes('#ffebee') || normalized.includes('#b71c1c')) {
532
+ return 'vermelho suave';
533
+ }
534
+ return value;
535
+ }
536
+ tooltipLabel(value) {
537
+ const tooltip = this.toRecord(value);
538
+ const text = this.stringValue(tooltip?.['text']) || this.stringValue(value);
539
+ return text || null;
540
+ }
541
+ toChatMessages(messages, prompt) {
542
+ const supported = (messages ?? [])
543
+ .filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
544
+ .map((message) => ({
545
+ role: message.role,
546
+ content: message.text,
547
+ }))
548
+ .filter((message) => message.content.trim().length > 0);
549
+ return supported.length ? supported : [{ role: 'user', content: prompt }];
550
+ }
551
+ toClarificationQuestions(response) {
552
+ const labels = response.questions?.length
553
+ ? response.questions
554
+ : response.message
555
+ ? [response.message]
556
+ : ['Qual ajuste voce quer aplicar na tabela?'];
557
+ const options = this.toQuickReplies(response).map((reply) => ({
558
+ id: reply.id,
559
+ label: reply.label,
560
+ value: reply.prompt,
561
+ }));
562
+ return labels.map((label, index) => ({
563
+ id: `table-clarification-${index + 1}`,
564
+ type: options.length ? 'single-choice' : 'text',
565
+ label,
566
+ allowCustom: true,
567
+ options,
568
+ }));
569
+ }
570
+ toQuickReplies(response) {
571
+ const payloads = response.optionPayloads ?? [];
572
+ if (payloads.length) {
573
+ return payloads
574
+ .map((option, index) => {
575
+ const rawLabel = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
576
+ const label = this.humanizeClarificationOptionLabel(rawLabel);
577
+ const prompt = option.value?.trim() || option.example?.trim() || label;
578
+ return {
579
+ id: `option-${index + 1}`,
580
+ label,
581
+ prompt,
582
+ kind: 'clarification-option',
583
+ description: this.optionDescription(option),
584
+ icon: this.optionIcon(option),
585
+ tone: this.optionTone(option),
586
+ presentation: this.optionPresentation(option) ?? this.defaultGuidedOptionPresentation(option),
587
+ contextHints: this.optionContextHints(option),
588
+ };
589
+ });
590
+ }
591
+ return (response.options ?? [])
592
+ .filter((option) => !!option?.trim())
593
+ .map((option, index) => ({
594
+ id: `option-${index + 1}`,
595
+ label: this.humanizeClarificationOptionLabel(option.trim()),
596
+ prompt: option.trim(),
597
+ kind: 'clarification-option',
598
+ presentation: {
599
+ kind: 'guided-option',
600
+ icon: 'check',
601
+ ctaLabel: 'Usar esta opcao',
602
+ },
603
+ }));
604
+ }
605
+ humanizeClarificationOptionLabel(label) {
606
+ return label
607
+ .replace(/\s*\((?:column|filter|behavior|toolbar|export|appearance)\.[^)]+\)\s*$/u, '')
608
+ .replace(/\s*—\s*ocultar\s*$/u, ' — ocultar coluna')
609
+ .replace(/\s*—\s*remover completamente\s*$/u, ' — remover coluna')
610
+ .replace(/\s*—\s*exibir\s*$/u, ' — exibir coluna')
611
+ .trim();
612
+ }
613
+ defaultGuidedOptionPresentation(option) {
614
+ const description = this.optionDescription(option);
615
+ const icon = this.optionIcon(option) || 'check';
616
+ const tone = this.optionTone(option);
617
+ return {
618
+ kind: 'guided-option',
619
+ icon,
620
+ tone,
621
+ description,
622
+ ctaLabel: 'Usar esta opcao',
623
+ };
624
+ }
625
+ optionContextHints(option) {
626
+ return this.toRecord(option.contextHints);
627
+ }
628
+ optionPresentation(option) {
629
+ const hints = this.optionContextHints(option);
630
+ return this.toRecord(hints?.['presentation']);
631
+ }
632
+ optionDescription(option) {
633
+ const presentation = this.optionPresentation(option);
634
+ const value = presentation?.['description'];
635
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
636
+ }
637
+ optionIcon(option) {
638
+ const presentation = this.optionPresentation(option);
639
+ const value = presentation?.['icon'];
640
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
641
+ }
642
+ optionTone(option) {
643
+ const presentation = this.optionPresentation(option);
644
+ const value = presentation?.['tone'];
645
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
646
+ }
647
+ booleanConditionValue(condition) {
648
+ const record = this.toRecord(condition);
649
+ if (!record)
650
+ return null;
651
+ for (const operator of ['==', '===']) {
652
+ const operands = record[operator];
653
+ if (!Array.isArray(operands))
654
+ continue;
655
+ const literal = operands.find((operand) => typeof operand === 'boolean');
656
+ if (typeof literal === 'boolean')
657
+ return literal;
658
+ }
659
+ return null;
660
+ }
661
+ booleanInput(value) {
662
+ return typeof value === 'boolean' ? value : null;
663
+ }
664
+ arrayOfStrings(value) {
665
+ return Array.isArray(value)
666
+ ? value.filter((item) => typeof item === 'string' && item.trim().length > 0)
667
+ : [];
668
+ }
669
+ formatLabel(value) {
670
+ const format = this.stringValue(value);
671
+ if (!format)
672
+ return 'o formato solicitado';
673
+ if (format === '000.000.000-00')
674
+ return 'CPF brasileiro';
675
+ if (/^BRL\b/u.test(format))
676
+ return 'moeda brasileira';
677
+ switch (format) {
678
+ case 'shortDate':
679
+ return 'data curta';
680
+ case 'mediumDate':
681
+ return 'data com mes abreviado';
682
+ case 'longDate':
683
+ return 'data por extenso';
684
+ case 'fullDate':
685
+ return 'data completa com dia da semana';
686
+ case 'MMM/yyyy':
687
+ return 'mes e ano';
688
+ case 'dd/MM/yyyy':
689
+ return 'data no padrao brasileiro';
690
+ case 'yyyy-MM-dd':
691
+ return 'data ISO';
692
+ case 'yyyy-MM-dd HH:mm':
693
+ return 'data e hora';
694
+ case 'shortTime':
695
+ return 'hora curta';
696
+ case 'short':
697
+ return 'data e hora curtas';
698
+ }
699
+ return format;
700
+ }
701
+ rendererLabel(value) {
702
+ switch (value.trim().toLowerCase()) {
703
+ case 'chip':
704
+ return 'chip';
705
+ case 'avatar':
706
+ return 'avatar';
707
+ case 'badge':
708
+ return 'badge';
709
+ default:
710
+ return 'indicador visual';
711
+ }
712
+ }
713
+ humanizeField(field) {
714
+ if (field.trim().toLowerCase() === 'cpf')
715
+ return 'CPF';
716
+ return field
717
+ .replace(/[_-]+/gu, ' ')
718
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
719
+ .trim()
720
+ .replace(/^./u, (char) => char.toLocaleUpperCase('pt-BR'));
721
+ }
722
+ humanizeFilterField(field) {
723
+ const normalized = field.trim();
724
+ if (!normalized)
725
+ return 'Campo';
726
+ const catalogLabel = this.filterFieldLabels.get(normalized);
727
+ if (catalogLabel)
728
+ return catalogLabel;
729
+ if (normalized.toLowerCase() === 'cpf')
730
+ return 'CPF';
731
+ if (/between$/iu.test(normalized)) {
732
+ const base = normalized.replace(/Between$/iu, '');
733
+ return `Faixa de ${this.humanizeField(base).toLocaleLowerCase('pt-BR')}`;
734
+ }
735
+ if (/range$/iu.test(normalized)) {
736
+ const base = normalized.replace(/Range$/iu, '');
737
+ return `Periodo de ${this.humanizeFilterBase(base).toLocaleLowerCase('pt-BR')}`;
738
+ }
739
+ if (/lastDays$/iu.test(normalized)) {
740
+ const base = normalized.replace(/LastDays$/iu, '');
741
+ if (this.normalizeLabel(base) === 'data admissao') {
742
+ return 'Admissões recentes';
743
+ }
744
+ return `${this.humanizeFilterBase(base)} recentes`;
745
+ }
746
+ if (/IdsIn$/iu.test(normalized)) {
747
+ return this.humanizeField(normalized.replace(/IdsIn$/iu, ''));
748
+ }
749
+ return this.humanizeField(normalized);
750
+ }
751
+ extractFilterFieldLabels(contextHints) {
752
+ const labels = new Map();
753
+ const fields = this.filterFieldCatalogFields(contextHints);
754
+ for (const field of fields) {
755
+ const record = this.toRecord(field);
756
+ const name = this.stringValue(record?.['name']);
757
+ const label = this.stringValue(record?.['label']);
758
+ if (name && label) {
759
+ labels.set(name, label);
760
+ }
761
+ }
762
+ return labels;
763
+ }
764
+ filterFieldCatalogFields(contextHints) {
765
+ const authoringContract = this.toRecord(contextHints?.['authoringContract']);
766
+ const componentEditPlan = this.toRecord(authoringContract?.['componentEditPlan']);
767
+ const nestedCatalog = this.toRecord(componentEditPlan?.['filterFieldCatalog']);
768
+ const nestedFields = nestedCatalog?.['fields'];
769
+ if (Array.isArray(nestedFields))
770
+ return nestedFields;
771
+ const directCatalog = this.toRecord(authoringContract?.['filterFieldCatalog'])
772
+ ?? this.toRecord(contextHints?.['filterFieldCatalog']);
773
+ const directFields = directCatalog?.['fields'];
774
+ return Array.isArray(directFields) ? directFields : [];
775
+ }
776
+ humanizeFilterBase(field) {
777
+ const label = this.humanizeField(field).replace(/^Data\s+/iu, '');
778
+ if (this.normalizeLabel(label) === 'admissao')
779
+ return 'admissão';
780
+ return label;
781
+ }
782
+ normalizeLabel(value) {
783
+ return value
784
+ .normalize('NFD')
785
+ .replace(/\p{Diacritic}/gu, '')
786
+ .replace(/[_-]+/gu, ' ')
787
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
788
+ .trim()
789
+ .toLocaleLowerCase('pt-BR')
790
+ .replace(/\s+/gu, ' ');
791
+ }
792
+ stringValue(value) {
793
+ return typeof value === 'string' ? value.trim() : '';
794
+ }
795
+ buildCurrentStateDigest(currentState, dataProfile) {
796
+ const columns = Array.isArray(currentState['columns'])
797
+ ? currentState['columns']
798
+ .map((column) => this.toRecord(column)?.['field'])
799
+ .filter((field) => typeof field === 'string' && field.length > 0)
800
+ : undefined;
801
+ const rowCount = typeof dataProfile?.['rowCount'] === 'number' ? dataProfile['rowCount'] : undefined;
802
+ return {
803
+ ...(columns?.length ? { columns } : {}),
804
+ ...(rowCount !== undefined ? { rowCount } : {}),
805
+ };
806
+ }
807
+ optionalJsonObject(value) {
808
+ if (value === undefined || value === null) {
809
+ return undefined;
810
+ }
811
+ const object = this.toAiJsonObject(value);
812
+ return Object.keys(object).length ? object : undefined;
813
+ }
814
+ async prepareAuthoringContext() {
815
+ const adapter = this.adapter;
816
+ await adapter.prepareAuthoringContext?.();
817
+ }
818
+ mergeJsonObjects(base, overlay) {
819
+ if (!base)
820
+ return overlay;
821
+ if (!overlay)
822
+ return base;
823
+ return {
824
+ ...base,
825
+ ...overlay,
826
+ };
827
+ }
828
+ toAiJsonObject(value) {
829
+ const record = this.toRecord(value);
830
+ if (!record) {
831
+ return {};
832
+ }
833
+ try {
834
+ return JSON.parse(JSON.stringify(record));
835
+ }
836
+ catch {
837
+ return {};
838
+ }
839
+ }
840
+ toRecord(value) {
841
+ return value && typeof value === 'object' && !Array.isArray(value)
842
+ ? value
843
+ : null;
844
+ }
845
+ }
846
+
847
+ export { TableAgenticAuthoringTurnFlow };