@praxisui/table 8.0.0-beta.7 → 8.0.0-beta.70
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 +191 -11
- package/docs/DSL-Extensions-Guide.md +23 -0
- package/docs/adr/2026-03-dynamic-filter-cross-lib-coupling.md +107 -0
- package/docs/adr/2026-03-filter-drawer-adapter-light-entrypoint.md +105 -0
- package/docs/adr/2026-03-table-editor-idfield-decision.md +85 -0
- package/docs/column-resize-reorder-implementation-plan.md +338 -0
- package/docs/column-resize-reorder-review-prompt.md +34 -0
- package/docs/dynamic-filter-architecture-overview.md +207 -0
- package/docs/dynamic-filter-backend-contract-cheatsheet.md +167 -0
- package/docs/dynamic-filter-editor-settings-guide.md +229 -0
- package/docs/dynamic-filter-host-integration-guide.md +266 -0
- package/docs/dynamic-filter-payload-contract.md +332 -0
- package/docs/dynamic-filter-range-filters-guide.md +296 -0
- package/docs/dynamic-filter-troubleshooting-guide.md +257 -0
- package/docs/dynamic-inline-filter-catalog.md +147 -0
- package/docs/e2e-column-drag-playwright.md +62 -0
- package/docs/expandable-rows-enterprise-big-leagues-plan.md +1080 -0
- package/docs/json-logic-operators-and-helpers.md +57 -0
- package/docs/local-data-mode-precedence.md +12 -0
- package/docs/local-data-pre-implementation-baseline.md +22 -0
- package/docs/local-data-preimplementation-go-no-go.md +39 -0
- package/docs/local-data-support-implementation-plan.md +524 -0
- package/docs/local-data-support-pr-package.md +66 -0
- package/docs/localization-persistence-merge.md +22 -0
- package/docs/performance-hardening-v2-implementation-plan.md +479 -0
- package/docs/playground-scenario-curation-plan.md +482 -0
- package/docs/playground-scenario-second-opinion-prompt.md +121 -0
- package/docs/playground-scenario-second-opinion-review.md +234 -0
- package/docs/release-notes-p1-hardening.md +76 -0
- package/docs/table-authoring-document-completeness-checklist.md +120 -0
- package/docs/table-editor-capability-review-prompt.md +349 -0
- package/docs/visual-rules-editor-transition.md +29 -0
- package/fesm2022/praxisui-table-filter-form-dialog-host.component-DbwGIMjF.mjs +232 -0
- package/fesm2022/praxisui-table-praxisui-table-CRPaOsiX.mjs +59795 -0
- package/fesm2022/praxisui-table-table-agentic-authoring-turn-flow-CmfIj9ao.mjs +2845 -0
- package/fesm2022/praxisui-table-table-ai.adapter-DMlNQpdn.mjs +3558 -0
- package/fesm2022/praxisui-table.mjs +1 -51444
- package/filter-drawer-adapter/package.json +2 -1
- package/package.json +22 -15
- package/src/lib/praxis-table.json-api.md +1357 -0
- package/{index.d.ts → types/praxisui-table.d.ts} +545 -120
- package/fesm2022/praxisui-table-filter-form-dialog-host.component-Dm2f0muy.mjs +0 -165
- package/fesm2022/praxisui-table-table-agentic-authoring-turn-flow-tu7jtTwV.mjs +0 -280
- package/fesm2022/praxisui-table-table-ai.adapter-DxjDaQqy.mjs +0 -895
- /package/{filter-drawer-adapter/index.d.ts → types/praxisui-table-filter-drawer-adapter.d.ts} +0 -0
|
@@ -0,0 +1,3558 @@
|
|
|
1
|
+
import { firstValueFrom } from 'rxjs';
|
|
2
|
+
import { BaseAiAdapter, createComponentAuthoringContext } from '@praxisui/ai';
|
|
3
|
+
import { deepMerge } from '@praxisui/core';
|
|
4
|
+
import { z as TABLE_COMPONENT_EDIT_PLAN_OPERATION_IDS, k as PRAXIS_TABLE_AUTHORING_MANIFEST, T as TABLE_AI_CAPABILITIES, R as coerceTableComponentEditPlans, W as compileTableComponentEditPlans, G as TASK_PRESETS, $ as getTableComponentEditPlanCapabilities, w as TABLE_COMPONENT_EDIT_PLAN_EXPECTED_PATHS, u as TABLE_COMPONENT_EDIT_PLAN_ALLOWED_CHANGE_KINDS, x as TABLE_COMPONENT_EDIT_PLAN_JSON_SCHEMA, E as TABLE_COMPONENT_EDIT_PLAN_VERSION, v as TABLE_COMPONENT_EDIT_PLAN_BATCH_KIND, y as TABLE_COMPONENT_EDIT_PLAN_KIND } from './praxisui-table-praxisui-table-CRPaOsiX.mjs';
|
|
5
|
+
|
|
6
|
+
const TABLE_COMPONENT_CONTEXT_PACK = {
|
|
7
|
+
version: 'v1',
|
|
8
|
+
optionsByPath: {
|
|
9
|
+
'appearance.density': {
|
|
10
|
+
mode: 'enum',
|
|
11
|
+
options: [
|
|
12
|
+
{ value: 'compact', label: 'Compact' },
|
|
13
|
+
{ value: 'comfortable', label: 'Comfortable' },
|
|
14
|
+
{ value: 'spacious', label: 'Spacious' },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
'export.formats': {
|
|
18
|
+
mode: 'enum',
|
|
19
|
+
options: [
|
|
20
|
+
{ value: 'excel', label: 'Excel' },
|
|
21
|
+
{ value: 'pdf', label: 'PDF' },
|
|
22
|
+
{ value: 'csv', label: 'CSV' },
|
|
23
|
+
{ value: 'json', label: 'JSON' },
|
|
24
|
+
{ value: 'print', label: 'Print' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
'columns[].type': {
|
|
28
|
+
mode: 'enum',
|
|
29
|
+
options: [
|
|
30
|
+
{ value: 'string', label: 'String' },
|
|
31
|
+
{ value: 'number', label: 'Number' },
|
|
32
|
+
{ value: 'currency', label: 'Currency' },
|
|
33
|
+
{ value: 'percentage', label: 'Percentage' },
|
|
34
|
+
{ value: 'date', label: 'Date' },
|
|
35
|
+
{ value: 'boolean', label: 'Boolean' },
|
|
36
|
+
{ value: 'custom', label: 'Custom' },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
'columns[].format': {
|
|
40
|
+
mode: 'suggested',
|
|
41
|
+
options: [
|
|
42
|
+
{ value: 'shortDate', label: 'Date short', example: '01/12/2023' },
|
|
43
|
+
{ value: 'mediumDate', label: 'Date medium', example: '01 Dec 2023' },
|
|
44
|
+
{ value: 'longDate', label: 'Date long', example: '01 December 2023' },
|
|
45
|
+
{ value: 'fullDate', label: 'Date full', example: 'Friday, 01 December 2023' },
|
|
46
|
+
{ value: 'MMM/yyyy', label: 'Month/Year', example: 'Dec/2023' },
|
|
47
|
+
{ value: 'shortTime', label: 'Time short', example: '14:30' },
|
|
48
|
+
{ value: 'short', label: 'Date+Time short', example: '01/12/2023 14:30' },
|
|
49
|
+
{ value: 'yyyy-MM-dd', label: 'ISO 8601', example: '2023-12-01' },
|
|
50
|
+
{ value: 'dd/MM/yyyy', label: 'Date dd/MM/yyyy', example: '01/12/2023' },
|
|
51
|
+
{ value: 'yyyy-MM-dd HH:mm', label: 'DateTime yyyy-MM-dd HH:mm', example: '2023-12-01 14:30' },
|
|
52
|
+
{ value: '1.0-0', label: 'Number integer', example: '1,234' },
|
|
53
|
+
{ value: '1.1-1', label: 'Number 1 decimal', example: '1,234.5' },
|
|
54
|
+
{ value: '1.2-2', label: 'Number 2 decimals', example: '1,234.56' },
|
|
55
|
+
{ value: '1.0-3', label: 'Number up to 3 decimals', example: '1,234.567' },
|
|
56
|
+
{ value: '1.0-0|nosep', label: 'Number no grouping', example: '1234' },
|
|
57
|
+
{ value: 'BRL|symbol|2', label: 'Currency BRL symbol', example: 'R$ 1.234,56' },
|
|
58
|
+
{ value: 'BRL|symbol|2|nosep', label: 'Currency BRL symbol (no grouping)', example: 'R$ 1234,56' },
|
|
59
|
+
{ value: 'USD|symbol|2', label: 'Currency USD symbol', example: 'US$ 1,234.56' },
|
|
60
|
+
{ value: 'EUR|symbol|2', label: 'Currency EUR symbol', example: 'EUR 1,234.56' },
|
|
61
|
+
{ value: 'BRL|code|2', label: 'Currency code', example: '1,234.56 BRL' },
|
|
62
|
+
{ value: 'USD|code|0', label: 'Currency USD code (0 decimals)', example: '1234 USD' },
|
|
63
|
+
{ value: 'EUR|symbol|2|nosep', label: 'Currency EUR symbol (no grouping)', example: 'EUR 1234,56' },
|
|
64
|
+
{ value: '1.0-0', label: 'Percent 0 decimals', example: '12%' },
|
|
65
|
+
{ value: '1.1-1', label: 'Percent 1 decimal', example: '12.3%' },
|
|
66
|
+
{ value: '1.2-2', label: 'Percent 2 decimals', example: '12.34%' },
|
|
67
|
+
{ value: '1.0-0|x100', label: 'Percent 0 decimals (x100)', example: '12%' },
|
|
68
|
+
{ value: '1.1-1|x100', label: 'Percent 1 decimal (x100)', example: '12.3%' },
|
|
69
|
+
{ value: '1.2-2|x100', label: 'Percent 2 decimals (x100)', example: '12.34%' },
|
|
70
|
+
{ value: 'none', label: 'String none', example: 'Sample' },
|
|
71
|
+
{ value: 'uppercase', label: 'String uppercase', example: 'SAMPLE' },
|
|
72
|
+
{ value: 'lowercase', label: 'String lowercase', example: 'sample' },
|
|
73
|
+
{ value: 'titlecase', label: 'String titlecase', example: 'Sample Text' },
|
|
74
|
+
{ value: 'capitalize', label: 'String capitalize', example: 'Sample' },
|
|
75
|
+
{ value: 'uppercase|truncate|50|...', label: 'String uppercase + truncate 50', example: 'SAMPLE...' },
|
|
76
|
+
{ value: 'true-false', label: 'Boolean true/false', example: 'True / False' },
|
|
77
|
+
{ value: 'yes-no', label: 'Boolean yes/no', example: 'Yes / No' },
|
|
78
|
+
{ value: 'active-inactive', label: 'Boolean active/inactive', example: 'Active / Inactive' },
|
|
79
|
+
{ value: 'on-off', label: 'Boolean on/off', example: 'On / Off' },
|
|
80
|
+
{ value: 'enabled-disabled', label: 'Boolean enabled/disabled', example: 'Enabled / Disabled' },
|
|
81
|
+
{ value: 'custom|Sim|Nao', label: 'Boolean custom (Sim/Nao)', example: 'Sim / Nao' },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
'columns[].computed.outputType': {
|
|
85
|
+
mode: 'enum',
|
|
86
|
+
options: [
|
|
87
|
+
{ value: 'string', label: 'String' },
|
|
88
|
+
{ value: 'number', label: 'Number' },
|
|
89
|
+
{ value: 'currency', label: 'Currency' },
|
|
90
|
+
{ value: 'percentage', label: 'Percentage' },
|
|
91
|
+
{ value: 'date', label: 'Date' },
|
|
92
|
+
{ value: 'boolean', label: 'Boolean' },
|
|
93
|
+
{ value: 'custom', label: 'Custom' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
'columns[].computed.format': {
|
|
97
|
+
mode: 'suggested',
|
|
98
|
+
options: [
|
|
99
|
+
{ value: 'shortDate', label: 'Date short', example: '01/12/2023' },
|
|
100
|
+
{ value: 'mediumDate', label: 'Date medium', example: '01 Dec 2023' },
|
|
101
|
+
{ value: 'longDate', label: 'Date long', example: '01 December 2023' },
|
|
102
|
+
{ value: 'fullDate', label: 'Date full', example: 'Friday, 01 December 2023' },
|
|
103
|
+
{ value: 'MMM/yyyy', label: 'Month/Year', example: 'Dec/2023' },
|
|
104
|
+
{ value: 'shortTime', label: 'Time short', example: '14:30' },
|
|
105
|
+
{ value: 'short', label: 'Date+Time short', example: '01/12/2023 14:30' },
|
|
106
|
+
{ value: 'yyyy-MM-dd', label: 'ISO 8601', example: '2023-12-01' },
|
|
107
|
+
{ value: 'dd/MM/yyyy', label: 'Date dd/MM/yyyy', example: '01/12/2023' },
|
|
108
|
+
{ value: 'yyyy-MM-dd HH:mm', label: 'DateTime yyyy-MM-dd HH:mm', example: '2023-12-01 14:30' },
|
|
109
|
+
{ value: '1.0-0', label: 'Number integer', example: '1,234' },
|
|
110
|
+
{ value: '1.1-1', label: 'Number 1 decimal', example: '1,234.5' },
|
|
111
|
+
{ value: '1.2-2', label: 'Number 2 decimals', example: '1,234.56' },
|
|
112
|
+
{ value: '1.0-3', label: 'Number up to 3 decimals', example: '1,234.567' },
|
|
113
|
+
{ value: '1.0-0|nosep', label: 'Number no grouping', example: '1234' },
|
|
114
|
+
{ value: 'BRL|symbol|2', label: 'Currency BRL symbol', example: 'R$ 1.234,56' },
|
|
115
|
+
{ value: 'BRL|symbol|2|nosep', label: 'Currency BRL symbol (no grouping)', example: 'R$ 1234,56' },
|
|
116
|
+
{ value: 'USD|symbol|2', label: 'Currency USD symbol', example: 'US$ 1,234.56' },
|
|
117
|
+
{ value: 'EUR|symbol|2', label: 'Currency EUR symbol', example: 'EUR 1,234.56' },
|
|
118
|
+
{ value: 'BRL|code|2', label: 'Currency code', example: '1,234.56 BRL' },
|
|
119
|
+
{ value: 'USD|code|0', label: 'Currency USD code (0 decimals)', example: '1234 USD' },
|
|
120
|
+
{ value: 'EUR|symbol|2|nosep', label: 'Currency EUR symbol (no grouping)', example: 'EUR 1234,56' },
|
|
121
|
+
{ value: '1.0-0', label: 'Percent 0 decimals', example: '12%' },
|
|
122
|
+
{ value: '1.1-1', label: 'Percent 1 decimal', example: '12.3%' },
|
|
123
|
+
{ value: '1.2-2', label: 'Percent 2 decimals', example: '12.34%' },
|
|
124
|
+
{ value: '1.0-0|x100', label: 'Percent 0 decimals (x100)', example: '12%' },
|
|
125
|
+
{ value: '1.1-1|x100', label: 'Percent 1 decimal (x100)', example: '12.3%' },
|
|
126
|
+
{ value: '1.2-2|x100', label: 'Percent 2 decimals (x100)', example: '12.34%' },
|
|
127
|
+
{ value: 'none', label: 'String none', example: 'Sample' },
|
|
128
|
+
{ value: 'uppercase', label: 'String uppercase', example: 'SAMPLE' },
|
|
129
|
+
{ value: 'lowercase', label: 'String lowercase', example: 'sample' },
|
|
130
|
+
{ value: 'titlecase', label: 'String titlecase', example: 'Sample Text' },
|
|
131
|
+
{ value: 'capitalize', label: 'String capitalize', example: 'Sample' },
|
|
132
|
+
{ value: 'uppercase|truncate|50|...', label: 'String uppercase + truncate 50', example: 'SAMPLE...' },
|
|
133
|
+
{ value: 'true-false', label: 'Boolean true/false', example: 'True / False' },
|
|
134
|
+
{ value: 'yes-no', label: 'Boolean yes/no', example: 'Yes / No' },
|
|
135
|
+
{ value: 'active-inactive', label: 'Boolean active/inactive', example: 'Active / Inactive' },
|
|
136
|
+
{ value: 'on-off', label: 'Boolean on/off', example: 'On / Off' },
|
|
137
|
+
{ value: 'enabled-disabled', label: 'Boolean enabled/disabled', example: 'Enabled / Disabled' },
|
|
138
|
+
{ value: 'custom|Sim|Nao', label: 'Boolean custom (Sim/Nao)', example: 'Sim / Nao' },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
'behavior.filtering.advancedFilters.settings.mode': {
|
|
142
|
+
mode: 'enum',
|
|
143
|
+
options: [
|
|
144
|
+
{ value: 'filter', label: 'Filter' },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
'behavior.expansion.state.mode': {
|
|
148
|
+
mode: 'enum',
|
|
149
|
+
options: [
|
|
150
|
+
{ value: 'controlled', label: 'Controlled' },
|
|
151
|
+
{ value: 'uncontrolled', label: 'Uncontrolled' },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
'behavior.expansion.interaction.trigger': {
|
|
155
|
+
mode: 'enum',
|
|
156
|
+
options: [
|
|
157
|
+
{ value: 'icon', label: 'Icon' },
|
|
158
|
+
{ value: 'row', label: 'Row' },
|
|
159
|
+
{ value: 'both', label: 'Both' },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
'behavior.expansion.interaction.keyboard.profile': {
|
|
163
|
+
mode: 'enum',
|
|
164
|
+
options: [
|
|
165
|
+
{ value: 'disclosure', label: 'Disclosure' },
|
|
166
|
+
{ value: 'grid', label: 'Grid' },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
'behavior.expansion.limits.onOverflow': {
|
|
170
|
+
mode: 'enum',
|
|
171
|
+
options: [
|
|
172
|
+
{ value: 'collapseOldest', label: 'Collapse Oldest' },
|
|
173
|
+
{ value: 'denyNew', label: 'Deny New' },
|
|
174
|
+
{ value: 'collapseAll', label: 'Collapse All' },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
'behavior.expansion.collapseOn.sortChange': {
|
|
178
|
+
mode: 'enum',
|
|
179
|
+
options: [
|
|
180
|
+
{ value: 'true', label: 'Enabled' },
|
|
181
|
+
{ value: 'false', label: 'Disabled' },
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
'behavior.expansion.collapseOn.pageChange': {
|
|
185
|
+
mode: 'enum',
|
|
186
|
+
options: [
|
|
187
|
+
{ value: 'true', label: 'Enabled' },
|
|
188
|
+
{ value: 'false', label: 'Disabled' },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
'behavior.expansion.collapseOn.filterChange': {
|
|
192
|
+
mode: 'enum',
|
|
193
|
+
options: [
|
|
194
|
+
{ value: 'true', label: 'Enabled' },
|
|
195
|
+
{ value: 'false', label: 'Disabled' },
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
'behavior.expansion.collapseOn.dataRefresh': {
|
|
199
|
+
mode: 'enum',
|
|
200
|
+
options: [
|
|
201
|
+
{ value: 'true', label: 'Enabled' },
|
|
202
|
+
{ value: 'false', label: 'Disabled' },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
'behavior.expansion.detail.source.mode': {
|
|
206
|
+
mode: 'enum',
|
|
207
|
+
options: [
|
|
208
|
+
{ value: 'inline', label: 'Inline' },
|
|
209
|
+
{ value: 'resource', label: 'Resource' },
|
|
210
|
+
{ value: 'resourcePath', label: 'Resource Path' },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
'behavior.expansion.detail.source.resource.kind': {
|
|
214
|
+
mode: 'enum',
|
|
215
|
+
options: [
|
|
216
|
+
{ value: 'ui-composition', label: 'UI Composition' },
|
|
217
|
+
{ value: 'form-schema', label: 'Form Schema' },
|
|
218
|
+
{ value: 'table-schema', label: 'Table Schema' },
|
|
219
|
+
{ value: 'dashboard-schema', label: 'Dashboard Schema' },
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
'behavior.expansion.detail.source.resourcePath.method': {
|
|
223
|
+
mode: 'enum',
|
|
224
|
+
options: [
|
|
225
|
+
{ value: 'GET', label: 'GET' },
|
|
226
|
+
{ value: 'POST', label: 'POST' },
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
'behavior.expansion.detail.source.resourcePath.paramsMap': {
|
|
230
|
+
mode: 'suggested',
|
|
231
|
+
options: [
|
|
232
|
+
{ value: '{"id":"$row.id"}', label: 'Row ID' },
|
|
233
|
+
{ value: '{"id":"$row.id","table":"$ctx.tableId"}', label: 'Row + Table' },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
'behavior.expansion.detail.source.resourceAllowList[]': {
|
|
237
|
+
mode: 'suggested',
|
|
238
|
+
options: [
|
|
239
|
+
{ value: 'api/orders/*/detail-schema', label: 'Orders detail schema' },
|
|
240
|
+
{ value: 'api/customers/*/detail-schema', label: 'Customers detail schema' },
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
'behavior.expansion.detail.rendering.strategy': {
|
|
244
|
+
mode: 'enum',
|
|
245
|
+
options: [
|
|
246
|
+
{ value: 'registry', label: 'Registry' },
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
'behavior.expansion.detail.rendering.hostLayout': {
|
|
250
|
+
mode: 'enum',
|
|
251
|
+
options: [
|
|
252
|
+
{ value: 'auto', label: 'Auto' },
|
|
253
|
+
{ value: 'stack', label: 'Stack' },
|
|
254
|
+
{ value: 'tabs', label: 'Tabs' },
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
'behavior.expansion.detail.rendering.fallbackNodePolicy': {
|
|
258
|
+
mode: 'enum',
|
|
259
|
+
options: [
|
|
260
|
+
{ value: 'failClosed', label: 'Fail Closed' },
|
|
261
|
+
{ value: 'renderPlaceholder', label: 'Render Placeholder' },
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
'behavior.expansion.detail.height.mode': {
|
|
265
|
+
mode: 'enum',
|
|
266
|
+
options: [
|
|
267
|
+
{ value: 'fixed', label: 'Fixed' },
|
|
268
|
+
{ value: 'dynamic', label: 'Dynamic' },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
'behavior.expansion.virtualization.policy': {
|
|
272
|
+
mode: 'enum',
|
|
273
|
+
options: [
|
|
274
|
+
{ value: 'fixed-height-only', label: 'Fixed Height Only' },
|
|
275
|
+
{ value: 'allow-dynamic-under-flag', label: 'Allow Dynamic Under Flag' },
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
'behavior.expansion.security.eventExposureDefault.rowId': {
|
|
279
|
+
mode: 'enum',
|
|
280
|
+
options: [
|
|
281
|
+
{ value: 'redacted', label: 'Redacted' },
|
|
282
|
+
{ value: 'hashed', label: 'Hashed' },
|
|
283
|
+
{ value: 'raw', label: 'Raw' },
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
'behavior.expansion.security.eventExposureDefault.expandedKeys': {
|
|
287
|
+
mode: 'enum',
|
|
288
|
+
options: [
|
|
289
|
+
{ value: 'none', label: 'None' },
|
|
290
|
+
{ value: 'hashed', label: 'Hashed' },
|
|
291
|
+
{ value: 'raw', label: 'Raw' },
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
'behavior.expansion.deepLink.encoding': {
|
|
295
|
+
mode: 'enum',
|
|
296
|
+
options: [
|
|
297
|
+
{ value: 'csv', label: 'CSV' },
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
'behavior.expansion.deepLink.parsing.duplicateParams': {
|
|
301
|
+
mode: 'enum',
|
|
302
|
+
options: [
|
|
303
|
+
{ value: 'firstWins', label: 'First Wins' },
|
|
304
|
+
{ value: 'reject', label: 'Reject' },
|
|
305
|
+
],
|
|
306
|
+
},
|
|
307
|
+
'behavior.expansion.deepLink.integrity.mode': {
|
|
308
|
+
mode: 'enum',
|
|
309
|
+
options: [
|
|
310
|
+
{ value: 'opaqueToken', label: 'Opaque Token' },
|
|
311
|
+
],
|
|
312
|
+
},
|
|
313
|
+
'behavior.expansion.deepLink.privacy.mode': {
|
|
314
|
+
mode: 'enum',
|
|
315
|
+
options: [
|
|
316
|
+
{ value: 'denyByDefault', label: 'Deny By Default' },
|
|
317
|
+
{ value: 'allowByDefault', label: 'Allow By Default' },
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
'columns[].renderer.type': {
|
|
321
|
+
mode: 'enum',
|
|
322
|
+
options: [
|
|
323
|
+
{ value: 'icon', label: 'Icon' },
|
|
324
|
+
{ value: 'image', label: 'Image' },
|
|
325
|
+
{ value: 'badge', label: 'Badge' },
|
|
326
|
+
{ value: 'link', label: 'Link' },
|
|
327
|
+
{ value: 'button', label: 'Button' },
|
|
328
|
+
{ value: 'chip', label: 'Chip' },
|
|
329
|
+
{ value: 'progress', label: 'Progress' },
|
|
330
|
+
{ value: 'avatar', label: 'Avatar' },
|
|
331
|
+
{ value: 'toggle', label: 'Toggle' },
|
|
332
|
+
{ value: 'menu', label: 'Menu' },
|
|
333
|
+
{ value: 'rating', label: 'Rating' },
|
|
334
|
+
{ value: 'html', label: 'HTML' },
|
|
335
|
+
{ value: 'compose', label: 'Compose' },
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
'columns[].renderer.image.shape': {
|
|
339
|
+
mode: 'enum',
|
|
340
|
+
options: [
|
|
341
|
+
{ value: 'square', label: 'Square' },
|
|
342
|
+
{ value: 'rounded', label: 'Rounded' },
|
|
343
|
+
{ value: 'circle', label: 'Circle' },
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
'columns[].renderer.image.fit': {
|
|
347
|
+
mode: 'enum',
|
|
348
|
+
options: [
|
|
349
|
+
{ value: 'cover', label: 'Cover' },
|
|
350
|
+
{ value: 'contain', label: 'Contain' },
|
|
351
|
+
],
|
|
352
|
+
},
|
|
353
|
+
'columns[].renderer.link.target': {
|
|
354
|
+
mode: 'enum',
|
|
355
|
+
options: [
|
|
356
|
+
{ value: '_blank', label: 'New tab' },
|
|
357
|
+
{ value: '_self', label: 'Same tab' },
|
|
358
|
+
{ value: '_parent', label: 'Parent frame' },
|
|
359
|
+
{ value: '_top', label: 'Top frame' },
|
|
360
|
+
],
|
|
361
|
+
},
|
|
362
|
+
'columns[].renderer.button.variant': {
|
|
363
|
+
mode: 'enum',
|
|
364
|
+
options: [
|
|
365
|
+
{ value: 'filled', label: 'Filled' },
|
|
366
|
+
{ value: 'outlined', label: 'Outlined' },
|
|
367
|
+
{ value: 'text', label: 'Text' },
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
'columns[].renderer.button.color': {
|
|
371
|
+
mode: 'enum',
|
|
372
|
+
options: [
|
|
373
|
+
{ value: 'primary', label: 'Primary' },
|
|
374
|
+
{ value: 'accent', label: 'Accent' },
|
|
375
|
+
{ value: 'warn', label: 'Warn' },
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
'columns[].renderer.button.size': {
|
|
379
|
+
mode: 'enum',
|
|
380
|
+
options: [
|
|
381
|
+
{ value: 'sm', label: 'Small' },
|
|
382
|
+
{ value: 'md', label: 'Medium' },
|
|
383
|
+
{ value: 'lg', label: 'Large' },
|
|
384
|
+
],
|
|
385
|
+
},
|
|
386
|
+
'columns[].renderer.badge.variant': {
|
|
387
|
+
mode: 'enum',
|
|
388
|
+
options: [
|
|
389
|
+
{ value: 'filled', label: 'Filled' },
|
|
390
|
+
{ value: 'outlined', label: 'Outlined' },
|
|
391
|
+
{ value: 'soft', label: 'Soft' },
|
|
392
|
+
],
|
|
393
|
+
},
|
|
394
|
+
'columns[].renderer.chip.variant': {
|
|
395
|
+
mode: 'enum',
|
|
396
|
+
options: [
|
|
397
|
+
{ value: 'filled', label: 'Filled' },
|
|
398
|
+
{ value: 'outlined', label: 'Outlined' },
|
|
399
|
+
{ value: 'soft', label: 'Soft' },
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
'columns[].renderer.rating.size': {
|
|
403
|
+
mode: 'enum',
|
|
404
|
+
options: [
|
|
405
|
+
{ value: 'small', label: 'Small' },
|
|
406
|
+
{ value: 'medium', label: 'Medium' },
|
|
407
|
+
{ value: 'large', label: 'Large' },
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
'columns[].renderer.compose.items[].type': {
|
|
411
|
+
mode: 'enum',
|
|
412
|
+
options: [
|
|
413
|
+
{ value: 'icon', label: 'Icon' },
|
|
414
|
+
{ value: 'image', label: 'Image' },
|
|
415
|
+
{ value: 'badge', label: 'Badge' },
|
|
416
|
+
{ value: 'link', label: 'Link' },
|
|
417
|
+
{ value: 'button', label: 'Button' },
|
|
418
|
+
{ value: 'chip', label: 'Chip' },
|
|
419
|
+
{ value: 'progress', label: 'Progress' },
|
|
420
|
+
{ value: 'avatar', label: 'Avatar' },
|
|
421
|
+
{ value: 'toggle', label: 'Toggle' },
|
|
422
|
+
{ value: 'menu', label: 'Menu' },
|
|
423
|
+
{ value: 'rating', label: 'Rating' },
|
|
424
|
+
{ value: 'html', label: 'HTML' },
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
'columns[].conditionalRenderers[].renderer.type': {
|
|
428
|
+
mode: 'enum',
|
|
429
|
+
options: [
|
|
430
|
+
{ value: 'icon', label: 'Icon' },
|
|
431
|
+
{ value: 'image', label: 'Image' },
|
|
432
|
+
{ value: 'badge', label: 'Badge' },
|
|
433
|
+
{ value: 'link', label: 'Link' },
|
|
434
|
+
{ value: 'button', label: 'Button' },
|
|
435
|
+
{ value: 'chip', label: 'Chip' },
|
|
436
|
+
{ value: 'progress', label: 'Progress' },
|
|
437
|
+
{ value: 'avatar', label: 'Avatar' },
|
|
438
|
+
{ value: 'toggle', label: 'Toggle' },
|
|
439
|
+
{ value: 'menu', label: 'Menu' },
|
|
440
|
+
{ value: 'rating', label: 'Rating' },
|
|
441
|
+
{ value: 'html', label: 'HTML' },
|
|
442
|
+
{ value: 'compose', label: 'Compose' },
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
'columns[].conditionalRenderers[].renderer.image.shape': {
|
|
446
|
+
mode: 'enum',
|
|
447
|
+
options: [
|
|
448
|
+
{ value: 'square', label: 'Square' },
|
|
449
|
+
{ value: 'rounded', label: 'Rounded' },
|
|
450
|
+
{ value: 'circle', label: 'Circle' },
|
|
451
|
+
],
|
|
452
|
+
},
|
|
453
|
+
'columns[].conditionalRenderers[].renderer.image.fit': {
|
|
454
|
+
mode: 'enum',
|
|
455
|
+
options: [
|
|
456
|
+
{ value: 'cover', label: 'Cover' },
|
|
457
|
+
{ value: 'contain', label: 'Contain' },
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
'columns[].conditionalRenderers[].renderer.link.target': {
|
|
461
|
+
mode: 'enum',
|
|
462
|
+
options: [
|
|
463
|
+
{ value: '_blank', label: 'New tab' },
|
|
464
|
+
{ value: '_self', label: 'Same tab' },
|
|
465
|
+
{ value: '_parent', label: 'Parent frame' },
|
|
466
|
+
{ value: '_top', label: 'Top frame' },
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
'columns[].conditionalRenderers[].renderer.button.variant': {
|
|
470
|
+
mode: 'enum',
|
|
471
|
+
options: [
|
|
472
|
+
{ value: 'filled', label: 'Filled' },
|
|
473
|
+
{ value: 'outlined', label: 'Outlined' },
|
|
474
|
+
{ value: 'text', label: 'Text' },
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
'columns[].conditionalRenderers[].renderer.button.color': {
|
|
478
|
+
mode: 'enum',
|
|
479
|
+
options: [
|
|
480
|
+
{ value: 'primary', label: 'Primary' },
|
|
481
|
+
{ value: 'accent', label: 'Accent' },
|
|
482
|
+
{ value: 'warn', label: 'Warn' },
|
|
483
|
+
],
|
|
484
|
+
},
|
|
485
|
+
'columns[].conditionalRenderers[].renderer.button.size': {
|
|
486
|
+
mode: 'enum',
|
|
487
|
+
options: [
|
|
488
|
+
{ value: 'sm', label: 'Small' },
|
|
489
|
+
{ value: 'md', label: 'Medium' },
|
|
490
|
+
{ value: 'lg', label: 'Large' },
|
|
491
|
+
],
|
|
492
|
+
},
|
|
493
|
+
'columns[].conditionalRenderers[].renderer.badge.variant': {
|
|
494
|
+
mode: 'enum',
|
|
495
|
+
options: [
|
|
496
|
+
{ value: 'filled', label: 'Filled' },
|
|
497
|
+
{ value: 'outlined', label: 'Outlined' },
|
|
498
|
+
{ value: 'soft', label: 'Soft' },
|
|
499
|
+
],
|
|
500
|
+
},
|
|
501
|
+
'columns[].conditionalRenderers[].renderer.chip.variant': {
|
|
502
|
+
mode: 'enum',
|
|
503
|
+
options: [
|
|
504
|
+
{ value: 'filled', label: 'Filled' },
|
|
505
|
+
{ value: 'outlined', label: 'Outlined' },
|
|
506
|
+
{ value: 'soft', label: 'Soft' },
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
'columns[].conditionalRenderers[].renderer.rating.size': {
|
|
510
|
+
mode: 'enum',
|
|
511
|
+
options: [
|
|
512
|
+
{ value: 'small', label: 'Small' },
|
|
513
|
+
{ value: 'medium', label: 'Medium' },
|
|
514
|
+
{ value: 'large', label: 'Large' },
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
'columns[].conditionalRenderers[].renderer.compose.items[].type': {
|
|
518
|
+
mode: 'enum',
|
|
519
|
+
options: [
|
|
520
|
+
{ value: 'icon', label: 'Icon' },
|
|
521
|
+
{ value: 'image', label: 'Image' },
|
|
522
|
+
{ value: 'badge', label: 'Badge' },
|
|
523
|
+
{ value: 'link', label: 'Link' },
|
|
524
|
+
{ value: 'button', label: 'Button' },
|
|
525
|
+
{ value: 'chip', label: 'Chip' },
|
|
526
|
+
{ value: 'progress', label: 'Progress' },
|
|
527
|
+
{ value: 'avatar', label: 'Avatar' },
|
|
528
|
+
{ value: 'toggle', label: 'Toggle' },
|
|
529
|
+
{ value: 'menu', label: 'Menu' },
|
|
530
|
+
{ value: 'rating', label: 'Rating' },
|
|
531
|
+
{ value: 'html', label: 'HTML' },
|
|
532
|
+
],
|
|
533
|
+
},
|
|
534
|
+
'actions.row.actions[].color': {
|
|
535
|
+
mode: 'enum',
|
|
536
|
+
options: [
|
|
537
|
+
{ value: 'primary', label: 'Primary' },
|
|
538
|
+
{ value: 'accent', label: 'Accent' },
|
|
539
|
+
{ value: 'warn', label: 'Warn' },
|
|
540
|
+
],
|
|
541
|
+
},
|
|
542
|
+
'actions.row.actions[].icon': {
|
|
543
|
+
mode: 'suggested',
|
|
544
|
+
options: [
|
|
545
|
+
{ value: 'edit', label: 'Edit' },
|
|
546
|
+
{ value: 'delete', label: 'Delete' },
|
|
547
|
+
{ value: 'visibility', label: 'View' },
|
|
548
|
+
{ value: 'more_vert', label: 'More' },
|
|
549
|
+
{ value: 'check', label: 'Approve' },
|
|
550
|
+
{ value: 'close', label: 'Reject' },
|
|
551
|
+
{ value: 'download', label: 'Download' },
|
|
552
|
+
],
|
|
553
|
+
},
|
|
554
|
+
'actions.row.actions[].globalAction.[actionId]': {
|
|
555
|
+
mode: 'suggested',
|
|
556
|
+
options: [
|
|
557
|
+
{ value: 'toast.success', label: 'Toast success' },
|
|
558
|
+
{ value: 'toast.error', label: 'Toast error' },
|
|
559
|
+
{ value: 'dialog.alert', label: 'Dialog alert' },
|
|
560
|
+
{ value: 'dialog.open', label: 'Dialog open' },
|
|
561
|
+
{ value: 'navigation.openExternal', label: 'Open external URL' },
|
|
562
|
+
{ value: 'navigation.openRoute', label: 'Open internal route' },
|
|
563
|
+
{ value: 'api.post', label: 'API POST' },
|
|
564
|
+
{ value: 'api.patch', label: 'API PATCH' },
|
|
565
|
+
],
|
|
566
|
+
},
|
|
567
|
+
'rowConditionalStyles[].condition': {
|
|
568
|
+
mode: 'suggested',
|
|
569
|
+
options: [
|
|
570
|
+
{ value: 'status == "active"', label: 'Status Active' },
|
|
571
|
+
{ value: 'amount < 0', label: 'Negative Amount' },
|
|
572
|
+
{ value: 'priority == "high"', label: 'High Priority' },
|
|
573
|
+
],
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
actionCatalog: [
|
|
577
|
+
// --- Column Management ---
|
|
578
|
+
{
|
|
579
|
+
id: 'ADD_COLUMN',
|
|
580
|
+
intentExamples: ['add column', 'nova coluna', 'criar coluna', 'insert column'],
|
|
581
|
+
scope: 'GLOBAL',
|
|
582
|
+
valueType: 'OBJECT',
|
|
583
|
+
operation: 'create',
|
|
584
|
+
requiresExistingTarget: false,
|
|
585
|
+
params: [
|
|
586
|
+
{ name: 'field', type: 'STRING', description: 'Nome técnico da nova coluna' },
|
|
587
|
+
{ name: 'header', type: 'STRING', description: 'Título da coluna' }
|
|
588
|
+
],
|
|
589
|
+
patchTemplate: {
|
|
590
|
+
columns: [
|
|
591
|
+
{
|
|
592
|
+
field: '{{params.field}}',
|
|
593
|
+
header: '{{params.header}}',
|
|
594
|
+
visible: true
|
|
595
|
+
}
|
|
596
|
+
]
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
id: 'ADD_COLUMN_COMPUTED',
|
|
601
|
+
intentExamples: ['calculated column', 'coluna calculada', 'compute', 'formula', 'expression'],
|
|
602
|
+
scope: 'GLOBAL',
|
|
603
|
+
valueType: 'OBJECT',
|
|
604
|
+
operation: 'create',
|
|
605
|
+
requiresExistingTarget: false,
|
|
606
|
+
params: [
|
|
607
|
+
{ name: 'field', type: 'STRING', description: 'Nome técnico da nova coluna (ex: total)' },
|
|
608
|
+
{ name: 'header', type: 'STRING', description: 'Título da coluna' },
|
|
609
|
+
{ name: 'expression', type: 'STRING', description: 'AST Json Logic canônico serializado em JSON para derived values (ex: { "*": [{ "var": "quantity" }, { "var": "price" }] })' },
|
|
610
|
+
{ name: 'type', type: 'ENUM', options: ['number', 'currency', 'percentage', 'string', 'boolean'], description: 'Tipo de dado resultante' }
|
|
611
|
+
],
|
|
612
|
+
relatedConcepts: ['computed-logic'],
|
|
613
|
+
patchTemplate: {
|
|
614
|
+
columns: [
|
|
615
|
+
{
|
|
616
|
+
field: '{{params.field}}',
|
|
617
|
+
header: '{{params.header}}',
|
|
618
|
+
visible: true,
|
|
619
|
+
computed: {
|
|
620
|
+
expression: '{{params.expression}}',
|
|
621
|
+
outputType: '{{params.type}}'
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
]
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: 'HIDE_COLUMN',
|
|
629
|
+
intentExamples: ['hide column', 'ocultar coluna', 'esconder coluna', 'invisible'],
|
|
630
|
+
scope: 'COLUMN',
|
|
631
|
+
valueType: 'BOOLEAN',
|
|
632
|
+
operation: 'update',
|
|
633
|
+
requiresExistingTarget: true,
|
|
634
|
+
defaultValue: false,
|
|
635
|
+
patchTemplate: {
|
|
636
|
+
columns: [{ field: '{{target}}', visible: false }],
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: 'SHOW_COLUMN',
|
|
641
|
+
intentExamples: ['show column', 'mostrar coluna', 'exibir coluna', 'visible'],
|
|
642
|
+
scope: 'COLUMN',
|
|
643
|
+
valueType: 'BOOLEAN',
|
|
644
|
+
operation: 'update',
|
|
645
|
+
requiresExistingTarget: true,
|
|
646
|
+
defaultValue: true,
|
|
647
|
+
patchTemplate: {
|
|
648
|
+
columns: [{ field: '{{target}}', visible: true }],
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
id: 'RENAME_COLUMN',
|
|
653
|
+
intentExamples: ['rename column', 'renomear coluna', 'titulo', 'cabecalho'],
|
|
654
|
+
scope: 'COLUMN',
|
|
655
|
+
valueType: 'STRING',
|
|
656
|
+
operation: 'update',
|
|
657
|
+
requiresExistingTarget: true,
|
|
658
|
+
patchTemplate: {
|
|
659
|
+
columns: [{ field: '{{target}}', header: '{{value}}' }],
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
id: 'column.format.set',
|
|
664
|
+
intentExamples: ['format', 'formatar', 'date format', 'number format'],
|
|
665
|
+
scope: 'COLUMN',
|
|
666
|
+
valueType: 'ENUM',
|
|
667
|
+
operation: 'update',
|
|
668
|
+
requiresExistingTarget: true,
|
|
669
|
+
patchTemplate: {
|
|
670
|
+
columns: [{ field: '{{target}}', format: '{{value}}' }],
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
id: 'column.type.set',
|
|
675
|
+
intentExamples: ['type', 'tipo', 'data type'],
|
|
676
|
+
scope: 'COLUMN',
|
|
677
|
+
valueType: 'ENUM',
|
|
678
|
+
operation: 'update',
|
|
679
|
+
requiresExistingTarget: true,
|
|
680
|
+
patchTemplate: {
|
|
681
|
+
columns: [{ field: '{{target}}', type: '{{value}}' }],
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
id: 'DISABLE_SORT',
|
|
686
|
+
intentExamples: ['disable sort', 'desativar ordenacao', 'sem ordenacao'],
|
|
687
|
+
scope: 'COLUMN',
|
|
688
|
+
valueType: 'BOOLEAN',
|
|
689
|
+
operation: 'update',
|
|
690
|
+
requiresExistingTarget: true,
|
|
691
|
+
defaultValue: false,
|
|
692
|
+
patchTemplate: {
|
|
693
|
+
columns: [{ field: '{{target}}', sortable: false }],
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
id: 'ENABLE_SORT',
|
|
698
|
+
intentExamples: ['enable sort', 'ativar ordenacao'],
|
|
699
|
+
scope: 'COLUMN',
|
|
700
|
+
valueType: 'BOOLEAN',
|
|
701
|
+
operation: 'update',
|
|
702
|
+
requiresExistingTarget: true,
|
|
703
|
+
defaultValue: true,
|
|
704
|
+
patchTemplate: {
|
|
705
|
+
columns: [{ field: '{{target}}', sortable: true }],
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
id: 'column.width.set',
|
|
710
|
+
intentExamples: ['width', 'largura', 'col width'],
|
|
711
|
+
scope: 'COLUMN',
|
|
712
|
+
valueType: 'STRING',
|
|
713
|
+
operation: 'update',
|
|
714
|
+
requiresExistingTarget: true,
|
|
715
|
+
patchTemplate: {
|
|
716
|
+
columns: [{ field: '{{target}}', width: '{{value}}' }],
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
id: 'column.order.set',
|
|
721
|
+
intentExamples: ['ordem visual', 'mover coluna', 'colocar depois de', 'colocar antes de'],
|
|
722
|
+
scope: 'COLUMN',
|
|
723
|
+
valueType: 'NUMBER',
|
|
724
|
+
operation: 'update',
|
|
725
|
+
requiresExistingTarget: true,
|
|
726
|
+
patchTemplate: {
|
|
727
|
+
columns: [{ field: '{{target}}', order: '{{value}}' }],
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
id: 'column.align.set',
|
|
732
|
+
intentExamples: ['align', 'alinhamento', 'left', 'center', 'right'],
|
|
733
|
+
scope: 'COLUMN',
|
|
734
|
+
valueType: 'ENUM',
|
|
735
|
+
operation: 'update',
|
|
736
|
+
requiresExistingTarget: true,
|
|
737
|
+
patchTemplate: {
|
|
738
|
+
columns: [{ field: '{{target}}', align: '{{value}}' }],
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
id: 'column.sticky.set',
|
|
743
|
+
intentExamples: ['sticky', 'fixar coluna', 'pin column'],
|
|
744
|
+
scope: 'COLUMN',
|
|
745
|
+
valueType: 'ENUM',
|
|
746
|
+
operation: 'update',
|
|
747
|
+
requiresExistingTarget: true,
|
|
748
|
+
patchTemplate: {
|
|
749
|
+
columns: [{ field: '{{target}}', sticky: '{{value}}' }],
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
id: 'column.renderer.type.set',
|
|
754
|
+
intentExamples: ['renderer', 'render', 'cell type'],
|
|
755
|
+
scope: 'COLUMN',
|
|
756
|
+
valueType: 'ENUM',
|
|
757
|
+
operation: 'update',
|
|
758
|
+
requiresExistingTarget: true,
|
|
759
|
+
patchTemplate: {
|
|
760
|
+
columns: [{ field: '{{target}}', renderer: { type: '{{value}}' } }],
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
id: 'column.renderer.image.shape.set',
|
|
765
|
+
intentExamples: ['image shape', 'avatar shape', 'imagem recorte'],
|
|
766
|
+
scope: 'COLUMN',
|
|
767
|
+
valueType: 'ENUM',
|
|
768
|
+
operation: 'update',
|
|
769
|
+
requiresExistingTarget: true,
|
|
770
|
+
patchTemplate: {
|
|
771
|
+
columns: [
|
|
772
|
+
{
|
|
773
|
+
field: '{{target}}',
|
|
774
|
+
renderer: { type: 'image', image: { shape: '{{value}}' } },
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
id: 'column.renderer.link.target.set',
|
|
781
|
+
intentExamples: ['link target', 'abrir em nova aba', 'target blank'],
|
|
782
|
+
scope: 'COLUMN',
|
|
783
|
+
valueType: 'ENUM',
|
|
784
|
+
operation: 'update',
|
|
785
|
+
requiresExistingTarget: true,
|
|
786
|
+
patchTemplate: {
|
|
787
|
+
columns: [
|
|
788
|
+
{
|
|
789
|
+
field: '{{target}}',
|
|
790
|
+
renderer: { type: 'link', link: { target: '{{value}}' } },
|
|
791
|
+
},
|
|
792
|
+
],
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
id: 'column.renderer.button.style.set',
|
|
797
|
+
intentExamples: ['button style', 'botao estilo', 'variant', 'color'],
|
|
798
|
+
scope: 'COLUMN',
|
|
799
|
+
valueType: 'OBJECT', // Uses params
|
|
800
|
+
operation: 'update',
|
|
801
|
+
requiresExistingTarget: true,
|
|
802
|
+
patchTemplate: {
|
|
803
|
+
columns: [
|
|
804
|
+
{
|
|
805
|
+
field: '{{target}}',
|
|
806
|
+
renderer: {
|
|
807
|
+
type: 'button',
|
|
808
|
+
button: {
|
|
809
|
+
variant: '{{params.variant}}',
|
|
810
|
+
color: '{{params.color}}',
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
// --- Rich Renderers (Badges/Icons/Chips) ---
|
|
818
|
+
{
|
|
819
|
+
id: 'SET_RENDERER_BADGE',
|
|
820
|
+
intentExamples: ['render badge', 'exibir como badge', 'status badge', 'etiqueta', 'tag'],
|
|
821
|
+
scope: 'COLUMN',
|
|
822
|
+
valueType: 'OBJECT',
|
|
823
|
+
operation: 'update',
|
|
824
|
+
requiresExistingTarget: true,
|
|
825
|
+
params: [
|
|
826
|
+
{
|
|
827
|
+
name: 'variant',
|
|
828
|
+
type: 'ENUM',
|
|
829
|
+
options: ['filled', 'outlined', 'soft'],
|
|
830
|
+
description: 'Estilo visual do badge'
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
name: 'color',
|
|
834
|
+
type: 'ENUM',
|
|
835
|
+
options: ['primary', 'accent', 'warn', 'success', 'info'],
|
|
836
|
+
description: 'Cor semântica do badge'
|
|
837
|
+
}
|
|
838
|
+
],
|
|
839
|
+
patchTemplate: {
|
|
840
|
+
columns: [
|
|
841
|
+
{
|
|
842
|
+
field: '{{target}}',
|
|
843
|
+
renderer: {
|
|
844
|
+
type: 'badge',
|
|
845
|
+
badge: {
|
|
846
|
+
text: '{{target}}', // Default to field value if text not generic
|
|
847
|
+
textField: '{{target}}', // Bind to field
|
|
848
|
+
color: '{{params.color}}', // primary, accent, warn, success, info
|
|
849
|
+
variant: '{{params.variant}}', // filled, outlined, soft
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
id: 'SET_RENDERER_CHIP',
|
|
858
|
+
intentExamples: ['render chip', 'exibir como chip', 'chip', 'pilula'],
|
|
859
|
+
scope: 'COLUMN',
|
|
860
|
+
valueType: 'OBJECT',
|
|
861
|
+
operation: 'update',
|
|
862
|
+
requiresExistingTarget: true,
|
|
863
|
+
patchTemplate: {
|
|
864
|
+
columns: [
|
|
865
|
+
{
|
|
866
|
+
field: '{{target}}',
|
|
867
|
+
renderer: {
|
|
868
|
+
type: 'chip',
|
|
869
|
+
chip: {
|
|
870
|
+
textField: '{{target}}',
|
|
871
|
+
color: '{{params.color}}',
|
|
872
|
+
variant: '{{params.variant}}',
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
],
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
id: 'SET_RENDERER_ICON',
|
|
881
|
+
intentExamples: ['render icon', 'exibir como icone', 'icone', 'icon'],
|
|
882
|
+
scope: 'COLUMN',
|
|
883
|
+
valueType: 'OBJECT',
|
|
884
|
+
operation: 'update',
|
|
885
|
+
requiresExistingTarget: true,
|
|
886
|
+
patchTemplate: {
|
|
887
|
+
columns: [
|
|
888
|
+
{
|
|
889
|
+
field: '{{target}}',
|
|
890
|
+
renderer: {
|
|
891
|
+
type: 'icon',
|
|
892
|
+
icon: {
|
|
893
|
+
name: '{{value}}', // icon name (e.g. check_circle)
|
|
894
|
+
color: '{{params.color}}',
|
|
895
|
+
size: 18,
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
// --- Compose Renderer Shortcuts ---
|
|
903
|
+
{
|
|
904
|
+
id: 'SET_RENDERER_COMPOSE_TITLE_SUBTITLE',
|
|
905
|
+
intentExamples: ['compose title subtitle', 'titulo e subtitulo', 'two lines', 'duas linhas'],
|
|
906
|
+
scope: 'COLUMN',
|
|
907
|
+
valueType: 'OBJECT',
|
|
908
|
+
operation: 'update',
|
|
909
|
+
requiresExistingTarget: true,
|
|
910
|
+
patchTemplate: {
|
|
911
|
+
columns: [
|
|
912
|
+
{
|
|
913
|
+
field: '{{target}}',
|
|
914
|
+
renderer: {
|
|
915
|
+
type: 'compose',
|
|
916
|
+
compose: {
|
|
917
|
+
layout: { direction: 'column', gap: 4 },
|
|
918
|
+
items: [
|
|
919
|
+
{ type: 'value', style: 'font-weight: 500;' }, // Title (current field)
|
|
920
|
+
{ type: 'value', textField: '{{params.subtitleField}}', style: 'font-size: 0.85em; color: var(--text-secondary);' } // Subtitle
|
|
921
|
+
]
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
]
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
id: 'SET_RENDERER_COMPOSE_AVATAR_TEXT',
|
|
930
|
+
intentExamples: ['compose avatar text', 'avatar e texto', 'user card'],
|
|
931
|
+
scope: 'COLUMN',
|
|
932
|
+
valueType: 'OBJECT',
|
|
933
|
+
operation: 'update',
|
|
934
|
+
requiresExistingTarget: true,
|
|
935
|
+
patchTemplate: {
|
|
936
|
+
columns: [
|
|
937
|
+
{
|
|
938
|
+
field: '{{target}}',
|
|
939
|
+
renderer: {
|
|
940
|
+
type: 'compose',
|
|
941
|
+
compose: {
|
|
942
|
+
layout: { direction: 'row', gap: 12, align: 'center' },
|
|
943
|
+
items: [
|
|
944
|
+
{ type: 'avatar', avatar: { srcField: '{{params.imageField}}', initialsField: '{{target}}', shape: 'circle' } },
|
|
945
|
+
{ type: 'compose', compose: {
|
|
946
|
+
layout: { direction: 'column', gap: 2 },
|
|
947
|
+
items: [
|
|
948
|
+
{ type: 'value', style: 'font-weight: 500;' },
|
|
949
|
+
{ type: 'value', textField: '{{params.subtitleField}}', style: 'font-size: 0.85em; opacity: 0.7;' }
|
|
950
|
+
]
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
]
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
]
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
id: 'ENABLE_PAGINATION',
|
|
962
|
+
intentExamples: ['enable pagination', 'paginar', 'paginação', 'pagination'],
|
|
963
|
+
scope: 'GLOBAL',
|
|
964
|
+
valueType: 'BOOLEAN',
|
|
965
|
+
operation: 'update',
|
|
966
|
+
requiresExistingTarget: false,
|
|
967
|
+
defaultValue: true,
|
|
968
|
+
patchTemplate: {
|
|
969
|
+
behavior: { pagination: { enabled: true, pageSize: '{{params.pageSize}}' } },
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
id: 'DISABLE_PAGINATION',
|
|
974
|
+
intentExamples: ['disable pagination', 'remover paginacao', 'lista infinita', 'no pagination'],
|
|
975
|
+
scope: 'GLOBAL',
|
|
976
|
+
valueType: 'BOOLEAN',
|
|
977
|
+
operation: 'update',
|
|
978
|
+
requiresExistingTarget: false,
|
|
979
|
+
defaultValue: false,
|
|
980
|
+
patchTemplate: {
|
|
981
|
+
behavior: { pagination: { enabled: false } },
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
id: 'ENABLE_FILTERING',
|
|
986
|
+
intentExamples: ['enable filtering', 'ligar filtros', 'filtrar', 'searchable'],
|
|
987
|
+
scope: 'GLOBAL',
|
|
988
|
+
valueType: 'BOOLEAN',
|
|
989
|
+
operation: 'update',
|
|
990
|
+
requiresExistingTarget: false,
|
|
991
|
+
defaultValue: true,
|
|
992
|
+
patchTemplate: {
|
|
993
|
+
behavior: { filtering: { enabled: true } },
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
id: 'DISABLE_FILTERING',
|
|
998
|
+
intentExamples: ['disable filtering', 'desligar filtros', 'no filter'],
|
|
999
|
+
scope: 'GLOBAL',
|
|
1000
|
+
valueType: 'BOOLEAN',
|
|
1001
|
+
operation: 'update',
|
|
1002
|
+
requiresExistingTarget: false,
|
|
1003
|
+
defaultValue: false,
|
|
1004
|
+
patchTemplate: {
|
|
1005
|
+
behavior: { filtering: { enabled: false } },
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
// --- Advanced Filters ---
|
|
1009
|
+
{
|
|
1010
|
+
id: 'ENABLE_ADVANCED_FILTERS',
|
|
1011
|
+
intentExamples: ['advanced filters', 'filtros avancados', 'filtro complexo'],
|
|
1012
|
+
scope: 'GLOBAL',
|
|
1013
|
+
valueType: 'BOOLEAN',
|
|
1014
|
+
operation: 'update',
|
|
1015
|
+
requiresExistingTarget: false,
|
|
1016
|
+
defaultValue: true,
|
|
1017
|
+
patchTemplate: {
|
|
1018
|
+
behavior: { filtering: { advancedFilters: { enabled: true } } },
|
|
1019
|
+
},
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
id: 'SET_FILTER_MODE',
|
|
1023
|
+
intentExamples: ['filter mode', 'modo de filtro', 'modo unico do filtro', 'painel de filtro'],
|
|
1024
|
+
scope: 'GLOBAL',
|
|
1025
|
+
valueType: 'ENUM',
|
|
1026
|
+
operation: 'update',
|
|
1027
|
+
requiresExistingTarget: false,
|
|
1028
|
+
patchTemplate: {
|
|
1029
|
+
behavior: { filtering: { advancedFilters: { settings: { mode: '{{value}}' } } } },
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
id: 'SET_ALWAYS_VISIBLE_FILTERS',
|
|
1034
|
+
intentExamples: ['always visible filter', 'filtro fixo', 'fixar filtro', 'pin filter'],
|
|
1035
|
+
scope: 'GLOBAL', // Global mas referencia coluna
|
|
1036
|
+
valueType: 'STRING', // Array na vdd
|
|
1037
|
+
operation: 'update',
|
|
1038
|
+
requiresExistingTarget: true,
|
|
1039
|
+
patchTemplate: {
|
|
1040
|
+
behavior: { filtering: { advancedFilters: { settings: { alwaysVisibleFields: ['{{target}}'] } } } },
|
|
1041
|
+
},
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
id: 'ENABLE_FILTER_SAVING',
|
|
1045
|
+
intentExamples: ['save filters', 'salvar filtros', 'filter tags'],
|
|
1046
|
+
scope: 'GLOBAL',
|
|
1047
|
+
valueType: 'BOOLEAN',
|
|
1048
|
+
operation: 'update',
|
|
1049
|
+
requiresExistingTarget: false,
|
|
1050
|
+
defaultValue: true,
|
|
1051
|
+
patchTemplate: {
|
|
1052
|
+
behavior: { filtering: { advancedFilters: { settings: { allowSaveTags: true } } } },
|
|
1053
|
+
},
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
id: 'ENABLE_SORTING',
|
|
1057
|
+
intentExamples: ['enable sorting', 'ligar ordenacao', 'ordenar'],
|
|
1058
|
+
scope: 'GLOBAL',
|
|
1059
|
+
valueType: 'BOOLEAN',
|
|
1060
|
+
operation: 'update',
|
|
1061
|
+
requiresExistingTarget: false,
|
|
1062
|
+
defaultValue: true,
|
|
1063
|
+
patchTemplate: {
|
|
1064
|
+
behavior: { sorting: { enabled: true } },
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
id: 'DISABLE_SORTING',
|
|
1069
|
+
intentExamples: ['disable sorting', 'desligar ordenacao', 'no sort'],
|
|
1070
|
+
scope: 'GLOBAL',
|
|
1071
|
+
valueType: 'BOOLEAN',
|
|
1072
|
+
operation: 'update',
|
|
1073
|
+
requiresExistingTarget: false,
|
|
1074
|
+
defaultValue: false,
|
|
1075
|
+
patchTemplate: {
|
|
1076
|
+
behavior: { sorting: { enabled: false } },
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
// --- Selection ---
|
|
1080
|
+
{
|
|
1081
|
+
id: 'ENABLE_SELECTION',
|
|
1082
|
+
intentExamples: ['enable selection', 'ativar selecao', 'selecionavel', 'selectable'],
|
|
1083
|
+
scope: 'GLOBAL',
|
|
1084
|
+
valueType: 'BOOLEAN',
|
|
1085
|
+
operation: 'update',
|
|
1086
|
+
requiresExistingTarget: false,
|
|
1087
|
+
defaultValue: true,
|
|
1088
|
+
patchTemplate: {
|
|
1089
|
+
behavior: { selection: { enabled: true } },
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
id: 'DISABLE_SELECTION',
|
|
1094
|
+
intentExamples: ['disable selection', 'remover selecao', 'not selectable'],
|
|
1095
|
+
scope: 'GLOBAL',
|
|
1096
|
+
valueType: 'BOOLEAN',
|
|
1097
|
+
operation: 'update',
|
|
1098
|
+
requiresExistingTarget: false,
|
|
1099
|
+
defaultValue: false,
|
|
1100
|
+
patchTemplate: {
|
|
1101
|
+
behavior: { selection: { enabled: false } },
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
id: 'SET_SELECTION_SINGLE',
|
|
1106
|
+
intentExamples: ['single selection', 'selecao unica', 'selecionar um'],
|
|
1107
|
+
scope: 'GLOBAL',
|
|
1108
|
+
valueType: 'ENUM',
|
|
1109
|
+
operation: 'update',
|
|
1110
|
+
requiresExistingTarget: false,
|
|
1111
|
+
defaultValue: 'single',
|
|
1112
|
+
patchTemplate: {
|
|
1113
|
+
behavior: { selection: { type: 'single' } },
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
id: 'SET_SELECTION_MULTIPLE',
|
|
1118
|
+
intentExamples: ['multiple selection', 'selecao multipla', 'selecionar varios'],
|
|
1119
|
+
scope: 'GLOBAL',
|
|
1120
|
+
valueType: 'ENUM',
|
|
1121
|
+
operation: 'update',
|
|
1122
|
+
requiresExistingTarget: false,
|
|
1123
|
+
defaultValue: 'multiple',
|
|
1124
|
+
patchTemplate: {
|
|
1125
|
+
behavior: { selection: { type: 'multiple' } },
|
|
1126
|
+
},
|
|
1127
|
+
},
|
|
1128
|
+
// --- Export ---
|
|
1129
|
+
{
|
|
1130
|
+
id: 'ENABLE_EXPORT',
|
|
1131
|
+
intentExamples: ['enable export', 'ativar exportacao', 'botão exportar', 'excel', 'pdf'],
|
|
1132
|
+
scope: 'GLOBAL',
|
|
1133
|
+
valueType: 'BOOLEAN',
|
|
1134
|
+
operation: 'update',
|
|
1135
|
+
requiresExistingTarget: false,
|
|
1136
|
+
defaultValue: true,
|
|
1137
|
+
patchTemplate: {
|
|
1138
|
+
export: { enabled: true },
|
|
1139
|
+
toolbar: { visible: true }, // Export usually lives in toolbar
|
|
1140
|
+
},
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
id: 'DISABLE_EXPORT',
|
|
1144
|
+
intentExamples: ['disable export', 'remover exportacao', 'esconder exportar'],
|
|
1145
|
+
scope: 'GLOBAL',
|
|
1146
|
+
valueType: 'BOOLEAN',
|
|
1147
|
+
operation: 'update',
|
|
1148
|
+
requiresExistingTarget: false,
|
|
1149
|
+
defaultValue: false,
|
|
1150
|
+
patchTemplate: {
|
|
1151
|
+
export: { enabled: false },
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1154
|
+
// --- Appearance ---
|
|
1155
|
+
{
|
|
1156
|
+
id: 'SET_DENSITY',
|
|
1157
|
+
intentExamples: ['set density', 'mudar densidade', 'compacto', 'confortavel', 'espacoso'],
|
|
1158
|
+
scope: 'GLOBAL',
|
|
1159
|
+
valueType: 'ENUM',
|
|
1160
|
+
operation: 'update',
|
|
1161
|
+
requiresExistingTarget: false,
|
|
1162
|
+
patchTemplate: {
|
|
1163
|
+
appearance: { density: '{{value}}' },
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
id: 'SHOW_BORDERS',
|
|
1168
|
+
intentExamples: ['show borders', 'mostrar bordas', 'com bordas', 'grid lines'],
|
|
1169
|
+
scope: 'GLOBAL',
|
|
1170
|
+
valueType: 'BOOLEAN',
|
|
1171
|
+
operation: 'update',
|
|
1172
|
+
requiresExistingTarget: false,
|
|
1173
|
+
defaultValue: true,
|
|
1174
|
+
patchTemplate: {
|
|
1175
|
+
appearance: { borders: { showRowBorders: true, showColumnBorders: true } },
|
|
1176
|
+
},
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
id: 'HIDE_BORDERS',
|
|
1180
|
+
intentExamples: ['hide borders', 'ocultar bordas', 'sem bordas', 'clean look'],
|
|
1181
|
+
scope: 'GLOBAL',
|
|
1182
|
+
valueType: 'BOOLEAN',
|
|
1183
|
+
operation: 'update',
|
|
1184
|
+
requiresExistingTarget: false,
|
|
1185
|
+
defaultValue: false,
|
|
1186
|
+
patchTemplate: {
|
|
1187
|
+
appearance: { borders: { showRowBorders: false, showColumnBorders: false, showOuterBorder: false } },
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
id: 'ENABLE_STRIPED_ROWS',
|
|
1192
|
+
intentExamples: ['striped rows', 'zebrado', 'linhas alternadas', 'zebra'],
|
|
1193
|
+
scope: 'GLOBAL',
|
|
1194
|
+
valueType: 'BOOLEAN',
|
|
1195
|
+
operation: 'update',
|
|
1196
|
+
requiresExistingTarget: false,
|
|
1197
|
+
defaultValue: true,
|
|
1198
|
+
patchTemplate: {
|
|
1199
|
+
appearance: { colors: { alternateRowBackground: '#f7f9fc' } }, // Default grey-ish
|
|
1200
|
+
},
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
id: 'DISABLE_STRIPED_ROWS',
|
|
1204
|
+
intentExamples: ['disable striped', 'remover zebrado', 'fundo solido'],
|
|
1205
|
+
scope: 'GLOBAL',
|
|
1206
|
+
valueType: 'BOOLEAN',
|
|
1207
|
+
operation: 'update',
|
|
1208
|
+
requiresExistingTarget: false,
|
|
1209
|
+
defaultValue: false,
|
|
1210
|
+
patchTemplate: {
|
|
1211
|
+
appearance: { colors: { alternateRowBackground: null } },
|
|
1212
|
+
},
|
|
1213
|
+
},
|
|
1214
|
+
// --- Toolbar ---
|
|
1215
|
+
{
|
|
1216
|
+
id: 'SHOW_TOOLBAR',
|
|
1217
|
+
intentExamples: ['show toolbar', 'mostrar toolbar', 'barra de ferramentas', 'titulo da tabela'],
|
|
1218
|
+
scope: 'GLOBAL',
|
|
1219
|
+
valueType: 'BOOLEAN',
|
|
1220
|
+
operation: 'update',
|
|
1221
|
+
requiresExistingTarget: false,
|
|
1222
|
+
defaultValue: true,
|
|
1223
|
+
patchTemplate: {
|
|
1224
|
+
toolbar: { visible: true },
|
|
1225
|
+
},
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
id: 'HIDE_TOOLBAR',
|
|
1229
|
+
intentExamples: ['hide toolbar', 'esconder toolbar', 'remover titulo'],
|
|
1230
|
+
scope: 'GLOBAL',
|
|
1231
|
+
valueType: 'BOOLEAN',
|
|
1232
|
+
operation: 'update',
|
|
1233
|
+
requiresExistingTarget: false,
|
|
1234
|
+
defaultValue: false,
|
|
1235
|
+
patchTemplate: {
|
|
1236
|
+
toolbar: { visible: false },
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
id: 'SET_TOOLBAR_POSITION',
|
|
1241
|
+
intentExamples: ['toolbar position', 'posicao da toolbar', 'toolbar no topo', 'toolbar embaixo', 'toolbar em cima', 'toolbar ambos'],
|
|
1242
|
+
scope: 'GLOBAL',
|
|
1243
|
+
valueType: 'ENUM',
|
|
1244
|
+
operation: 'update',
|
|
1245
|
+
requiresExistingTarget: false,
|
|
1246
|
+
patchTemplate: {
|
|
1247
|
+
toolbar: { position: '{{value}}' },
|
|
1248
|
+
},
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
id: 'SET_TOOLBAR_ACTIONS_POSITION',
|
|
1252
|
+
intentExamples: ['acoes da toolbar', 'barra de acoes', 'acoes no topo', 'acoes embaixo', 'acoes em cima', 'acoes em ambos'],
|
|
1253
|
+
scope: 'GLOBAL',
|
|
1254
|
+
valueType: 'ENUM',
|
|
1255
|
+
operation: 'update',
|
|
1256
|
+
requiresExistingTarget: false,
|
|
1257
|
+
patchTemplate: {
|
|
1258
|
+
toolbar: { actionsPosition: '{{value}}' },
|
|
1259
|
+
},
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
id: 'SET_TOOLBAR_ACTIONS_BG',
|
|
1263
|
+
intentExamples: ['cor barra de acoes', 'fundo da barra de acoes', 'background acoes', 'acoes com cor'],
|
|
1264
|
+
scope: 'GLOBAL',
|
|
1265
|
+
valueType: 'STRING',
|
|
1266
|
+
operation: 'update',
|
|
1267
|
+
requiresExistingTarget: false,
|
|
1268
|
+
patchTemplate: {
|
|
1269
|
+
toolbar: { actionsBackgroundColor: '{{value}}' },
|
|
1270
|
+
},
|
|
1271
|
+
},
|
|
1272
|
+
// --- Virtualization (Performance) ---
|
|
1273
|
+
{
|
|
1274
|
+
id: 'ENABLE_VIRTUALIZATION',
|
|
1275
|
+
intentExamples: ['enable virtualization', 'virtualizar', 'scroll infinito', 'performance', 'large dataset'],
|
|
1276
|
+
scope: 'GLOBAL',
|
|
1277
|
+
valueType: 'BOOLEAN',
|
|
1278
|
+
operation: 'update',
|
|
1279
|
+
requiresExistingTarget: false,
|
|
1280
|
+
defaultValue: true,
|
|
1281
|
+
patchTemplate: {
|
|
1282
|
+
behavior: { virtualization: { enabled: true, itemHeight: 48 } },
|
|
1283
|
+
performance: { virtualization: { enabled: true, itemHeight: 48 } },
|
|
1284
|
+
},
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
id: 'DISABLE_VIRTUALIZATION',
|
|
1288
|
+
intentExamples: ['disable virtualization', 'desativar virtualizacao'],
|
|
1289
|
+
scope: 'GLOBAL',
|
|
1290
|
+
valueType: 'BOOLEAN',
|
|
1291
|
+
operation: 'update',
|
|
1292
|
+
requiresExistingTarget: false,
|
|
1293
|
+
defaultValue: false,
|
|
1294
|
+
patchTemplate: {
|
|
1295
|
+
behavior: { virtualization: { enabled: false } },
|
|
1296
|
+
performance: { virtualization: { enabled: false } },
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
// --- Row expansion (Schema-Driven detail) ---
|
|
1300
|
+
{
|
|
1301
|
+
id: 'ENABLE_ROW_EXPANSION',
|
|
1302
|
+
intentExamples: ['expand row', 'expandir linha', 'detalhe expansivel', 'row details', 'master detail'],
|
|
1303
|
+
scope: 'GLOBAL',
|
|
1304
|
+
valueType: 'BOOLEAN',
|
|
1305
|
+
operation: 'update',
|
|
1306
|
+
requiresExistingTarget: false,
|
|
1307
|
+
defaultValue: true,
|
|
1308
|
+
patchTemplate: {
|
|
1309
|
+
behavior: {
|
|
1310
|
+
expansion: {
|
|
1311
|
+
enabled: true,
|
|
1312
|
+
contractVersion: '1.0.0',
|
|
1313
|
+
interaction: { trigger: 'icon', keyboard: { profile: 'disclosure' } },
|
|
1314
|
+
limits: { allowMultiple: false, maxExpandedRows: 1, onOverflow: 'collapseOldest' },
|
|
1315
|
+
detail: {
|
|
1316
|
+
schemaContract: { kind: 'praxis.detail.schema', version: '1.0.0' },
|
|
1317
|
+
source: { mode: 'inline', inlineSchema: { layout: 'stack', items: [] } },
|
|
1318
|
+
rendering: { strategy: 'registry', registryId: 'praxis.detail.default' }
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
id: 'SET_EXPANSION_SOURCE_MODE',
|
|
1326
|
+
intentExamples: ['expansion source', 'detail source', 'schema source', 'resource detail', 'resourcePath detail'],
|
|
1327
|
+
scope: 'GLOBAL',
|
|
1328
|
+
valueType: 'ENUM',
|
|
1329
|
+
operation: 'update',
|
|
1330
|
+
requiresExistingTarget: false,
|
|
1331
|
+
patchTemplate: {
|
|
1332
|
+
behavior: { expansion: { detail: { source: { mode: '{{value}}' } } } }
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
id: 'SET_EXPANSION_RESOURCE',
|
|
1337
|
+
intentExamples: ['detail resource id', 'resource detail id', 'ui composition detail', 'schema detail resource'],
|
|
1338
|
+
scope: 'GLOBAL',
|
|
1339
|
+
valueType: 'OBJECT',
|
|
1340
|
+
operation: 'update',
|
|
1341
|
+
requiresExistingTarget: false,
|
|
1342
|
+
patchTemplate: {
|
|
1343
|
+
behavior: {
|
|
1344
|
+
expansion: {
|
|
1345
|
+
detail: {
|
|
1346
|
+
source: {
|
|
1347
|
+
mode: 'resource',
|
|
1348
|
+
resource: {
|
|
1349
|
+
kind: '{{params.kind}}',
|
|
1350
|
+
id: '{{params.id}}',
|
|
1351
|
+
version: '{{params.version}}'
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
},
|
|
1359
|
+
// --- Fine-grained Styles (Columns/Headers) ---
|
|
1360
|
+
{
|
|
1361
|
+
id: 'SET_COLUMN_BOLD',
|
|
1362
|
+
intentExamples: ['column bold', 'coluna negrito', 'destacar coluna', 'bold text'],
|
|
1363
|
+
scope: 'COLUMN',
|
|
1364
|
+
valueType: 'STRING',
|
|
1365
|
+
operation: 'update',
|
|
1366
|
+
requiresExistingTarget: true,
|
|
1367
|
+
patchTemplate: {
|
|
1368
|
+
columns: [{ field: '{{target}}', style: 'font-weight: 600;' }],
|
|
1369
|
+
},
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
id: 'SET_COLUMN_COLOR',
|
|
1373
|
+
intentExamples: ['column color', 'cor da coluna', 'pintar coluna', 'text color'],
|
|
1374
|
+
scope: 'COLUMN',
|
|
1375
|
+
valueType: 'STRING',
|
|
1376
|
+
operation: 'update',
|
|
1377
|
+
requiresExistingTarget: true,
|
|
1378
|
+
patchTemplate: {
|
|
1379
|
+
columns: [{ field: '{{target}}', style: 'color: {{value}};' }],
|
|
1380
|
+
},
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
id: 'SET_HEADER_UPPERCASE',
|
|
1384
|
+
intentExamples: ['header uppercase', 'cabecalho maiuscula', 'titulo caixa alta'],
|
|
1385
|
+
scope: 'COLUMN',
|
|
1386
|
+
valueType: 'STRING',
|
|
1387
|
+
operation: 'update',
|
|
1388
|
+
requiresExistingTarget: true,
|
|
1389
|
+
patchTemplate: {
|
|
1390
|
+
columns: [{ field: '{{target}}', headerStyle: 'text-transform: uppercase;' }],
|
|
1391
|
+
},
|
|
1392
|
+
},
|
|
1393
|
+
{
|
|
1394
|
+
id: 'SET_HEADER_BOLD',
|
|
1395
|
+
intentExamples: ['header bold', 'cabecalho negrito', 'titulo forte'],
|
|
1396
|
+
scope: 'COLUMN',
|
|
1397
|
+
valueType: 'STRING',
|
|
1398
|
+
operation: 'update',
|
|
1399
|
+
requiresExistingTarget: true,
|
|
1400
|
+
patchTemplate: {
|
|
1401
|
+
columns: [{ field: '{{target}}', headerStyle: 'font-weight: 600;' }],
|
|
1402
|
+
},
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
id: 'SET_HEADER_BG_COLOR',
|
|
1406
|
+
intentExamples: ['header background', 'fundo cabecalho', 'cor titulo'],
|
|
1407
|
+
scope: 'COLUMN',
|
|
1408
|
+
valueType: 'STRING',
|
|
1409
|
+
operation: 'update',
|
|
1410
|
+
requiresExistingTarget: true,
|
|
1411
|
+
patchTemplate: {
|
|
1412
|
+
columns: [{ field: '{{target}}', headerStyle: 'background-color: {{value}};' }],
|
|
1413
|
+
},
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
id: 'SET_HEADER_TEXT_COLOR',
|
|
1417
|
+
intentExamples: ['header text color', 'cor texto cabecalho'],
|
|
1418
|
+
scope: 'COLUMN',
|
|
1419
|
+
valueType: 'STRING',
|
|
1420
|
+
operation: 'update',
|
|
1421
|
+
requiresExistingTarget: true,
|
|
1422
|
+
patchTemplate: {
|
|
1423
|
+
columns: [{ field: '{{target}}', headerStyle: 'color: {{value}};' }],
|
|
1424
|
+
},
|
|
1425
|
+
},
|
|
1426
|
+
// --- Toolbar Layout ---
|
|
1427
|
+
{
|
|
1428
|
+
id: 'SET_TOOLBAR_ALIGNMENT',
|
|
1429
|
+
intentExamples: ['toolbar align', 'alinhar toolbar', 'alinhamento barra', 'space-between', 'start', 'end'],
|
|
1430
|
+
scope: 'GLOBAL',
|
|
1431
|
+
valueType: 'ENUM',
|
|
1432
|
+
operation: 'update',
|
|
1433
|
+
requiresExistingTarget: false,
|
|
1434
|
+
patchTemplate: {
|
|
1435
|
+
toolbar: { layout: { alignment: '{{value}}' } },
|
|
1436
|
+
},
|
|
1437
|
+
},
|
|
1438
|
+
// --- Grouping ---
|
|
1439
|
+
{
|
|
1440
|
+
id: 'ENABLE_GROUPING',
|
|
1441
|
+
intentExamples: ['group by', 'agrupar por', 'agrupamento'],
|
|
1442
|
+
scope: 'GLOBAL', // Global action, but target is field name
|
|
1443
|
+
valueType: 'STRING',
|
|
1444
|
+
operation: 'update',
|
|
1445
|
+
requiresExistingTarget: true,
|
|
1446
|
+
patchTemplate: {
|
|
1447
|
+
behavior: {
|
|
1448
|
+
grouping: {
|
|
1449
|
+
enabled: true,
|
|
1450
|
+
fields: ['{{target}}'],
|
|
1451
|
+
expanded: true,
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
id: 'CLEAR_GROUPING',
|
|
1458
|
+
intentExamples: ['ungroup', 'desagrupar', 'remover agrupamento'],
|
|
1459
|
+
scope: 'GLOBAL',
|
|
1460
|
+
valueType: 'BOOLEAN',
|
|
1461
|
+
operation: 'update',
|
|
1462
|
+
requiresExistingTarget: false,
|
|
1463
|
+
defaultValue: true,
|
|
1464
|
+
patchTemplate: {
|
|
1465
|
+
behavior: { grouping: { enabled: false, fields: [] } },
|
|
1466
|
+
},
|
|
1467
|
+
},
|
|
1468
|
+
// --- Row Actions ---
|
|
1469
|
+
{
|
|
1470
|
+
id: 'ENABLE_ROW_ACTIONS',
|
|
1471
|
+
intentExamples: ['enable row actions', 'acoes de linha', 'botoes na linha'],
|
|
1472
|
+
scope: 'GLOBAL',
|
|
1473
|
+
valueType: 'BOOLEAN',
|
|
1474
|
+
operation: 'update',
|
|
1475
|
+
requiresExistingTarget: false,
|
|
1476
|
+
defaultValue: true,
|
|
1477
|
+
patchTemplate: {
|
|
1478
|
+
actions: { row: { enabled: true, sticky: 'end' } }
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
id: 'ADD_ROW_ACTION_ICON',
|
|
1483
|
+
intentExamples: ['add row action', 'botao de acao', 'adicionar botao'],
|
|
1484
|
+
scope: 'GLOBAL', // Adds to global row actions list
|
|
1485
|
+
valueType: 'OBJECT',
|
|
1486
|
+
operation: 'create',
|
|
1487
|
+
requiresExistingTarget: false,
|
|
1488
|
+
patchTemplate: {
|
|
1489
|
+
actions: {
|
|
1490
|
+
row: {
|
|
1491
|
+
actions: [
|
|
1492
|
+
{
|
|
1493
|
+
id: '{{id}}',
|
|
1494
|
+
icon: '{{icon}}',
|
|
1495
|
+
label: '{{label}}',
|
|
1496
|
+
color: '{{params.color}}',
|
|
1497
|
+
overflow: false
|
|
1498
|
+
}
|
|
1499
|
+
]
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
id: 'ADD_ROW_ACTION_MENU',
|
|
1506
|
+
intentExamples: ['add row menu', 'acao no menu', 'item de menu'],
|
|
1507
|
+
scope: 'GLOBAL',
|
|
1508
|
+
valueType: 'OBJECT',
|
|
1509
|
+
operation: 'create',
|
|
1510
|
+
requiresExistingTarget: false,
|
|
1511
|
+
patchTemplate: {
|
|
1512
|
+
actions: {
|
|
1513
|
+
row: {
|
|
1514
|
+
actions: [
|
|
1515
|
+
{
|
|
1516
|
+
id: '{{id}}',
|
|
1517
|
+
icon: '{{icon}}',
|
|
1518
|
+
label: '{{label}}',
|
|
1519
|
+
overflow: true
|
|
1520
|
+
}
|
|
1521
|
+
]
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
},
|
|
1526
|
+
// --- Conditional Row Styles ---
|
|
1527
|
+
{
|
|
1528
|
+
id: 'ADD_ROW_STYLE_BG',
|
|
1529
|
+
intentExamples: ['row background condition', 'pintar linha se', 'cor de fundo condicional'],
|
|
1530
|
+
scope: 'GLOBAL',
|
|
1531
|
+
valueType: 'OBJECT',
|
|
1532
|
+
operation: 'create',
|
|
1533
|
+
requiresExistingTarget: false,
|
|
1534
|
+
patchTemplate: {
|
|
1535
|
+
rowConditionalStyles: [
|
|
1536
|
+
{
|
|
1537
|
+
condition: '{{condition}}',
|
|
1538
|
+
style: { 'background-color': '{{value}}' }
|
|
1539
|
+
}
|
|
1540
|
+
]
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
id: 'ADD_ROW_STYLE_COLOR',
|
|
1545
|
+
intentExamples: ['row text color condition', 'cor texto linha se'],
|
|
1546
|
+
scope: 'GLOBAL',
|
|
1547
|
+
valueType: 'OBJECT',
|
|
1548
|
+
operation: 'create',
|
|
1549
|
+
requiresExistingTarget: false,
|
|
1550
|
+
patchTemplate: {
|
|
1551
|
+
rowConditionalStyles: [
|
|
1552
|
+
{
|
|
1553
|
+
condition: '{{condition}}',
|
|
1554
|
+
style: { 'color': '{{value}}' }
|
|
1555
|
+
}
|
|
1556
|
+
]
|
|
1557
|
+
}
|
|
1558
|
+
},
|
|
1559
|
+
// --- Conditional Renderers (Column) ---
|
|
1560
|
+
{
|
|
1561
|
+
id: 'ADD_CONDITIONAL_RENDERER',
|
|
1562
|
+
intentExamples: [
|
|
1563
|
+
'conditional renderer',
|
|
1564
|
+
'renderer condicional',
|
|
1565
|
+
'badge condicional',
|
|
1566
|
+
'chip condicional',
|
|
1567
|
+
'badge se',
|
|
1568
|
+
'chip se',
|
|
1569
|
+
'conditional badge',
|
|
1570
|
+
'conditional chip'
|
|
1571
|
+
],
|
|
1572
|
+
scope: 'COLUMN',
|
|
1573
|
+
valueType: 'OBJECT',
|
|
1574
|
+
operation: 'create',
|
|
1575
|
+
requiresExistingTarget: true,
|
|
1576
|
+
params: [
|
|
1577
|
+
{
|
|
1578
|
+
name: 'condition',
|
|
1579
|
+
type: 'STRING',
|
|
1580
|
+
description: 'Expressao condicional (ex: recompensa > 500000)'
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
name: 'renderer',
|
|
1584
|
+
type: 'STRING',
|
|
1585
|
+
description: 'Renderer completo com type e config (ex: { type: "badge", badge: { textField: "recompensa", color: "#ff69b4", variant: "filled" } })'
|
|
1586
|
+
}
|
|
1587
|
+
],
|
|
1588
|
+
patchTemplate: {
|
|
1589
|
+
columns: [
|
|
1590
|
+
{
|
|
1591
|
+
field: '{{target}}',
|
|
1592
|
+
conditionalRenderers: [
|
|
1593
|
+
{
|
|
1594
|
+
condition: '{{params.condition}}',
|
|
1595
|
+
renderer: '{{params.renderer}}'
|
|
1596
|
+
}
|
|
1597
|
+
]
|
|
1598
|
+
}
|
|
1599
|
+
]
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
id: 'ADD_CONDITIONAL_ICON',
|
|
1604
|
+
intentExamples: ['conditional icon', 'icone condicional', 'icone se'],
|
|
1605
|
+
scope: 'COLUMN',
|
|
1606
|
+
valueType: 'OBJECT',
|
|
1607
|
+
operation: 'create',
|
|
1608
|
+
requiresExistingTarget: true,
|
|
1609
|
+
patchTemplate: {
|
|
1610
|
+
columns: [
|
|
1611
|
+
{
|
|
1612
|
+
field: '{{target}}',
|
|
1613
|
+
conditionalRenderers: [
|
|
1614
|
+
{
|
|
1615
|
+
condition: '{{condition}}',
|
|
1616
|
+
renderer: {
|
|
1617
|
+
type: 'icon',
|
|
1618
|
+
icon: { name: '{{value}}', color: '{{params.color}}' }
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
]
|
|
1622
|
+
}
|
|
1623
|
+
]
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
],
|
|
1627
|
+
fieldResolvers: {
|
|
1628
|
+
'columns[].field': ['field', 'header'],
|
|
1629
|
+
'actions.row.actions[].id': ['id', 'label'],
|
|
1630
|
+
'rowConditionalStyles[].condition': ['condition'],
|
|
1631
|
+
},
|
|
1632
|
+
hints: [
|
|
1633
|
+
'Use semantic patches with columns[] and field, not JSON Patch or column indexes.',
|
|
1634
|
+
'Set columns[].type before choosing columns[].format presets to avoid mismatched formatting.',
|
|
1635
|
+
'Renderer config overrides format/valueMapping; avoid mixing unless required.',
|
|
1636
|
+
'Use ADD_CONDITIONAL_RENDERER with renderer.type + renderer.<type> config for conditional badges/chips.',
|
|
1637
|
+
'When the table already shows a status/boolean column as valueMapping or chip/badge and the user refines colors, variants, icons, animation or tooltip, preserve the target column and create conditionalRenderers per semantic value instead of repeating the same valueMapping.',
|
|
1638
|
+
'For boolean chips/badges, model true/false branches explicitly: condition {"==":[{"var":"<field>"},true]} for the positive label and {"==":[{"var":"<field>"},false]} for the negative label; use renderer.type "chip" or "badge" with text, color and variant.',
|
|
1639
|
+
'DOC: Widget purpose: data table with sorting, filtering, pagination, and actions.',
|
|
1640
|
+
'DOC: Host inputs: tableId (required), resourcePath (optional), config (optional).',
|
|
1641
|
+
'DOC: config defaults to createDefaultTableConfig(); columns may start empty.',
|
|
1642
|
+
'DOC: behavior.expansion.detail é schema-driven; altere source.mode (inline/resource/resourcePath), não use detail.type.',
|
|
1643
|
+
'DOC: Para detail.source.mode=resource, sempre informar resource.kind + resource.id + resource.version.',
|
|
1644
|
+
'DOC: Para detail.source.mode=resourcePath, sempre informar resourcePath.paramsMap + resourceAllowList[]; URL absoluta/scheme (http:, https:, data:, javascript:) é bloqueada e o runtime aplica fail-closed.',
|
|
1645
|
+
'DOC: collapseOn ativo no runtime P0A: sortChange, pageChange, filterChange e dataRefresh.',
|
|
1646
|
+
'DOC: When resourcePath is available, the table can load schema and data; ask the user to pick an API resource when missing.',
|
|
1647
|
+
'DOC: resourcePath must be the base resource path only (no /filter, /all, /{id}). CRUD endpoints are handled internally.',
|
|
1648
|
+
'DOC: columns[].computed.expression agora usa AST Json Logic canônico para derived values; não use fórmulas string.',
|
|
1649
|
+
'DOC: Operadores/funções canônicos úteis: +, -, *, /, %, min, max, cat, substr, round, ceil, floor, abs, coalesce, now, date, yearsSince, monthsSince, daysSince, len, toNumber e stringify.',
|
|
1650
|
+
'DOC: Examples: {"yearsSince":[{"var":"dataNascimento"}]} → idade; {"yearsSince":[{"var":"dataAdmissao"}]} → tempoEmpresa (anos); {"monthsSince":[{"var":"dataAdmissao"}]} → tempoEmpresa (meses); {"daysSince":[{"var":"dataCadastro"}]} → dias desde cadastro; {"+":[{"coalesce":[{"var":"bonus"},0]},{"var":"salario"}]} → total com fallback.',
|
|
1651
|
+
'### DATA ARCHETYPE TO VISUAL STRATEGY',
|
|
1652
|
+
'Apply these patterns based on the SEMANTIC NATURE of the column data:',
|
|
1653
|
+
'- STATUS/STATE (e.g., phase, stage, is_active, priority):',
|
|
1654
|
+
' -> Use "badge" or "chip" with conditional colors (success/warn/error/info).',
|
|
1655
|
+
' -> Use "valueMapping" to convert codes (1, "A") to labels ("Active", "Approved").',
|
|
1656
|
+
'- METRICS/KPI (e.g., progress, score, rating, completion):',
|
|
1657
|
+
' -> Use "progress" (bars), "rating" (stars), or "percentage" type.',
|
|
1658
|
+
' -> Align "right".',
|
|
1659
|
+
'- FINANCIAL (e.g., price, cost, balance, total):',
|
|
1660
|
+
' -> Use type "currency" and format "BRL|symbol|2" (or per locale).',
|
|
1661
|
+
' -> Align "right". Style: "font-variant-numeric: tabular-nums".',
|
|
1662
|
+
'- ENTITIES (e.g., user, product, thumb):',
|
|
1663
|
+
' -> Use "avatar" (for people/users) or "image" (for products/files).',
|
|
1664
|
+
' -> Combine with "link" or "compose" for title+subtitle layouts.',
|
|
1665
|
+
'- INTERACTIVE/NAV (e.g., details, website, download):',
|
|
1666
|
+
' -> Use "button" (actions), "link" (navigation) or "icon" (simple triggers).',
|
|
1667
|
+
'- BOOLEAN (e.g., has_tax, is_valid):',
|
|
1668
|
+
' -> Use "toggle" (if editable) or "icon" (check/close) or "badge" (Yes/No).',
|
|
1669
|
+
'- IDENTIFIERS (e.g., uuid, sku, order_id, cpf):',
|
|
1670
|
+
' -> Style: "font-family: monospace".',
|
|
1671
|
+
' -> CRITICAL: Do NOT use numeric formatting (like "1.0-0") for IDs. Treat them as strings.',
|
|
1672
|
+
' -> Consider adding a "copy" context action.',
|
|
1673
|
+
'- GROUPING (e.g., category, department):',
|
|
1674
|
+
' -> If the user asks to "group by X", use the ENABLE_GROUPING action with target=X.',
|
|
1675
|
+
'- TEMPORAL (e.g., created_at, deadline):',
|
|
1676
|
+
' -> Use type "date". Prefer "short" or "shortDate" for compact views.',
|
|
1677
|
+
' -> Use standard format (dd/MM/yyyy) for strict records.',
|
|
1678
|
+
],
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
const COMPONENT_EDIT_PLAN_OPERATION_IDS = new Set(TABLE_COMPONENT_EDIT_PLAN_OPERATION_IDS);
|
|
1682
|
+
const TABLE_AUTHORING_REGISTRY = buildTableAuthoringRegistry();
|
|
1683
|
+
const TABLE_AUTHORING_REGISTRY_COVERAGE = summarizeTableAuthoringRegistry(TABLE_AUTHORING_REGISTRY);
|
|
1684
|
+
function buildTableAuthoringRegistry() {
|
|
1685
|
+
return PRAXIS_TABLE_AUTHORING_MANIFEST.operations.map(operation => {
|
|
1686
|
+
const affectedPaths = uniqueStrings(operation.affectedPaths);
|
|
1687
|
+
const effectPaths = uniqueStrings(operation.effects.map(effect => effect.path).filter(isNonEmptyString));
|
|
1688
|
+
const coveragePaths = uniqueStrings([...affectedPaths, ...effectPaths]);
|
|
1689
|
+
return {
|
|
1690
|
+
operationId: operation.operationId,
|
|
1691
|
+
title: operation.title,
|
|
1692
|
+
scope: operation.scope,
|
|
1693
|
+
targetKind: operation.target?.kind ?? operation.targetKind ?? operation.scope,
|
|
1694
|
+
requiresTarget: operation.target?.required ?? operation.scope !== 'global',
|
|
1695
|
+
affectedPaths,
|
|
1696
|
+
effectPaths,
|
|
1697
|
+
capabilityPaths: capabilityPathsForOperation(coveragePaths),
|
|
1698
|
+
optionPaths: optionPathsForOperation(coveragePaths),
|
|
1699
|
+
actionCatalogIds: actionCatalogIdsForOperation(operation.operationId),
|
|
1700
|
+
exampleIds: exampleIdsForOperation(operation.operationId),
|
|
1701
|
+
validatorIds: uniqueStrings(operation.validators ?? []),
|
|
1702
|
+
preconditions: uniqueStrings(operation.preconditions ?? []),
|
|
1703
|
+
requiresConfirmation: operation.requiresConfirmation === true,
|
|
1704
|
+
destructive: operation.destructive === true,
|
|
1705
|
+
submissionImpact: operation.submissionImpact,
|
|
1706
|
+
materializationMode: COMPONENT_EDIT_PLAN_OPERATION_IDS.has(operation.operationId)
|
|
1707
|
+
? 'component-edit-plan-compiler'
|
|
1708
|
+
: 'authoring-manifest-effects',
|
|
1709
|
+
};
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
function summarizeTableAuthoringRegistry(entries = TABLE_AUTHORING_REGISTRY) {
|
|
1713
|
+
return {
|
|
1714
|
+
operationCount: entries.length,
|
|
1715
|
+
capabilityPathCount: TABLE_AI_CAPABILITIES.capabilities.length,
|
|
1716
|
+
contextOptionPathCount: Object.keys(TABLE_COMPONENT_CONTEXT_PACK.optionsByPath).length,
|
|
1717
|
+
componentEditPlanOperationCount: TABLE_COMPONENT_EDIT_PLAN_OPERATION_IDS.length,
|
|
1718
|
+
operationIdsWithoutExamples: entries
|
|
1719
|
+
.filter(entry => entry.exampleIds.length === 0)
|
|
1720
|
+
.map(entry => entry.operationId),
|
|
1721
|
+
operationIdsWithoutContextOptions: entries
|
|
1722
|
+
.filter(entry => entry.optionPaths.length === 0)
|
|
1723
|
+
.map(entry => entry.operationId),
|
|
1724
|
+
operationIdsWithoutComponentEditPlanCompiler: entries
|
|
1725
|
+
.filter(entry => entry.materializationMode !== 'component-edit-plan-compiler')
|
|
1726
|
+
.map(entry => entry.operationId),
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
function capabilityPathsForOperation(coveragePaths) {
|
|
1730
|
+
return uniqueStrings(TABLE_AI_CAPABILITIES.capabilities
|
|
1731
|
+
.map(capability => capability.path)
|
|
1732
|
+
.filter(capabilityPath => coveragePaths.some(path => pathsOverlap(path, capabilityPath))));
|
|
1733
|
+
}
|
|
1734
|
+
function optionPathsForOperation(coveragePaths) {
|
|
1735
|
+
return uniqueStrings(Object.keys(TABLE_COMPONENT_CONTEXT_PACK.optionsByPath)
|
|
1736
|
+
.filter(optionPath => coveragePaths.some(path => pathsOverlap(path, optionPath))));
|
|
1737
|
+
}
|
|
1738
|
+
function actionCatalogIdsForOperation(operationId) {
|
|
1739
|
+
return uniqueStrings(TABLE_COMPONENT_CONTEXT_PACK.actionCatalog
|
|
1740
|
+
.filter(action => action.id === operationId)
|
|
1741
|
+
.map(action => action.id));
|
|
1742
|
+
}
|
|
1743
|
+
function exampleIdsForOperation(operationId) {
|
|
1744
|
+
return uniqueStrings(PRAXIS_TABLE_AUTHORING_MANIFEST.examples
|
|
1745
|
+
.filter(example => example.operationId === operationId)
|
|
1746
|
+
.map(example => example.id));
|
|
1747
|
+
}
|
|
1748
|
+
function pathsOverlap(left, right) {
|
|
1749
|
+
const normalizedLeft = normalizePath(left);
|
|
1750
|
+
const normalizedRight = normalizePath(right);
|
|
1751
|
+
return normalizedLeft === normalizedRight
|
|
1752
|
+
|| normalizedLeft.startsWith(`${normalizedRight}.`)
|
|
1753
|
+
|| normalizedRight.startsWith(`${normalizedLeft}.`);
|
|
1754
|
+
}
|
|
1755
|
+
function normalizePath(path) {
|
|
1756
|
+
return path.replace(/\[\]/g, '').replace(/\.+/g, '.').replace(/^\./, '').replace(/\.$/, '');
|
|
1757
|
+
}
|
|
1758
|
+
function uniqueStrings(values) {
|
|
1759
|
+
return Array.from(new Set(values.filter(isNonEmptyString)));
|
|
1760
|
+
}
|
|
1761
|
+
function isNonEmptyString(value) {
|
|
1762
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
/**
|
|
1766
|
+
* Analisa uma amostra de dados da tabela para gerar estatísticas
|
|
1767
|
+
* que ajudem a IA a sugerir renderers e formatos adequados.
|
|
1768
|
+
*/
|
|
1769
|
+
class TableDataProfiler {
|
|
1770
|
+
static SAMPLE_LIMIT = 50; // Mantem custo baixo sem perder categorias comuns da pagina.
|
|
1771
|
+
static VALUE_TRUNCATE = 50; // Trunca strings longas no contexto
|
|
1772
|
+
static profile(data, config, options) {
|
|
1773
|
+
const sampleLimit = options?.sampleLimit ?? this.SAMPLE_LIMIT;
|
|
1774
|
+
const rowCount = options?.rowCountOverride ?? data.length;
|
|
1775
|
+
const profile = {
|
|
1776
|
+
rowCount,
|
|
1777
|
+
columns: {},
|
|
1778
|
+
generatedAt: Date.now()
|
|
1779
|
+
};
|
|
1780
|
+
if (!data || data.length === 0 || !config.columns) {
|
|
1781
|
+
return profile;
|
|
1782
|
+
}
|
|
1783
|
+
// 1. Selecionar amostra representativa. Para tabelas maiores, distribui a
|
|
1784
|
+
// amostra pelo conjunto inteiro em vez de olhar apenas as primeiras linhas.
|
|
1785
|
+
const sample = this.sampleRows(data, sampleLimit);
|
|
1786
|
+
// 2. Iterar colunas configuradas
|
|
1787
|
+
for (const col of config.columns) {
|
|
1788
|
+
if (!col || !col.field)
|
|
1789
|
+
continue;
|
|
1790
|
+
const values = sample.map(row => this.getValue(row, col.field)).filter(v => v !== null && v !== undefined && v !== '');
|
|
1791
|
+
const stats = {
|
|
1792
|
+
field: col.field,
|
|
1793
|
+
header: col.header,
|
|
1794
|
+
inferredType: 'empty',
|
|
1795
|
+
cardinality: 0,
|
|
1796
|
+
hasNulls: values.length < sample.length,
|
|
1797
|
+
topValues: []
|
|
1798
|
+
};
|
|
1799
|
+
if (values.length > 0) {
|
|
1800
|
+
stats.inferredType = this.inferType(values);
|
|
1801
|
+
stats.cardinality = new Set(values.map(v => String(v))).size;
|
|
1802
|
+
// Coletar Top Values (frequência)
|
|
1803
|
+
const freqMap = new Map();
|
|
1804
|
+
values.forEach(v => {
|
|
1805
|
+
const k = String(v);
|
|
1806
|
+
freqMap.set(k, (freqMap.get(k) || 0) + 1);
|
|
1807
|
+
});
|
|
1808
|
+
stats.topValues = Array.from(freqMap.entries())
|
|
1809
|
+
.sort((a, b) => b[1] - a[1]) // Sort by count desc
|
|
1810
|
+
.slice(0, 5)
|
|
1811
|
+
.map(([val]) => this.sanitizeValue(val, stats.inferredType));
|
|
1812
|
+
// Stats específicas por tipo
|
|
1813
|
+
if (stats.inferredType === 'number') {
|
|
1814
|
+
const nums = values.map(v => Number(v)).filter(n => !isNaN(n));
|
|
1815
|
+
if (nums.length) {
|
|
1816
|
+
stats.min = Math.min(...nums);
|
|
1817
|
+
stats.max = Math.max(...nums);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
else if (stats.inferredType === 'date') {
|
|
1821
|
+
const dates = values.map(v => new Date(v).getTime()).filter(t => !isNaN(t));
|
|
1822
|
+
if (dates.length) {
|
|
1823
|
+
stats.dateRange = {
|
|
1824
|
+
start: new Date(Math.min(...dates)).toISOString(),
|
|
1825
|
+
end: new Date(Math.max(...dates)).toISOString()
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
else if (stats.inferredType === 'string') {
|
|
1830
|
+
const strVal = String(values[0]);
|
|
1831
|
+
stats.isUrl = strVal.startsWith('http');
|
|
1832
|
+
stats.isEmail = strVal.includes('@') && strVal.includes('.');
|
|
1833
|
+
stats.isLongText = values.some(v => String(v).length > 50);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
profile.columns[col.field] = stats;
|
|
1837
|
+
}
|
|
1838
|
+
return profile;
|
|
1839
|
+
}
|
|
1840
|
+
static getValue(row, path) {
|
|
1841
|
+
if (!path || typeof path !== 'string')
|
|
1842
|
+
return undefined;
|
|
1843
|
+
return path.split('.').reduce((obj, key) => obj?.[key], row);
|
|
1844
|
+
}
|
|
1845
|
+
static sampleRows(data, sampleLimit) {
|
|
1846
|
+
if (!Array.isArray(data) || data.length === 0 || sampleLimit <= 0) {
|
|
1847
|
+
return [];
|
|
1848
|
+
}
|
|
1849
|
+
if (data.length <= sampleLimit) {
|
|
1850
|
+
return data.slice();
|
|
1851
|
+
}
|
|
1852
|
+
const indexes = new Set();
|
|
1853
|
+
const maxIndex = data.length - 1;
|
|
1854
|
+
for (let i = 0; i < sampleLimit; i += 1) {
|
|
1855
|
+
indexes.add(Math.round((i * maxIndex) / Math.max(1, sampleLimit - 1)));
|
|
1856
|
+
}
|
|
1857
|
+
return Array.from(indexes)
|
|
1858
|
+
.sort((a, b) => a - b)
|
|
1859
|
+
.map(index => data[index])
|
|
1860
|
+
.filter(row => row !== undefined && row !== null);
|
|
1861
|
+
}
|
|
1862
|
+
static inferType(values) {
|
|
1863
|
+
const first = values[0];
|
|
1864
|
+
if (typeof first === 'number')
|
|
1865
|
+
return 'number';
|
|
1866
|
+
if (typeof first === 'boolean')
|
|
1867
|
+
return 'boolean';
|
|
1868
|
+
if (first instanceof Date)
|
|
1869
|
+
return 'date';
|
|
1870
|
+
// Check string formats
|
|
1871
|
+
const str = String(first);
|
|
1872
|
+
if (!isNaN(Number(str)) && str.trim() !== '')
|
|
1873
|
+
return 'number';
|
|
1874
|
+
if (!isNaN(Date.parse(str)) && str.length > 5)
|
|
1875
|
+
return 'date'; // length check avoids short strings being parsed as dates
|
|
1876
|
+
return 'string';
|
|
1877
|
+
}
|
|
1878
|
+
static sanitizeValue(val, type) {
|
|
1879
|
+
if (type === 'string' && val.length > this.VALUE_TRUNCATE) {
|
|
1880
|
+
return val.substring(0, this.VALUE_TRUNCATE) + '...';
|
|
1881
|
+
}
|
|
1882
|
+
return val;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
const TABLE_RUNTIME_OPERATION_PATCH_KEY = 'tableRuntimeOperations';
|
|
1887
|
+
const TABLE_RUNTIME_OPERATION_BATCH_KIND = 'praxis.table.runtime-operation.batch';
|
|
1888
|
+
const TABLE_RUNTIME_OPERATION_IDS = ['table.filter.apply', 'table.export.run', 'dynamicPage.surface.open'];
|
|
1889
|
+
const TABLE_EXPORT_SCOPES = ['auto', 'selected', 'filtered', 'currentPage', 'all'];
|
|
1890
|
+
const TABLE_EXPORT_FORMATS = ['excel', 'pdf', 'csv', 'json', 'print'];
|
|
1891
|
+
const TABLE_UNSUPPORTED_FILTER_COMBINATOR_KEYS = new Set([
|
|
1892
|
+
'$and',
|
|
1893
|
+
'$not',
|
|
1894
|
+
'$or',
|
|
1895
|
+
'allOf',
|
|
1896
|
+
'and',
|
|
1897
|
+
'anyOf',
|
|
1898
|
+
'not',
|
|
1899
|
+
'oneOf',
|
|
1900
|
+
'or',
|
|
1901
|
+
]);
|
|
1902
|
+
/**
|
|
1903
|
+
* Adapter that connects the AI Engine to a specific PraxisTable instance.
|
|
1904
|
+
* Implements two-step intent flow + heuristics for contextual suggestions.
|
|
1905
|
+
*/
|
|
1906
|
+
class TableAiAdapter extends BaseAiAdapter {
|
|
1907
|
+
table;
|
|
1908
|
+
aiService;
|
|
1909
|
+
componentName = 'Data Table';
|
|
1910
|
+
componentId = 'praxis-table';
|
|
1911
|
+
componentType = 'table';
|
|
1912
|
+
constructor(table, aiService) {
|
|
1913
|
+
super();
|
|
1914
|
+
this.table = table;
|
|
1915
|
+
this.aiService = aiService;
|
|
1916
|
+
}
|
|
1917
|
+
// -------- Core contract --------
|
|
1918
|
+
async prepareAuthoringContext() {
|
|
1919
|
+
const tableWithFilterSchema = this.table;
|
|
1920
|
+
await tableWithFilterSchema.ensureFilterSchemaFieldsSnapshot?.();
|
|
1921
|
+
await tableWithFilterSchema.ensureAiAssistantRecordSurfaceContext?.();
|
|
1922
|
+
}
|
|
1923
|
+
compileAiResponse(response) {
|
|
1924
|
+
const runtimeOperations = this.coerceTableRuntimeOperationBatch(response);
|
|
1925
|
+
if (runtimeOperations) {
|
|
1926
|
+
return {
|
|
1927
|
+
patch: {
|
|
1928
|
+
[TABLE_RUNTIME_OPERATION_PATCH_KEY]: runtimeOperations,
|
|
1929
|
+
},
|
|
1930
|
+
explanation: typeof response['explanation'] === 'string'
|
|
1931
|
+
? response['explanation']
|
|
1932
|
+
: 'Preparei uma operacao de runtime para a tabela.',
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
const componentEditPlans = this.hydrateRecordSurfaceRowActionPlans(coerceTableComponentEditPlans(response));
|
|
1936
|
+
if (!componentEditPlans) {
|
|
1937
|
+
return null;
|
|
1938
|
+
}
|
|
1939
|
+
const compiled = compileTableComponentEditPlans(componentEditPlans, this.getCompileConfig());
|
|
1940
|
+
if (compiled.patch) {
|
|
1941
|
+
const patch = this.hydratePatchColumnsFromSchema(compiled.patch);
|
|
1942
|
+
return {
|
|
1943
|
+
patch: this.normalizePatch(patch),
|
|
1944
|
+
explanation: typeof response['explanation'] === 'string' ? response['explanation'] : compiled.explanation,
|
|
1945
|
+
warnings: compiled.warnings,
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
return {
|
|
1949
|
+
type: 'error',
|
|
1950
|
+
message: 'O plano de edicao da tabela nao passou na validacao de capacidades.',
|
|
1951
|
+
warnings: [...compiled.warnings, ...compiled.failureCodes],
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
getCurrentConfig() {
|
|
1955
|
+
try {
|
|
1956
|
+
return structuredClone(this.table.config);
|
|
1957
|
+
}
|
|
1958
|
+
catch {
|
|
1959
|
+
return JSON.parse(JSON.stringify(this.table.config));
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
getCapabilities() {
|
|
1963
|
+
return TABLE_AI_CAPABILITIES.capabilities;
|
|
1964
|
+
}
|
|
1965
|
+
getTaskPresets() {
|
|
1966
|
+
return TASK_PRESETS;
|
|
1967
|
+
}
|
|
1968
|
+
getRuntimeState() {
|
|
1969
|
+
const selectedRowsContext = this.table.getAiAssistantSelectedRowsContext?.();
|
|
1970
|
+
return {
|
|
1971
|
+
resourcePath: this.table.resourcePath || null,
|
|
1972
|
+
rowsTotal: this.table.dataSource.data.length,
|
|
1973
|
+
rowsVisible: this.table.paginator ? this.table.paginator.pageSize : this.table.dataSource.data.length,
|
|
1974
|
+
pageIndex: this.table.paginator ? this.table.paginator.pageIndex : 0,
|
|
1975
|
+
sort: this.table.sort ? { active: this.table.sort.active, direction: this.table.sort.direction } : null,
|
|
1976
|
+
filters: this.table.filterCriteria,
|
|
1977
|
+
selectionCount: this.table.selection.selected.length,
|
|
1978
|
+
...(selectedRowsContext ? { selection: selectedRowsContext } : {}),
|
|
1979
|
+
isLoading: false // TODO: expose real loading flag
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
getAuthoringContext() {
|
|
1983
|
+
const filterFieldCatalog = this.buildFilterFieldCatalog();
|
|
1984
|
+
const selectedRowsContext = this.getSelectedRowsAuthoringContext();
|
|
1985
|
+
const resourceCapabilities = this.getResourceCapabilityAuthoringContext();
|
|
1986
|
+
const recordSurfaces = this.getRecordSurfaceAuthoringContext();
|
|
1987
|
+
const filterExpressionSupported = resourceCapabilities?.filterExpressionSupported === true;
|
|
1988
|
+
const runtimeExportFormats = this.getRuntimeExportFormats();
|
|
1989
|
+
const runtimeOperations = [
|
|
1990
|
+
{
|
|
1991
|
+
operationId: 'table.filter.apply',
|
|
1992
|
+
inputSchema: {
|
|
1993
|
+
criteria: 'object',
|
|
1994
|
+
criteriaSemantics: 'simple-conjunction',
|
|
1995
|
+
source: ['selected-records', 'manual', 'current-context'],
|
|
1996
|
+
},
|
|
1997
|
+
materializes: 'Aplica criterios em conjunto no estado runtime dos filtros avancados.',
|
|
1998
|
+
},
|
|
1999
|
+
...(runtimeExportFormats.length
|
|
2000
|
+
? [{
|
|
2001
|
+
operationId: 'table.export.run',
|
|
2002
|
+
inputSchema: {
|
|
2003
|
+
format: runtimeExportFormats,
|
|
2004
|
+
scope: [...TABLE_EXPORT_SCOPES],
|
|
2005
|
+
},
|
|
2006
|
+
materializes: 'Dispara exportacao da tabela usando o pipeline canonico de Collection Export.',
|
|
2007
|
+
}]
|
|
2008
|
+
: []),
|
|
2009
|
+
...(recordSurfaces?.surfaces?.length
|
|
2010
|
+
? [{
|
|
2011
|
+
operationId: 'dynamicPage.surface.open',
|
|
2012
|
+
inputSchema: {
|
|
2013
|
+
surfaceId: recordSurfaces.surfaces.map((surface) => surface.id),
|
|
2014
|
+
source: ['selected-records'],
|
|
2015
|
+
},
|
|
2016
|
+
materializes: 'Solicita ao host da pagina dinamica abrir uma superficie relacionada ao registro selecionado.',
|
|
2017
|
+
}]
|
|
2018
|
+
: []),
|
|
2019
|
+
];
|
|
2020
|
+
const runtimeRules = [
|
|
2021
|
+
'Use table.filter.apply only when the user asks to apply current table filters now; criteria must be grounded in declared filterFieldCatalog fields and selectedRecordsContext values when selection is the source.',
|
|
2022
|
+
'When selectedRecordsContext.filterCandidates contains a candidate whose label or field matches the requested selected-record filter, emit table.filter.apply with that candidate criteria exactly.',
|
|
2023
|
+
'For filterFieldCatalog fields with criterionKind "range" and selectedRecordsContext as the source, derive criteria from the minimum and maximum selected values in relatedColumnFields and emit { start, end }; for criterionKind "date-range", emit { startDate, endDate } from the earliest and latest selected dates.',
|
|
2024
|
+
'When the user asks for similar, matching, or comparable records from selected records without naming which property should define similarity, return clarification with options derived from filterFieldCatalog labels instead of info text or a patch.',
|
|
2025
|
+
'When recordSurfaces exists and the user asks to open, consult, view, navigate, or inspect information related to the selected record, answer from recordSurfaces before considering selection-derived filters.',
|
|
2026
|
+
'Resolve related-surface requests by the declared recordSurfaces semantic catalog, including label, description, semanticIntent, kind, scope, and tags; do not fall back to selected-record filters when a declared surface semantically satisfies the request.',
|
|
2027
|
+
'Use dynamicPage.surface.open only when recordSurfaces declares the requested surface and the user asks to open, show, navigate to, or display that related surface now.',
|
|
2028
|
+
'Do not use table.filter.apply for related-surface requests unless the user explicitly asks to find other records.',
|
|
2029
|
+
'When runtimeOperations.filterExpression.supported is not true, do not materialize OR, alternative groups, or nested boolean filters; ask which single filter field/value should be applied or explain the limitation.',
|
|
2030
|
+
'table.filter.apply accepts only simple conjunction criteria over declared fields; do not emit OR/AND groups, anyOf/oneOf/allOf, or nested boolean filter expressions. Ask for clarification or explain the limitation when the user needs alternatives.',
|
|
2031
|
+
...(runtimeExportFormats.length
|
|
2032
|
+
? [
|
|
2033
|
+
'Use table.export.run only when the user asks to run an export now; choose scope selected for selected records, filtered for filtered results, and ask for clarification when the user does not specify enough context.',
|
|
2034
|
+
'For combined runtime requests, return every requested operation in order inside the same tableRuntimeOperations batch; when the user asks to filter and then export the filtered results, emit table.filter.apply first and table.export.run second with scope filtered.',
|
|
2035
|
+
]
|
|
2036
|
+
: [
|
|
2037
|
+
'Do not emit table.export.run because export is not enabled by the current table configuration.',
|
|
2038
|
+
]),
|
|
2039
|
+
];
|
|
2040
|
+
const responseModes = [
|
|
2041
|
+
{
|
|
2042
|
+
kind: 'consult',
|
|
2043
|
+
operationKind: 'consult',
|
|
2044
|
+
changeKind: 'answer',
|
|
2045
|
+
preferredResponse: 'info',
|
|
2046
|
+
useWhen: [
|
|
2047
|
+
'The user asks how the current table is configured or connected.',
|
|
2048
|
+
'The user asks which resourcePath, endpoint, schema fields, capabilities, styling options, or computed-column patterns are available.',
|
|
2049
|
+
'The user asks for an explanation, documentation-style answer, or how-to guidance without requesting an immediate change.',
|
|
2050
|
+
],
|
|
2051
|
+
rules: [
|
|
2052
|
+
'Return type "info" with message when answering a consultative question.',
|
|
2053
|
+
'Do not ask the user to choose a dataset when the current table already exposes resourcePath.',
|
|
2054
|
+
'When runtimeState.selection exists, treat it as governed context for the rows selected by the user, using only its sanitized ids and sampleRows.',
|
|
2055
|
+
'When selectedRecordsContext.selectedCount is greater than zero, do not claim that no record is selected; answer from selectedRecordsContext.sampleRows even when there is only one selected row.',
|
|
2056
|
+
'When selectedRecordsContext.filterCandidates exists, treat those candidates as the canonical selection-derived bridge to advanced filters; prefer their labels and criteria over re-inferring values from prose.',
|
|
2057
|
+
'When recordSurfaces exists and the user asks what can be opened, consulted, navigated, or viewed for selected records, answer or clarify using recordSurfaces before considering selection-derived filters.',
|
|
2058
|
+
'For record-related surface requests, ground the target in recordSurfaces semantic fields such as label, description, semanticIntent, kind, scope, and tags before considering filters.',
|
|
2059
|
+
'Do not use selection-derived filters for requests about related surfaces, detail panels, timelines, teams, profiles, histories, payroll, or other record-adjacent information unless the user explicitly asks to find other records.',
|
|
2060
|
+
'If the user asks for a related surface that is not declared in recordSurfaces, say that it is not available in the current context and list the declared surfaces in human-facing labels.',
|
|
2061
|
+
'When the user asks how selected records can drive advanced filters or export, bridge the selected records to declared filterFieldCatalog fields and export scopes conceptually; ask for clarification when the shared property or export scope is ambiguous.',
|
|
2062
|
+
'For ambiguous selected-record bridge requests that can become a real table operation, prefer type "clarification" with concise user-facing options grounded in filterFieldCatalog labels or export scopes; do not answer with a long documentation-style explanation.',
|
|
2063
|
+
'Keep selected-record bridge responses human-facing: mention selected record count and meaningful labels, but do not expose raw ids, endpoint paths, operationIds, internal field names, schema keys, or payload examples unless the user explicitly asks for technical details.',
|
|
2064
|
+
'For how-to or documentation-style turns, explain the governed operation shape and give a safe example instead of collecting missing edit parameters.',
|
|
2065
|
+
'Do not return clarification for missing target fields, labels, formats, colors, conditions, or columns unless the selected response mode is edit/componentEditPlan.',
|
|
2066
|
+
'Only produce componentEditPlan when the user asks to apply or materialize a table change.',
|
|
2067
|
+
],
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
kind: 'edit',
|
|
2071
|
+
operationKind: 'author',
|
|
2072
|
+
preferredResponse: 'componentEditPlan',
|
|
2073
|
+
useWhen: [
|
|
2074
|
+
'The user asks to change table configuration, visual rules, renderers, columns, behavior, actions, or appearance.',
|
|
2075
|
+
],
|
|
2076
|
+
rules: [
|
|
2077
|
+
'When the user asks to add a row button, row option, or action in each row for a declared record surface, use rowAction.add with capabilityPath "actions.row.actions[]".',
|
|
2078
|
+
'For a rowAction.add that opens a declared record surface, set action id/label for the row button and preserve the declared surface under value.recordSurface; never overwrite the canonical recordSurface.id with the row action id.',
|
|
2079
|
+
'Use human-facing labels from recordSurfaces for row action labels; do not ask for technical fields such as surface to open, button label, or placement in row when the conversation already grounded one declared surface.',
|
|
2080
|
+
],
|
|
2081
|
+
},
|
|
2082
|
+
{
|
|
2083
|
+
kind: 'runtime',
|
|
2084
|
+
operationKind: 'execute',
|
|
2085
|
+
changeKind: 'runtimeOperation',
|
|
2086
|
+
preferredResponse: TABLE_RUNTIME_OPERATION_BATCH_KIND,
|
|
2087
|
+
useWhen: [
|
|
2088
|
+
'The user asks to execute a declared table runtime operation now, such as applying filters, exporting data, or opening a related dynamic-page surface.',
|
|
2089
|
+
'The user selects records and asks to open, show, display, navigate to, or inspect a declared record surface such as a detail, team, timeline, history, payroll, or other related surface.',
|
|
2090
|
+
'The user selects records and asks to filter or export using declared selectedRecordsContext and runtimeOperations.',
|
|
2091
|
+
],
|
|
2092
|
+
rules: [
|
|
2093
|
+
'Return type "patch" with tableRuntimeOperations when the request can be materialized by a declared runtimeOperations operation.',
|
|
2094
|
+
'For dynamicPage.surface.open, emit tableRuntimeOperations.operations[] with operationId "dynamicPage.surface.open" and input.surfaceId equal to one declared recordSurfaces.surfaces[].id.',
|
|
2095
|
+
'Do not answer with a textual confirmation before emitting dynamicPage.surface.open when a single requested record surface is declared and the user asks to open or show it now.',
|
|
2096
|
+
'Ask clarification only when more than one declared runtime operation or record surface could satisfy the same user request.',
|
|
2097
|
+
'Keep the explanation user-facing; do not expose operationIds, resource paths, field names, or payload details unless the user explicitly asks for technical details.',
|
|
2098
|
+
],
|
|
2099
|
+
},
|
|
2100
|
+
];
|
|
2101
|
+
const authoringContract = {
|
|
2102
|
+
kind: 'praxis.component-authoring-context',
|
|
2103
|
+
componentId: this.componentId,
|
|
2104
|
+
componentType: this.componentType,
|
|
2105
|
+
preferredResponse: 'componentEditPlan',
|
|
2106
|
+
responseModes,
|
|
2107
|
+
consultativeContext: {
|
|
2108
|
+
resourcePath: this.table.resourcePath || null,
|
|
2109
|
+
...(resourceCapabilities ? { resourceCapabilities } : {}),
|
|
2110
|
+
...(selectedRowsContext ? { selectedRecordsContext: selectedRowsContext } : {}),
|
|
2111
|
+
...(recordSurfaces ? { recordSurfaces } : {}),
|
|
2112
|
+
answerableQuestionKinds: [
|
|
2113
|
+
'current-resource-path',
|
|
2114
|
+
'current-endpoint',
|
|
2115
|
+
'schema-fields',
|
|
2116
|
+
'selected-records',
|
|
2117
|
+
'selection-derived-filter-candidates',
|
|
2118
|
+
'record-related-surfaces',
|
|
2119
|
+
'selection-derived-export-scope',
|
|
2120
|
+
'styling-capabilities',
|
|
2121
|
+
'conditional-renderers',
|
|
2122
|
+
'conditional-styles',
|
|
2123
|
+
'conditional-animations',
|
|
2124
|
+
'computed-column-how-to',
|
|
2125
|
+
],
|
|
2126
|
+
},
|
|
2127
|
+
componentEditPlan: {
|
|
2128
|
+
kind: TABLE_COMPONENT_EDIT_PLAN_KIND,
|
|
2129
|
+
batchKind: TABLE_COMPONENT_EDIT_PLAN_BATCH_KIND,
|
|
2130
|
+
version: TABLE_COMPONENT_EDIT_PLAN_VERSION,
|
|
2131
|
+
schemaId: TABLE_COMPONENT_EDIT_PLAN_JSON_SCHEMA.$id,
|
|
2132
|
+
allowedOperationIds: [...TABLE_COMPONENT_EDIT_PLAN_OPERATION_IDS],
|
|
2133
|
+
allowedChangeKinds: [...TABLE_COMPONENT_EDIT_PLAN_ALLOWED_CHANGE_KINDS],
|
|
2134
|
+
expectedPaths: { ...TABLE_COMPONENT_EDIT_PLAN_EXPECTED_PATHS },
|
|
2135
|
+
capabilities: getTableComponentEditPlanCapabilities(),
|
|
2136
|
+
...(filterFieldCatalog ? { filterFieldCatalog } : {}),
|
|
2137
|
+
rules: [
|
|
2138
|
+
'Use componentEditPlan instead of free patch when the request fits an allowed table operationId or changeKind.',
|
|
2139
|
+
'Prefer manifest operationId for governed agentic authoring responses.',
|
|
2140
|
+
'Use batch kind with operations for multiple table edits in one request.',
|
|
2141
|
+
'For column.order.set, use a zero-based visual index from currentStateDigest.columnOrder; when the user says after a column, use that column index + 1, and when the user says before a column, use that column index.',
|
|
2142
|
+
'Use Json Logic objects for computed expressions and conditional rules.',
|
|
2143
|
+
'For selected-record bridge requests, configure available table capabilities such as filter.advanced.configure, filter.advanced.fields.add, toolbar.action.add, or export.configure; do not invent a runtime apply-filter or run-export operation unless it is declared in the contract.',
|
|
2144
|
+
'Do not invent fields, operationIds, changeKinds, capabilityPaths, formats, badge variants, or colors outside the provided contract.',
|
|
2145
|
+
],
|
|
2146
|
+
operationRegistryCoverage: TABLE_AUTHORING_REGISTRY_COVERAGE,
|
|
2147
|
+
},
|
|
2148
|
+
runtimeOperations: {
|
|
2149
|
+
kind: TABLE_RUNTIME_OPERATION_BATCH_KIND,
|
|
2150
|
+
allowedOperationIds: runtimeOperations.map((operation) => operation.operationId),
|
|
2151
|
+
filterExpression: {
|
|
2152
|
+
supported: filterExpressionSupported,
|
|
2153
|
+
source: resourceCapabilities?.source ?? 'unavailable',
|
|
2154
|
+
materialization: filterExpressionSupported
|
|
2155
|
+
? 'Capability advertised by resource metadata; this table runtime still declares only table.filter.apply for simple conjunction criteria.'
|
|
2156
|
+
: 'Compound OR/nested boolean filters are not materializable by the current resource/runtime contract.',
|
|
2157
|
+
},
|
|
2158
|
+
operations: runtimeOperations,
|
|
2159
|
+
rules: runtimeRules,
|
|
2160
|
+
},
|
|
2161
|
+
};
|
|
2162
|
+
return createComponentAuthoringContext(authoringContract);
|
|
2163
|
+
}
|
|
2164
|
+
getRuntimeExportFormats() {
|
|
2165
|
+
const exportConfig = this.getCurrentConfig().export;
|
|
2166
|
+
if (!exportConfig?.enabled)
|
|
2167
|
+
return [];
|
|
2168
|
+
const configuredFormats = Array.isArray(exportConfig.formats) ? exportConfig.formats : [];
|
|
2169
|
+
if (!configuredFormats.length)
|
|
2170
|
+
return [...TABLE_EXPORT_FORMATS];
|
|
2171
|
+
const formats = configuredFormats
|
|
2172
|
+
.map((format) => this.normalizeExportFormat(format))
|
|
2173
|
+
.filter((format) => !!format);
|
|
2174
|
+
return Array.from(new Set(formats));
|
|
2175
|
+
}
|
|
2176
|
+
normalizeExportFormat(value) {
|
|
2177
|
+
const format = this.stringValue(value).toLowerCase();
|
|
2178
|
+
if (format === 'xlsx')
|
|
2179
|
+
return 'excel';
|
|
2180
|
+
return TABLE_EXPORT_FORMATS.includes(format)
|
|
2181
|
+
? format
|
|
2182
|
+
: null;
|
|
2183
|
+
}
|
|
2184
|
+
getSelectedRowsAuthoringContext() {
|
|
2185
|
+
try {
|
|
2186
|
+
const selection = this.table.getAiAssistantSelectedRowsContext?.();
|
|
2187
|
+
return selection && typeof selection === 'object' ? selection : null;
|
|
2188
|
+
}
|
|
2189
|
+
catch {
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
getRecordSurfaceAuthoringContext() {
|
|
2194
|
+
const tableWithRecordSurfaces = this.table;
|
|
2195
|
+
const recordSurfaces = tableWithRecordSurfaces.getAiAssistantRecordSurfacesContext?.()
|
|
2196
|
+
?? this.table.aiContext?.recordSurfaces;
|
|
2197
|
+
if (!recordSurfaces || typeof recordSurfaces !== 'object')
|
|
2198
|
+
return null;
|
|
2199
|
+
const surfaces = Array.isArray(recordSurfaces.surfaces) ? recordSurfaces.surfaces : [];
|
|
2200
|
+
if (!surfaces.length)
|
|
2201
|
+
return null;
|
|
2202
|
+
return {
|
|
2203
|
+
source: recordSurfaces.source || 'dynamic-page-composition',
|
|
2204
|
+
surfaces: surfaces.map((surface) => ({
|
|
2205
|
+
id: surface.id,
|
|
2206
|
+
label: surface.label,
|
|
2207
|
+
relation: surface.relation,
|
|
2208
|
+
operationId: surface.operationId,
|
|
2209
|
+
...(surface.description ? { description: surface.description } : {}),
|
|
2210
|
+
...(surface.statePath ? { statePath: surface.statePath } : {}),
|
|
2211
|
+
...this.buildRecordSurfaceSemanticDigest(surface),
|
|
2212
|
+
source: surface.source,
|
|
2213
|
+
target: surface.target,
|
|
2214
|
+
...(surface.resourceSurface ? { resourceSurface: this.sanitizeRecordSurfaceForAuthoring(surface.resourceSurface) } : {}),
|
|
2215
|
+
})),
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
sanitizeRecordSurfaceForAuthoring(value) {
|
|
2219
|
+
const surface = this.toRecord(value);
|
|
2220
|
+
if (!surface)
|
|
2221
|
+
return null;
|
|
2222
|
+
const tags = this.compactStringArray(surface['tags']);
|
|
2223
|
+
const out = {};
|
|
2224
|
+
[
|
|
2225
|
+
'id',
|
|
2226
|
+
'resourceKey',
|
|
2227
|
+
'kind',
|
|
2228
|
+
'scope',
|
|
2229
|
+
'title',
|
|
2230
|
+
'description',
|
|
2231
|
+
'intent',
|
|
2232
|
+
'operationId',
|
|
2233
|
+
'path',
|
|
2234
|
+
'method',
|
|
2235
|
+
'schemaId',
|
|
2236
|
+
'schemaUrl',
|
|
2237
|
+
].forEach((key) => {
|
|
2238
|
+
const text = this.stringValue(surface[key]);
|
|
2239
|
+
if (text)
|
|
2240
|
+
out[key] = text;
|
|
2241
|
+
});
|
|
2242
|
+
const availability = this.toRecord(surface['availability']);
|
|
2243
|
+
if (availability)
|
|
2244
|
+
out['availability'] = availability;
|
|
2245
|
+
if (typeof surface['order'] === 'number')
|
|
2246
|
+
out['order'] = surface['order'];
|
|
2247
|
+
if (tags.length)
|
|
2248
|
+
out['tags'] = tags;
|
|
2249
|
+
return Object.keys(out).length ? out : null;
|
|
2250
|
+
}
|
|
2251
|
+
buildRecordSurfaceSemanticDigest(surface) {
|
|
2252
|
+
const resourceSurface = this.toRecord(surface['resourceSurface']);
|
|
2253
|
+
if (!resourceSurface) {
|
|
2254
|
+
return {};
|
|
2255
|
+
}
|
|
2256
|
+
const semanticIntent = this.stringValue(resourceSurface['intent']);
|
|
2257
|
+
const kind = this.stringValue(resourceSurface['kind']);
|
|
2258
|
+
const scope = this.stringValue(resourceSurface['scope']);
|
|
2259
|
+
const title = this.stringValue(resourceSurface['title']);
|
|
2260
|
+
const description = this.stringValue(resourceSurface['description']);
|
|
2261
|
+
const tags = this.compactStringArray(resourceSurface['tags']);
|
|
2262
|
+
const digest = {};
|
|
2263
|
+
if (kind)
|
|
2264
|
+
digest['kind'] = kind;
|
|
2265
|
+
if (scope)
|
|
2266
|
+
digest['scope'] = scope;
|
|
2267
|
+
if (semanticIntent)
|
|
2268
|
+
digest['semanticIntent'] = semanticIntent;
|
|
2269
|
+
if (title && title !== this.stringValue(surface['label']))
|
|
2270
|
+
digest['title'] = title;
|
|
2271
|
+
if (description && description !== this.stringValue(surface['description'])) {
|
|
2272
|
+
digest['semanticDescription'] = description;
|
|
2273
|
+
}
|
|
2274
|
+
if (tags.length)
|
|
2275
|
+
digest['tags'] = tags;
|
|
2276
|
+
return digest;
|
|
2277
|
+
}
|
|
2278
|
+
getResourceCapabilityAuthoringContext() {
|
|
2279
|
+
const tableWithCapabilities = this.table;
|
|
2280
|
+
try {
|
|
2281
|
+
const digest = tableWithCapabilities.getResourceCapabilityDigest?.();
|
|
2282
|
+
if (!digest || typeof digest !== 'object')
|
|
2283
|
+
return null;
|
|
2284
|
+
return {
|
|
2285
|
+
source: digest['source'] || 'schema-x-ui-resource',
|
|
2286
|
+
resourcePath: digest['resourcePath'] || this.table.resourcePath || null,
|
|
2287
|
+
canonicalOperations: { ...(digest['canonicalOperations'] || {}) },
|
|
2288
|
+
filterExpressionSupported: digest['filterExpressionSupported'] === true,
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
catch {
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
buildFilterFieldCatalog() {
|
|
2296
|
+
const fields = this.getFilterSchemaFields();
|
|
2297
|
+
if (!fields.length)
|
|
2298
|
+
return null;
|
|
2299
|
+
const columns = this.getCurrentConfig().columns || [];
|
|
2300
|
+
return {
|
|
2301
|
+
source: 'resource-filter-schema',
|
|
2302
|
+
resourcePath: this.table.resourcePath || null,
|
|
2303
|
+
fields: fields.map((field) => {
|
|
2304
|
+
const aliases = this.buildFilterFieldAliases(field, columns);
|
|
2305
|
+
const relatedColumnFields = this.toStringArray(field['relatedColumnFields']);
|
|
2306
|
+
const relatedColumnLabels = this.toStringArray(field['relatedColumnLabels']);
|
|
2307
|
+
const criterionSemantics = this.inferFilterCriterionSemantics(field);
|
|
2308
|
+
return {
|
|
2309
|
+
name: field['name'],
|
|
2310
|
+
label: field['label'] ?? field['name'],
|
|
2311
|
+
type: field['type'] ?? null,
|
|
2312
|
+
controlType: field['controlType'] ?? null,
|
|
2313
|
+
...(relatedColumnFields.length ? { relatedColumnFields } : {}),
|
|
2314
|
+
...(relatedColumnLabels.length ? { relatedColumnLabels } : {}),
|
|
2315
|
+
...(criterionSemantics ? criterionSemantics : {}),
|
|
2316
|
+
...(aliases.length ? { aliases } : {}),
|
|
2317
|
+
};
|
|
2318
|
+
}),
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
getFilterSchemaFields() {
|
|
2322
|
+
const tableWithFilterSchema = this.table;
|
|
2323
|
+
try {
|
|
2324
|
+
return tableWithFilterSchema.getFilterSchemaFieldsSnapshot?.() ?? [];
|
|
2325
|
+
}
|
|
2326
|
+
catch {
|
|
2327
|
+
return [];
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
buildFilterFieldAliases(field, columns) {
|
|
2331
|
+
const aliases = new Set();
|
|
2332
|
+
const declaredAliases = Array.isArray(field['aliases']) ? field['aliases'] : [];
|
|
2333
|
+
for (const value of declaredAliases) {
|
|
2334
|
+
this.addFilterFieldAlias(aliases, value);
|
|
2335
|
+
}
|
|
2336
|
+
for (const key of ['name', 'label']) {
|
|
2337
|
+
const value = this.normalizeAliasValue(field[key]);
|
|
2338
|
+
this.addFilterFieldAlias(aliases, value);
|
|
2339
|
+
}
|
|
2340
|
+
for (const value of field['relatedColumnFields'] || []) {
|
|
2341
|
+
const alias = this.normalizeAliasValue(value);
|
|
2342
|
+
this.addFilterFieldAlias(aliases, alias);
|
|
2343
|
+
}
|
|
2344
|
+
for (const value of field['relatedColumnLabels'] || []) {
|
|
2345
|
+
const alias = this.normalizeAliasValue(value);
|
|
2346
|
+
this.addFilterFieldAlias(aliases, alias);
|
|
2347
|
+
}
|
|
2348
|
+
for (const column of columns || []) {
|
|
2349
|
+
const columnField = this.normalizeAliasValue(column?.field);
|
|
2350
|
+
if (!columnField)
|
|
2351
|
+
continue;
|
|
2352
|
+
const fieldName = this.normalizeAliasValue(field['name']);
|
|
2353
|
+
const fieldLabel = this.normalizeAliasValue(field['label']);
|
|
2354
|
+
if (fieldName.includes(columnField) || fieldLabel.includes(columnField)) {
|
|
2355
|
+
this.addFilterFieldAlias(aliases, columnField);
|
|
2356
|
+
const header = this.normalizeAliasValue(column?.header);
|
|
2357
|
+
this.addFilterFieldAlias(aliases, header);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return [...aliases];
|
|
2361
|
+
}
|
|
2362
|
+
addFilterFieldAlias(aliases, value) {
|
|
2363
|
+
const alias = this.normalizeAliasValue(value);
|
|
2364
|
+
if (!alias)
|
|
2365
|
+
return;
|
|
2366
|
+
aliases.add(alias);
|
|
2367
|
+
for (const variant of this.semanticFilterAliasVariants(alias)) {
|
|
2368
|
+
aliases.add(variant);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
semanticFilterAliasVariants(alias) {
|
|
2372
|
+
const variants = new Set();
|
|
2373
|
+
const tokens = alias.split(/\s+/u).filter((token) => token.length > 0);
|
|
2374
|
+
const addIfTokenPresent = (token, values) => {
|
|
2375
|
+
if (tokens.includes(token)) {
|
|
2376
|
+
for (const value of values)
|
|
2377
|
+
variants.add(value);
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
addIfTokenPresent('departamento', ['depto', 'depart', 'department', 'departamento']);
|
|
2381
|
+
addIfTokenPresent('departamentos', ['depto', 'depart', 'department', 'departamento']);
|
|
2382
|
+
addIfTokenPresent('admissao', ['admitido', 'admitidos', 'entrada', 'ingresso']);
|
|
2383
|
+
addIfTokenPresent('admissoes', ['admitido', 'admitidos', 'entrada', 'ingresso']);
|
|
2384
|
+
addIfTokenPresent('salario', ['remuneracao', 'remuneracoes', 'ganho', 'ganhos']);
|
|
2385
|
+
addIfTokenPresent('salarial', ['remuneracao', 'remuneracoes', 'ganho', 'ganhos']);
|
|
2386
|
+
addIfTokenPresent('cargo', ['funcao', 'funcoes', 'papel', 'papeis']);
|
|
2387
|
+
addIfTokenPresent('cargos', ['funcao', 'funcoes', 'papel', 'papeis']);
|
|
2388
|
+
return [...variants];
|
|
2389
|
+
}
|
|
2390
|
+
normalizeAliasValue(value) {
|
|
2391
|
+
if (typeof value !== 'string')
|
|
2392
|
+
return '';
|
|
2393
|
+
return value
|
|
2394
|
+
.normalize('NFD')
|
|
2395
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
2396
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
2397
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
|
2398
|
+
.trim()
|
|
2399
|
+
.toLowerCase();
|
|
2400
|
+
}
|
|
2401
|
+
toStringArray(value) {
|
|
2402
|
+
if (!Array.isArray(value))
|
|
2403
|
+
return [];
|
|
2404
|
+
return value.filter((item) => typeof item === 'string' && item.trim().length > 0);
|
|
2405
|
+
}
|
|
2406
|
+
inferFilterCriterionSemantics(field) {
|
|
2407
|
+
const name = typeof field['name'] === 'string' ? field['name'] : '';
|
|
2408
|
+
const normalizedName = this.normalizeAliasValue(name);
|
|
2409
|
+
const normalizedType = this.normalizeAliasValue(field['type']);
|
|
2410
|
+
const normalizedControlType = this.normalizeAliasValue(field['controlType']);
|
|
2411
|
+
const isRange = normalizedControlType.includes('range')
|
|
2412
|
+
|| /(?:Between|Range)$/u.test(name);
|
|
2413
|
+
if (!isRange) {
|
|
2414
|
+
return /IdsIn$/u.test(name)
|
|
2415
|
+
? {
|
|
2416
|
+
criterionKind: 'set',
|
|
2417
|
+
criterionValueShape: 'array',
|
|
2418
|
+
}
|
|
2419
|
+
: null;
|
|
2420
|
+
}
|
|
2421
|
+
const isDate = normalizedType.includes('date')
|
|
2422
|
+
|| normalizedControlType.includes('date')
|
|
2423
|
+
|| normalizedType.includes('time')
|
|
2424
|
+
|| normalizedControlType.includes('time');
|
|
2425
|
+
return {
|
|
2426
|
+
criterionKind: isDate ? 'date-range' : 'range',
|
|
2427
|
+
criterionValueShape: isDate ? 'startDate-endDate' : 'start-end',
|
|
2428
|
+
selectedRecordsAggregation: 'min-max',
|
|
2429
|
+
...(normalizedName ? { semanticSourceHint: normalizedName } : {}),
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
getSuggestionContext() {
|
|
2433
|
+
const config = this.getCurrentConfig();
|
|
2434
|
+
const behavior = config.behavior || {};
|
|
2435
|
+
const actions = config.actions || {};
|
|
2436
|
+
const availableFeatures = [];
|
|
2437
|
+
const missingCapabilities = [];
|
|
2438
|
+
const effectiveIdField = this.normalizeString(this.table.getIdField?.());
|
|
2439
|
+
if (!this.table.resourcePath) {
|
|
2440
|
+
availableFeatures.push('data-connection');
|
|
2441
|
+
}
|
|
2442
|
+
if (!effectiveIdField) {
|
|
2443
|
+
availableFeatures.push('meta.idField');
|
|
2444
|
+
}
|
|
2445
|
+
if (!behavior.filtering?.enabled) {
|
|
2446
|
+
availableFeatures.push('behavior.filtering');
|
|
2447
|
+
missingCapabilities.push('filtering');
|
|
2448
|
+
}
|
|
2449
|
+
if (!behavior.sorting?.enabled) {
|
|
2450
|
+
availableFeatures.push('behavior.sorting');
|
|
2451
|
+
missingCapabilities.push('sorting');
|
|
2452
|
+
}
|
|
2453
|
+
if (!behavior.pagination?.enabled) {
|
|
2454
|
+
availableFeatures.push('behavior.pagination');
|
|
2455
|
+
missingCapabilities.push('pagination');
|
|
2456
|
+
}
|
|
2457
|
+
if (!behavior.selection?.enabled) {
|
|
2458
|
+
availableFeatures.push('behavior.selection');
|
|
2459
|
+
missingCapabilities.push('selection');
|
|
2460
|
+
}
|
|
2461
|
+
if (!config.toolbar?.visible) {
|
|
2462
|
+
availableFeatures.push('toolbar');
|
|
2463
|
+
missingCapabilities.push('toolbar');
|
|
2464
|
+
}
|
|
2465
|
+
if (!actions.row?.enabled) {
|
|
2466
|
+
availableFeatures.push('actions.row');
|
|
2467
|
+
missingCapabilities.push('actions');
|
|
2468
|
+
}
|
|
2469
|
+
if (!actions.bulk?.enabled) {
|
|
2470
|
+
availableFeatures.push('actions.bulk');
|
|
2471
|
+
missingCapabilities.push('actions');
|
|
2472
|
+
}
|
|
2473
|
+
if (!actions.context?.enabled) {
|
|
2474
|
+
availableFeatures.push('actions.context');
|
|
2475
|
+
missingCapabilities.push('actions');
|
|
2476
|
+
}
|
|
2477
|
+
if (!config.export?.enabled) {
|
|
2478
|
+
availableFeatures.push('export');
|
|
2479
|
+
missingCapabilities.push('export');
|
|
2480
|
+
}
|
|
2481
|
+
if (!config.localization) {
|
|
2482
|
+
availableFeatures.push('localization');
|
|
2483
|
+
missingCapabilities.push('localization');
|
|
2484
|
+
}
|
|
2485
|
+
if (!config.messages) {
|
|
2486
|
+
availableFeatures.push('messages');
|
|
2487
|
+
missingCapabilities.push('messages');
|
|
2488
|
+
}
|
|
2489
|
+
if (!config.performance) {
|
|
2490
|
+
availableFeatures.push('performance');
|
|
2491
|
+
missingCapabilities.push('performance');
|
|
2492
|
+
}
|
|
2493
|
+
if (!config.accessibility) {
|
|
2494
|
+
availableFeatures.push('accessibility');
|
|
2495
|
+
missingCapabilities.push('accessibility');
|
|
2496
|
+
}
|
|
2497
|
+
if (!config.rowConditionalStyles || config.rowConditionalStyles.length === 0) {
|
|
2498
|
+
availableFeatures.push('row-conditional-styles');
|
|
2499
|
+
missingCapabilities.push('conditional');
|
|
2500
|
+
}
|
|
2501
|
+
return {
|
|
2502
|
+
authoringContract: {
|
|
2503
|
+
kind: 'praxis.table.editor',
|
|
2504
|
+
usesBindings: true,
|
|
2505
|
+
bindingsPaths: ['bindings.resourcePath', 'bindings.horizontalScroll'],
|
|
2506
|
+
configPaths: ['meta.idField'],
|
|
2507
|
+
runtimeConfigProjection: 'TableConfig',
|
|
2508
|
+
},
|
|
2509
|
+
availableFeatures,
|
|
2510
|
+
missingCapabilities: Array.from(new Set(missingCapabilities)),
|
|
2511
|
+
inputs: {
|
|
2512
|
+
resourcePath: this.table.resourcePath || null,
|
|
2513
|
+
idField: effectiveIdField || null,
|
|
2514
|
+
horizontalScroll: this.table.horizontalScroll || null,
|
|
2515
|
+
},
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
getDataProfile() {
|
|
2519
|
+
const data = this.table.dataSource.data || [];
|
|
2520
|
+
return TableDataProfiler.profile(data, this.table.config);
|
|
2521
|
+
}
|
|
2522
|
+
getSchemaFields() {
|
|
2523
|
+
const fieldsByName = new Map();
|
|
2524
|
+
try {
|
|
2525
|
+
for (const field of this.table.getSchemaFieldsSnapshot()) {
|
|
2526
|
+
const name = String(field?.['name'] || '').trim();
|
|
2527
|
+
if (name)
|
|
2528
|
+
fieldsByName.set(name, field);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
catch {
|
|
2532
|
+
// Optional runtime snapshot; filter schema below can still ground available fields.
|
|
2533
|
+
}
|
|
2534
|
+
for (const field of this.getFilterSchemaFields()) {
|
|
2535
|
+
const name = String(field?.['name'] || '').trim();
|
|
2536
|
+
if (!name || fieldsByName.has(name))
|
|
2537
|
+
continue;
|
|
2538
|
+
fieldsByName.set(name, field);
|
|
2539
|
+
}
|
|
2540
|
+
return Array.from(fieldsByName.values());
|
|
2541
|
+
}
|
|
2542
|
+
getCompileConfig() {
|
|
2543
|
+
const currentConfig = this.getCurrentConfig();
|
|
2544
|
+
const columns = [...(currentConfig.columns || [])];
|
|
2545
|
+
const existingFields = new Set(columns
|
|
2546
|
+
.map((column) => String(column?.field || '').trim())
|
|
2547
|
+
.filter(Boolean));
|
|
2548
|
+
for (const field of this.getSchemaFields()) {
|
|
2549
|
+
const name = typeof field['name'] === 'string' ? field['name'].trim() : '';
|
|
2550
|
+
if (!name || existingFields.has(name))
|
|
2551
|
+
continue;
|
|
2552
|
+
existingFields.add(name);
|
|
2553
|
+
const header = typeof field['label'] === 'string' && field['label'].trim()
|
|
2554
|
+
? field['label'].trim()
|
|
2555
|
+
: name;
|
|
2556
|
+
const type = typeof field['type'] === 'string' && field['type'].trim()
|
|
2557
|
+
? field['type'].trim()
|
|
2558
|
+
: undefined;
|
|
2559
|
+
columns.push({
|
|
2560
|
+
field: name,
|
|
2561
|
+
header,
|
|
2562
|
+
...(type ? { type } : {}),
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
return {
|
|
2566
|
+
...currentConfig,
|
|
2567
|
+
columns,
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
hydratePatchColumnsFromSchema(patch) {
|
|
2571
|
+
const patchColumns = patch.columns;
|
|
2572
|
+
if (!Array.isArray(patchColumns) || patchColumns.length === 0)
|
|
2573
|
+
return patch;
|
|
2574
|
+
const currentFields = new Set((this.getCurrentConfig().columns || [])
|
|
2575
|
+
.map((column) => String(column?.field || '').trim())
|
|
2576
|
+
.filter(Boolean));
|
|
2577
|
+
const schemaFieldsByName = new Map(this.getSchemaFields()
|
|
2578
|
+
.map((field) => [String(field['name'] || '').trim(), field])
|
|
2579
|
+
.filter(([name]) => !!name));
|
|
2580
|
+
const columns = patchColumns.map((column) => {
|
|
2581
|
+
const field = String(column?.field || '').trim();
|
|
2582
|
+
if (!field || currentFields.has(field))
|
|
2583
|
+
return column;
|
|
2584
|
+
const schemaField = schemaFieldsByName.get(field);
|
|
2585
|
+
if (!schemaField)
|
|
2586
|
+
return column;
|
|
2587
|
+
const header = typeof schemaField['label'] === 'string' && schemaField['label'].trim()
|
|
2588
|
+
? schemaField['label'].trim()
|
|
2589
|
+
: field;
|
|
2590
|
+
const type = typeof schemaField['type'] === 'string' && schemaField['type'].trim()
|
|
2591
|
+
? schemaField['type'].trim()
|
|
2592
|
+
: undefined;
|
|
2593
|
+
return {
|
|
2594
|
+
...column,
|
|
2595
|
+
...(!column.header ? { header } : {}),
|
|
2596
|
+
...(type && !column.type ? { type } : {}),
|
|
2597
|
+
};
|
|
2598
|
+
});
|
|
2599
|
+
return {
|
|
2600
|
+
...patch,
|
|
2601
|
+
columns,
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
createSnapshot() {
|
|
2605
|
+
return this.getCurrentConfig();
|
|
2606
|
+
}
|
|
2607
|
+
async restoreSnapshot(snapshot) {
|
|
2608
|
+
if (!snapshot)
|
|
2609
|
+
return;
|
|
2610
|
+
this.applyConfig(snapshot);
|
|
2611
|
+
}
|
|
2612
|
+
coerceTableRuntimeOperationBatch(source) {
|
|
2613
|
+
if (!source)
|
|
2614
|
+
return null;
|
|
2615
|
+
const textual = this.coerceTableRuntimeOperationBatchFromText(source);
|
|
2616
|
+
if (textual)
|
|
2617
|
+
return textual;
|
|
2618
|
+
const nestedPatch = this.toRecord(source['patch']);
|
|
2619
|
+
if (nestedPatch) {
|
|
2620
|
+
const nested = this.coerceTableRuntimeOperationBatch(nestedPatch);
|
|
2621
|
+
if (nested)
|
|
2622
|
+
return nested;
|
|
2623
|
+
}
|
|
2624
|
+
const nestedComponentEditPlan = this.toRecord(source['componentEditPlan'])
|
|
2625
|
+
?? (this.stringValue(source['type']) === 'componentEditPlan' ? source : null);
|
|
2626
|
+
if (nestedComponentEditPlan && nestedComponentEditPlan !== source) {
|
|
2627
|
+
const nested = this.coerceTableRuntimeOperationBatch(nestedComponentEditPlan);
|
|
2628
|
+
if (nested)
|
|
2629
|
+
return nested;
|
|
2630
|
+
}
|
|
2631
|
+
const raw = this.toRecord(source[TABLE_RUNTIME_OPERATION_PATCH_KEY])
|
|
2632
|
+
?? this.toRecord(source['runtimeOperationBatch'])
|
|
2633
|
+
?? this.toRecord(source['tableRuntimeOperation'])
|
|
2634
|
+
?? nestedComponentEditPlan;
|
|
2635
|
+
const directOperation = this.toRecord(source['runtimeOperation']);
|
|
2636
|
+
const directOperations = Array.isArray(source['runtimeOperations']) ? source['runtimeOperations'] : null;
|
|
2637
|
+
const operationCandidates = raw
|
|
2638
|
+
? raw['operations']
|
|
2639
|
+
: directOperation
|
|
2640
|
+
? [directOperation]
|
|
2641
|
+
: directOperations;
|
|
2642
|
+
if (!Array.isArray(operationCandidates))
|
|
2643
|
+
return null;
|
|
2644
|
+
const operations = operationCandidates
|
|
2645
|
+
.map((candidate) => this.coerceTableRuntimeOperation(candidate))
|
|
2646
|
+
.filter((operation) => !!operation);
|
|
2647
|
+
if (!operations.length)
|
|
2648
|
+
return null;
|
|
2649
|
+
return {
|
|
2650
|
+
kind: TABLE_RUNTIME_OPERATION_BATCH_KIND,
|
|
2651
|
+
operations,
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
coerceTableRuntimeOperationBatchFromText(source) {
|
|
2655
|
+
for (const key of ['message', 'assistantMessage', 'content', 'explanation']) {
|
|
2656
|
+
const text = this.stringValue(source[key]);
|
|
2657
|
+
if (!text || !text.includes(TABLE_RUNTIME_OPERATION_PATCH_KEY) || !text.includes('operationId')) {
|
|
2658
|
+
continue;
|
|
2659
|
+
}
|
|
2660
|
+
const parsed = this.parseRuntimeOperationJsonFromText(text);
|
|
2661
|
+
if (!parsed || parsed === source) {
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
const batch = this.coerceTableRuntimeOperationBatch(parsed);
|
|
2665
|
+
if (batch)
|
|
2666
|
+
return batch;
|
|
2667
|
+
}
|
|
2668
|
+
return null;
|
|
2669
|
+
}
|
|
2670
|
+
parseRuntimeOperationJsonFromText(text) {
|
|
2671
|
+
const candidates = this.extractJsonObjectCandidates(text);
|
|
2672
|
+
for (const candidate of candidates) {
|
|
2673
|
+
try {
|
|
2674
|
+
const parsed = JSON.parse(candidate);
|
|
2675
|
+
const record = this.toRecord(parsed);
|
|
2676
|
+
if (record)
|
|
2677
|
+
return record;
|
|
2678
|
+
}
|
|
2679
|
+
catch {
|
|
2680
|
+
continue;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
return null;
|
|
2684
|
+
}
|
|
2685
|
+
extractJsonObjectCandidates(text) {
|
|
2686
|
+
const candidates = [];
|
|
2687
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/iu);
|
|
2688
|
+
if (fenced?.[1]) {
|
|
2689
|
+
candidates.push(fenced[1].trim());
|
|
2690
|
+
}
|
|
2691
|
+
const firstBrace = text.indexOf('{');
|
|
2692
|
+
const lastBrace = text.lastIndexOf('}');
|
|
2693
|
+
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
2694
|
+
candidates.push(text.slice(firstBrace, lastBrace + 1).trim());
|
|
2695
|
+
}
|
|
2696
|
+
return candidates.filter((candidate, index, values) => !!candidate && values.indexOf(candidate) === index);
|
|
2697
|
+
}
|
|
2698
|
+
coerceTableRuntimeOperation(value) {
|
|
2699
|
+
const record = this.toRecord(value);
|
|
2700
|
+
if (!record)
|
|
2701
|
+
return null;
|
|
2702
|
+
const operationId = this.stringValue(record['operationId'] ?? record['type']);
|
|
2703
|
+
if (!this.isRuntimeOperationId(operationId))
|
|
2704
|
+
return null;
|
|
2705
|
+
const input = this.toRecord(record['input'] ?? record['params']) ?? {};
|
|
2706
|
+
return {
|
|
2707
|
+
operationId,
|
|
2708
|
+
input,
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
async applyPatch(patch, intent) {
|
|
2712
|
+
const runtimeOperations = this.coerceTableRuntimeOperationBatch(patch);
|
|
2713
|
+
if (runtimeOperations) {
|
|
2714
|
+
return this.executeTableRuntimeOperations(runtimeOperations);
|
|
2715
|
+
}
|
|
2716
|
+
const current = this.getCurrentConfig();
|
|
2717
|
+
const normalizedPatch = this.normalizePatch(patch);
|
|
2718
|
+
const nextConfig = this.smartMergeTableConfig(current, normalizedPatch);
|
|
2719
|
+
this.applyConfig(nextConfig);
|
|
2720
|
+
return { success: true };
|
|
2721
|
+
}
|
|
2722
|
+
async executeTableRuntimeOperations(batch) {
|
|
2723
|
+
const warnings = [];
|
|
2724
|
+
for (const operation of batch.operations) {
|
|
2725
|
+
if (operation.operationId === 'table.filter.apply') {
|
|
2726
|
+
const criteria = this.toRecord(operation.input['criteria']);
|
|
2727
|
+
if (!criteria) {
|
|
2728
|
+
return {
|
|
2729
|
+
success: false,
|
|
2730
|
+
error: 'A operacao table.filter.apply exige criterios de filtro.',
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
if (this.hasUnsupportedFilterCombinator(criteria)) {
|
|
2734
|
+
return {
|
|
2735
|
+
success: false,
|
|
2736
|
+
error: 'A operacao table.filter.apply aceita apenas criterios simples em conjunto. Expressoes booleanas compostas ainda nao fazem parte do contrato runtime da tabela.',
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
this.table.onAdvancedFilterSubmit(criteria);
|
|
2740
|
+
continue;
|
|
2741
|
+
}
|
|
2742
|
+
if (operation.operationId === 'table.export.run') {
|
|
2743
|
+
const format = this.stringValue(operation.input['format']).toLowerCase();
|
|
2744
|
+
if (!this.isExportFormat(format)) {
|
|
2745
|
+
return {
|
|
2746
|
+
success: false,
|
|
2747
|
+
error: 'A operacao table.export.run exige um formato de exportacao valido.',
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
if (!this.table.canRunExportAction(format)) {
|
|
2751
|
+
return {
|
|
2752
|
+
success: false,
|
|
2753
|
+
error: 'A operacao table.export.run exige exportacao habilitada e um formato permitido pela configuracao da tabela.',
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
const scope = this.stringValue(operation.input['scope']);
|
|
2757
|
+
await this.table.onExportAction({
|
|
2758
|
+
format,
|
|
2759
|
+
...(this.isExportScope(scope) ? { scope } : {}),
|
|
2760
|
+
});
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
if (operation.operationId === 'dynamicPage.surface.open') {
|
|
2764
|
+
const surfaceId = this.stringValue(operation.input['surfaceId'] ?? operation.input['id']);
|
|
2765
|
+
if (!surfaceId) {
|
|
2766
|
+
return {
|
|
2767
|
+
success: false,
|
|
2768
|
+
error: 'A operacao dynamicPage.surface.open exige surfaceId.',
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
const surface = this.getRecordSurfaceForOpenInput(operation.input);
|
|
2772
|
+
if (!surface) {
|
|
2773
|
+
return {
|
|
2774
|
+
success: false,
|
|
2775
|
+
error: 'A operacao dynamicPage.surface.open exige uma superficie declarada em recordSurfaces.',
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
const opened = this.table.requestRecordSurfaceOpen?.(surface);
|
|
2779
|
+
if (!opened) {
|
|
2780
|
+
return {
|
|
2781
|
+
success: false,
|
|
2782
|
+
error: 'A operacao dynamicPage.surface.open exige um registro selecionado e um host de pagina dinamica capaz de materializar a superficie.',
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
continue;
|
|
2786
|
+
}
|
|
2787
|
+
warnings.push(`Operacao runtime ignorada: ${operation.operationId}`);
|
|
2788
|
+
}
|
|
2789
|
+
return {
|
|
2790
|
+
success: true,
|
|
2791
|
+
...(warnings.length ? { warnings } : {}),
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
isRuntimeOperationId(value) {
|
|
2795
|
+
return TABLE_RUNTIME_OPERATION_IDS.includes(value);
|
|
2796
|
+
}
|
|
2797
|
+
isExportFormat(value) {
|
|
2798
|
+
return TABLE_EXPORT_FORMATS.includes(value);
|
|
2799
|
+
}
|
|
2800
|
+
isExportScope(value) {
|
|
2801
|
+
return TABLE_EXPORT_SCOPES.includes(value);
|
|
2802
|
+
}
|
|
2803
|
+
getRecordSurfaceById(surfaceId) {
|
|
2804
|
+
const normalized = surfaceId.trim().toLowerCase();
|
|
2805
|
+
if (!normalized)
|
|
2806
|
+
return null;
|
|
2807
|
+
const recordSurfaces = this.getRecordSurfaceAuthoringContext();
|
|
2808
|
+
const surfaces = Array.isArray(recordSurfaces?.['surfaces']) ? recordSurfaces['surfaces'] : [];
|
|
2809
|
+
return surfaces
|
|
2810
|
+
.map((surface) => this.toRecord(surface))
|
|
2811
|
+
.find((surface) => this.stringValue(surface?.['id']).toLowerCase() === normalized) ?? null;
|
|
2812
|
+
}
|
|
2813
|
+
getRecordSurfaceForOpenInput(input) {
|
|
2814
|
+
const surfaceId = this.stringValue(input['surfaceId'] ?? input['id']);
|
|
2815
|
+
const exact = this.getRecordSurfaceById(surfaceId);
|
|
2816
|
+
if (exact)
|
|
2817
|
+
return exact;
|
|
2818
|
+
const aliases = [
|
|
2819
|
+
surfaceId,
|
|
2820
|
+
this.stringValue(input['surfaceLabel']),
|
|
2821
|
+
this.stringValue(input['label']),
|
|
2822
|
+
this.stringValue(input['title']),
|
|
2823
|
+
]
|
|
2824
|
+
.map((value) => this.normalizeRecordSurfaceAlias(value))
|
|
2825
|
+
.filter(Boolean);
|
|
2826
|
+
if (!aliases.length)
|
|
2827
|
+
return null;
|
|
2828
|
+
const recordSurfaces = this.getRecordSurfaceAuthoringContext();
|
|
2829
|
+
const surfaces = Array.isArray(recordSurfaces?.['surfaces']) ? recordSurfaces['surfaces'] : [];
|
|
2830
|
+
const matches = surfaces
|
|
2831
|
+
.map((surface) => this.toRecord(surface))
|
|
2832
|
+
.filter((surface) => !!surface && aliases.some((alias) => this.recordSurfaceAliases(surface).has(alias)));
|
|
2833
|
+
return matches.length === 1 ? matches[0] : null;
|
|
2834
|
+
}
|
|
2835
|
+
recordSurfaceAliases(surface) {
|
|
2836
|
+
const aliases = new Set();
|
|
2837
|
+
const resourceSurface = this.toRecord(surface['resourceSurface']);
|
|
2838
|
+
const add = (value) => {
|
|
2839
|
+
const normalized = this.normalizeRecordSurfaceAlias(value);
|
|
2840
|
+
if (!normalized)
|
|
2841
|
+
return;
|
|
2842
|
+
aliases.add(normalized);
|
|
2843
|
+
aliases.add(this.normalizeRecordSurfaceAlias(`open-${normalized}`));
|
|
2844
|
+
};
|
|
2845
|
+
add(surface['id']);
|
|
2846
|
+
add(surface['label']);
|
|
2847
|
+
add(surface['description']);
|
|
2848
|
+
add(resourceSurface?.['id']);
|
|
2849
|
+
add(resourceSurface?.['title']);
|
|
2850
|
+
add(resourceSurface?.['description']);
|
|
2851
|
+
return aliases;
|
|
2852
|
+
}
|
|
2853
|
+
normalizeRecordSurfaceAlias(value) {
|
|
2854
|
+
return this.stringValue(value)
|
|
2855
|
+
.normalize('NFD')
|
|
2856
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
2857
|
+
.toLowerCase()
|
|
2858
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
2859
|
+
.replace(/-+/g, '-')
|
|
2860
|
+
.replace(/^-|-$/g, '');
|
|
2861
|
+
}
|
|
2862
|
+
hydrateRecordSurfaceRowActionPlans(plans) {
|
|
2863
|
+
if (!plans?.length)
|
|
2864
|
+
return plans;
|
|
2865
|
+
return plans.map((plan) => this.hydrateRecordSurfaceRowActionPlan(plan));
|
|
2866
|
+
}
|
|
2867
|
+
hydrateRecordSurfaceRowActionPlan(plan) {
|
|
2868
|
+
const operationId = this.stringValue(plan.operationId);
|
|
2869
|
+
const changeKind = this.stringValue(plan.changeKind);
|
|
2870
|
+
if (operationId !== 'rowAction.add' && changeKind !== 'add_row_action')
|
|
2871
|
+
return plan;
|
|
2872
|
+
const input = this.toRecord(plan.input) ?? this.toRecord(plan.value);
|
|
2873
|
+
if (!input)
|
|
2874
|
+
return plan;
|
|
2875
|
+
const nestedValue = this.toRecord(input['value']);
|
|
2876
|
+
const declaredSurfaceId = this.stringValue(this.toRecord(input['recordSurface'])?.['id'])
|
|
2877
|
+
|| this.stringValue(this.toRecord(nestedValue?.['recordSurface'])?.['id']);
|
|
2878
|
+
const actionId = this.stringValue(input['action'])
|
|
2879
|
+
|| this.stringValue(this.toRecord(input['globalAction'])?.['actionId']);
|
|
2880
|
+
const surfaceEntry = declaredSurfaceId
|
|
2881
|
+
? this.getRecordSurfaceById(declaredSurfaceId)
|
|
2882
|
+
: actionId === 'surface.open'
|
|
2883
|
+
? this.getRecordSurfaceForOpenInput(input)
|
|
2884
|
+
: null;
|
|
2885
|
+
if (!surfaceEntry)
|
|
2886
|
+
return plan;
|
|
2887
|
+
const resourceSurface = this.toRecord(surfaceEntry?.['resourceSurface']) ?? surfaceEntry;
|
|
2888
|
+
if (!resourceSurface)
|
|
2889
|
+
return plan;
|
|
2890
|
+
const surfaceId = this.stringValue(surfaceEntry['id'])
|
|
2891
|
+
|| this.stringValue(resourceSurface['id'])
|
|
2892
|
+
|| declaredSurfaceId;
|
|
2893
|
+
const label = this.stringValue(input['label'])
|
|
2894
|
+
|| this.stringValue(surfaceEntry?.['label'])
|
|
2895
|
+
|| this.stringValue(resourceSurface['title'])
|
|
2896
|
+
|| surfaceId;
|
|
2897
|
+
const rowAction = {
|
|
2898
|
+
...input,
|
|
2899
|
+
id: this.stringValue(input['id']) || `open-${surfaceId}`,
|
|
2900
|
+
label,
|
|
2901
|
+
action: this.stringValue(input['action']) || 'surface.open',
|
|
2902
|
+
icon: this.stringValue(input['icon']) || 'open_in_new',
|
|
2903
|
+
recordSurface: resourceSurface,
|
|
2904
|
+
};
|
|
2905
|
+
return {
|
|
2906
|
+
...plan,
|
|
2907
|
+
input: rowAction,
|
|
2908
|
+
value: rowAction,
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
toRecord(value) {
|
|
2912
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
2913
|
+
? value
|
|
2914
|
+
: null;
|
|
2915
|
+
}
|
|
2916
|
+
hasUnsupportedFilterCombinator(criteria) {
|
|
2917
|
+
return Object.entries(criteria).some(([key, value]) => {
|
|
2918
|
+
if (!TABLE_UNSUPPORTED_FILTER_COMBINATOR_KEYS.has(key))
|
|
2919
|
+
return false;
|
|
2920
|
+
return Array.isArray(value) || this.toRecord(value) !== null;
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
stringValue(value) {
|
|
2924
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
2925
|
+
}
|
|
2926
|
+
compactStringArray(value) {
|
|
2927
|
+
if (!Array.isArray(value)) {
|
|
2928
|
+
return [];
|
|
2929
|
+
}
|
|
2930
|
+
return value
|
|
2931
|
+
.map((item) => this.stringValue(item))
|
|
2932
|
+
.filter((item, index, items) => !!item && items.indexOf(item) === index);
|
|
2933
|
+
}
|
|
2934
|
+
normalizeString(value) {
|
|
2935
|
+
if (typeof value !== 'string') {
|
|
2936
|
+
return null;
|
|
2937
|
+
}
|
|
2938
|
+
const normalized = value.trim();
|
|
2939
|
+
return normalized ? normalized : null;
|
|
2940
|
+
}
|
|
2941
|
+
// -------- Context & suggestions --------
|
|
2942
|
+
/**
|
|
2943
|
+
* Human-friendly summary for prompt/context (columns + feature flags + data stats).
|
|
2944
|
+
*/
|
|
2945
|
+
getContextDescription(contextOverride) {
|
|
2946
|
+
// 1. Generate Data Profile
|
|
2947
|
+
const data = this.table.dataSource.data || [];
|
|
2948
|
+
const effectiveConfig = contextOverride ? deepMerge(this.table.config, contextOverride) : this.table.config;
|
|
2949
|
+
const profile = TableDataProfiler.profile(data, effectiveConfig);
|
|
2950
|
+
// 2. Format Column Stats
|
|
2951
|
+
const colDetails = Object.values(profile.columns).map(stats => {
|
|
2952
|
+
let desc = `${stats.field} (${stats.inferredType})`;
|
|
2953
|
+
if (stats.cardinality > 0 && stats.cardinality < 10) {
|
|
2954
|
+
desc += `, values=[${stats.topValues.join(', ')}]`;
|
|
2955
|
+
}
|
|
2956
|
+
else if (stats.inferredType === 'number' && stats.min !== undefined) {
|
|
2957
|
+
desc += `, range=[${stats.min}-${stats.max}]`;
|
|
2958
|
+
}
|
|
2959
|
+
// Check existing config to see if it has renderer/format
|
|
2960
|
+
const colCfg = effectiveConfig.columns?.find(c => c.field === stats.field);
|
|
2961
|
+
if (colCfg?.renderer)
|
|
2962
|
+
desc += ` [has-renderer: ${colCfg.renderer.type}]`;
|
|
2963
|
+
if (colCfg?.format)
|
|
2964
|
+
desc += ` [has-format]`;
|
|
2965
|
+
return desc;
|
|
2966
|
+
});
|
|
2967
|
+
// 3. Active Features
|
|
2968
|
+
const behaviorFlags = [];
|
|
2969
|
+
const b = this.table.config?.behavior;
|
|
2970
|
+
if (b?.filtering?.enabled)
|
|
2971
|
+
behaviorFlags.push('filtering');
|
|
2972
|
+
if (b?.sorting?.enabled)
|
|
2973
|
+
behaviorFlags.push('sorting');
|
|
2974
|
+
if (b?.pagination?.enabled)
|
|
2975
|
+
behaviorFlags.push('pagination');
|
|
2976
|
+
if (b?.selection?.enabled)
|
|
2977
|
+
behaviorFlags.push('selection');
|
|
2978
|
+
return `
|
|
2979
|
+
Dataset: ${profile.rowCount} rows.
|
|
2980
|
+
Active Features: ${behaviorFlags.join(', ') || 'None'}.
|
|
2981
|
+
Columns Analysis:
|
|
2982
|
+
- ${colDetails.join('\n- ')}
|
|
2983
|
+
`.trim();
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Dynamic suggestions based on current config/data.
|
|
2987
|
+
* Uses AI Service if available, otherwise falls back to heuristics.
|
|
2988
|
+
*/
|
|
2989
|
+
async getSuggestions(forceReload = false) {
|
|
2990
|
+
const storage = this.table?.asyncConfigStorage;
|
|
2991
|
+
const key = this.suggestionsKey();
|
|
2992
|
+
// 1. Try cache
|
|
2993
|
+
if (storage && !forceReload) {
|
|
2994
|
+
try {
|
|
2995
|
+
const cached = await firstValueFrom(storage.loadConfig(key));
|
|
2996
|
+
if (cached && Array.isArray(cached) && cached.length) {
|
|
2997
|
+
return cached;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
catch { }
|
|
3001
|
+
}
|
|
3002
|
+
let suggestions = [];
|
|
3003
|
+
// 2. Clear cache explicitly when forcing reload
|
|
3004
|
+
if (forceReload && storage && typeof storage.clearConfig === 'function') {
|
|
3005
|
+
try {
|
|
3006
|
+
await firstValueFrom(storage.clearConfig(key));
|
|
3007
|
+
}
|
|
3008
|
+
catch { }
|
|
3009
|
+
}
|
|
3010
|
+
// 3. Try AI Generation (if service available)
|
|
3011
|
+
if (this.aiService) {
|
|
3012
|
+
try {
|
|
3013
|
+
const filtered = this.filterCompletedColumnsFromContext();
|
|
3014
|
+
suggestions = await this.generateAiSuggestions(filtered);
|
|
3015
|
+
}
|
|
3016
|
+
catch (err) {
|
|
3017
|
+
console.warn('[TableAiAdapter] AI suggestion failed, falling back to heuristics', err);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
// 4. Fallback to Heuristics if AI failed or not available
|
|
3021
|
+
if (!suggestions.length) {
|
|
3022
|
+
suggestions = this.getHeuristicSuggestions();
|
|
3023
|
+
}
|
|
3024
|
+
// 5. Save to cache
|
|
3025
|
+
const sorted = suggestions.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
3026
|
+
if (storage && sorted.length > 0) {
|
|
3027
|
+
try {
|
|
3028
|
+
await firstValueFrom(storage.saveConfig(key, sorted));
|
|
3029
|
+
}
|
|
3030
|
+
catch { }
|
|
3031
|
+
}
|
|
3032
|
+
return sorted;
|
|
3033
|
+
}
|
|
3034
|
+
async generateAiSuggestions(contextOverride) {
|
|
3035
|
+
if (!this.aiService)
|
|
3036
|
+
return [];
|
|
3037
|
+
const prompt = `
|
|
3038
|
+
Analyze the following data table context and stats.
|
|
3039
|
+
Suggest 3 to 5 specific UX/UI improvements based on the data distribution and active features.
|
|
3040
|
+
|
|
3041
|
+
Guidelines for Suggestions:
|
|
3042
|
+
- Focus on: Data formatting (dates/currency), Visual indicators (badges/icons for low-cardinality or mapped fields), Filter types, and missing critical features (pagination/export).
|
|
3043
|
+
- Prefer explicit metadata (column types, value mappings, data stats). Avoid guessing semantics from field names.
|
|
3044
|
+
- Intents MUST be actionable user commands, not just labels.
|
|
3045
|
+
|
|
3046
|
+
Intent Examples:
|
|
3047
|
+
[GOOD]: "Format column 'birthDate' as dd/MM/yyyy", "Use badges for column 'state' (3 distinct values)", "Enable pagination with 10 rows".
|
|
3048
|
+
[BAD]: "Improve formatting", "Add badges", "Pagination settings".
|
|
3049
|
+
|
|
3050
|
+
Context:
|
|
3051
|
+
${this.getContextDescription(contextOverride)}
|
|
3052
|
+
|
|
3053
|
+
Return a JSON array of suggestions with: id, label, description, icon, group, intent (the actionable command), and score (0-1).
|
|
3054
|
+
`;
|
|
3055
|
+
const result = await firstValueFrom(this.aiService.generateJson(prompt, undefined, {
|
|
3056
|
+
type: 'OBJECT',
|
|
3057
|
+
properties: {
|
|
3058
|
+
suggestions: {
|
|
3059
|
+
type: 'ARRAY',
|
|
3060
|
+
items: {
|
|
3061
|
+
type: 'OBJECT',
|
|
3062
|
+
properties: {
|
|
3063
|
+
id: { type: 'STRING' },
|
|
3064
|
+
label: { type: 'STRING' },
|
|
3065
|
+
description: { type: 'STRING' },
|
|
3066
|
+
icon: { type: 'STRING' },
|
|
3067
|
+
group: { type: 'STRING' },
|
|
3068
|
+
intent: { type: 'STRING' },
|
|
3069
|
+
score: { type: 'NUMBER' }
|
|
3070
|
+
},
|
|
3071
|
+
required: ['id', 'label', 'intent']
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
}));
|
|
3076
|
+
return result?.suggestions || [];
|
|
3077
|
+
}
|
|
3078
|
+
getHeuristicSuggestions() {
|
|
3079
|
+
const suggestions = [];
|
|
3080
|
+
const config = this.table.config;
|
|
3081
|
+
const data = this.table.dataSource.data || [];
|
|
3082
|
+
const columns = config.columns || [];
|
|
3083
|
+
const profile = TableDataProfiler.profile(data, config);
|
|
3084
|
+
const profileColumns = profile.columns || {};
|
|
3085
|
+
const capabilities = this.getCapabilities?.() || [];
|
|
3086
|
+
const categorySet = new Set(capabilities
|
|
3087
|
+
.map((cap) => String(cap?.category || '').toLowerCase())
|
|
3088
|
+
.filter((cat) => cat.length > 0));
|
|
3089
|
+
const allowCategory = (categories) => {
|
|
3090
|
+
if (!categorySet.size)
|
|
3091
|
+
return false;
|
|
3092
|
+
return categories.some((cat) => categorySet.has(cat));
|
|
3093
|
+
};
|
|
3094
|
+
// Pagination if many rows and disabled
|
|
3095
|
+
if (allowCategory(['pagination', 'behavior']) && !config.behavior?.pagination?.enabled && data.length > 20) {
|
|
3096
|
+
suggestions.push({
|
|
3097
|
+
id: 'enable-paging',
|
|
3098
|
+
label: 'Habilitar Paginação',
|
|
3099
|
+
description: `Tabela tem ${data.length} linhas. Paginação melhora a performance.`,
|
|
3100
|
+
icon: 'list_alt',
|
|
3101
|
+
group: 'Performance',
|
|
3102
|
+
intent: 'Habilitar paginação com 10 itens por página',
|
|
3103
|
+
score: 0.9
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
// Modo compacto se muitas colunas visíveis
|
|
3107
|
+
const visibleCols = columns.filter((c) => c.visible !== false).length;
|
|
3108
|
+
if (allowCategory(['appearance']) && visibleCols > 8 && config.appearance?.density !== 'compact') {
|
|
3109
|
+
suggestions.push({
|
|
3110
|
+
id: 'compact-mode',
|
|
3111
|
+
label: 'Modo Compacto',
|
|
3112
|
+
description: 'Muitas colunas visíveis. Modo compacto exibe mais dados.',
|
|
3113
|
+
icon: 'compress',
|
|
3114
|
+
group: 'Visual',
|
|
3115
|
+
intent: 'Usar densidade compacta',
|
|
3116
|
+
score: 0.8
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
// Badge/Icon para valores discretos
|
|
3120
|
+
const badgeCardinalityMax = 6;
|
|
3121
|
+
const badgeCol = columns.find((col) => {
|
|
3122
|
+
if (!col || col.renderer || !col.field)
|
|
3123
|
+
return false;
|
|
3124
|
+
const stats = profileColumns[col.field];
|
|
3125
|
+
const inferredType = stats?.inferredType;
|
|
3126
|
+
const cardinality = stats?.cardinality ?? -1;
|
|
3127
|
+
const isLongText = stats?.isLongText ?? false;
|
|
3128
|
+
const hasValueMapping = !!col.valueMapping && Object.keys(col.valueMapping).length > 0;
|
|
3129
|
+
const typeMatch = col.type === 'boolean'
|
|
3130
|
+
|| col.type === 'string'
|
|
3131
|
+
|| inferredType === 'boolean'
|
|
3132
|
+
|| inferredType === 'string';
|
|
3133
|
+
const lowCardinality = cardinality > 0 && cardinality <= badgeCardinalityMax;
|
|
3134
|
+
return !isLongText && (hasValueMapping || (typeMatch && lowCardinality));
|
|
3135
|
+
});
|
|
3136
|
+
if (allowCategory(['renderer', 'columns']) && badgeCol) {
|
|
3137
|
+
suggestions.push({
|
|
3138
|
+
id: 'status-badge',
|
|
3139
|
+
label: `Badges para ${badgeCol.header || badgeCol.field}`,
|
|
3140
|
+
description: 'Destacar valores com badges ou ícones.',
|
|
3141
|
+
icon: 'verified',
|
|
3142
|
+
group: 'Visual',
|
|
3143
|
+
intent: `Usar renderer badge para a coluna ${badgeCol.field}`,
|
|
3144
|
+
score: 0.7
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
// Datas sem format
|
|
3148
|
+
const dateCol = columns.find((c) => c.type === 'date' && !c.format);
|
|
3149
|
+
if (allowCategory(['format', 'columns']) && dateCol) {
|
|
3150
|
+
suggestions.push({
|
|
3151
|
+
id: 'date-format',
|
|
3152
|
+
label: `Formatar ${dateCol.header || dateCol.field}`,
|
|
3153
|
+
description: 'Padronizar exibição de data.',
|
|
3154
|
+
icon: 'calendar_today',
|
|
3155
|
+
group: 'Formatação',
|
|
3156
|
+
intent: `Formatar coluna ${dateCol.field} como dd/MM/yyyy`,
|
|
3157
|
+
score: 0.6
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
return suggestions;
|
|
3161
|
+
}
|
|
3162
|
+
/**
|
|
3163
|
+
* Remove colunas já formatadas/tipadas para evitar sugestões redundantes (ex.: salário já em currency)
|
|
3164
|
+
*/
|
|
3165
|
+
filterCompletedColumnsFromContext() {
|
|
3166
|
+
const cfg = this.getCurrentConfig();
|
|
3167
|
+
const filteredCols = (cfg.columns || []).filter((col) => {
|
|
3168
|
+
// Keep if missing type or format (needs suggestion)
|
|
3169
|
+
if (!col.type)
|
|
3170
|
+
return true;
|
|
3171
|
+
if (col.type === 'currency' && col.format)
|
|
3172
|
+
return false;
|
|
3173
|
+
if (col.type === 'date' && col.format)
|
|
3174
|
+
return false;
|
|
3175
|
+
return true;
|
|
3176
|
+
});
|
|
3177
|
+
return { ...cfg, columns: filteredCols };
|
|
3178
|
+
}
|
|
3179
|
+
// -------- Two-step flow helpers --------
|
|
3180
|
+
getFilteredCapabilities(category) {
|
|
3181
|
+
const all = TABLE_AI_CAPABILITIES.capabilities;
|
|
3182
|
+
const editPlanCaps = getTableComponentEditPlanCapabilities();
|
|
3183
|
+
if (!category || category === 'unknown')
|
|
3184
|
+
return [...all, ...editPlanCaps];
|
|
3185
|
+
const categoryMap = {
|
|
3186
|
+
columns: ['columns', 'format', 'mapping', 'renderer', 'conditional'],
|
|
3187
|
+
appearance: ['appearance', 'conditional'],
|
|
3188
|
+
conditional: ['conditional', 'renderer', 'columns'], // Include 'columns' to allow field context
|
|
3189
|
+
behavior: ['behavior', 'pagination', 'sorting', 'filtering', 'selection', 'interaction'],
|
|
3190
|
+
actions: ['actions', 'toolbar', 'export']
|
|
3191
|
+
};
|
|
3192
|
+
const targets = categoryMap[category] || [category];
|
|
3193
|
+
return [
|
|
3194
|
+
...all.filter((c) => targets.includes(c.category)),
|
|
3195
|
+
...editPlanCaps.filter((c) => targets.includes(c.category)),
|
|
3196
|
+
];
|
|
3197
|
+
}
|
|
3198
|
+
suggestionsKey() {
|
|
3199
|
+
const id = this.table.tableId || 'default';
|
|
3200
|
+
const rp = this.table.resourcePath || 'default';
|
|
3201
|
+
// bump version to invalidate old cached suggestions
|
|
3202
|
+
return `ai-suggestions:v4:${id}:${rp}`;
|
|
3203
|
+
}
|
|
3204
|
+
getColumnNames() {
|
|
3205
|
+
return (this.table.config?.columns || [])
|
|
3206
|
+
.map((c) => c.field)
|
|
3207
|
+
.filter((f) => !!f);
|
|
3208
|
+
}
|
|
3209
|
+
extractContextForIntent(intent) {
|
|
3210
|
+
const fullConfig = this.getCurrentConfig();
|
|
3211
|
+
if ((intent.category === 'columns' || intent.category === 'conditional') && intent.targetField) {
|
|
3212
|
+
const col = fullConfig.columns?.find((c) => c.field === intent.targetField);
|
|
3213
|
+
if (col) {
|
|
3214
|
+
return { desc: `Contexto focado na coluna '${intent.targetField}'.`, config: { columns: [col] } };
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
if (intent.category === 'behavior' && fullConfig.behavior) {
|
|
3218
|
+
return { desc: 'Contexto focado em Comportamento (pagination/sorting/filtering/selection).', config: { behavior: fullConfig.behavior } };
|
|
3219
|
+
}
|
|
3220
|
+
if (intent.category === 'appearance' && fullConfig.appearance) {
|
|
3221
|
+
return { desc: 'Contexto focado em Aparência.', config: { appearance: fullConfig.appearance } };
|
|
3222
|
+
}
|
|
3223
|
+
if (intent.category === 'actions' && (fullConfig.actions || fullConfig.toolbar)) {
|
|
3224
|
+
return { desc: 'Contexto focado em Ações e Toolbar.', config: { actions: fullConfig.actions, toolbar: fullConfig.toolbar } };
|
|
3225
|
+
}
|
|
3226
|
+
return { desc: 'Contexto completo da tabela.', config: fullConfig };
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* Two-step flow: classify intent, build focused context, call enriched prompt.
|
|
3230
|
+
* Returns the patch WITHOUT applying, so the UI can review.
|
|
3231
|
+
*/
|
|
3232
|
+
async processUserIntent(userInput, aiService // Use interface type
|
|
3233
|
+
) {
|
|
3234
|
+
const classification = await firstValueFrom(aiService.classifyIntent(userInput, this.getColumnNames()));
|
|
3235
|
+
if (classification?.needsClarification) {
|
|
3236
|
+
return {
|
|
3237
|
+
type: 'clarification',
|
|
3238
|
+
message: 'Preciso de mais detalhes para continuar.',
|
|
3239
|
+
options: classification.options || []
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
// Handle QA intent
|
|
3243
|
+
if (classification?.intent === 'ask_about_config') {
|
|
3244
|
+
const answer = await firstValueFrom(aiService.answerQuestion(userInput, this.getCurrentConfig()));
|
|
3245
|
+
return {
|
|
3246
|
+
type: 'info',
|
|
3247
|
+
message: answer,
|
|
3248
|
+
explanation: answer // Map to explanation for UI compatibility if needed, or UI should handle 'info' type
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3251
|
+
const context = this.extractContextForIntent(classification);
|
|
3252
|
+
const caps = this.getFilteredCapabilities(classification?.category);
|
|
3253
|
+
const result = await firstValueFrom(aiService.executeEnrichedPrompt(userInput, context.desc, context.config, caps));
|
|
3254
|
+
const componentEditPlans = this.hydrateRecordSurfaceRowActionPlans(coerceTableComponentEditPlans(result));
|
|
3255
|
+
if (componentEditPlans) {
|
|
3256
|
+
const compiled = compileTableComponentEditPlans(componentEditPlans, this.getCompileConfig());
|
|
3257
|
+
if (compiled.patch) {
|
|
3258
|
+
const patch = this.hydratePatchColumnsFromSchema(compiled.patch);
|
|
3259
|
+
return {
|
|
3260
|
+
patch: this.normalizePatch(patch),
|
|
3261
|
+
explanation: result.explanation || compiled.explanation,
|
|
3262
|
+
warnings: compiled.warnings,
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
return {
|
|
3266
|
+
type: 'error',
|
|
3267
|
+
message: 'O plano de edicao da tabela nao passou na validacao de capacidades.',
|
|
3268
|
+
warnings: [...compiled.warnings, ...compiled.failureCodes],
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
if (!result || !result.patch) {
|
|
3272
|
+
return { type: 'error', message: 'Nenhum patch gerado.' };
|
|
3273
|
+
}
|
|
3274
|
+
const { sanitized, warnings } = this.validateAndSanitizePatch(result.patch, caps);
|
|
3275
|
+
// Passar warnings para o retorno, para que o UI possa mostrá-los
|
|
3276
|
+
// Se o patch estiver vazio após sanitização, informar ao usuário
|
|
3277
|
+
// Se sobrou algo válido
|
|
3278
|
+
if (Object.keys(sanitized).length > 0) {
|
|
3279
|
+
const explanation = result.explanation + (warnings.length ? ` (Ajustado: ${warnings.length} itens inválidos removidos)` : '');
|
|
3280
|
+
return { patch: sanitized, explanation, warnings };
|
|
3281
|
+
}
|
|
3282
|
+
else {
|
|
3283
|
+
return { type: 'error', message: 'O patch gerado não continha configurações válidas permitidas.', warnings };
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
validateAndSanitizePatch(patch, allowedCaps) {
|
|
3287
|
+
const allowedPaths = new Set(allowedCaps.map(c => c.path));
|
|
3288
|
+
const warnings = [];
|
|
3289
|
+
// Some schema-driven nodes are intentionally open objects and must be preserved as-is.
|
|
3290
|
+
const passthroughObjectPaths = new Set([
|
|
3291
|
+
'behavior.expansion.detail.source.inlineSchema',
|
|
3292
|
+
'behavior.expansion.detail.source.resourcePath.paramsMap',
|
|
3293
|
+
]);
|
|
3294
|
+
const jsonLogicObjectPaths = new Set([
|
|
3295
|
+
'columns[].computed.expression',
|
|
3296
|
+
'columns[].conditionalStyles[].condition',
|
|
3297
|
+
'columns[].conditionalRenderers[].condition',
|
|
3298
|
+
'rowConditionalStyles[].condition',
|
|
3299
|
+
'toolbar.actions[].visibleWhen',
|
|
3300
|
+
'toolbar.actions[].children[].visibleWhen',
|
|
3301
|
+
'actions.row.actions[].visibleWhen',
|
|
3302
|
+
'actions.row.actions[].disabledWhen',
|
|
3303
|
+
'columns[].renderer.button.disabledCondition',
|
|
3304
|
+
'columns[].renderer.toggle.disabledCondition',
|
|
3305
|
+
'columns[].conditionalRenderers[].renderer.button.disabledCondition',
|
|
3306
|
+
'columns[].conditionalRenderers[].renderer.toggle.disabledCondition',
|
|
3307
|
+
]);
|
|
3308
|
+
const recurse = (obj, currentPath) => {
|
|
3309
|
+
if (typeof obj !== 'object' || obj === null)
|
|
3310
|
+
return obj;
|
|
3311
|
+
if (Array.isArray(obj)) {
|
|
3312
|
+
// Se estamos em um array conhecido (ex: columns), o path vira columns[]
|
|
3313
|
+
// Se não, assume que é valor final (ex: options: [1,2])
|
|
3314
|
+
const isRootArray = currentPath && (allowedPaths.has(currentPath + '[]') || Array.from(allowedPaths).some(p => p.startsWith(currentPath + '[].')));
|
|
3315
|
+
if (!isRootArray)
|
|
3316
|
+
return obj; // É um valor array (ex: tags), retorna direto
|
|
3317
|
+
return obj.map(item => recurse(item, currentPath + '[]'));
|
|
3318
|
+
}
|
|
3319
|
+
const cleanObj = {};
|
|
3320
|
+
for (const key of Object.keys(obj)) {
|
|
3321
|
+
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
3322
|
+
// Verifica se este path exato é permitido OU se é um prefixo de algo permitido
|
|
3323
|
+
// Ex: 'columns' é permitido se 'columns[].width' existe
|
|
3324
|
+
// Special case: 'columns[].field' is identity, always allow it if we are inside columns[]
|
|
3325
|
+
const isIdentityField = currentPath === 'columns[]' && key === 'field';
|
|
3326
|
+
const exactMatch = allowedPaths.has(newPath) || isIdentityField;
|
|
3327
|
+
const prefixMatch = Array.from(allowedPaths).some(p => p.startsWith(newPath + '.') || p.startsWith(newPath + '['));
|
|
3328
|
+
if (exactMatch || prefixMatch) {
|
|
3329
|
+
if (exactMatch
|
|
3330
|
+
&& (passthroughObjectPaths.has(newPath) || jsonLogicObjectPaths.has(newPath))
|
|
3331
|
+
&& typeof obj[key] === 'object'
|
|
3332
|
+
&& obj[key] !== null
|
|
3333
|
+
&& !Array.isArray(obj[key])) {
|
|
3334
|
+
cleanObj[key] = obj[key];
|
|
3335
|
+
continue;
|
|
3336
|
+
}
|
|
3337
|
+
if (newPath === 'columns[].computed.expression') {
|
|
3338
|
+
if (!obj[key] || typeof obj[key] !== 'object' || Array.isArray(obj[key])) {
|
|
3339
|
+
warnings.push(`Computed expression inválida: ${newPath} (deve ser Json Logic)`);
|
|
3340
|
+
continue;
|
|
3341
|
+
}
|
|
3342
|
+
if (Object.keys(obj[key]).length === 0) {
|
|
3343
|
+
warnings.push(`Computed expression vazia: ${newPath}`);
|
|
3344
|
+
continue;
|
|
3345
|
+
}
|
|
3346
|
+
cleanObj[key] = obj[key];
|
|
3347
|
+
continue;
|
|
3348
|
+
}
|
|
3349
|
+
const val = recurse(obj[key], newPath);
|
|
3350
|
+
// Se for objeto vazio após limpeza, não inclui (salvo se for intenção explicita de limpar config, mas patch geralmente é aditivo)
|
|
3351
|
+
if (typeof val === 'object' && val !== null && !Array.isArray(val) && Object.keys(val).length === 0) {
|
|
3352
|
+
// ignora
|
|
3353
|
+
}
|
|
3354
|
+
else {
|
|
3355
|
+
cleanObj[key] = val;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
else {
|
|
3359
|
+
warnings.push(`Campo ignorado: ${newPath}`);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
return cleanObj;
|
|
3363
|
+
};
|
|
3364
|
+
const sanitized = recurse(patch, '');
|
|
3365
|
+
return { sanitized, warnings };
|
|
3366
|
+
}
|
|
3367
|
+
// -------- Internal helpers --------
|
|
3368
|
+
/**
|
|
3369
|
+
* Specialized merge for TableConfig that handles Array reconciliation safely.
|
|
3370
|
+
* - 'columns': merges by 'field' ID.
|
|
3371
|
+
*/
|
|
3372
|
+
smartMergeTableConfig(base, patch) {
|
|
3373
|
+
const result = deepMerge(base, patch);
|
|
3374
|
+
if (patch.columns && Array.isArray(patch.columns)) {
|
|
3375
|
+
const originalCols = base.columns || [];
|
|
3376
|
+
const patchCols = patch.columns;
|
|
3377
|
+
const mergedCols = originalCols.map((origCol) => {
|
|
3378
|
+
const match = patchCols.find((p) => p.field === origCol.field);
|
|
3379
|
+
return match ? deepMerge(origCol, match) : origCol;
|
|
3380
|
+
});
|
|
3381
|
+
patchCols.forEach((pCol) => {
|
|
3382
|
+
if (!originalCols.find((o) => o.field === pCol.field)) {
|
|
3383
|
+
mergedCols.push(pCol);
|
|
3384
|
+
}
|
|
3385
|
+
});
|
|
3386
|
+
result.columns = mergedCols;
|
|
3387
|
+
}
|
|
3388
|
+
return result;
|
|
3389
|
+
}
|
|
3390
|
+
applyConfig(config) {
|
|
3391
|
+
const prev = this.table.config;
|
|
3392
|
+
this.table.config = config;
|
|
3393
|
+
this.table.ngOnChanges({
|
|
3394
|
+
config: {
|
|
3395
|
+
previousValue: prev,
|
|
3396
|
+
currentValue: config,
|
|
3397
|
+
firstChange: false,
|
|
3398
|
+
isFirstChange: () => false
|
|
3399
|
+
}
|
|
3400
|
+
});
|
|
3401
|
+
this.table.configChange?.emit?.(config);
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* If a patch adds a typed format, ensure the column type is set so the
|
|
3405
|
+
* formatter runs. Avoid overriding an explicit type.
|
|
3406
|
+
*/
|
|
3407
|
+
normalizePatch(patch) {
|
|
3408
|
+
if (!patch.columns || !Array.isArray(patch.columns))
|
|
3409
|
+
return patch;
|
|
3410
|
+
const cols = patch.columns.map((col) => this.normalizeColumn(col));
|
|
3411
|
+
return { ...patch, columns: cols };
|
|
3412
|
+
}
|
|
3413
|
+
looksLikeCurrencyFormat(format) {
|
|
3414
|
+
if (!format)
|
|
3415
|
+
return false;
|
|
3416
|
+
const fmt = format.trim();
|
|
3417
|
+
return /^[A-Z]{3}(\|.*)?$/.test(fmt);
|
|
3418
|
+
}
|
|
3419
|
+
looksLikeDateFormat(format) {
|
|
3420
|
+
if (!format)
|
|
3421
|
+
return false;
|
|
3422
|
+
const fmt = format.trim();
|
|
3423
|
+
if (this.looksLikeMaskFormat(fmt))
|
|
3424
|
+
return false;
|
|
3425
|
+
// Heuristics: contains date tokens like d/M/y or common date separators
|
|
3426
|
+
return /(d|M|y){2,}/i.test(fmt) || fmt.includes('/') || fmt.includes('-');
|
|
3427
|
+
}
|
|
3428
|
+
looksLikeBooleanFormat(format) {
|
|
3429
|
+
if (!format)
|
|
3430
|
+
return false;
|
|
3431
|
+
const fmt = format.trim();
|
|
3432
|
+
return [
|
|
3433
|
+
'true-false',
|
|
3434
|
+
'yes-no',
|
|
3435
|
+
'active-inactive',
|
|
3436
|
+
'on-off',
|
|
3437
|
+
'enabled-disabled',
|
|
3438
|
+
].includes(fmt) || fmt.startsWith('custom|');
|
|
3439
|
+
}
|
|
3440
|
+
looksLikeMaskFormat(format) {
|
|
3441
|
+
if (!format)
|
|
3442
|
+
return false;
|
|
3443
|
+
const fmt = format.trim();
|
|
3444
|
+
return /[0#9X]/i.test(fmt) && /^[0#9X.\-/()\s]+$/i.test(fmt);
|
|
3445
|
+
}
|
|
3446
|
+
isIconRendererHint(renderer) {
|
|
3447
|
+
if (!renderer)
|
|
3448
|
+
return false;
|
|
3449
|
+
if (renderer.type && renderer.type !== 'icon')
|
|
3450
|
+
return false;
|
|
3451
|
+
const icon = renderer.icon || renderer.name || renderer.iconName;
|
|
3452
|
+
return typeof icon === 'string' || typeof renderer.color === 'string' || typeof renderer.size === 'number';
|
|
3453
|
+
}
|
|
3454
|
+
normalizeColumn(col) {
|
|
3455
|
+
if (!col)
|
|
3456
|
+
return col;
|
|
3457
|
+
const next = { ...col };
|
|
3458
|
+
// Normalize formats
|
|
3459
|
+
if (next.format) {
|
|
3460
|
+
if (this.looksLikeMaskFormat(next.format)) {
|
|
3461
|
+
next.type = 'string';
|
|
3462
|
+
}
|
|
3463
|
+
else if (!next.type && this.looksLikeCurrencyFormat(next.format)) {
|
|
3464
|
+
next.type = 'currency';
|
|
3465
|
+
}
|
|
3466
|
+
else if (!next.type && this.looksLikeBooleanFormat(next.format)) {
|
|
3467
|
+
next.type = 'boolean';
|
|
3468
|
+
}
|
|
3469
|
+
else if (!next.type && this.looksLikeDateFormat(next.format)) {
|
|
3470
|
+
next.type = 'date';
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
// Normalize renderer structure (icon shorthand)
|
|
3474
|
+
if (next.renderer) {
|
|
3475
|
+
next.renderer = this.normalizeRenderer(next.renderer);
|
|
3476
|
+
}
|
|
3477
|
+
// Normalize conditional renderers
|
|
3478
|
+
if (Array.isArray(next.conditionalRenderers)) {
|
|
3479
|
+
next.conditionalRenderers = next.conditionalRenderers.map((r) => {
|
|
3480
|
+
if (!r)
|
|
3481
|
+
return r;
|
|
3482
|
+
const out = { ...r };
|
|
3483
|
+
if (out.renderer)
|
|
3484
|
+
out.renderer = this.normalizeRenderer(out.renderer);
|
|
3485
|
+
return out;
|
|
3486
|
+
});
|
|
3487
|
+
}
|
|
3488
|
+
return next;
|
|
3489
|
+
}
|
|
3490
|
+
normalizeRenderer(renderer) {
|
|
3491
|
+
if (!renderer)
|
|
3492
|
+
return renderer;
|
|
3493
|
+
const out = { ...renderer };
|
|
3494
|
+
if (out.type === 'icon') {
|
|
3495
|
+
// Move icon shorthand to renderer.icon.*
|
|
3496
|
+
const iconObj = { ...(out.icon || {}) };
|
|
3497
|
+
if (renderer.icon && typeof renderer.icon === 'string')
|
|
3498
|
+
iconObj.name = renderer.icon;
|
|
3499
|
+
if (renderer.icon?.name)
|
|
3500
|
+
iconObj.name = renderer.icon.name;
|
|
3501
|
+
if (renderer.icon?.color)
|
|
3502
|
+
iconObj.color = renderer.icon.color;
|
|
3503
|
+
if (renderer.icon?.size)
|
|
3504
|
+
iconObj.size = renderer.icon.size;
|
|
3505
|
+
if (renderer.color && !iconObj.color)
|
|
3506
|
+
iconObj.color = renderer.color;
|
|
3507
|
+
if (renderer.size && !iconObj.size)
|
|
3508
|
+
iconObj.size = renderer.size;
|
|
3509
|
+
out.icon = iconObj;
|
|
3510
|
+
delete out.color;
|
|
3511
|
+
delete out.size;
|
|
3512
|
+
}
|
|
3513
|
+
// Badge shorthand (allow badge.color/variant/icon/text via renderer.badge or root)
|
|
3514
|
+
if (out.type === 'badge') {
|
|
3515
|
+
const badge = { ...(out.badge || {}) };
|
|
3516
|
+
if (renderer.color && !badge.color)
|
|
3517
|
+
badge.color = renderer.color;
|
|
3518
|
+
if (renderer.variant && !badge.variant)
|
|
3519
|
+
badge.variant = renderer.variant;
|
|
3520
|
+
if (renderer.text && !badge.text)
|
|
3521
|
+
badge.text = renderer.text;
|
|
3522
|
+
if (renderer.icon && !badge.icon && typeof renderer.icon === 'string')
|
|
3523
|
+
badge.icon = renderer.icon;
|
|
3524
|
+
out.badge = badge;
|
|
3525
|
+
delete out.color;
|
|
3526
|
+
delete out.variant;
|
|
3527
|
+
delete out.text;
|
|
3528
|
+
if (typeof out.icon === 'string')
|
|
3529
|
+
delete out.icon;
|
|
3530
|
+
}
|
|
3531
|
+
// Chip shorthand
|
|
3532
|
+
if (out.type === 'chip') {
|
|
3533
|
+
const chip = { ...(out.chip || {}) };
|
|
3534
|
+
if (renderer.color && !chip.color)
|
|
3535
|
+
chip.color = renderer.color;
|
|
3536
|
+
if (renderer.variant && !chip.variant)
|
|
3537
|
+
chip.variant = renderer.variant;
|
|
3538
|
+
if (renderer.text && !chip.text)
|
|
3539
|
+
chip.text = renderer.text;
|
|
3540
|
+
if (renderer.icon && !chip.icon && typeof renderer.icon === 'string')
|
|
3541
|
+
chip.icon = renderer.icon;
|
|
3542
|
+
out.chip = chip;
|
|
3543
|
+
delete out.color;
|
|
3544
|
+
delete out.variant;
|
|
3545
|
+
delete out.text;
|
|
3546
|
+
if (typeof out.icon === 'string')
|
|
3547
|
+
delete out.icon;
|
|
3548
|
+
}
|
|
3549
|
+
// Link shorthand
|
|
3550
|
+
if (out.type === 'link' && typeof renderer.href === 'string') {
|
|
3551
|
+
out.link = { ...(out.link || {}), href: renderer.href };
|
|
3552
|
+
delete out.href;
|
|
3553
|
+
}
|
|
3554
|
+
return out;
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
export { TableAiAdapter };
|