@praxisui/dynamic-form 8.0.0-beta.20 → 8.0.0-beta.22
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 +12 -2
- package/docs/dynamic-form-llm-rule-authoring-guide.md +5 -0
- package/docs/dynamic-form-rules-authoring-plan.md +11 -1
- package/fesm2022/praxisui-dynamic-form-dynamic-form-agentic-authoring-turn-flow-DuH1ad7h.mjs +417 -0
- package/fesm2022/praxisui-dynamic-form.mjs +1856 -95
- package/index.d.ts +188 -6
- package/package.json +8 -8
- package/src/lib/config-editor/praxis-dynamic-form-config-editor.json-api.md +1 -0
- package/src/lib/praxis-dynamic-form.json-api.md +58 -2
package/README.md
CHANGED
|
@@ -585,6 +585,8 @@ Guia completo: `projects/praxis-dynamic-form/docs/layout-items-visual-blocks.md`
|
|
|
585
585
|
|
|
586
586
|
## IA — catálogo de capacidades (composição)
|
|
587
587
|
|
|
588
|
+
O copiloto semântico do formulário usa o shell global de `@praxisui/ai` e registra uma sessão segura no `PraxisAssistantSessionRegistryService`. O botão local apenas abre o contexto do formulário; minimizar/reabrir deve preservar a sessão no cockpit global, sem tratar o formulário como fonte primária de regra de negócio.
|
|
589
|
+
|
|
588
590
|
O assistente usa um catálogo agregado de capabilities para gerar patches seguros:
|
|
589
591
|
|
|
590
592
|
- **Macro (FormConfig)**: layout, regras, ações, hooks e mensagens (`form-ai-capabilities`).
|
|
@@ -593,6 +595,8 @@ O assistente usa um catálogo agregado de capabilities para gerar patches seguro
|
|
|
593
595
|
|
|
594
596
|
Os paths micro são normalizados para `fieldMetadata[].<prop>` para garantir que os patches apontem para a raiz correta do formulário.
|
|
595
597
|
|
|
598
|
+
O fluxo agentic-authoring do formulário exige `componentEditPlan` validado pelo `PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST` antes de aplicar patch local. Prompts de regra de negócio, política, elegibilidade, compliance, LGPD, aprovação/publicação ou decisão compartilhada devem seguir para `domain-rules`/`shared_rule_authoring`; em `view` sem customização ou readonly, o assistente opera apenas como ajuda/diagnóstico e não aplica mutação local.
|
|
599
|
+
|
|
596
600
|
## Regras de formulário (novo contrato)
|
|
597
601
|
|
|
598
602
|
- Formato: cada regra tem `targetType` (`field | section | action | row | column | visualBlock`), `targets: string[]` (IDs canônicos do alvo), e `effect` com `condition` (`JsonLogicExpression | null`), `properties` e `propertiesWhenFalse`.
|
|
@@ -600,7 +604,7 @@ Os paths micro são normalizados para `fieldMetadata[].<prop>` para garantir que
|
|
|
600
604
|
- Compatibilidade: regras antigas (`context/targetField`) são migradas para `properties/targets` automaticamente; prefixes legados `section:/action:/row:/column:` continuam sendo normalizados quando representarem alvos tradicionais. Para header actions de seção, o ID canônico é preservado como `section:<sectionId>:header-action:<actionLogicalId>`.
|
|
601
605
|
- Semântica de limpeza: valores `null` em `properties/propertiesWhenFalse` removem o override e retornam ao valor base do layout; ausência mantém o valor base.
|
|
602
606
|
- Whitelist por tipo (somente propriedades a seguir são aplicadas; demais são descartadas e logadas em dev):
|
|
603
|
-
- `field`: `visible`, `required`, `readonly`, `disabled`, `className`, `style`, `label`, `description`, `placeholder`, `hint`, `tooltip`, `prefixIcon`, `suffixIcon`, `prefixText`, `suffixText`, `defaultValue`, `options` (array `{label,value,disabled?}`), `appearance` (`fill|outline`), `color` (`primary|accent|warn`), `floatLabel` (`auto|always|never`), `hintPosition` (`start|end`), `validators` (primitivos por chave).
|
|
607
|
+
- `field`: `visible`, `required`, `readonly`, `disabled`, `className`, `style`, `label`, `description`, `placeholder`, `hint`, `tooltip`, `prefixIcon`, `suffixIcon`, `prefixText`, `suffixText`, `defaultValue`, `value`, `options` (array `{label,value,disabled?}`), `appearance` (`fill|outline`), `color` (`primary|accent|warn`), `floatLabel` (`auto|always|never`), `hintPosition` (`start|end`), `validators` (primitivos por chave).
|
|
604
608
|
- `section`: `visible`, `title`, `description`, `icon`, `sectionHeader` (objeto rico), `headerActions` (ações contextuais do cabeçalho) e tambem subpropriedades tipadas como `sectionHeader.mode`, `sectionHeader.sourceField`, `sectionHeader.initialsSourceField`, `sectionHeader.altField`, `sectionHeader.fallbackIcon`, `sectionHeader.emptyState`, `sectionHeader.size`, `sectionHeader.initialsMaxLength`, `className`, `style`, `collapsible`, `collapsed`, `headerTooltip`, `headerAlign` (`start|center`), `appearance` (`card|plain|step`), `stepLabel`, gaps (`gapBottom`, `titleGapBottom`, `descriptionGapBottom`), cores/tipografia (`titleColor`, `descriptionColor`, `titleStyle`, `descriptionStyle`).
|
|
605
609
|
- `action`: `visible`, `disabled`, `loading`, `label`, `icon`, `tooltip`, `color` (`primary|accent|warn|basic`), `variant` (`raised|stroked|flat|fab`), `size` (`small|medium|large`), `className`, `style`.
|
|
606
610
|
- `row`: `visible`, `gap`, `rowGap`, `className`, `style`.
|
|
@@ -608,10 +612,15 @@ Os paths micro são normalizados para `fieldMetadata[].<prop>` para garantir que
|
|
|
608
612
|
- `visualBlock`: `visible`, `hidden`, `layout`, `className`, `rootClassName`, `style`, `text`, `title`, `message`; para documentos compostos, use `textNodeId`, `titleNodeId` ou `messageNodeId` para apontar o node textual seguro.
|
|
609
613
|
- IDs canônicos de regras para actions do header de seção usam o formato `section:<sectionId>:header-action:<actionLogicalId>`, evitando colisão com botões globais e com ações repetidas em seções diferentes.
|
|
610
614
|
- Se a seção ainda não tiver `id`, o runtime aceita o fallback `header-action:<actionLogicalId>` por compatibilidade; para contratos persistidos, prefira sempre materializar `section.id`.
|
|
611
|
-
-
|
|
615
|
+
- Valores calculados: regras de `targetType: "field"` podem escrever `effect.properties.value` ou `effect.propertiesWhenFalse.value`. Use envelope fechado e exclusivo: exatamente `{ "expression": <JsonLogic> }` para calcular ou exatamente `{ "literal": <valor> }` para valor estruturado. Primitivos, `null` e arrays continuam aceitos como literais; objetos literais devem usar `{ "literal": ... }` para não serem tratados como Json Logic legado. Envelopes com chaves extras ou com `literal` e `expression` juntos são inválidos.
|
|
616
|
+
- Ordem de regras: valores calculados são resolvidos em fase anterior, com iteração limitada até estabilizar, antes da aplicação das demais propriedades. Assim uma regra pode calcular `age` e outra regra pode depender de `age` sem depender da posição no array.
|
|
617
|
+
- Comandos condicionais: `formCommandRules` é a superfície aditiva para side effects governados, avaliada depois que `formRules` estabilizam valores calculados. O primeiro corte suporta `effects[].kind: "global-action"` com `globalAction: GlobalActionRef`, policy operacional (`trigger`, `distinct`, `distinctBy`, `debounceMs`, `errorPolicy`, `runOnInitialEvaluation`) e default `trigger: "on-condition-enter"`. Comandos não devem ser colocados em `formRules[].effect.properties`. No editor visual, `payloadExpr` permanece escape hatch de JSON avancado: ele e preservado quando ja existe e nenhum payload estruturado e definido, mas novos campos visuais authoram `payload` literal.
|
|
618
|
+
- Runtime (FormRulesService): filtra por whitelist, converte tipos (enum/number/boolean/string), saneia objetos (`options/validators/style`), aplica remoção de chaves com `null`, resolve `fieldValues` calculados e retorna mapas `fieldProps/sectionProps/actionProps/rowProps/columnProps/visualBlockProps`.
|
|
612
619
|
- Renderização: `PraxisDynamicForm` aplica overrides em campos/seções/ações/linhas/colunas/blocos visuais (visibilidade, gaps, padding, classes, estilos, labels e texto seguro, conforme o tipo); colunas respeitam `align/span/offset/order/hidden/padding` vindo das regras.
|
|
613
620
|
- Regras de `visualBlock` são visual-only: elas não criam `FieldMetadata`, `FormControl`, valor em `formData` ou payload HTTP. Use campo local com `submitPolicy` quando a experiência precisar de valor persistível ou leitura em `rawFormData`.
|
|
614
621
|
|
|
622
|
+
Politica de erro dos comandos condicionais: falhas de `GlobalActionService.executeRef(...)` geram diagnostico sem propagar excecao para o ciclo do form, e a memoria `distinct` evita repeticao enquanto a condicao segue verdadeira. Sem `GlobalActionService`, actions mutaveis (`api.post`, `api.put`, `api.patch`, `api.delete`) falham fechado e nao sao emitidas via `customAction`.
|
|
623
|
+
|
|
615
624
|
### Builder integrado
|
|
616
625
|
|
|
617
626
|
- No editor visual, use a aba “Propriedades” (integrada ao builder) para selecionar o alvo (`targetType` + autocomplete de IDs de campos/seções/ações/linhas/colunas/blocos visuais), escolher propriedades whitelisted e definir valores para `properties` (branch true) e `propertiesWhenFalse` (branch false). Botão “Limpar override” remove a propriedade (equivalente a `null`).
|
|
@@ -817,6 +826,7 @@ Apache-2.0 – see the `LICENSE` packaged with this library or the repository ro
|
|
|
817
826
|
- Guia de implementação e metadados da cascata: `docs/CASCADE-NATIVA.md`
|
|
818
827
|
- Padrões de endpoints (Options vs Filter) para selects: `projects/praxis-dynamic-fields/docs/generic-crud-service.md` (canônico) e `docs/DEVS-GENERIC-CRUD-SERVICE.md` (resumo operacional)
|
|
819
828
|
- Recipe oficial de Entity Lookup corporativo com `RESOURCE_ENTITY`, `dependsOn`, `dependencyFilterMap`, reidratação por `by-ids` e política de seleção: `examples/ai-recipes/praxis-dynamic-form.entity-lookup-procurement.json`
|
|
829
|
+
- Recipe oficial de comandos condicionais governados com `formCommandRules`, `GlobalActionRef`, policy operacional, valor calculado e date range confirmado: `examples/ai-recipes/praxis-dynamic-form.command-rules.json`
|
|
820
830
|
|
|
821
831
|
## Verificação de Schema (ETag/If-None-Match)
|
|
822
832
|
|
|
@@ -119,6 +119,9 @@ obrigatorio e visivel. Quando nao for temporario, dataFim deve ficar opcional.
|
|
|
119
119
|
|
|
120
120
|
Use somente JSON Logic canonico.
|
|
121
121
|
Use somente alvos existentes no target catalog.
|
|
122
|
+
Para valores calculados de campos, use somente envelope fechado
|
|
123
|
+
`{ "expression": <JsonLogic> }` ou `{ "literal": <valor> }`.
|
|
124
|
+
Nao misture `literal` e `expression` nem adicione chaves extras ao envelope.
|
|
122
125
|
Nao altere formRulesState.
|
|
123
126
|
Marque metadata.origin como "llm" e metadata.reviewStatus como "pending".
|
|
124
127
|
Explique o impacto antes de aplicar.
|
|
@@ -252,6 +255,8 @@ Mapeamento operacional:
|
|
|
252
255
|
| `field-target-not-found` | Trocar por `fieldMetadata[].name` existente. |
|
|
253
256
|
| `target-not-found` | Trocar por alvo existente no mesmo `targetType`. |
|
|
254
257
|
| `invalid-condition` | Reescrever como Json Logic canonico. |
|
|
258
|
+
| `invalid-value-expression` | Corrigir `effect.properties.value` ou `propertiesWhenFalse.value` para Json Logic valido ou envelope fechado `{ "expression": ... }` / `{ "literal": ... }`. |
|
|
259
|
+
| `computed-value-iteration-limit` | Remover ciclo ou ambiguidade entre valores calculados encadeados. |
|
|
255
260
|
| `unsupported-property` | Usar propriedade permitida por `RULE_PROPERTY_SCHEMA`. |
|
|
256
261
|
| `unsupported-target-type` | Escolher um `targetType` permitido. |
|
|
257
262
|
| `missing-effect` | Criar `effect.condition` e `effect.properties`. |
|
|
@@ -81,7 +81,7 @@ A funcionalidade completa tem oito partes, que hoje estao espalhadas:
|
|
|
81
81
|
| Condicao | Builder converte condicoes simples para Json Logic. | Condicoes complexas podem virar runtime-only. |
|
|
82
82
|
| Efeito | `RULE_PROPERTY_SCHEMA` define propriedades permitidas por alvo. | UI mostra propriedades por alvo, rejeicoes do sanitizer e oferece correcao assistida. |
|
|
83
83
|
| Conversao | `rule-converters.ts` faz round-trip `formRules` <-> `RuleBuilderState` e preserva metadados semanticos como `type: "visualBlockGuidance"` via `config.ruleType`. | Nao ha status explicito de compatibilidade visual. |
|
|
84
|
-
| Execucao | `FormRulesService` aplica propriedades saneadas e o editor oferece preview true/false para revisar o efeito antes de persistir. | Falta ampliar mensagens de diagnóstico para casos de sanitizer parcial. |
|
|
84
|
+
| Execucao | `FormRulesService` resolve valores calculados de campos em fase anterior, aplica propriedades saneadas e o editor oferece preview true/false para revisar o efeito antes de persistir. | Falta ampliar mensagens de diagnóstico para casos de sanitizer parcial. |
|
|
85
85
|
| LLM | Manifesto publica operacoes locais de component/form authoring e preserva `rule.visualBlockGuidance.add` apenas como projecao visual opcional. | Business-rule authoring deve entrar por `domain-rules`/`shared_rule_authoring` antes de virar `form_config`. |
|
|
86
86
|
| Visual blocks | Alvo `visualBlock` existe, entra em `targetSchemas.visualBlocks`, aceita `text`, `title` e `message` saneados e o painel de propriedades guia `textNodeId`, `titleNodeId` e `messageNodeId`. | Falta apenas ampliar biblioteca de exemplos visuais por domínio, sem mudar o contrato. |
|
|
87
87
|
| Governanca | Propriedades nao whitelisted sao descartadas, aparecem para o autor e podem ser corrigidas pelo painel de diagnosticos. | Expandir telemetria/curadoria conforme o catalogo publico amadurecer. |
|
|
@@ -110,6 +110,8 @@ Toda regra deve receber um status calculado:
|
|
|
110
110
|
| `valid` | Executavel no runtime. |
|
|
111
111
|
| `invalid-target` | Um ou mais targets nao existem. |
|
|
112
112
|
| `invalid-condition` | Condicao nao e Json Logic valida. |
|
|
113
|
+
| `invalid-value-expression` | Valor calculado de campo usa Json Logic invalido ou envelope ambiguo. |
|
|
114
|
+
| `computed-value-iteration-limit` | Valores calculados encadeados nao estabilizaram dentro do limite do runtime. |
|
|
113
115
|
| `unsupported-property` | Efeito contem propriedade fora do whitelist. |
|
|
114
116
|
| `runtime-only` | Regra executa, mas nao reabre fielmente no builder visual. |
|
|
115
117
|
| `visual-builder-compatible` | Regra executa e reabre no builder visual. |
|
|
@@ -133,6 +135,14 @@ LLM pode sugerir materializacoes visuais derivadas, mas deve seguir estas politi
|
|
|
133
135
|
`rule.visualBlockGuidance.add` com `type: "visualBlockGuidance"`,
|
|
134
136
|
`targetType: "visualBlock"` e `context: "notification"`.
|
|
135
137
|
- Toda condicao deve ser Json Logic canonico.
|
|
138
|
+
- Valores calculados em `effect.properties.value` e
|
|
139
|
+
`effect.propertiesWhenFalse.value` devem usar envelope fechado e exclusivo:
|
|
140
|
+
exatamente `{ "expression": <JsonLogic> }` ou exatamente
|
|
141
|
+
`{ "literal": <valor> }`. Objetos literais estruturados precisam do envelope
|
|
142
|
+
`literal`; envelopes com chaves extras sao rejeitados.
|
|
143
|
+
- Valores calculados sao resolvidos antes das demais propriedades por iteracao
|
|
144
|
+
limitada ate estabilizacao, entao regras dependentes nao devem depender da
|
|
145
|
+
ordem no array de `formRules`.
|
|
136
146
|
- Toda propriedade deve existir em `RULE_PROPERTY_SCHEMA[targetType]`.
|
|
137
147
|
- Todo target deve existir no `ruleBuilderConfig.targetSchemas`.
|
|
138
148
|
- Remocao de regra exige confirmacao humana.
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { firstValueFrom } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
class DynamicFormAgenticAuthoringTurnFlow {
|
|
4
|
+
adapter;
|
|
5
|
+
aiApi;
|
|
6
|
+
mode = 'agentic-authoring';
|
|
7
|
+
constructor(adapter, aiApi) {
|
|
8
|
+
this.adapter = adapter;
|
|
9
|
+
this.aiApi = aiApi;
|
|
10
|
+
}
|
|
11
|
+
async submit(request) {
|
|
12
|
+
const prompt = (request.prompt ?? '').trim();
|
|
13
|
+
if (!prompt) {
|
|
14
|
+
return {
|
|
15
|
+
state: 'listening',
|
|
16
|
+
phase: 'capture',
|
|
17
|
+
statusText: '',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const componentId = this.adapter.componentId || request.componentId || 'praxis-dynamic-form';
|
|
21
|
+
const componentType = this.adapter.componentType || request.componentType || 'form';
|
|
22
|
+
const currentState = this.toAiJsonObject(this.adapter.getCurrentConfig());
|
|
23
|
+
const runtimeState = this.optionalJsonObject(this.adapter.getRuntimeState?.());
|
|
24
|
+
const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
|
|
25
|
+
if (this.shouldRestrictToInlineHelp(contextHints)) {
|
|
26
|
+
return this.toInlineHelpOnlyResult(prompt, request, contextHints);
|
|
27
|
+
}
|
|
28
|
+
if (this.shouldRouteToGovernedDecision(prompt, contextHints)) {
|
|
29
|
+
return this.toGovernedDecisionHandoff(prompt, request);
|
|
30
|
+
}
|
|
31
|
+
const response = await firstValueFrom(this.aiApi.getPatch({
|
|
32
|
+
componentId,
|
|
33
|
+
componentType,
|
|
34
|
+
userPrompt: prompt,
|
|
35
|
+
sessionId: request.sessionId,
|
|
36
|
+
clientTurnId: request.clientTurnId,
|
|
37
|
+
messages: this.toChatMessages(request.messages, prompt),
|
|
38
|
+
currentState,
|
|
39
|
+
currentStateDigest: this.buildCurrentStateDigest(currentState, runtimeState),
|
|
40
|
+
uiContextRef: {
|
|
41
|
+
componentId,
|
|
42
|
+
componentType,
|
|
43
|
+
},
|
|
44
|
+
...(runtimeState ? { runtimeState } : {}),
|
|
45
|
+
...(contextHints ? { contextHints } : {}),
|
|
46
|
+
}));
|
|
47
|
+
return this.toTurnResult(this.compileAdapterResponse(response), request);
|
|
48
|
+
}
|
|
49
|
+
async apply(request) {
|
|
50
|
+
const contextHints = this.optionalJsonObject(this.adapter.getAuthoringContext?.());
|
|
51
|
+
if (this.shouldRestrictToInlineHelp(contextHints)) {
|
|
52
|
+
return {
|
|
53
|
+
state: 'error',
|
|
54
|
+
phase: 'apply',
|
|
55
|
+
assistantMessage: 'Este formulario esta em modo de leitura. Posso explicar ou diagnosticar, mas nao aplicar mudancas locais.',
|
|
56
|
+
errorText: 'Aplicacao bloqueada em view/readonly sem customizacao.',
|
|
57
|
+
canApply: false,
|
|
58
|
+
pendingPatch: null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const patch = this.toRecord(request.pendingPatch);
|
|
62
|
+
if (!patch) {
|
|
63
|
+
return {
|
|
64
|
+
state: 'error',
|
|
65
|
+
phase: 'apply',
|
|
66
|
+
assistantMessage: 'Nao ha alteracao de formulario pronta para aplicar.',
|
|
67
|
+
errorText: 'Nao ha alteracao de formulario pronta para aplicar.',
|
|
68
|
+
canApply: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const result = await this.adapter.applyPatch(patch, request.prompt);
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
return {
|
|
74
|
+
state: 'error',
|
|
75
|
+
phase: 'apply',
|
|
76
|
+
assistantMessage: result.error || 'Nao foi possivel aplicar as alteracoes no formulario.',
|
|
77
|
+
errorText: result.error || 'Nao foi possivel aplicar as alteracoes no formulario.',
|
|
78
|
+
canApply: true,
|
|
79
|
+
pendingPatch: patch,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
state: 'success',
|
|
84
|
+
phase: 'summarize',
|
|
85
|
+
assistantMessage: 'Alteracoes aplicadas no formulario.',
|
|
86
|
+
statusText: 'Alteracoes aplicadas no formulario.',
|
|
87
|
+
canApply: false,
|
|
88
|
+
pendingPatch: null,
|
|
89
|
+
diagnostics: result.warnings?.length ? { warnings: result.warnings } : undefined,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
cancel() {
|
|
93
|
+
return Promise.resolve({
|
|
94
|
+
state: 'listening',
|
|
95
|
+
phase: 'capture',
|
|
96
|
+
assistantMessage: 'Solicitacao cancelada.',
|
|
97
|
+
statusText: '',
|
|
98
|
+
canApply: false,
|
|
99
|
+
pendingPatch: null,
|
|
100
|
+
pendingClarification: null,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
retry(request) {
|
|
104
|
+
const lastPrompt = [...(request.messages ?? [])].reverse()
|
|
105
|
+
.find((message) => message.role === 'user')?.text;
|
|
106
|
+
return this.submit({
|
|
107
|
+
...request,
|
|
108
|
+
prompt: lastPrompt ?? request.prompt,
|
|
109
|
+
action: { kind: 'retry' },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
toTurnResult(response, request) {
|
|
113
|
+
if (!response) {
|
|
114
|
+
return {
|
|
115
|
+
state: 'error',
|
|
116
|
+
phase: 'capture',
|
|
117
|
+
assistantMessage: 'Resposta vazia da IA.',
|
|
118
|
+
errorText: 'Resposta vazia da IA.',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (response.type === 'clarification') {
|
|
122
|
+
const questions = this.toClarificationQuestions(response);
|
|
123
|
+
return {
|
|
124
|
+
state: 'clarification',
|
|
125
|
+
phase: 'clarify',
|
|
126
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
127
|
+
assistantMessage: response.message || 'Preciso de mais detalhes para continuar.',
|
|
128
|
+
clarificationQuestions: questions,
|
|
129
|
+
quickReplies: this.toQuickReplies(response),
|
|
130
|
+
canApply: false,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (response.type === 'info') {
|
|
134
|
+
const message = response.message || response.explanation || 'Informacao gerada.';
|
|
135
|
+
return {
|
|
136
|
+
state: 'success',
|
|
137
|
+
phase: 'summarize',
|
|
138
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
139
|
+
assistantMessage: message,
|
|
140
|
+
statusText: message,
|
|
141
|
+
canApply: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (response.type === 'error') {
|
|
145
|
+
const message = response.message || 'Falha ao gerar alteracao de formulario.';
|
|
146
|
+
return {
|
|
147
|
+
state: 'error',
|
|
148
|
+
phase: 'capture',
|
|
149
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
150
|
+
assistantMessage: message,
|
|
151
|
+
errorText: message,
|
|
152
|
+
diagnostics: response.warnings?.length ? { warnings: response.warnings } : undefined,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (response.patch && Object.keys(response.patch).length > 0) {
|
|
156
|
+
const warnings = response.warnings?.filter(Boolean) ?? [];
|
|
157
|
+
const suffix = warnings.length ? ` Avisos: ${warnings.join('; ')}` : '';
|
|
158
|
+
return {
|
|
159
|
+
state: 'review',
|
|
160
|
+
phase: 'review',
|
|
161
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
162
|
+
assistantMessage: `${response.explanation || 'Proposta de alteracao pronta para revisar.'}${suffix}`,
|
|
163
|
+
statusText: 'Revise a proposta antes de aplicar.',
|
|
164
|
+
canApply: true,
|
|
165
|
+
pendingPatch: response.patch,
|
|
166
|
+
preview: {
|
|
167
|
+
kind: 'dynamic-form-config-patch',
|
|
168
|
+
diff: response.diff ?? [],
|
|
169
|
+
},
|
|
170
|
+
diagnostics: warnings.length ? { warnings } : undefined,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
state: 'success',
|
|
175
|
+
phase: 'summarize',
|
|
176
|
+
sessionId: response.sessionId ?? request.sessionId,
|
|
177
|
+
assistantMessage: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
178
|
+
statusText: response.message || response.explanation || 'Nenhuma alteracao necessaria.',
|
|
179
|
+
canApply: false,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
compileAdapterResponse(response) {
|
|
183
|
+
const compiled = this.adapter.compileAiResponse?.(response);
|
|
184
|
+
if (!compiled && response.patch && Object.keys(response.patch).length > 0) {
|
|
185
|
+
return {
|
|
186
|
+
type: 'error',
|
|
187
|
+
message: 'O formulario exige componentEditPlan validado pelo manifesto antes de gerar patch local.',
|
|
188
|
+
warnings: [
|
|
189
|
+
'free-dynamic-form-patch-rejected',
|
|
190
|
+
'Use componentEditPlan validado contra PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST.',
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (!compiled) {
|
|
195
|
+
return response;
|
|
196
|
+
}
|
|
197
|
+
if (compiled.type === 'error') {
|
|
198
|
+
return {
|
|
199
|
+
type: 'error',
|
|
200
|
+
message: compiled.message || 'O componentEditPlan do formulario nao passou na validacao de capacidades.',
|
|
201
|
+
warnings: compiled.warnings,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const warnings = [
|
|
205
|
+
...(response.warnings ?? []),
|
|
206
|
+
...(compiled.warnings ?? []),
|
|
207
|
+
];
|
|
208
|
+
return {
|
|
209
|
+
...response,
|
|
210
|
+
...compiled,
|
|
211
|
+
patch: compiled.patch,
|
|
212
|
+
warnings: warnings.length ? warnings : undefined,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
toChatMessages(messages, prompt) {
|
|
216
|
+
const supported = (messages ?? [])
|
|
217
|
+
.filter((message) => message.role === 'user' || message.role === 'assistant' || message.role === 'system')
|
|
218
|
+
.map((message) => ({
|
|
219
|
+
role: message.role,
|
|
220
|
+
content: message.text,
|
|
221
|
+
}))
|
|
222
|
+
.filter((message) => message.content.trim().length > 0);
|
|
223
|
+
return supported.length ? supported : [{ role: 'user', content: prompt }];
|
|
224
|
+
}
|
|
225
|
+
toClarificationQuestions(response) {
|
|
226
|
+
const labels = response.questions?.length
|
|
227
|
+
? response.questions
|
|
228
|
+
: response.message
|
|
229
|
+
? [response.message]
|
|
230
|
+
: ['Qual ajuste voce quer aplicar no formulario?'];
|
|
231
|
+
const options = this.toQuickReplies(response).map((reply) => ({
|
|
232
|
+
id: reply.id,
|
|
233
|
+
label: reply.label,
|
|
234
|
+
value: reply.prompt,
|
|
235
|
+
}));
|
|
236
|
+
return labels.map((label, index) => ({
|
|
237
|
+
id: `dynamic-form-clarification-${index + 1}`,
|
|
238
|
+
type: options.length ? 'single-choice' : 'text',
|
|
239
|
+
label,
|
|
240
|
+
allowCustom: true,
|
|
241
|
+
options,
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
toQuickReplies(response) {
|
|
245
|
+
const payloads = response.optionPayloads ?? [];
|
|
246
|
+
if (payloads.length) {
|
|
247
|
+
return payloads
|
|
248
|
+
.map((option, index) => {
|
|
249
|
+
const label = option.label?.trim() || option.value?.trim() || `Opcao ${index + 1}`;
|
|
250
|
+
const prompt = option.example?.trim() || option.value?.trim() || label;
|
|
251
|
+
return {
|
|
252
|
+
id: `option-${index + 1}`,
|
|
253
|
+
label,
|
|
254
|
+
prompt,
|
|
255
|
+
kind: 'clarification-option',
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return (response.options ?? [])
|
|
260
|
+
.filter((option) => !!option?.trim())
|
|
261
|
+
.map((option, index) => ({
|
|
262
|
+
id: `option-${index + 1}`,
|
|
263
|
+
label: option.trim(),
|
|
264
|
+
prompt: option.trim(),
|
|
265
|
+
kind: 'clarification-option',
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
buildCurrentStateDigest(currentState, runtimeState) {
|
|
269
|
+
const fields = Array.isArray(currentState['fieldMetadata'])
|
|
270
|
+
? currentState['fieldMetadata']
|
|
271
|
+
.map((field) => this.toRecord(field)?.['name'])
|
|
272
|
+
.filter((field) => typeof field === 'string' && field.length > 0)
|
|
273
|
+
: undefined;
|
|
274
|
+
const sections = Array.isArray(currentState['sections']) ? currentState['sections'].length : undefined;
|
|
275
|
+
return {
|
|
276
|
+
...(fields?.length ? { columns: fields } : {}),
|
|
277
|
+
...(typeof runtimeState?.['totalFields'] === 'number'
|
|
278
|
+
? { rowCount: runtimeState['totalFields'] }
|
|
279
|
+
: sections !== undefined
|
|
280
|
+
? { rowCount: sections }
|
|
281
|
+
: {}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
shouldRestrictToInlineHelp(contextHints) {
|
|
285
|
+
const policy = this.toRecord(contextHints?.['runtimeAuthoringPolicy']);
|
|
286
|
+
return policy?.['canApplyLocalPatch'] === false;
|
|
287
|
+
}
|
|
288
|
+
toInlineHelpOnlyResult(prompt, request, contextHints) {
|
|
289
|
+
const policy = this.toRecord(contextHints?.['runtimeAuthoringPolicy']);
|
|
290
|
+
const reason = typeof policy?.['reason'] === 'string'
|
|
291
|
+
? policy['reason']
|
|
292
|
+
: 'Formulario em modo de leitura ou readonly.';
|
|
293
|
+
return {
|
|
294
|
+
state: 'success',
|
|
295
|
+
phase: 'summarize',
|
|
296
|
+
sessionId: request.sessionId,
|
|
297
|
+
assistantMessage: `Posso ajudar explicando este formulario ou diagnosticando o estado atual, mas nao vou aplicar alteracoes locais aqui. Motivo: ${reason}`,
|
|
298
|
+
statusText: 'Modo somente assistencia.',
|
|
299
|
+
canApply: false,
|
|
300
|
+
pendingPatch: null,
|
|
301
|
+
quickReplies: [
|
|
302
|
+
{
|
|
303
|
+
id: 'explain-current-form',
|
|
304
|
+
label: 'Explicar formulario atual',
|
|
305
|
+
prompt: `Explique este formulario considerando: ${prompt}`,
|
|
306
|
+
kind: 'inline-help',
|
|
307
|
+
icon: 'help',
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
shouldRouteToGovernedDecision(prompt, contextHints) {
|
|
313
|
+
const normalized = prompt.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
|
314
|
+
const recommendedFlow = this.toRecord(contextHints?.['domainCatalog'])?.['recommendedAuthoringFlow'];
|
|
315
|
+
if (recommendedFlow === 'shared_rule_authoring')
|
|
316
|
+
return true;
|
|
317
|
+
return [
|
|
318
|
+
'regra',
|
|
319
|
+
'politica',
|
|
320
|
+
'policy',
|
|
321
|
+
'compliance',
|
|
322
|
+
'lgpd',
|
|
323
|
+
'privacidade',
|
|
324
|
+
'aprovacao',
|
|
325
|
+
'aprovar',
|
|
326
|
+
'publicar',
|
|
327
|
+
'materializar',
|
|
328
|
+
'enforcement',
|
|
329
|
+
'validacao de negocio',
|
|
330
|
+
'validar negocio',
|
|
331
|
+
'elegibilidade',
|
|
332
|
+
'permissao',
|
|
333
|
+
'acesso',
|
|
334
|
+
].some((term) => normalized.includes(term));
|
|
335
|
+
}
|
|
336
|
+
toGovernedDecisionHandoff(prompt, request) {
|
|
337
|
+
const message = 'Esse pedido parece alterar uma decisao de negocio compartilhada. O formulario pode contextualizar campo/secao, mas a regra deve seguir pelo fluxo governado de domain-rules antes de qualquer materializacao runtime.';
|
|
338
|
+
return {
|
|
339
|
+
state: 'clarification',
|
|
340
|
+
phase: 'clarify',
|
|
341
|
+
sessionId: request.sessionId,
|
|
342
|
+
assistantMessage: message,
|
|
343
|
+
statusText: 'Handoff governado necessario.',
|
|
344
|
+
canApply: false,
|
|
345
|
+
quickReplies: [
|
|
346
|
+
{
|
|
347
|
+
id: 'shared-rule-handoff',
|
|
348
|
+
label: 'Continuar como regra governada',
|
|
349
|
+
prompt,
|
|
350
|
+
kind: 'shared-rule-handoff',
|
|
351
|
+
description: 'Criar intake de domain-rules em vez de aplicar regra local no formulario.',
|
|
352
|
+
icon: 'rule',
|
|
353
|
+
tone: 'warning',
|
|
354
|
+
contextHints: {
|
|
355
|
+
flowId: 'shared_rule_authoring',
|
|
356
|
+
source: 'praxis-dynamic-form',
|
|
357
|
+
recommendedAction: 'domain-rules/intake',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
clarificationQuestions: [
|
|
362
|
+
{
|
|
363
|
+
id: 'dynamic-form-governed-rule-confirmation',
|
|
364
|
+
type: 'confirm',
|
|
365
|
+
label: 'Deseja continuar pelo fluxo governado de regras compartilhadas?',
|
|
366
|
+
description: 'Esse caminho permite intake, simulacao, aprovacao/publicacao, materializacao e validacao de enforcement.',
|
|
367
|
+
required: true,
|
|
368
|
+
options: [
|
|
369
|
+
{
|
|
370
|
+
id: 'shared-rule-handoff',
|
|
371
|
+
label: 'Sim, continuar governado',
|
|
372
|
+
value: prompt,
|
|
373
|
+
description: 'Nao aplicar como patch local do formulario.',
|
|
374
|
+
contextHints: {
|
|
375
|
+
flowId: 'shared_rule_authoring',
|
|
376
|
+
source: 'praxis-dynamic-form',
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
diagnostics: {
|
|
383
|
+
governedDecisionHandoff: {
|
|
384
|
+
flowId: 'shared_rule_authoring',
|
|
385
|
+
sourcePrompt: prompt,
|
|
386
|
+
sourceComponent: 'praxis-dynamic-form',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
optionalJsonObject(value) {
|
|
392
|
+
if (value === undefined || value === null) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
const object = this.toAiJsonObject(value);
|
|
396
|
+
return Object.keys(object).length ? object : undefined;
|
|
397
|
+
}
|
|
398
|
+
toAiJsonObject(value) {
|
|
399
|
+
const record = this.toRecord(value);
|
|
400
|
+
if (!record) {
|
|
401
|
+
return {};
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
return JSON.parse(JSON.stringify(record));
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return {};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
toRecord(value) {
|
|
411
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
412
|
+
? value
|
|
413
|
+
: null;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export { DynamicFormAgenticAuthoringTurnFlow };
|