@praxisui/table 8.0.0-beta.0 → 8.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -3
- package/fesm2022/{praxisui-table-filter-form-dialog-host.component-EHoM1uuJ.mjs → praxisui-table-filter-form-dialog-host.component-Dm2f0muy.mjs} +2 -2
- package/fesm2022/praxisui-table-table-agentic-authoring-turn-flow-tu7jtTwV.mjs +280 -0
- package/fesm2022/{praxisui-table-table-ai.adapter-CFyyQB26.mjs → praxisui-table-table-ai.adapter-DxjDaQqy.mjs} +67 -3
- package/fesm2022/praxisui-table.mjs +1457 -398
- package/index.d.ts +321 -10
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
2
|
title: "Table"
|
|
3
3
|
slug: "table-overview"
|
|
4
4
|
description: "Visao geral do @praxisui/table com TableConfig unificada, filtros, renderers, performance e integracao enterprise."
|
|
@@ -256,6 +256,40 @@ O novo editor é "column-first" e usa uma tipagem compartilhada com o Editor d
|
|
|
256
256
|
- Preview: execute “Testar†para ver quantas linhas seriam afetadas pelas regras ativas.
|
|
257
257
|
- Import/Export: exporta JSON sem o campo `enabled`; importa com validação estrutural de JSON Logic e sanitização de estilos (allowlist).
|
|
258
258
|
|
|
259
|
+
#### Estados de linha vs estilos de célula
|
|
260
|
+
|
|
261
|
+
Use `rowConditionalStyles` quando a condição descreve o estado semântico da linha inteira, como prioridade, SLA vencido, item sem responsável, bloqueio ou estagnação. Para temas e faixas visuais, prefira `cssClass` em `rowConditionalStyles` e resolva a aparência no CSS do host. Isso preserva o ownership de tema do host e evita que gradientes ou backgrounds reiniciem em cada célula.
|
|
262
|
+
|
|
263
|
+
Use `columns[].conditionalStyles` quando o destaque pertence a uma coluna específica, como texto negativo em vermelho, status em badge, valor monetário fora da faixa ou ícone contextual de uma célula.
|
|
264
|
+
|
|
265
|
+
Padrão recomendado:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
rowConditionalStyles: [
|
|
269
|
+
{
|
|
270
|
+
condition: { contains: [{ var: 'prioridadeNome' }, 'Alta'] },
|
|
271
|
+
cssClass: 'app-row--priority-high',
|
|
272
|
+
description: 'Prioridade alta recebe âncora visual de linha.',
|
|
273
|
+
},
|
|
274
|
+
]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```scss
|
|
278
|
+
.app-table .mat-mdc-row.app-row--priority-high {
|
|
279
|
+
background: linear-gradient(
|
|
280
|
+
90deg,
|
|
281
|
+
color-mix(in srgb, var(--md-sys-color-error-container) 42%, transparent),
|
|
282
|
+
transparent
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.app-table .mat-mdc-row.app-row--priority-high .mat-mdc-cell {
|
|
287
|
+
background: transparent;
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Evite aplicar gradientes de estado de linha diretamente em `.mat-mdc-cell`. Como cada célula tem largura, sticky behavior e renderer próprios, o gradiente pode parecer quebrado por coluna, especialmente em tema dark.
|
|
292
|
+
|
|
259
293
|
Exemplos de JSON Logic:
|
|
260
294
|
|
|
261
295
|
```json
|
|
@@ -428,14 +462,22 @@ Um botão de engrenagem será exibido no canto superior direito. Ao clicar n
|
|
|
428
462
|
|
|
429
463
|
As alterações podem ser aplicadas temporariamente com **Aplicar** ou salvas de forma persistente com **Salvar & Fechar**.
|
|
430
464
|
|
|
465
|
+
### Assistente de IA da Tabela
|
|
466
|
+
|
|
467
|
+
Quando `enableCustomization` esta ativo, a tabela expoe o assistente de IA pelo shell canonico de `@praxisui/ai`.
|
|
468
|
+
O chrome conversacional, `sessionId`, `clientTurnId`, respostas rapidas e ciclo de revisao/aplicacao sao orquestrados
|
|
469
|
+
por `PraxisAssistantTurnOrchestratorService`; a semantica especifica da tabela continua em `TableAiAdapter` e no
|
|
470
|
+
`TableAgenticAuthoringTurnFlow`.
|
|
471
|
+
|
|
431
472
|
## 🚀 Instalação
|
|
432
473
|
|
|
433
474
|
```bash
|
|
434
|
-
npm install @praxisui/core @praxisui/table
|
|
475
|
+
npm install @praxisui/ai @praxisui/core @praxisui/table
|
|
435
476
|
```
|
|
436
477
|
|
|
437
478
|
Peers necessários (instale no app host):
|
|
438
479
|
- `@angular/core` `^20.0.0`, `@angular/common` `^20.0.0`
|
|
480
|
+
- `@praxisui/ai` `^8.0.0-beta.0`
|
|
439
481
|
- `@praxisui/core` `^0.0.1`
|
|
440
482
|
- `@praxisui/dynamic-fields` `^0.0.1` (quando usar editores/inputs dinâmicos)
|
|
441
483
|
- `@praxisui/dynamic-form` `^0.0.1` (quando integrar com formulários dinâmicos)
|
|
@@ -1716,4 +1758,3 @@ Apache-2.0 — consulte `LICENSE` na raiz do workspace para detalhes.
|
|
|
1716
1758
|
**Parte do Praxis UI Workspace**
|
|
1717
1759
|
**Versão**: 2.0.0 (Unified Architecture)
|
|
1718
1760
|
**Compatibilidade**: Angular 18+
|
|
1719
|
-
|
|
@@ -115,7 +115,7 @@ class FilterFormDialogHostComponent {
|
|
|
115
115
|
{{ data.i18n?.apply || 'Aplicar' }}
|
|
116
116
|
</button>
|
|
117
117
|
</mat-dialog-actions>
|
|
118
|
-
`, isInline: true, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--md-sys-color-on-surface)}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--md-sys-color-on-surface-variant)}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i15.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisFilterForm, selector: "praxis-filter-form", inputs: ["config", "formId", "resourcePath", "mode"], outputs: ["formReady", "valueChange", "submit", "requestSearch", "validityChange"] }] });
|
|
118
|
+
`, isInline: true, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--mdc-dialog-subhead-color, var(--md-sys-color-on-surface))}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--mdc-dialog-supporting-text-color, var(--md-sys-color-on-surface-variant))}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i15.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisFilterForm, selector: "praxis-filter-form", inputs: ["config", "formId", "resourcePath", "mode"], outputs: ["formReady", "valueChange", "submit", "requestSearch", "validityChange"] }] });
|
|
119
119
|
}
|
|
120
120
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FilterFormDialogHostComponent, decorators: [{
|
|
121
121
|
type: Component,
|
|
@@ -156,7 +156,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
156
156
|
{{ data.i18n?.apply || 'Aplicar' }}
|
|
157
157
|
</button>
|
|
158
158
|
</mat-dialog-actions>
|
|
159
|
-
`, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--md-sys-color-on-surface)}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--md-sys-color-on-surface-variant)}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}\n"] }]
|
|
159
|
+
`, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--mdc-dialog-subhead-color, var(--md-sys-color-on-surface))}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--mdc-dialog-supporting-text-color, var(--md-sys-color-on-surface-variant))}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}\n"] }]
|
|
160
160
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
161
161
|
type: Inject,
|
|
162
162
|
args: [MAT_DIALOG_DATA]
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { firstValueFrom } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
class TableAgenticAuthoringTurnFlow {
|
|
4
|
+
adapter;
|
|
5
|
+
aiApi;
|
|
6
|
+
mode = 'config';
|
|
7
|
+
constructor(adapter, aiApi) {
|
|
8
|
+
this.adapter = adapter;
|
|
9
|
+
this.aiApi = aiApi;
|
|
10
|
+
}
|
|
11
|
+
async submit(request) {
|
|
12
|
+
const prompt = (request.prompt ?? '').trim();
|
|
13
|
+
if (!prompt) {
|
|
14
|
+
return {
|
|
15
|
+
state: 'listening',
|
|
16
|
+
phase: 'capture',
|
|
17
|
+
statusText: '',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const componentId = this.adapter.componentId || request.componentId || 'praxis-table';
|
|
21
|
+
const componentType = this.adapter.componentType || request.componentType || 'table';
|
|
22
|
+
const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
|
|
23
|
+
const dataProfile = this.optionalJsonObject(this.adapter.getDataProfile?.());
|
|
24
|
+
const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
|
|
25
|
+
const schemaFields = this.adapter.getSchemaFields?.()
|
|
26
|
+
?.map((field) => this.toAiJsonObject(field))
|
|
27
|
+
.filter((field) => Object.keys(field).length > 0);
|
|
28
|
+
const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
|
|
29
|
+
const response = await firstValueFrom(this.aiApi.getPatch({
|
|
30
|
+
componentId,
|
|
31
|
+
componentType,
|
|
32
|
+
userPrompt: prompt,
|
|
33
|
+
sessionId: request.sessionId,
|
|
34
|
+
clientTurnId: request.clientTurnId,
|
|
35
|
+
messages: this.toChatMessages(request.messages, prompt),
|
|
36
|
+
currentState,
|
|
37
|
+
currentStateDigest: this.buildCurrentStateDigest(currentState, dataProfile),
|
|
38
|
+
uiContextRef: {
|
|
39
|
+
componentId,
|
|
40
|
+
componentType,
|
|
41
|
+
},
|
|
42
|
+
...(dataProfile ? { dataProfile } : {}),
|
|
43
|
+
...(runtimeState ? { runtimeState } : {}),
|
|
44
|
+
...(schemaFields?.length ? { schemaFields } : {}),
|
|
45
|
+
...(contextHints ? { contextHints } : {}),
|
|
46
|
+
}));
|
|
47
|
+
return this.toTurnResult(this.compileAdapterResponse(response), request);
|
|
48
|
+
}
|
|
49
|
+
async apply(request) {
|
|
50
|
+
const patch = this.toRecord(request.pendingPatch);
|
|
51
|
+
if (!patch) {
|
|
52
|
+
return {
|
|
53
|
+
state: 'error',
|
|
54
|
+
phase: 'apply',
|
|
55
|
+
assistantMessage: 'Nao ha alteracao de tabela pronta para aplicar.',
|
|
56
|
+
errorText: 'Nao ha alteracao de tabela pronta para aplicar.',
|
|
57
|
+
canApply: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const result = await this.adapter.applyPatch(patch, request.prompt);
|
|
61
|
+
if (!result.success) {
|
|
62
|
+
return {
|
|
63
|
+
state: 'error',
|
|
64
|
+
phase: 'apply',
|
|
65
|
+
assistantMessage: result.error || 'Nao foi possivel aplicar as alteracoes na tabela.',
|
|
66
|
+
errorText: result.error || 'Nao foi possivel aplicar as alteracoes na tabela.',
|
|
67
|
+
canApply: true,
|
|
68
|
+
pendingPatch: patch,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
state: 'success',
|
|
73
|
+
phase: 'summarize',
|
|
74
|
+
assistantMessage: 'Alteracoes aplicadas na tabela.',
|
|
75
|
+
statusText: 'Alteracoes aplicadas na tabela.',
|
|
76
|
+
canApply: false,
|
|
77
|
+
pendingPatch: null,
|
|
78
|
+
diagnostics: result.warnings?.length ? { warnings: result.warnings } : undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
cancel() {
|
|
82
|
+
return Promise.resolve({
|
|
83
|
+
state: 'listening',
|
|
84
|
+
phase: 'capture',
|
|
85
|
+
assistantMessage: 'Solicitacao cancelada.',
|
|
86
|
+
statusText: '',
|
|
87
|
+
canApply: false,
|
|
88
|
+
pendingPatch: null,
|
|
89
|
+
pendingClarification: null,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
retry(request) {
|
|
93
|
+
const lastPrompt = [...(request.messages ?? [])].reverse()
|
|
94
|
+
.find((message) => message.role === 'user')?.text;
|
|
95
|
+
return this.submit({
|
|
96
|
+
...request,
|
|
97
|
+
prompt: lastPrompt ?? request.prompt,
|
|
98
|
+
action: { kind: 'retry' },
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
toTurnResult(response, request) {
|
|
102
|
+
if (!response) {
|
|
103
|
+
return {
|
|
104
|
+
state: 'error',
|
|
105
|
+
phase: 'capture',
|
|
106
|
+
assistantMessage: 'Resposta vazia da IA.',
|
|
107
|
+
errorText: 'Resposta vazia da IA.',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (response.sessionId && response.sessionId !== request.sessionId) {
|
|
111
|
+
request = { ...request, sessionId: response.sessionId };
|
|
112
|
+
}
|
|
113
|
+
if (response.type === 'clarification') {
|
|
114
|
+
const questions = this.toClarificationQuestions(response);
|
|
115
|
+
return {
|
|
116
|
+
state: 'clarification',
|
|
117
|
+
phase: 'clarify',
|
|
118
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
119
|
+
assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
|
|
120
|
+
clarificationQuestions: questions,
|
|
121
|
+
quickReplies: this.toQuickReplies(response),
|
|
122
|
+
canApply: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (response.type === 'info') {
|
|
126
|
+
const message = response.message || response.explanation || 'Informacao gerada.';
|
|
127
|
+
return {
|
|
128
|
+
state: 'success',
|
|
129
|
+
phase: 'summarize',
|
|
130
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
131
|
+
assistantMessage: message,
|
|
132
|
+
statusText: message,
|
|
133
|
+
canApply: false,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (response.type === 'error') {
|
|
137
|
+
const message = response.message || 'Falha ao gerar alteracao de tabela.';
|
|
138
|
+
return {
|
|
139
|
+
state: 'error',
|
|
140
|
+
phase: 'capture',
|
|
141
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
142
|
+
assistantMessage: message,
|
|
143
|
+
errorText: message,
|
|
144
|
+
diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
148
|
+
const warnings = response.warnings?.filter(Boolean) ?? [];
|
|
149
|
+
const suffix = warnings.length ? ` Avisos: ${warnings.join('; ')}` : '';
|
|
150
|
+
return {
|
|
151
|
+
state: 'review',
|
|
152
|
+
phase: 'review',
|
|
153
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
154
|
+
assistantMessage: `${response.explanation || 'Proposta de alteracao pronta para revisar.'}${suffix}`,
|
|
155
|
+
statusText: 'Revise a proposta antes de aplicar.',
|
|
156
|
+
canApply: true,
|
|
157
|
+
pendingPatch: response.patch,
|
|
158
|
+
preview: {
|
|
159
|
+
kind: 'table-config-patch',
|
|
160
|
+
diff: response.diff ?? [],
|
|
161
|
+
},
|
|
162
|
+
diagnostics: warnings.length ? { warnings } : undefined,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
state: 'success',
|
|
167
|
+
phase: 'summarize',
|
|
168
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
169
|
+
assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
170
|
+
statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
171
|
+
canApply: false,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
compileAdapterResponse(response) {
|
|
175
|
+
const compiled = this.adapter.compileAiResponse?.(response);
|
|
176
|
+
if (!compiled) {
|
|
177
|
+
return response;
|
|
178
|
+
}
|
|
179
|
+
const warnings = [
|
|
180
|
+
...(response.warnings ?? []),
|
|
181
|
+
...(compiled.warnings ?? []),
|
|
182
|
+
];
|
|
183
|
+
return {
|
|
184
|
+
...response,
|
|
185
|
+
...compiled,
|
|
186
|
+
warnings: warnings.length ? warnings : undefined,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
toChatMessages(messages, prompt) {
|
|
190
|
+
const supported = (messages ?? [])
|
|
191
|
+
.filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
|
|
192
|
+
.map((message) => ({
|
|
193
|
+
role: message.role,
|
|
194
|
+
content: message.text,
|
|
195
|
+
}))
|
|
196
|
+
.filter((message) => message.content.trim().length > 0);
|
|
197
|
+
return supported.length ? supported : [{ role: 'user', content: prompt }];
|
|
198
|
+
}
|
|
199
|
+
toClarificationQuestions(response) {
|
|
200
|
+
const labels = response.questions?.length
|
|
201
|
+
? response.questions
|
|
202
|
+
: response.message
|
|
203
|
+
? [response.message]
|
|
204
|
+
: ['Qual ajuste voce quer aplicar na tabela?'];
|
|
205
|
+
const options = this.toQuickReplies(response).map((reply) => ({
|
|
206
|
+
id: reply.id,
|
|
207
|
+
label: reply.label,
|
|
208
|
+
value: reply.prompt,
|
|
209
|
+
}));
|
|
210
|
+
return labels.map((label, index) => ({
|
|
211
|
+
id: `table-clarification-${index + 1}`,
|
|
212
|
+
type: options.length ? 'single-choice' : 'text',
|
|
213
|
+
label,
|
|
214
|
+
allowCustom: true,
|
|
215
|
+
options,
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
toQuickReplies(response) {
|
|
219
|
+
const payloads = response.optionPayloads ?? [];
|
|
220
|
+
if (payloads.length) {
|
|
221
|
+
return payloads
|
|
222
|
+
.map((option, index) => {
|
|
223
|
+
const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
224
|
+
const prompt = option.example?.trim() || option.value?.trim() || label;
|
|
225
|
+
return {
|
|
226
|
+
id: `option-${index + 1}`,
|
|
227
|
+
label,
|
|
228
|
+
prompt,
|
|
229
|
+
kind: 'clarification-option',
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return (response.options ?? [])
|
|
234
|
+
.filter((option) => !!option?.trim())
|
|
235
|
+
.map((option, index) => ({
|
|
236
|
+
id: `option-${index + 1}`,
|
|
237
|
+
label: option.trim(),
|
|
238
|
+
prompt: option.trim(),
|
|
239
|
+
kind: 'clarification-option',
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
buildCurrentStateDigest(currentState, dataProfile) {
|
|
243
|
+
const columns = Array.isArray(currentState['columns'])
|
|
244
|
+
? currentState['columns']
|
|
245
|
+
.map((column) => this.toRecord(column)?.['field'])
|
|
246
|
+
.filter((field) => typeof field === 'string' && field.length > 0)
|
|
247
|
+
: undefined;
|
|
248
|
+
const rowCount = typeof dataProfile?.['rowCount'] === 'number' ? dataProfile['rowCount'] : undefined;
|
|
249
|
+
return {
|
|
250
|
+
...(columns?.length ? { columns } : {}),
|
|
251
|
+
...(rowCount !== undefined ? { rowCount } : {}),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
optionalJsonObject(value) {
|
|
255
|
+
if (value === undefined || value === null) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
const object = this.toAiJsonObject(value);
|
|
259
|
+
return Object.keys(object).length ? object : undefined;
|
|
260
|
+
}
|
|
261
|
+
toAiJsonObject(value) {
|
|
262
|
+
const record = this.toRecord(value);
|
|
263
|
+
if (!record) {
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
return JSON.parse(JSON.stringify(record));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return {};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
toRecord(value) {
|
|
274
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
275
|
+
? value
|
|
276
|
+
: null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export { TableAgenticAuthoringTurnFlow };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { firstValueFrom } from 'rxjs';
|
|
2
2
|
import { BaseAiAdapter } from '@praxisui/ai';
|
|
3
3
|
import { deepMerge } from '@praxisui/core';
|
|
4
|
-
import { TABLE_AI_CAPABILITIES, TASK_PRESETS } from './praxisui-table.mjs';
|
|
4
|
+
import { coerceTableComponentEditPlans, compileTableComponentEditPlans, TABLE_AI_CAPABILITIES, TASK_PRESETS, getTableComponentEditPlanCapabilities, TABLE_COMPONENT_EDIT_PLAN_EXPECTED_PATHS, TABLE_COMPONENT_EDIT_PLAN_ALLOWED_CHANGE_KINDS, TABLE_COMPONENT_EDIT_PLAN_JSON_SCHEMA, TABLE_COMPONENT_EDIT_PLAN_VERSION, TABLE_COMPONENT_EDIT_PLAN_BATCH_KIND, TABLE_COMPONENT_EDIT_PLAN_KIND } from './praxisui-table.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Analisa uma amostra de dados da tabela para gerar estatísticas
|
|
@@ -123,6 +123,25 @@ class TableAiAdapter extends BaseAiAdapter {
|
|
|
123
123
|
this.aiService = aiService;
|
|
124
124
|
}
|
|
125
125
|
// -------- Core contract --------
|
|
126
|
+
compileAiResponse(response) {
|
|
127
|
+
const componentEditPlans = coerceTableComponentEditPlans(response);
|
|
128
|
+
if (!componentEditPlans) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const compiled = compileTableComponentEditPlans(componentEditPlans, this.getCurrentConfig());
|
|
132
|
+
if (compiled.patch) {
|
|
133
|
+
return {
|
|
134
|
+
patch: this.normalizePatch(compiled.patch),
|
|
135
|
+
explanation: typeof response['explanation'] === 'string' ? response['explanation'] : compiled.explanation,
|
|
136
|
+
warnings: compiled.warnings,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
type: 'error',
|
|
141
|
+
message: 'O plano de edicao da tabela nao passou na validacao de capacidades.',
|
|
142
|
+
warnings: [...compiled.warnings, ...compiled.failureCodes],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
126
145
|
getCurrentConfig() {
|
|
127
146
|
try {
|
|
128
147
|
return structuredClone(this.table.config);
|
|
@@ -148,6 +167,31 @@ class TableAiAdapter extends BaseAiAdapter {
|
|
|
148
167
|
isLoading: false // TODO: expose real loading flag
|
|
149
168
|
};
|
|
150
169
|
}
|
|
170
|
+
getAuthoringContext() {
|
|
171
|
+
return {
|
|
172
|
+
authoringContract: {
|
|
173
|
+
kind: 'praxis.component-authoring-context',
|
|
174
|
+
componentId: this.componentId,
|
|
175
|
+
componentType: this.componentType,
|
|
176
|
+
preferredResponse: 'componentEditPlan',
|
|
177
|
+
componentEditPlan: {
|
|
178
|
+
kind: TABLE_COMPONENT_EDIT_PLAN_KIND,
|
|
179
|
+
batchKind: TABLE_COMPONENT_EDIT_PLAN_BATCH_KIND,
|
|
180
|
+
version: TABLE_COMPONENT_EDIT_PLAN_VERSION,
|
|
181
|
+
schemaId: TABLE_COMPONENT_EDIT_PLAN_JSON_SCHEMA.$id,
|
|
182
|
+
allowedChangeKinds: [...TABLE_COMPONENT_EDIT_PLAN_ALLOWED_CHANGE_KINDS],
|
|
183
|
+
expectedPaths: { ...TABLE_COMPONENT_EDIT_PLAN_EXPECTED_PATHS },
|
|
184
|
+
capabilities: getTableComponentEditPlanCapabilities(),
|
|
185
|
+
rules: [
|
|
186
|
+
'Use componentEditPlan instead of free patch when the request fits an allowed table edit changeKind.',
|
|
187
|
+
'Use batch kind with operations for multiple table edits in one request.',
|
|
188
|
+
'Use Json Logic objects for computed expressions and conditional rules.',
|
|
189
|
+
'Do not invent fields, changeKinds, capabilityPaths, formats, badge variants, or colors outside the provided contract.',
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
151
195
|
getSuggestionContext() {
|
|
152
196
|
const config = this.getCurrentConfig();
|
|
153
197
|
const behavior = config.behavior || {};
|
|
@@ -500,8 +544,9 @@ Columns Analysis:
|
|
|
500
544
|
// -------- Two-step flow helpers --------
|
|
501
545
|
getFilteredCapabilities(category) {
|
|
502
546
|
const all = TABLE_AI_CAPABILITIES.capabilities;
|
|
547
|
+
const editPlanCaps = getTableComponentEditPlanCapabilities();
|
|
503
548
|
if (!category || category === 'unknown')
|
|
504
|
-
return all;
|
|
549
|
+
return [...all, ...editPlanCaps];
|
|
505
550
|
const categoryMap = {
|
|
506
551
|
columns: ['columns', 'format', 'mapping', 'renderer', 'conditional'],
|
|
507
552
|
appearance: ['appearance', 'conditional'],
|
|
@@ -510,7 +555,10 @@ Columns Analysis:
|
|
|
510
555
|
actions: ['actions', 'toolbar', 'export']
|
|
511
556
|
};
|
|
512
557
|
const targets = categoryMap[category] || [category];
|
|
513
|
-
return
|
|
558
|
+
return [
|
|
559
|
+
...all.filter((c) => targets.includes(c.category)),
|
|
560
|
+
...editPlanCaps.filter((c) => targets.includes(c.category)),
|
|
561
|
+
];
|
|
514
562
|
}
|
|
515
563
|
suggestionsKey() {
|
|
516
564
|
const id = this.table.tableId || 'default';
|
|
@@ -568,6 +616,22 @@ Columns Analysis:
|
|
|
568
616
|
const context = this.extractContextForIntent(classification);
|
|
569
617
|
const caps = this.getFilteredCapabilities(classification?.category);
|
|
570
618
|
const result = await firstValueFrom(aiService.executeEnrichedPrompt(userInput, context.desc, context.config, caps));
|
|
619
|
+
const componentEditPlans = coerceTableComponentEditPlans(result);
|
|
620
|
+
if (componentEditPlans) {
|
|
621
|
+
const compiled = compileTableComponentEditPlans(componentEditPlans, this.getCurrentConfig());
|
|
622
|
+
if (compiled.patch) {
|
|
623
|
+
return {
|
|
624
|
+
patch: this.normalizePatch(compiled.patch),
|
|
625
|
+
explanation: result.explanation || compiled.explanation,
|
|
626
|
+
warnings: compiled.warnings,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
type: 'error',
|
|
631
|
+
message: 'O plano de edicao da tabela nao passou na validacao de capacidades.',
|
|
632
|
+
warnings: [...compiled.warnings, ...compiled.failureCodes],
|
|
633
|
+
};
|
|
634
|
+
}
|
|
571
635
|
if (!result || !result.patch) {
|
|
572
636
|
return { type: 'error', message: 'Nenhum patch gerado.' };
|
|
573
637
|
}
|