@mosaicoo/svg-engine 0.1.0
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/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +249 -0
- package/fesm2022/mosaicoo-svg-engine-ai-nlu-ui.mjs +459 -0
- package/fesm2022/mosaicoo-svg-engine-ai-nlu-voice-wasm.mjs +1 -0
- package/fesm2022/mosaicoo-svg-engine-ai-nlu.mjs +11 -0
- package/fesm2022/mosaicoo-svg-engine-core.mjs +3 -0
- package/fesm2022/mosaicoo-svg-engine-edit.mjs +2292 -0
- package/fesm2022/mosaicoo-svg-engine-io.mjs +47 -0
- package/fesm2022/mosaicoo-svg-engine-optimize.mjs +1 -0
- package/fesm2022/mosaicoo-svg-engine-render.mjs +301 -0
- package/fesm2022/mosaicoo-svg-engine-ui.mjs +14236 -0
- package/fesm2022/mosaicoo-svg-engine.mjs +1 -0
- package/package.json +105 -0
- package/types/mosaicoo-svg-engine-ai-nlu-ui.d.ts +416 -0
- package/types/mosaicoo-svg-engine-ai-nlu-voice-wasm.d.ts +175 -0
- package/types/mosaicoo-svg-engine-ai-nlu.d.ts +1834 -0
- package/types/mosaicoo-svg-engine-core.d.ts +5139 -0
- package/types/mosaicoo-svg-engine-edit.d.ts +11922 -0
- package/types/mosaicoo-svg-engine-io.d.ts +476 -0
- package/types/mosaicoo-svg-engine-optimize.d.ts +183 -0
- package/types/mosaicoo-svg-engine-render.d.ts +628 -0
- package/types/mosaicoo-svg-engine-ui.d.ts +6861 -0
- package/types/mosaicoo-svg-engine.d.ts +30 -0
|
@@ -0,0 +1,1834 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injector, Signal, InjectionToken, Provider } from '@angular/core';
|
|
3
|
+
import { Disposable, NodeId } from '@mosaicoo/svg-engine/core';
|
|
4
|
+
import { MenuContributionRegistry, MenuContribution, EditorPlugin } from '@mosaicoo/svg-engine/edit';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* **NLU (Natural Language Understanding) types — D-046 Fase 1**
|
|
8
|
+
*
|
|
9
|
+
* Rule-based natural-language → command pipeline. Zero ML, zero
|
|
10
|
+
* external download — pure regex + dictionary + Levenshtein fuzzy
|
|
11
|
+
* matching. Cobre ~70–80% dos comandos comuns ("undo", "delete",
|
|
12
|
+
* "criar retângulo vermelho"). Fase 2 (ML classifier) e Fase 3 (SLM)
|
|
13
|
+
* são entry points separados que reúsam esse mesmo contrato.
|
|
14
|
+
*
|
|
15
|
+
* **Entry point separado `svg-engine/ai/nlu`** (não dentro de `edit`)
|
|
16
|
+
* pra que a camada AI fique 100% desacoplada — Modo 1 (headless puro
|
|
17
|
+
* D-037) não importa nada de `ai/` quando não usar. Fase 2 e 3
|
|
18
|
+
* (Transformers.js / WebLLM) entram em `svg-engine/ai/nlu-ml` e
|
|
19
|
+
* `svg-engine/ai/nlu-slm`, ambos reaproveitando o contrato
|
|
20
|
+
* {@link NluIntent} / {@link NaturalLanguageService} definido aqui.
|
|
21
|
+
*
|
|
22
|
+
* **Por que mirrors `MenuContributionContext`**: o NLU é uma surface
|
|
23
|
+
* a mais (`<svge-menu-bar>`, `<svge-toolbar>`, `<svge-context-menu>`
|
|
24
|
+
* + agora command palette por voz/texto). Mantém-se o mesmo
|
|
25
|
+
* contrato multi-editor scope (D-042/D-043): `injector` na ctx,
|
|
26
|
+
* services resolvidos lazy do scope ativo.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Per-fire context passed to {@link NluIntent.execute} (and to the
|
|
30
|
+
* NLU service `parse` / `execute` entry points). Espelho exato do
|
|
31
|
+
* {@link MenuContributionContext} pra que handlers reaproveitem a
|
|
32
|
+
* mesma resolução de scope ativo (per-editor no D-042).
|
|
33
|
+
*/
|
|
34
|
+
interface NluContext {
|
|
35
|
+
/**
|
|
36
|
+
* Injector do consumer (UI component que disparou o parse — command
|
|
37
|
+
* palette, voice input, etc). Em apps route-scoped, é o injector
|
|
38
|
+
* do editor ativo; em single-editor é equivalente ao root.
|
|
39
|
+
*/
|
|
40
|
+
readonly injector: Injector;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Tipo declarativo de slot que um intent pode extrair do input.
|
|
44
|
+
*
|
|
45
|
+
* **Variantes**:
|
|
46
|
+
* - `number`: número decimal/inteiro (com unidade opcional como `px`).
|
|
47
|
+
* - `color`: nome de cor (PT/EN via dicionário) OU hex `#rrggbb` /
|
|
48
|
+
* `rgb()` / `hsl()`, com suporte a **intensificadores adjacentes**
|
|
49
|
+
* ("azul claro", "verde bem escuro") via `parseColorPhrase`.
|
|
50
|
+
* - `shape`: nome de forma (PT/EN via `SHAPE_DICTIONARY`) — resolve
|
|
51
|
+
* automaticamente "círculo"→`circle`, "retângulo"→`rect`,
|
|
52
|
+
* "balão"→`group`, etc. **Use este em vez de `enum` quando o
|
|
53
|
+
* slot for forma SVG** — `enum` exige match exato do canonical
|
|
54
|
+
* ('rect'), não suporta vocabulário PT/EN nem aliases semânticos.
|
|
55
|
+
* - `enum`: valor de uma lista fechada (lookup case-insensitive,
|
|
56
|
+
* com fuzzy match dist ≤ 1). Use para domínios fechados sem
|
|
57
|
+
* vocabulário multilíngue (e.g., 'landscape'/'portrait').
|
|
58
|
+
* - `string`: token livre — usado raramente, intent precisa ser
|
|
59
|
+
* tolerante a ruído.
|
|
60
|
+
*
|
|
61
|
+
* **`optional`**: quando `false` (default), confidence cai abaixo do
|
|
62
|
+
* threshold se o slot não for preenchido. Quando `true`, o slot é
|
|
63
|
+
* preenchido com `default` (ou `undefined`) e confidence mantém.
|
|
64
|
+
*/
|
|
65
|
+
type NluSlotSchema = {
|
|
66
|
+
readonly kind: 'number';
|
|
67
|
+
readonly optional?: boolean;
|
|
68
|
+
readonly default?: number;
|
|
69
|
+
readonly anchorKeywords?: readonly string[];
|
|
70
|
+
/**
|
|
71
|
+
* Quando `false`, o slot **não** é preenchido pelo pass posicional
|
|
72
|
+
* genérico — só por pre-passes específicos (ex.: `count` da
|
|
73
|
+
* repetição, extraído como "número antes de uma forma"). Evita que
|
|
74
|
+
* um número de dimensão seja capturado como contagem por engano.
|
|
75
|
+
* Default `true` (positional).
|
|
76
|
+
*/
|
|
77
|
+
readonly positional?: boolean;
|
|
78
|
+
} | {
|
|
79
|
+
readonly kind: 'color';
|
|
80
|
+
readonly optional?: boolean;
|
|
81
|
+
readonly default?: string;
|
|
82
|
+
readonly anchorKeywords?: readonly string[];
|
|
83
|
+
} | {
|
|
84
|
+
readonly kind: 'shape';
|
|
85
|
+
readonly optional?: boolean;
|
|
86
|
+
readonly default?: string;
|
|
87
|
+
readonly anchorKeywords?: readonly string[];
|
|
88
|
+
} | {
|
|
89
|
+
readonly kind: 'enum';
|
|
90
|
+
readonly values: readonly string[];
|
|
91
|
+
readonly optional?: boolean;
|
|
92
|
+
readonly default?: string;
|
|
93
|
+
readonly anchorKeywords?: readonly string[];
|
|
94
|
+
/**
|
|
95
|
+
* Quando `false`, o match do enum é **exato** (apenas
|
|
96
|
+
* `values.includes(token)`), sem fuzzy Levenshtein. Use para
|
|
97
|
+
* vocabulários curtos em que o fuzzy geraria falso-positivo
|
|
98
|
+
* (ex.: layout `'grade'` casaria `'grande'` por distância 1).
|
|
99
|
+
* Default `true` (fuzzy ligado).
|
|
100
|
+
*/
|
|
101
|
+
readonly fuzzy?: boolean;
|
|
102
|
+
} | {
|
|
103
|
+
readonly kind: 'string';
|
|
104
|
+
readonly optional?: boolean;
|
|
105
|
+
readonly default?: string;
|
|
106
|
+
readonly anchorKeywords?: readonly string[];
|
|
107
|
+
} | {
|
|
108
|
+
/**
|
|
109
|
+
* **`point`** — extrai par `{ x, y }` de **dois números adjacentes**
|
|
110
|
+
* (ex: "100 50" ou "100x50"). Combinar com `anchorKeywords`
|
|
111
|
+
* (`['posicao','position','em','at']`) pra desambiguar de outros
|
|
112
|
+
* slots numéricos no mesmo intent.
|
|
113
|
+
*/
|
|
114
|
+
readonly kind: 'point';
|
|
115
|
+
readonly optional?: boolean;
|
|
116
|
+
readonly default?: {
|
|
117
|
+
readonly x: number;
|
|
118
|
+
readonly y: number;
|
|
119
|
+
};
|
|
120
|
+
readonly anchorKeywords?: readonly string[];
|
|
121
|
+
} | {
|
|
122
|
+
/**
|
|
123
|
+
* **`gradient`** — preenchimento por gradiente. Extraído **só** por
|
|
124
|
+
* um pre-pass dedicado e **só quando a palavra-chave** ("gradiente"/
|
|
125
|
+
* "degradê"/"degrade"/"gradient") aparece — assim cores sólidas
|
|
126
|
+
* ("amarelo", "vermelho") seguem 100% intactas. O valor extraído é
|
|
127
|
+
* `{ kind: 'linear'|'radial', direction: 'horizontal'|'vertical'|
|
|
128
|
+
* 'diagonal', colors: string[] }`: o handler deriva os stops (1 cor
|
|
129
|
+
* → clara→escura; N cores → distribuídas) e a geometria. Nunca é
|
|
130
|
+
* posicional (não compete com o slot `fill`).
|
|
131
|
+
*/
|
|
132
|
+
readonly kind: 'gradient';
|
|
133
|
+
readonly optional?: boolean;
|
|
134
|
+
/** Não usado (gradient é só pre-pass) — presente p/ uniformidade do union. */
|
|
135
|
+
readonly anchorKeywords?: readonly string[];
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* **`anchorKeywords`** — palavras que precedem o valor do slot no
|
|
139
|
+
* input ("**borda** azul" → slot `stroke=azul`; "**posição** 100 50"
|
|
140
|
+
* → slot `position={x:100,y:50}`).
|
|
141
|
+
*
|
|
142
|
+
* Como funciona:
|
|
143
|
+
* 1. Extractor faz primeiro um **pass anchored**: pra cada slot com
|
|
144
|
+
* `anchorKeywords`, procura o anchor token (exato ou fuzzy ≤1) e
|
|
145
|
+
* consome o(s) próximo(s) token(s) compatível(eis) com o `kind`.
|
|
146
|
+
* 2. Depois faz o pass **posicional** normal pros slots sem anchor.
|
|
147
|
+
* 3. Tokens já consumidos no anchored pass ficam de fora do posicional
|
|
148
|
+
* — evita dupla atribuição.
|
|
149
|
+
*
|
|
150
|
+
* Útil quando o mesmo intent tem múltiplos slots de mesmo `kind`
|
|
151
|
+
* (e.g., `create-shape` com `fill` + `stroke` ambos `kind: 'color'`):
|
|
152
|
+
* sem âncora, o extractor pega a primeira cor pra `fill` e ignora a
|
|
153
|
+
* segunda. Com âncora `'borda'/'contorno'/'stroke'`, "fill vermelho
|
|
154
|
+
* borda azul" produz `{fill:'red', stroke:'blue'}` corretamente.
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
157
|
+
* Definição de um intent registrável no {@link NaturalLanguageService}.
|
|
158
|
+
*
|
|
159
|
+
* **Filosofia**:
|
|
160
|
+
* - `keywords`: palavras-chave **disparadoras** (PT/EN). Match exato
|
|
161
|
+
* (após tokenize/deacento) vira anchor da intent — se nenhuma
|
|
162
|
+
* keyword aparecer (mesmo aproximada via Levenshtein), o intent
|
|
163
|
+
* nem é considerado candidato.
|
|
164
|
+
* - `slots`: opcionais; cada um declara `kind` (number/color/enum/string)
|
|
165
|
+
* e se é `optional`. Extractor preenche o que conseguir.
|
|
166
|
+
* - `execute`: handler que recebe slots já extraídos + ctx. MUST NOT
|
|
167
|
+
* throw — falhas devem ser logged + ignored (assim como
|
|
168
|
+
* `MenuContribution.run`).
|
|
169
|
+
*
|
|
170
|
+
* **Multi-editor (D-042/D-043)**: `execute` recebe `NluContext` e
|
|
171
|
+
* deve resolver services do `ctx.injector` — não capturar root
|
|
172
|
+
* services em closures.
|
|
173
|
+
*
|
|
174
|
+
* **Exemplo**:
|
|
175
|
+
* ```ts
|
|
176
|
+
* nlu.registerIntent({
|
|
177
|
+
* id: 'create-rect',
|
|
178
|
+
* keywords: ['rectangle', 'rect', 'retângulo', 'retangulo'],
|
|
179
|
+
* actionKeywords: ['create', 'add', 'criar', 'desenhar'],
|
|
180
|
+
* slots: {
|
|
181
|
+
* fill: { kind: 'color', optional: true },
|
|
182
|
+
* width: { kind: 'number', optional: true, default: 100 },
|
|
183
|
+
* height: { kind: 'number', optional: true, default: 100 },
|
|
184
|
+
* },
|
|
185
|
+
* execute(slots, ctx) {
|
|
186
|
+
* const bus = ctx.injector.get(CommandBus);
|
|
187
|
+
* bus.dispatch(new InsertNodeCommand(rootId, createRect({...}, {style: {fill: slots.fill}})));
|
|
188
|
+
* },
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
interface NluIntent {
|
|
193
|
+
/** Stable unique id. Reverse-DNS recommended (`svge.builtin.nlu.create-rect`). */
|
|
194
|
+
readonly id: string;
|
|
195
|
+
/**
|
|
196
|
+
* Palavras-chave **primárias** que identificam o intent (substantivos,
|
|
197
|
+
* objetos: "rectangle", "retângulo", "circle", "círculo", "selection").
|
|
198
|
+
* Pelo menos uma deve aparecer (exato ou fuzzy ≤ 2 dist) pra intent
|
|
199
|
+
* ser candidato. Comparação após `tokenize` (lowercase + deacento).
|
|
200
|
+
*/
|
|
201
|
+
readonly keywords: readonly string[];
|
|
202
|
+
/**
|
|
203
|
+
* **`requiredAllGroups`** (D-046 review-7) — strict-AND matching:
|
|
204
|
+
* cada **grupo** representa um elemento semântico do intent que
|
|
205
|
+
* DEVE estar presente no input (pelo menos uma palavra do grupo
|
|
206
|
+
* casa, exato ou fuzzy). Diferente de `keywords` (OR fraco),
|
|
207
|
+
* requiredAllGroups exige TODOS os grupos preenchidos.
|
|
208
|
+
*
|
|
209
|
+
* **Caso de uso típico**: intents auto-descobertos de menu items
|
|
210
|
+
* multi-token como "Select All" precisam garantir que TANTO o
|
|
211
|
+
* verbo (select / selecionar / selecione) QUANTO o qualificador
|
|
212
|
+
* (all / tudo / todos) apareçam — senão "selecione estrela"
|
|
213
|
+
* matcharia "Select All" e selecionaria tudo erradamente.
|
|
214
|
+
*
|
|
215
|
+
* **Estrutura**: array de grupos; cada grupo é array de variantes
|
|
216
|
+
* sinônimas pra uma posição semântica. Exemplo "Select All":
|
|
217
|
+
* ```
|
|
218
|
+
* requiredAllGroups: [
|
|
219
|
+
* ['select', 'selecionar', 'selecione', 'marcar', ...], // verbo
|
|
220
|
+
* ['all', 'tudo', 'todos', 'todas', 'everything'], // qualificador
|
|
221
|
+
* ]
|
|
222
|
+
* ```
|
|
223
|
+
*
|
|
224
|
+
* Quando `requiredAllGroups` é declarado, o `keywords` ainda é
|
|
225
|
+
* usado pra ranking de confidence mas o **gate** de candidato vira
|
|
226
|
+
* o requiredAllGroups (todos satisfeitos OR keywords match com ≥1
|
|
227
|
+
* candidato — política pragmática).
|
|
228
|
+
*/
|
|
229
|
+
readonly requiredAllGroups?: readonly (readonly string[])[];
|
|
230
|
+
/**
|
|
231
|
+
* Verbos / ações associadas (opcional): "create", "criar", "add",
|
|
232
|
+
* "desenhar", "delete", "deletar". Quando presente, eleva confidence
|
|
233
|
+
* mas não é obrigatório — alguns intents são triggados só pelo
|
|
234
|
+
* substantivo ("rectangle" sozinho pode criar um). Útil pra
|
|
235
|
+
* desambiguar intents que compartilham keywords (e.g.,
|
|
236
|
+
* "delete circle" vs "create circle").
|
|
237
|
+
*/
|
|
238
|
+
readonly actionKeywords?: readonly string[];
|
|
239
|
+
/** Schema dos slots extraíveis (por nome). */
|
|
240
|
+
readonly slots?: Record<string, NluSlotSchema>;
|
|
241
|
+
/**
|
|
242
|
+
* Marca o intent como **destrutivo** — UI deve pedir confirmação
|
|
243
|
+
* antes de executar (mesmo confidence alta). Ex: delete, clear,
|
|
244
|
+
* reset all. Default `false`.
|
|
245
|
+
*/
|
|
246
|
+
readonly destructive?: boolean;
|
|
247
|
+
/** Hint humano pra UI exibir como sugestão / autocomplete. */
|
|
248
|
+
readonly description?: string;
|
|
249
|
+
/** Handler executado quando o intent é o top candidate + acima do threshold. */
|
|
250
|
+
execute(slots: Record<string, unknown>, ctx: NluContext): void | Promise<void>;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Resultado de {@link NaturalLanguageService.parse} — um candidato
|
|
254
|
+
* que casou com o input, com confidence + slots extraídos. Ordenado
|
|
255
|
+
* por confidence desc.
|
|
256
|
+
*/
|
|
257
|
+
interface NluCandidate {
|
|
258
|
+
/** Intent que casou. */
|
|
259
|
+
readonly intent: NluIntent;
|
|
260
|
+
/**
|
|
261
|
+
* Confidence em `[0, 1]`.
|
|
262
|
+
* - `≥ 0.7`: executa direto.
|
|
263
|
+
* - `0.4–0.7`: pede confirmação (UI decide via threshold configurável).
|
|
264
|
+
* - `< 0.4`: rejeita (parse retorna candidate mesmo assim — UI usa
|
|
265
|
+
* pra sugerir alternativas).
|
|
266
|
+
*/
|
|
267
|
+
readonly confidence: number;
|
|
268
|
+
/** Slots extraídos do input (preenche `default` quando ausente). */
|
|
269
|
+
readonly slots: Record<string, unknown>;
|
|
270
|
+
/**
|
|
271
|
+
* Razões que levaram ao score — útil pra debug + UI explicar
|
|
272
|
+
* "achei isso porque casou 'criar' (exato) e 'retângulo' (fuzzy)".
|
|
273
|
+
*/
|
|
274
|
+
readonly matches: readonly NluMatchReason[];
|
|
275
|
+
}
|
|
276
|
+
/** Detalhe de um match individual contribuindo pro confidence. */
|
|
277
|
+
interface NluMatchReason {
|
|
278
|
+
/** O que casou: keyword, actionKeyword, ou slot. */
|
|
279
|
+
readonly kind: 'keyword' | 'action' | 'slot';
|
|
280
|
+
/** O termo da intent que foi matched. */
|
|
281
|
+
readonly term: string;
|
|
282
|
+
/** O token do input que casou (após tokenize). */
|
|
283
|
+
readonly token: string;
|
|
284
|
+
/** Distância de Levenshtein (0 = exato). */
|
|
285
|
+
readonly distance: number;
|
|
286
|
+
/**
|
|
287
|
+
* Índice do token no array de input (D-046 review-10).
|
|
288
|
+
*
|
|
289
|
+
* Quando `kind === 'slot'`, é `-1` (slot value não é necessariamente
|
|
290
|
+
* um único token — pode ter sido extraído de uma frase de cor ou
|
|
291
|
+
* `kind: 'point'` compondo 2 números).
|
|
292
|
+
*
|
|
293
|
+
* Para `kind: 'keyword'` ou `'action'`, é o índice exato do token
|
|
294
|
+
* que casou. Usado pelo extractor pra marcar consumed e evitar
|
|
295
|
+
* dupla atribuição (resolve bug de `tokens.indexOf(value)` retornando
|
|
296
|
+
* sempre 1ª ocorrência em inputs com tokens repetidos).
|
|
297
|
+
*/
|
|
298
|
+
readonly tokenIndex?: number;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Threshold semântico do parse. UI consumers podem customizar via
|
|
302
|
+
* `parse(text, ctx, { threshold: 0.5 })`.
|
|
303
|
+
*/
|
|
304
|
+
interface NluParseOptions {
|
|
305
|
+
/** Mínimo confidence pra candidate ser retornado. Default `0.3`. */
|
|
306
|
+
readonly threshold?: number;
|
|
307
|
+
/** Máximo de candidates retornados (ordenados por confidence). Default `5`. */
|
|
308
|
+
readonly maxResults?: number;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Threshold semântico do execute. UI consumers podem customizar.
|
|
312
|
+
*/
|
|
313
|
+
interface NluExecuteOptions extends NluParseOptions {
|
|
314
|
+
/**
|
|
315
|
+
* Confidence mínima pra dispatchar direto. Default `0.7`.
|
|
316
|
+
* Abaixo disso, o `confirmGate` decide.
|
|
317
|
+
*/
|
|
318
|
+
readonly autoExecuteThreshold?: number;
|
|
319
|
+
/**
|
|
320
|
+
* Hook opcional pra confirmação interativa (e.g., mostrar dialog
|
|
321
|
+
* com "Você quis dizer: criar retângulo?"). Recebe o top candidate
|
|
322
|
+
* e retorna se deve executar.
|
|
323
|
+
*
|
|
324
|
+
* **Default**: `null` — sem confirmação, executa qualquer
|
|
325
|
+
* candidate ≥ `autoExecuteThreshold`, rejeita os abaixo.
|
|
326
|
+
*
|
|
327
|
+
* **Destrutivos**: quando `intent.destructive === true`,
|
|
328
|
+
* `confirmGate` é **obrigatório** — sem ele, intents destrutivos
|
|
329
|
+
* são sempre rejeitados (defesa contra dispatch acidental de
|
|
330
|
+
* delete/clear via fuzzy match ruim).
|
|
331
|
+
*/
|
|
332
|
+
readonly confirmGate?: ((candidate: NluCandidate) => boolean | Promise<boolean>) | null;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Resultado final de {@link NaturalLanguageService.execute}.
|
|
336
|
+
*/
|
|
337
|
+
interface NluExecuteResult {
|
|
338
|
+
/** `true` se um candidate foi executado, `false` se rejeitado / abaixo do threshold / sem match. */
|
|
339
|
+
readonly executed: boolean;
|
|
340
|
+
/** O candidate executado (ou top candidate quando `executed === false`, pra UI exibir alternativas). */
|
|
341
|
+
readonly candidate: NluCandidate | null;
|
|
342
|
+
/**
|
|
343
|
+
* Lista de candidates considerados (top N por confidence). Útil
|
|
344
|
+
* pra UI mostrar "também achei: ...".
|
|
345
|
+
*/
|
|
346
|
+
readonly alternatives: readonly NluCandidate[];
|
|
347
|
+
/** Por que não executou (`null` quando `executed === true`). */
|
|
348
|
+
readonly rejection: 'no-match' | 'below-threshold' | 'confirmation-declined' | 'destructive-no-gate' | 'execute-error' | null;
|
|
349
|
+
/**
|
|
350
|
+
* Erro capturado quando `rejection === 'execute-error'` (D-046
|
|
351
|
+
* review-10 / M4). Service envolve `intent.execute()` em try/catch
|
|
352
|
+
* — handler que lança não derruba a UI nem deixa Promise pendurada.
|
|
353
|
+
*/
|
|
354
|
+
readonly error?: Error;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* **`NaturalLanguageService`** — D-046 Fase 1 (rule-based NLU).
|
|
359
|
+
*
|
|
360
|
+
* Singleton root-provided que registra {@link NluIntent}s e
|
|
361
|
+
* traduz texto livre em comandos dispatcháveis. Composição direta
|
|
362
|
+
* dos parsers (`tokenize` → `fuzzyMatch` → `extractSlots`) com
|
|
363
|
+
* scoring de confidence.
|
|
364
|
+
*
|
|
365
|
+
* **Algoritmo de `parse(text)`**:
|
|
366
|
+
*
|
|
367
|
+
* 1. **Tokenize**: lowercase + deacento + split (preserva números/hex).
|
|
368
|
+
* 2. **Para cada intent registrado**, calcula `score`:
|
|
369
|
+
* a. **Keywords**: fuzzy match com adaptive distance. Se nenhuma
|
|
370
|
+
* keyword bate (nem aproximada), pula a intent. Score base do
|
|
371
|
+
* melhor match.
|
|
372
|
+
* b. **Action keywords** (opcional): match contra `actionKeywords`
|
|
373
|
+
* da intent + lookup canonical no `ACTION_DICTIONARY`. Eleva
|
|
374
|
+
* score quando casa.
|
|
375
|
+
* c. **Slots**: extrai com `extractSlots`. Slots obrigatórios não
|
|
376
|
+
* preenchidos penalizam (subtrai 0.15 cada). Slots opcionais
|
|
377
|
+
* preenchidos elevam levemente (0.05 cada).
|
|
378
|
+
* d. **Penalidade por distância**: cada match fuzzy não-exato
|
|
379
|
+
* reduz o score proporcionalmente.
|
|
380
|
+
* 3. **Sort + filter** por threshold + cap em `maxResults`.
|
|
381
|
+
*
|
|
382
|
+
* **`execute(text)`**: parse + auto-execute se ≥ `autoExecuteThreshold`
|
|
383
|
+
* (default 0.7), respeitando `confirmGate` pra destrutivos.
|
|
384
|
+
*
|
|
385
|
+
* **Multi-editor (D-042/D-043)**: o `NluContext` recebido pelos
|
|
386
|
+
* handlers tem `injector` do consumer — services devem ser resolvidos
|
|
387
|
+
* dele, nunca cacheados em closure.
|
|
388
|
+
*
|
|
389
|
+
* **Por que `Injectable({ providedIn: 'root' })`**: o registry de
|
|
390
|
+
* intents é global (todos os editors compartilham a definição), mas
|
|
391
|
+
* a EXECUÇÃO é per-scope via `NluContext.injector`. Mesmo padrão
|
|
392
|
+
* que `MenuContributionRegistry`.
|
|
393
|
+
*/
|
|
394
|
+
declare class NaturalLanguageService {
|
|
395
|
+
private readonly _intents;
|
|
396
|
+
/** Snapshot reativo dos intents registrados (insertion order). */
|
|
397
|
+
readonly intents: Signal<readonly NluIntent[]>;
|
|
398
|
+
/**
|
|
399
|
+
* **D-046 review-10 (L12)**: counter conveniente — consumers que só
|
|
400
|
+
* precisam do número (e.g., status bar "26 intents disponíveis")
|
|
401
|
+
* subscrevem aqui em vez do array inteiro (evita re-render quando
|
|
402
|
+
* o array muda mas length não).
|
|
403
|
+
*/
|
|
404
|
+
readonly intentsCount: Signal<number>;
|
|
405
|
+
/**
|
|
406
|
+
* **Cache de description tokens por intent** — usado pelo
|
|
407
|
+
* description-matching pass (meio-termo "semantic disambiguator"
|
|
408
|
+
* SEM ML deps). Computado lazy quando o intent aparece pela 1ª vez
|
|
409
|
+
* num scoring run; invalidado quando o intent é desregistrado.
|
|
410
|
+
*
|
|
411
|
+
* **Por que cache**: tokenize + filter stopwords é ~10ms por
|
|
412
|
+
* description, e o parse roda em cada keystroke do usuário —
|
|
413
|
+
* computar 30+ intents toda vez ficaria perceptível.
|
|
414
|
+
*/
|
|
415
|
+
private readonly descriptionTokensCache;
|
|
416
|
+
/**
|
|
417
|
+
* Registra um intent novo. Retorna `Disposable` pra remover (em geral
|
|
418
|
+
* trackeada pelo plugin via `ctx.track()` igual aos outros registries).
|
|
419
|
+
*
|
|
420
|
+
* **Erros**:
|
|
421
|
+
* - Throw em `id` vazio
|
|
422
|
+
* - Throw em `id` duplicado
|
|
423
|
+
* - Throw em `keywords` vazio (intent sem keyword nunca seria matched)
|
|
424
|
+
*/
|
|
425
|
+
registerIntent(intent: NluIntent): Disposable;
|
|
426
|
+
/**
|
|
427
|
+
* Tokeniza + filtra description do intent e armazena no cache.
|
|
428
|
+
* No-op se o intent não tem description ou já está cached.
|
|
429
|
+
*/
|
|
430
|
+
private populateDescriptionCache;
|
|
431
|
+
/**
|
|
432
|
+
* **Description-matching boost** (meio-termo "semantic disambiguator"
|
|
433
|
+
* sem ML deps).
|
|
434
|
+
*
|
|
435
|
+
* Tokeniza a `description` do intent (sem stopwords) e conta hits
|
|
436
|
+
* vs tokens do input. Cada hit adiciona +0.04 (cap 0.2 total). Não
|
|
437
|
+
* substitui keyword matching — só complementa quando há candidatos
|
|
438
|
+
* com score próximo (e.g., dois intents com keyword exata, ambos
|
|
439
|
+
* 0.65; o de description mais alinhada vence).
|
|
440
|
+
*
|
|
441
|
+
* **Cache**: tokens da description são computados na 1ª chamada
|
|
442
|
+
* e guardados em `descriptionTokensCache` keyed por `intent.id`.
|
|
443
|
+
*
|
|
444
|
+
* **Quando contribui**: SEMPRE — score é monotônico. UI consumers
|
|
445
|
+
* que queiram desligar podem passar `options.disableDescriptionBoost`
|
|
446
|
+
* (não implementado ainda — Fase 2 quando ML chegar e a heurística
|
|
447
|
+
* for mais relevante).
|
|
448
|
+
*/
|
|
449
|
+
private descriptionBoost;
|
|
450
|
+
/** Procura intent por id (`null` se ausente). */
|
|
451
|
+
getIntent(id: string): NluIntent | null;
|
|
452
|
+
/**
|
|
453
|
+
* Parse `text` em candidates ordenados por confidence desc.
|
|
454
|
+
*
|
|
455
|
+
* @param text input natural do usuário
|
|
456
|
+
* @param _ctx NluContext (não usado no parse — só no execute; aqui
|
|
457
|
+
* mantido por simetria de API)
|
|
458
|
+
* @param options threshold + maxResults
|
|
459
|
+
*/
|
|
460
|
+
parse(text: string, _ctx: NluContext, options?: NluParseOptions): readonly NluCandidate[];
|
|
461
|
+
/**
|
|
462
|
+
* Parse + dispatch o top candidate.
|
|
463
|
+
*
|
|
464
|
+
* **Política**:
|
|
465
|
+
* - `intent.destructive === true` SEM `confirmGate` → rejeita
|
|
466
|
+
* (defesa contra delete acidental).
|
|
467
|
+
* - `confidence ≥ autoExecuteThreshold` (default 0.7) → executa direto
|
|
468
|
+
* (ou via confirmGate quando destrutivo).
|
|
469
|
+
* - `confidence < autoExecuteThreshold` E `confirmGate` presente →
|
|
470
|
+
* chama o gate; só executa se retornar `true`.
|
|
471
|
+
* - Senão → rejeita com `'below-threshold'`.
|
|
472
|
+
*/
|
|
473
|
+
execute(text: string, ctx: NluContext, options?: NluExecuteOptions): Promise<NluExecuteResult>;
|
|
474
|
+
/**
|
|
475
|
+
* Conectores que separam comandos numa frase composta
|
|
476
|
+
* ("crie X, e um Y"). Split em `,`/`;` + " e "/" depois "/" também ".
|
|
477
|
+
*/
|
|
478
|
+
private static readonly CLAUSE_SPLIT_RE;
|
|
479
|
+
/**
|
|
480
|
+
* Verbos de comando (criação/edição) que marcam uma cláusula como
|
|
481
|
+
* comando autônomo. Deaccentuados/lowercase (forma que o tokenizer gera).
|
|
482
|
+
*/
|
|
483
|
+
private static readonly COMMAND_VERBS;
|
|
484
|
+
/**
|
|
485
|
+
* **Multi-comando por frase** — divide o texto nos conectores e executa
|
|
486
|
+
* cada cláusula como um comando independente. Cada `execute()` despacha
|
|
487
|
+
* seu próprio command no bus, então **cada forma é um passo de undo
|
|
488
|
+
* separado**.
|
|
489
|
+
*
|
|
490
|
+
* **Sem regressão**: quando não há split real (frase simples, ou os
|
|
491
|
+
* "pedaços" são apenas fragmentos — listas de cor "preto e branco",
|
|
492
|
+
* números "100 e 200"), recai EXATAMENTE no `execute(text)` de sempre.
|
|
493
|
+
*
|
|
494
|
+
* Heurística anti over-split: um pedaço só vira comando separado se
|
|
495
|
+
* contiver uma **forma** ou um **verbo de comando**; senão é re-fundido
|
|
496
|
+
* ao anterior.
|
|
497
|
+
*
|
|
498
|
+
* @returns um {@link NluExecuteResult} por comando, na ordem da frase.
|
|
499
|
+
*/
|
|
500
|
+
executeSequence(text: string, ctx: NluContext, options?: NluExecuteOptions): Promise<readonly NluExecuteResult[]>;
|
|
501
|
+
/**
|
|
502
|
+
* Divide `text` em cláusulas-comando. Pedaços que não parecem comando
|
|
503
|
+
* (sem forma nem verbo) são re-fundidos ao anterior. Retorna ≤1 item
|
|
504
|
+
* quando não há split real.
|
|
505
|
+
*/
|
|
506
|
+
private splitIntoClauses;
|
|
507
|
+
/** `true` se a cláusula contém uma forma ou um verbo de comando. */
|
|
508
|
+
private clauseLooksLikeCommand;
|
|
509
|
+
/**
|
|
510
|
+
* Executa um candidate **específico** (escolhido pelo usuário via UI
|
|
511
|
+
* — e.g., clique numa alternativa) aplicando as mesmas regras de
|
|
512
|
+
* segurança do {@link execute}: destructive sem gate rejeita;
|
|
513
|
+
* confidence baixa sem gate rejeita.
|
|
514
|
+
*
|
|
515
|
+
* **Por que precisa de método separado**: chamar `candidate.intent.execute()`
|
|
516
|
+
* direto bypassa toda a lógica de threshold/destructive. UI components
|
|
517
|
+
* (`<svge-nlu-input>` alternatives list) DEVEM usar este método em
|
|
518
|
+
* vez de `intent.execute()` direto, pra preservar a defesa contra
|
|
519
|
+
* delete acidental.
|
|
520
|
+
*
|
|
521
|
+
* **Política idêntica ao `execute`**:
|
|
522
|
+
* - Destrutivo SEM `confirmGate` → rejeita `'destructive-no-gate'`.
|
|
523
|
+
* - Destrutivo COM gate → roda gate; só executa se aprovar.
|
|
524
|
+
* - Não-destrutivo com confidence ≥ `autoExecuteThreshold` → executa.
|
|
525
|
+
* - Não-destrutivo com confidence < threshold E gate presente →
|
|
526
|
+
* roda gate.
|
|
527
|
+
* - Senão → `'below-threshold'`.
|
|
528
|
+
*
|
|
529
|
+
* @param candidate o NluCandidate a executar (vindo de `parse()`).
|
|
530
|
+
* @param ctx contexto (injector do consumer).
|
|
531
|
+
* @param options threshold + confirmGate.
|
|
532
|
+
*/
|
|
533
|
+
executeCandidate(candidate: NluCandidate, ctx: NluContext, options?: NluExecuteOptions): Promise<NluExecuteResult>;
|
|
534
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NaturalLanguageService, never>;
|
|
535
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NaturalLanguageService>;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* **`discoverMenuIntents`** — D-046 Fase 1.
|
|
540
|
+
*
|
|
541
|
+
* Auto-promove TODAS as contribuições do {@link MenuContributionRegistry}
|
|
542
|
+
* em intents NLU. Cada menu item vira um intent cujo handler executa
|
|
543
|
+
* o `run()` original com o `MenuContributionContext` derivado do
|
|
544
|
+
* `NluContext.injector`.
|
|
545
|
+
*
|
|
546
|
+
* **Como derivamos keywords a partir do `label`**: tokenize o label
|
|
547
|
+
* (lowercase + deacento), remove stopwords, mantém o resto. "Bring
|
|
548
|
+
* to Front" → `['bring', 'front']`; "Selecionar tudo" →
|
|
549
|
+
* `['selecionar', 'tudo']`. Plugins que queiram aliases adicionais
|
|
550
|
+
* podem registrar intents customizados via
|
|
551
|
+
* {@link NaturalLanguageService.registerIntent} sem conflito.
|
|
552
|
+
*
|
|
553
|
+
* **Skip de dividers + items sem label + roadmap**: `divider: true`,
|
|
554
|
+
* label vazio, ou `comingSoon: true` (D-085) não produzem intent — não
|
|
555
|
+
* são ações executáveis (o `run()` de um item roadmap é no-op).
|
|
556
|
+
*
|
|
557
|
+
* **Destrutivos**: items cujo label contém "delete"/"remove"/"clear"/
|
|
558
|
+
* "deletar"/"excluir"/"remover"/"apagar" são marcados `destructive: true`
|
|
559
|
+
* — a NLU exige `confirmGate` pra dispatch automático.
|
|
560
|
+
*
|
|
561
|
+
* **Multi-editor (D-042/D-043)**: o handler propaga `ctx.injector`
|
|
562
|
+
* para o `MenuContributionContext` esperado pelo `run()` do menu
|
|
563
|
+
* contribution. Cadeia de scope ativo preservada end-to-end.
|
|
564
|
+
*
|
|
565
|
+
* **Reactivity**: este helper é one-shot — registra os intents que
|
|
566
|
+
* EXISTEM no momento da chamada. Para auto-discovery contínuo
|
|
567
|
+
* (plugins instalados depois também viram intents),
|
|
568
|
+
* use {@link discoverMenuIntentsReactive} — wrapper baseado em
|
|
569
|
+
* `effect()` que reflete mudanças do registry em tempo real
|
|
570
|
+
* (Audit #12).
|
|
571
|
+
*
|
|
572
|
+
* **Retorno**: array de `Disposable` (um por intent registrado) +
|
|
573
|
+
* count. O `composedDispose` permite cleanup em massa via
|
|
574
|
+
* `ctx.track()`.
|
|
575
|
+
*/
|
|
576
|
+
interface DiscoverMenuIntentsResult {
|
|
577
|
+
/** Disposables dos intents criados — array vazio quando nada foi descoberto. */
|
|
578
|
+
readonly disposables: readonly Disposable[];
|
|
579
|
+
/** Quantos intents foram efetivamente criados. */
|
|
580
|
+
readonly count: number;
|
|
581
|
+
/** Disposable composto que dispara dispose() em todos. */
|
|
582
|
+
readonly composedDispose: Disposable;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Cria intent NLU a partir de uma `MenuContribution` específica.
|
|
586
|
+
* Retorna `null` quando a contribution não é elegível (divider, label
|
|
587
|
+
* vazio, ou tokens insuficientes pra match útil).
|
|
588
|
+
*/
|
|
589
|
+
declare function menuContributionToIntent(contrib: MenuContribution): NluIntent | null;
|
|
590
|
+
/**
|
|
591
|
+
* Walks o registry, cria intents pra cada contribution elegível, e
|
|
592
|
+
* registra todos no service. Skip silencioso pra contribuições que
|
|
593
|
+
* gerariam intent duplicado (caller pode já ter registrado um
|
|
594
|
+
* customizado com o mesmo id) — não throws.
|
|
595
|
+
*/
|
|
596
|
+
declare function discoverMenuIntents(registry: MenuContributionRegistry, service: NaturalLanguageService): DiscoverMenuIntentsResult;
|
|
597
|
+
/**
|
|
598
|
+
* **`discoverMenuIntentsReactive`** — Audit #12. Reactive companion to
|
|
599
|
+
* {@link discoverMenuIntents}.
|
|
600
|
+
*
|
|
601
|
+
* Performs an **immediate synchronous discovery** of all current menu
|
|
602
|
+
* contributions (so callers reading `service.intents()` right after
|
|
603
|
+
* this returns see the auto-discovered intents — same observable
|
|
604
|
+
* behavior as the one-shot helper at install time), AND registers an
|
|
605
|
+
* Angular `effect()` that re-runs discovery whenever
|
|
606
|
+
* `registry.contributions()` emits — covering plugins installed AFTER
|
|
607
|
+
* the NLU plugin and contributions disposed at any later time.
|
|
608
|
+
*
|
|
609
|
+
* **Strategy on each change**: tear down the previous batch via its
|
|
610
|
+
* `composedDispose`, then rebuild a fresh batch from the new registry
|
|
611
|
+
* snapshot. Coarse-grained but correct: no diff/merge bookkeeping
|
|
612
|
+
* means no chance of half-state on race conditions, at the cost of
|
|
613
|
+
* O(N) work per registry mutation (N is small for menu items, usually
|
|
614
|
+
* ≤ 50). Custom intents the consumer registered out-of-band stay
|
|
615
|
+
* untouched because `discoverMenuIntents` skips ids already present.
|
|
616
|
+
*
|
|
617
|
+
* **Why an effect, not a manual subscription**: Angular signals don't
|
|
618
|
+
* expose a `subscribe()` — `effect()` IS the official subscription
|
|
619
|
+
* mechanism. It also gets disposed cleanly via `effectRef.destroy()`,
|
|
620
|
+
* letting the returned `Disposable` cover both the effect AND the
|
|
621
|
+
* current batch of intents.
|
|
622
|
+
*
|
|
623
|
+
* **Injection context requirement**: `effect()` may only be created
|
|
624
|
+
* inside an injection context. We accept an explicit `Injector` and
|
|
625
|
+
* use `runInInjectionContext` so the function works from anywhere —
|
|
626
|
+
* including plugin `install(ctx)` blocks that aren't injection
|
|
627
|
+
* contexts themselves.
|
|
628
|
+
*
|
|
629
|
+
* **Echo skipping via reference identity**: Angular schedules the
|
|
630
|
+
* effect's first run for a later microtask, NOT immediately at
|
|
631
|
+
* creation. That first run might happen BEFORE or AFTER unrelated
|
|
632
|
+
* signal mutations. Using a "skip first" flag is unreliable. Instead
|
|
633
|
+
* we capture the exact array reference that the initial sync
|
|
634
|
+
* discovery consumed and short-circuit any firing where the signal
|
|
635
|
+
* still returns that same reference — guaranteed safe because
|
|
636
|
+
* `MenuContributionRegistry.register` and the returned dispose both
|
|
637
|
+
* produce a NEW array (immutable update), so identity comparison
|
|
638
|
+
* cleanly distinguishes "no real change" from "actual mutation".
|
|
639
|
+
*/
|
|
640
|
+
interface DiscoverMenuIntentsReactiveResult {
|
|
641
|
+
/**
|
|
642
|
+
* Composite disposable — stops the effect AND tears down the
|
|
643
|
+
* currently-tracked batch of intents. Idempotent: calling
|
|
644
|
+
* `dispose()` twice is a silent no-op.
|
|
645
|
+
*/
|
|
646
|
+
readonly disposable: Disposable;
|
|
647
|
+
}
|
|
648
|
+
declare function discoverMenuIntentsReactive(registry: MenuContributionRegistry, service: NaturalLanguageService, injector: Injector): DiscoverMenuIntentsReactiveResult;
|
|
649
|
+
|
|
650
|
+
declare const builtinNluPlugin: EditorPlugin;
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Tokenizer multilíngue (PT + EN) — D-046 Fase 1.
|
|
654
|
+
*
|
|
655
|
+
* Operações em ordem:
|
|
656
|
+
* 1. Lowercase
|
|
657
|
+
* 2. **Deacentuação** via `String.prototype.normalize('NFD')` + strip
|
|
658
|
+
* de marks combining — "vermelho" → "vermelho", "círculo" →
|
|
659
|
+
* "circulo", "ação" → "acao". Permite o dicionário ser keyed em
|
|
660
|
+
* ASCII puro e ainda casar com input acentuado.
|
|
661
|
+
* 3. Split por whitespace + pontuação comum, **preservando**:
|
|
662
|
+
* - números (incluindo decimais `1.5`, `2,5` — pt/en)
|
|
663
|
+
* - dimensões compostas `100x50`
|
|
664
|
+
* - hex colors `#ff0000`
|
|
665
|
+
* 4. Filter de tokens vazios.
|
|
666
|
+
*
|
|
667
|
+
* **Não filtra stopwords aqui** — algumas etapas (slot-extractor)
|
|
668
|
+
* precisam dos tokens originais pra ordem posicional. Caller decide
|
|
669
|
+
* quando aplicar `isStopword`.
|
|
670
|
+
*
|
|
671
|
+
* **Por que não usar Intl.Segmenter / NLP libs**: zero-deps é
|
|
672
|
+
* requisito explícito da Fase 1 (D-046). Tokenização "ingênua"
|
|
673
|
+
* cobre o vocabulário de comandos de editor de SVG sem ruído.
|
|
674
|
+
*/
|
|
675
|
+
/**
|
|
676
|
+
* Normaliza um texto removendo acentos via NFD + strip de marks
|
|
677
|
+
* combining (Unicode category `Mn`).
|
|
678
|
+
*
|
|
679
|
+
* **Por que NFD**: separa o caractere base da marca combinante
|
|
680
|
+
* (`á` → `a` + ` ́ `), depois removemos só as marcas. Funciona pra PT
|
|
681
|
+
* (acentos agudos, til, cedilha) e qualquer alfabeto latino estendido.
|
|
682
|
+
*
|
|
683
|
+
* **Edge case** mantido: caracteres sem decomposição NFD passam
|
|
684
|
+
* intactos (preserva tokens em outros alfabetos se aparecerem).
|
|
685
|
+
*/
|
|
686
|
+
declare function deaccent(input: string): string;
|
|
687
|
+
/**
|
|
688
|
+
* Normaliza texto: lowercase + deacentuação. Não tokeniza — só
|
|
689
|
+
* normaliza pra usar em dicionários, comparações.
|
|
690
|
+
*/
|
|
691
|
+
declare function normalize(input: string): string;
|
|
692
|
+
/**
|
|
693
|
+
* Tokeniza um input textual seguindo a pipeline descrita no header.
|
|
694
|
+
*
|
|
695
|
+
* **Algoritmo (single-pass scanner)**:
|
|
696
|
+
* 1. Itera caractere por caractere.
|
|
697
|
+
* 2. Caractere de pontuação (`PUNCT_CHARS`) OU `,`/`.` que NÃO está
|
|
698
|
+
* entre dígitos → fecha o token corrente.
|
|
699
|
+
* 3. Caractere "normal" (incluindo `,` / `.` entre dígitos) → acumula.
|
|
700
|
+
* 4. Fim do input → fecha o último token se houver.
|
|
701
|
+
*
|
|
702
|
+
* **Retorno**: array de tokens normalizados (lowercase, sem acento),
|
|
703
|
+
* sem vazios, preservando ordem de aparição (importante pro
|
|
704
|
+
* slot-extractor encontrar "vermelho" depois de "fill").
|
|
705
|
+
*
|
|
706
|
+
* **Custo**: O(n) no tamanho do input.
|
|
707
|
+
*/
|
|
708
|
+
declare function tokenize(input: string): readonly string[];
|
|
709
|
+
/**
|
|
710
|
+
* Tokeniza E remove stopwords. Atalho conveniente; quando você
|
|
711
|
+
* precisa preservar a ordem original (slot extraction posicional),
|
|
712
|
+
* use `tokenize()` direto.
|
|
713
|
+
*/
|
|
714
|
+
declare function tokenizeWithoutStopwords(input: string, stopwords: ReadonlySet<string>): readonly string[];
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Distância de Levenshtein — edit distance clássica (insertion,
|
|
718
|
+
* deletion, substitution; cada uma custa 1). Usada pelo fuzzy
|
|
719
|
+
* matcher pra encontrar a palavra mais próxima no dicionário quando
|
|
720
|
+
* o usuário digita errado ("vermelo" → "vermelho", dist 1;
|
|
721
|
+
* "retangulo" → "retangulo", dist 0).
|
|
722
|
+
*
|
|
723
|
+
* **Implementação**: 2-row DP (memória O(min(m,n))), curto-circuita
|
|
724
|
+
* cedo via "early termination on max distance" — útil porque o
|
|
725
|
+
* matcher fuzzy só aceita distâncias pequenas (≤ 2 default) e abandonar
|
|
726
|
+
* cedo evita varrer a tabela inteira pra strings totalmente diferentes.
|
|
727
|
+
*
|
|
728
|
+
* **Sem cache**: as strings são curtas (tokens de palavras, ≤ 20
|
|
729
|
+
* chars) e o overhead de hashmap supera o custo de recomputar. Se
|
|
730
|
+
* benchmarks futuros mostrarem hot spot, adicionar cache LRU
|
|
731
|
+
* Map<`${a}|${b}`, number> com cap pequeno.
|
|
732
|
+
*
|
|
733
|
+
* **Zero deps** (requisito Fase 1 D-046).
|
|
734
|
+
*/
|
|
735
|
+
/**
|
|
736
|
+
* Calcula a distância de Levenshtein entre `a` e `b`.
|
|
737
|
+
*
|
|
738
|
+
* @param a primeira string (lowercase + deacentuada já preferível)
|
|
739
|
+
* @param b segunda string
|
|
740
|
+
* @param maxDist se informado, retorna `Infinity` assim que prova
|
|
741
|
+
* que a distância real excede `maxDist` (early
|
|
742
|
+
* termination, ~10× speedup em mismatches grandes).
|
|
743
|
+
* @returns distância em [0, max(a.length, b.length)] ou `Infinity`
|
|
744
|
+
* se `maxDist` foi excedida.
|
|
745
|
+
*/
|
|
746
|
+
declare function levenshtein(a: string, b: string, maxDist?: number): number;
|
|
747
|
+
/**
|
|
748
|
+
* Encontra o melhor match em `candidates` pra `query`, retornando
|
|
749
|
+
* a string + distância. Curto-circuita em match exato.
|
|
750
|
+
*
|
|
751
|
+
* @param query termo a buscar (normalizado)
|
|
752
|
+
* @param candidates lista de strings candidatas (normalizadas)
|
|
753
|
+
* @param maxDist distância máxima aceitável; ignora candidates além disso
|
|
754
|
+
* @returns `{ match, distance }` ou `null` se nenhum candidate dentro do limite
|
|
755
|
+
*/
|
|
756
|
+
declare function bestMatch(query: string, candidates: readonly string[], maxDist: number): {
|
|
757
|
+
match: string;
|
|
758
|
+
distance: number;
|
|
759
|
+
} | null;
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Resultado de um match fuzzy entre um token de input e um termo
|
|
763
|
+
* de dicionário/keyword.
|
|
764
|
+
*/
|
|
765
|
+
interface FuzzyMatch {
|
|
766
|
+
/** Termo do dicionário que casou. */
|
|
767
|
+
readonly term: string;
|
|
768
|
+
/** Token do input (preservado pra `NluMatchReason`). */
|
|
769
|
+
readonly token: string;
|
|
770
|
+
/** Distância de Levenshtein (0 = exato). */
|
|
771
|
+
readonly distance: number;
|
|
772
|
+
/**
|
|
773
|
+
* Confidence sub-1 derivada da distância vs comprimento do termo.
|
|
774
|
+
* Match exato = 1.0; match com 1 erro em palavra de 8 letras ~0.875.
|
|
775
|
+
* Match com 2 erros em palavra de 4 letras = 0.5.
|
|
776
|
+
*/
|
|
777
|
+
readonly score: number;
|
|
778
|
+
/**
|
|
779
|
+
* **`tokenIndex`** (D-046 review-10): índice do token original no
|
|
780
|
+
* array de entrada. Sempre populado por {@link fuzzyMatchAny} e
|
|
781
|
+
* {@link fuzzyMatchAll}; em {@link fuzzyMatchToken} fica `-1` porque
|
|
782
|
+
* o caller não passa array (e sabe o índice por contexto próprio).
|
|
783
|
+
*
|
|
784
|
+
* **Motivação**: `NaturalLanguageService` precisa marcar o ÍNDICE
|
|
785
|
+
* do token consumido (não só seu valor string) para evitar bug onde
|
|
786
|
+
* `tokens.indexOf(value)` retorna sempre a 1ª ocorrência — quebrando
|
|
787
|
+
* em inputs com tokens repetidos ("vermelho borda vermelho").
|
|
788
|
+
*/
|
|
789
|
+
readonly tokenIndex: number;
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Política de distância adaptativa por tamanho do termo:
|
|
793
|
+
* - Termos ≤ 3 chars: distância máxima 0 (matches exatos só — "rect",
|
|
794
|
+
* "red" são tão curtas que 1 typo já vira outra palavra).
|
|
795
|
+
* - Termos 4–6 chars: distância máxima 1.
|
|
796
|
+
* - Termos ≥ 7 chars: distância máxima 2.
|
|
797
|
+
*
|
|
798
|
+
* Cap absoluto: 2 (acima disso é palavra diferente, não typo).
|
|
799
|
+
*/
|
|
800
|
+
declare function adaptiveMaxDistance(termLength: number): number;
|
|
801
|
+
/**
|
|
802
|
+
* Tenta casar um único token de input contra uma lista de termos
|
|
803
|
+
* (keys de dicionário OU keywords de intent), respeitando distância
|
|
804
|
+
* adaptativa por tamanho do termo.
|
|
805
|
+
*
|
|
806
|
+
* **Retorno**: melhor match (menor distância) ou `null` se nenhum
|
|
807
|
+
* dentro do orçamento de distância adaptativo.
|
|
808
|
+
*
|
|
809
|
+
* **Custo**: O(N) onde N = `terms.length`, com early termination
|
|
810
|
+
* herdada do `levenshtein()`.
|
|
811
|
+
*/
|
|
812
|
+
declare function fuzzyMatchToken(token: string, terms: readonly string[]): FuzzyMatch | null;
|
|
813
|
+
/**
|
|
814
|
+
* Match fuzzy de **qualquer** token de uma lista contra **qualquer**
|
|
815
|
+
* termo de uma lista. Retorna o melhor casamento (ou `null`).
|
|
816
|
+
*
|
|
817
|
+
* Útil pra "alguma das keywords da intent aparece no input?". Pára
|
|
818
|
+
* cedo no primeiro match exato (distância 0).
|
|
819
|
+
*/
|
|
820
|
+
declare function fuzzyMatchAny(tokens: readonly string[], terms: readonly string[]): FuzzyMatch | null;
|
|
821
|
+
/**
|
|
822
|
+
* Versão "all matches" — retorna **todos** os matches fuzzy de um
|
|
823
|
+
* token contra a lista, sem early-termination. Útil quando precisamos
|
|
824
|
+
* de ranking completo (intent registry com muitos candidates).
|
|
825
|
+
*/
|
|
826
|
+
declare function fuzzyMatchAll(token: string, terms: readonly string[], maxDistOverride?: number): readonly FuzzyMatch[];
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Resultado da extração: valores por nome de slot. Slots não
|
|
830
|
+
* encontrados são `undefined` (caller decide se aplica default).
|
|
831
|
+
*/
|
|
832
|
+
type ExtractedSlots = Record<string, unknown>;
|
|
833
|
+
/**
|
|
834
|
+
* Indica quais tokens foram consumidos por matchings (keywords/
|
|
835
|
+
* actions/slots) — útil pra evitar dupla atribuição (token "100"
|
|
836
|
+
* não deve preencher dois slots `number` distintos).
|
|
837
|
+
*/
|
|
838
|
+
interface ExtractContext {
|
|
839
|
+
/** Set mutável de índices de tokens já consumidos. */
|
|
840
|
+
readonly consumedIndices: Set<number>;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Parse um token como número. Aceita "100", "1.5", "1,5", "100px".
|
|
844
|
+
* Trata `,` como separador decimal (PT). Não aceita signos relativos
|
|
845
|
+
* exóticos. Retorna `null` quando não é número.
|
|
846
|
+
*/
|
|
847
|
+
declare function parseNumberToken(token: string): number | null;
|
|
848
|
+
/**
|
|
849
|
+
* Tenta resolver um token ISOLADO como cor: hex direto, rgb/hsl
|
|
850
|
+
* funcional OU lookup no dicionário com fuzzy match (dist ≤ 1 pra
|
|
851
|
+
* typos como "vermelo"). Para frases com intensificador adjacente
|
|
852
|
+
* ("azul claro"), use {@link parseColorPhrase}.
|
|
853
|
+
*/
|
|
854
|
+
declare function parseColorToken(token: string): string | null;
|
|
855
|
+
/**
|
|
856
|
+
* Resultado de {@link parseColorPhrase}: cor resolvida + quantos
|
|
857
|
+
* tokens adjacentes foram consumidos (sempre ≥ 1).
|
|
858
|
+
*/
|
|
859
|
+
interface ColorPhraseMatch {
|
|
860
|
+
/** Cor resolvida (`#rrggbb`, CSS keyword, ou outro do dicionário). */
|
|
861
|
+
readonly color: string;
|
|
862
|
+
/**
|
|
863
|
+
* Quantos tokens (a partir de `startIdx`) compõem a frase de cor.
|
|
864
|
+
* Sempre ≥ 1. Caller deve marcar todos esses índices como consumed.
|
|
865
|
+
*
|
|
866
|
+
* Exemplos:
|
|
867
|
+
* - "azul" → tokensConsumed = 1
|
|
868
|
+
* - "azul claro" → tokensConsumed = 2 (intensificador adjacente)
|
|
869
|
+
* - "bem azul escuro" → tokensConsumed = 3 (multiplier + cor + mod)
|
|
870
|
+
* - "muito claro azul" → tokensConsumed = 3 (multiplier + mod ANTES da cor)
|
|
871
|
+
*/
|
|
872
|
+
readonly tokensConsumed: number;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Resolve uma cor a partir de `tokens[startIdx]`, considerando
|
|
876
|
+
* **intensificadores adjacentes** que modificam o lightness do hex
|
|
877
|
+
* base. Suporta padrões:
|
|
878
|
+
*
|
|
879
|
+
* - `[modifier] cor [modifier]` — "azul claro" / "claro azul" /
|
|
880
|
+
* "verde escuro" / "dark green"
|
|
881
|
+
* - `[multiplier] [modifier] cor` — "muito claro azul" / "very
|
|
882
|
+
* dark red"
|
|
883
|
+
* - `cor [multiplier] [modifier]` — "verde bem escuro" / "blue
|
|
884
|
+
* really dark"
|
|
885
|
+
*
|
|
886
|
+
* Não modifica `none` / `currentColor` / `inherit` (sem hex base).
|
|
887
|
+
*
|
|
888
|
+
* Retorna `null` quando `tokens[startIdx]` não é cor nem
|
|
889
|
+
* intensificador-seguido-de-cor.
|
|
890
|
+
*/
|
|
891
|
+
declare function parseColorPhrase(tokens: readonly string[], startIdx: number): ColorPhraseMatch | null;
|
|
892
|
+
/**
|
|
893
|
+
* Extrai dimensão composta `100x50` em `{ width, height }` quando
|
|
894
|
+
* presente. Retorna `null` quando o token não é dimensão.
|
|
895
|
+
*/
|
|
896
|
+
declare function parseDimensionToken(token: string): {
|
|
897
|
+
width: number;
|
|
898
|
+
height: number;
|
|
899
|
+
} | null;
|
|
900
|
+
/**
|
|
901
|
+
* Extrai os slots declarados em `schemas` a partir dos `tokens`,
|
|
902
|
+
* marcando consumed indices no `ctx`.
|
|
903
|
+
*
|
|
904
|
+
* **Algoritmo (single pass por slot, ordem declarada)**:
|
|
905
|
+
* 1. Para cada slot, varre tokens ainda não consumidos.
|
|
906
|
+
* 2. Tenta resolver de acordo com o `kind`.
|
|
907
|
+
* 3. Se encontrar, marca consumed + atribui ao slot.
|
|
908
|
+
* 4. Aplica `default` quando não encontrou + `optional: true`.
|
|
909
|
+
*
|
|
910
|
+
* **Edge case `kind: 'number'`** com dimensão `100x50`: se houver
|
|
911
|
+
* slot `width` E `height` no schema (nessa ordem), o token de
|
|
912
|
+
* dimensão preenche os dois e consome o índice uma vez. Caso
|
|
913
|
+
* contrário, só o primeiro number do par é usado.
|
|
914
|
+
*
|
|
915
|
+
* **Edge case `kind: 'color'`** com intensificador adjacente
|
|
916
|
+
* ("azul claro"): {@link parseColorPhrase} consome todos os
|
|
917
|
+
* tokens da frase de cor — caller marca múltiplos consumed indices.
|
|
918
|
+
*/
|
|
919
|
+
declare function extractSlots(tokens: readonly string[], schemas: Record<string, NluSlotSchema>, ctx?: ExtractContext): ExtractedSlots;
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Color parsing helpers — D-046 Fase 1 enrich.
|
|
923
|
+
*
|
|
924
|
+
* Reconhece formatos CSS além de hex puro:
|
|
925
|
+
* - `rgb(255, 0, 0)` / `rgba(255, 0, 0, 0.5)`
|
|
926
|
+
* - `hsl(0, 100%, 50%)` / `hsla(0, 100%, 50%, 0.5)`
|
|
927
|
+
* - Hex (3, 4, 6, 8 dígitos) — delegado pro caller via `HEX_COLOR_RE`
|
|
928
|
+
*
|
|
929
|
+
* Também expõe `lightenHex` / `darkenHex` usados pelos intensificadores
|
|
930
|
+
* ("azul claro" / "verde escuro" / "bem escuro") no slot extractor.
|
|
931
|
+
*
|
|
932
|
+
* **Por que arquivo separado** (em vez de empilhar tudo no
|
|
933
|
+
* slot-extractor): coesão. Color math é responsabilidade única;
|
|
934
|
+
* tornar reaproveitável por outros parsers / plugins / UI components.
|
|
935
|
+
*
|
|
936
|
+
* **Sem dependências externas** (requisito Fase 1).
|
|
937
|
+
*/
|
|
938
|
+
/** Hex 3, 4, 6 ou 8 dígitos (com `#`). */
|
|
939
|
+
declare const HEX_COLOR_RE: RegExp;
|
|
940
|
+
/**
|
|
941
|
+
* Parse `rgb(...)` / `rgba(...)` → hex (ignora alpha por ora, sem
|
|
942
|
+
* suporte a alpha no slot color). Retorna `null` se não casar.
|
|
943
|
+
*/
|
|
944
|
+
declare function parseRgbFunction(input: string): string | null;
|
|
945
|
+
/**
|
|
946
|
+
* Parse `hsl(...)` / `hsla(...)` → hex. Retorna `null` se não casar.
|
|
947
|
+
*/
|
|
948
|
+
declare function parseHslFunction(input: string): string | null;
|
|
949
|
+
/**
|
|
950
|
+
* Vocabulário de intensificadores que **modificam** uma cor adjacente.
|
|
951
|
+
* `delta` é a variação de lightness (HSL, 0..1) aplicada ao hex base.
|
|
952
|
+
* Positivo = mais claro, negativo = mais escuro.
|
|
953
|
+
*
|
|
954
|
+
* Multiplicador "bem" / "muito" / "very" / "really" dobra o efeito do
|
|
955
|
+
* intensificador subsequente (vide `LIGHTNESS_MULTIPLIERS`).
|
|
956
|
+
*/
|
|
957
|
+
declare const LIGHTNESS_MODIFIERS: Readonly<Record<string, number>>;
|
|
958
|
+
/**
|
|
959
|
+
* Multiplicadores que amplificam o intensificador SEGUINTE.
|
|
960
|
+
* "bem escuro" / "muito claro" / "very dark" → multiplica o delta.
|
|
961
|
+
*/
|
|
962
|
+
declare const LIGHTNESS_MULTIPLIERS: Readonly<Record<string, number>>;
|
|
963
|
+
/**
|
|
964
|
+
* Aplica delta de lightness a um hex `#rrggbb` (ou `#rgb`). Mantém
|
|
965
|
+
* matiz e saturação; só altera L no espaço HSL.
|
|
966
|
+
*/
|
|
967
|
+
declare function adjustHexLightness(hex: string, delta: number): string;
|
|
968
|
+
/** Conveniência: clarear. */
|
|
969
|
+
declare function lightenHex(hex: string, amount?: number): string;
|
|
970
|
+
/** Conveniência: escurecer. */
|
|
971
|
+
declare function darkenHex(hex: string, amount?: number): string;
|
|
972
|
+
/**
|
|
973
|
+
* `#rrggbb` (ou `#rgb`) → `[r, g, b]` em [0, 255]. `null` se inválido.
|
|
974
|
+
* Ignora alpha quando 4 ou 8 dígitos.
|
|
975
|
+
*/
|
|
976
|
+
declare function hexToRgb(hex: string): [number, number, number] | null;
|
|
977
|
+
/** RGB [0,255] → HSL [h:0-360, s:0-1, l:0-1]. */
|
|
978
|
+
declare function rgbToHsl(r: number, g: number, b: number): [number, number, number];
|
|
979
|
+
/** HSL [h:0-360, s:0-1, l:0-1] → RGB [0,255]. */
|
|
980
|
+
declare function hslToRgb(h: number, s: number, l: number): [number, number, number];
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* **Color dictionary — English (EN).**
|
|
984
|
+
*
|
|
985
|
+
* Kept separate from PT so idiomatic / regional variants can be reviewed
|
|
986
|
+
* independently. Final dict consumed by the parser
|
|
987
|
+
* (`COLOR_DICTIONARY` in `colors.ts`) merges PT + EN.
|
|
988
|
+
*
|
|
989
|
+
* **Key convention**: lowercase, no diacritics (tokenizer applies
|
|
990
|
+
* `deaccent()` before lookup). Don't duplicate with PT — when a token
|
|
991
|
+
* is the same in both (e.g., CSS keywords like `'royalblue'`), keep
|
|
992
|
+
* it ONLY here.
|
|
993
|
+
*
|
|
994
|
+
* **Why split by language**:
|
|
995
|
+
* - Audit: spot vocabulary gaps PT vs EN without scanning 200 lines.
|
|
996
|
+
* - Maintenance: extend EN dict without touching PT.
|
|
997
|
+
* - Language detection (`detectLanguage()` in `language-detect.ts`)
|
|
998
|
+
* uses per-dict hit counts to estimate the dominant input language.
|
|
999
|
+
*/
|
|
1000
|
+
declare const COLOR_DICTIONARY_EN: Readonly<Record<string, string>>;
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* **Dicionário de cores — Português (PT-BR/PT-PT).**
|
|
1004
|
+
*
|
|
1005
|
+
* Mantido **separado de EN** pra que extensões idiomáticas / regionais
|
|
1006
|
+
* (variantes brasileiras vs portuguesas, gírias, neologismos) possam
|
|
1007
|
+
* ser revisadas isoladamente. O dict final consumido pelo parser
|
|
1008
|
+
* (`COLOR_DICTIONARY` em `colors.ts`) faz merge de PT + EN.
|
|
1009
|
+
*
|
|
1010
|
+
* **Convenção de chave**: lowercase + SEM acento (o tokenizer aplica
|
|
1011
|
+
* `deaccent()` antes da busca). NÃO duplicar com EN — se a palavra é a
|
|
1012
|
+
* mesma nos dois idiomas (e.g., `'royalblue'` é universal CSS), coloque
|
|
1013
|
+
* SÓ em `colors-en.ts`.
|
|
1014
|
+
*
|
|
1015
|
+
* **Por que separar por idioma**:
|
|
1016
|
+
* - Auditoria: ver lacunas de vocabulário PT vs EN sem ler 200 linhas.
|
|
1017
|
+
* - Manutenção: adicionar variante regional sem tocar EN.
|
|
1018
|
+
* - Detecção de idioma (`detectLanguage()` em `language-detect.ts`)
|
|
1019
|
+
* usa contagem de hits por dict pra estimar idioma dominante.
|
|
1020
|
+
*/
|
|
1021
|
+
declare const COLOR_DICTIONARY_PT: Readonly<Record<string, string>>;
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* **Merged dict** — PT + EN. EN tem precedência em colisão (mas como
|
|
1025
|
+
* convencionamos não duplicar, colisões só acontecem em CSS keywords
|
|
1026
|
+
* universais — comportamento idempotente).
|
|
1027
|
+
*/
|
|
1028
|
+
declare const COLOR_DICTIONARY: Readonly<Record<string, string>>;
|
|
1029
|
+
/**
|
|
1030
|
+
* Lista de keys (lowercase, sem acento) — usada pelo fuzzy matcher
|
|
1031
|
+
* pra propor cores próximas quando o usuário digita errado
|
|
1032
|
+
* ("vermelo" → "vermelho").
|
|
1033
|
+
*/
|
|
1034
|
+
declare const COLOR_KEYS: readonly string[];
|
|
1035
|
+
/**
|
|
1036
|
+
* Resolve um nome de cor (lowercased, deacentuado) para hex/CSS.
|
|
1037
|
+
* Retorna `null` quando o nome não consta no dicionário.
|
|
1038
|
+
*
|
|
1039
|
+
* **Multi-idioma**: consulta o merged dict (PT + EN), então funciona
|
|
1040
|
+
* pra qualquer input independente do idioma do usuário.
|
|
1041
|
+
*/
|
|
1042
|
+
declare function resolveColorName(name: string): string | null;
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* **NLU-specific shape vocabulary** — separado do `ShapeKind` em
|
|
1046
|
+
* `svg-engine/edit/lib/tool` (que tem `'rect' | 'ellipse' | 'polygon'`
|
|
1047
|
+
* pras shape tools de drawing). O NLU precisa de uma lista maior
|
|
1048
|
+
* porque mapeia vocabulário falado/escrito, não capability de tool.
|
|
1049
|
+
*
|
|
1050
|
+
* **Polígonos específicos** (D-046 review-5): triangle/pentagon/
|
|
1051
|
+
* hexagon/octagon/rhombus/star são kinds próprios para que o handler
|
|
1052
|
+
* `create-shape` saiba **quantos lados** gerar sem precisar inferir
|
|
1053
|
+
* do input. Antes, "triangulo" e "hexagono" caíam em `'polygon'`
|
|
1054
|
+
* genérico e o handler não tinha info pra desenhar — virava no-op.
|
|
1055
|
+
*/
|
|
1056
|
+
type NluShapeKind = 'rect' | 'ellipse' | 'circle' | 'line' | 'path' | 'triangle' | 'rhombus' | 'pentagon' | 'hexagon' | 'octagon' | 'star' | 'polygon' | 'polyline' | 'text' | 'image' | 'group' | 'svg';
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* **Shape dictionary — English.**
|
|
1060
|
+
*
|
|
1061
|
+
* Maps EN vocabulary (technical names + common UX semantic aliases)
|
|
1062
|
+
* to {@link NluShapeKind} canonical. Kept separate from PT for clarity.
|
|
1063
|
+
*
|
|
1064
|
+
* **Key convention**: lowercase, no diacritics.
|
|
1065
|
+
*/
|
|
1066
|
+
|
|
1067
|
+
declare const SHAPE_DICTIONARY_EN: Readonly<Record<string, NluShapeKind>>;
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* **Dicionário de formas — Português.**
|
|
1071
|
+
*
|
|
1072
|
+
* Mapeia vocabulário PT (nomes técnicos + semanticos UX) pra
|
|
1073
|
+
* {@link NluShapeKind} canonical. Separado de EN pra cobrir variantes
|
|
1074
|
+
* regionais isoladamente. Final merged em `shapes.ts`.
|
|
1075
|
+
*
|
|
1076
|
+
* **Convenção de chave**: lowercase + SEM acento.
|
|
1077
|
+
*/
|
|
1078
|
+
|
|
1079
|
+
declare const SHAPE_DICTIONARY_PT: Readonly<Record<string, NluShapeKind>>;
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Dicionário de formas — **PT + EN merged** → canonical shape kind.
|
|
1083
|
+
*
|
|
1084
|
+
* O kind canonical mapeia para os factories existentes em
|
|
1085
|
+
* `svg-engine/core` (`createRect`, `createEllipse`, `createPath`,
|
|
1086
|
+
* `createLine`, `createPolygon`, `createPolyline`, `createText`,
|
|
1087
|
+
* `createImage`, `createGroup`). Plugins built-in usam esse mapeamento
|
|
1088
|
+
* pra resolver o slot `shape` (e.g., "criar retângulo" → `rect` →
|
|
1089
|
+
* `createRect`).
|
|
1090
|
+
*
|
|
1091
|
+
* **Convenções de chaves**:
|
|
1092
|
+
* - **lowercase + SEM acento** (tokenizer faz `deaccent()` antes).
|
|
1093
|
+
* - PT/EN vivem em arquivos separados (`shapes-pt.ts`, `shapes-en.ts`).
|
|
1094
|
+
* - Quando uma palavra é idêntica nos dois idiomas, fica APENAS em EN.
|
|
1095
|
+
* - **Semantic aliases** ("nó" → circle, "conector" → line, "balão"
|
|
1096
|
+
* → group) mapeiam vocabulário UX para shapes existentes.
|
|
1097
|
+
*
|
|
1098
|
+
* **Geometria por nome**: "estrela" mapeia para `'star'` com geometria
|
|
1099
|
+
* real (5 pontas via `regularStarPoints`); "coração" cai em `'star'`
|
|
1100
|
+
* como fallback semântico até ganhar geometria própria. Outros ícones
|
|
1101
|
+
* nomeados ainda fora do vocabulário ficam para a fase que integrar
|
|
1102
|
+
* uma icon library (Fase 1.x).
|
|
1103
|
+
*/
|
|
1104
|
+
|
|
1105
|
+
declare const SHAPE_DICTIONARY: Readonly<Record<string, NluShapeKind>>;
|
|
1106
|
+
/** Keys disponíveis pro fuzzy matcher. */
|
|
1107
|
+
declare const SHAPE_KEYS: readonly string[];
|
|
1108
|
+
/**
|
|
1109
|
+
* Resolve um nome de forma (lowercased + deacentuado) para kind.
|
|
1110
|
+
* Retorna `null` quando não é uma forma conhecida.
|
|
1111
|
+
*
|
|
1112
|
+
* **Multi-idioma**: consulta o merged dict (PT + EN).
|
|
1113
|
+
*/
|
|
1114
|
+
declare function resolveShapeKind(name: string): NluShapeKind | null;
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* **Action canonicals** — vocabulário fechado de verbos suportados.
|
|
1118
|
+
*
|
|
1119
|
+
* Cada termo PT ou EN é mapeado pra um canonical aqui. Plugins NLU
|
|
1120
|
+
* declaram `actionKeywords: ['create', 'delete']` (canonicals) em
|
|
1121
|
+
* vez de listar todas as conjugações — o `resolveActionCanonical(token)`
|
|
1122
|
+
* faz a tradução transparente.
|
|
1123
|
+
*
|
|
1124
|
+
* **Por que canonical em vez de só strings**:
|
|
1125
|
+
* - TypeScript valida cobertura nos consumers (intent não pode
|
|
1126
|
+
* declarar action que não existe).
|
|
1127
|
+
* - Refactor de vocabulário sem quebrar intents (renomear `'paint'`
|
|
1128
|
+
* pra `'fill'` é mudança LOCAL aqui + nas tabelas).
|
|
1129
|
+
*
|
|
1130
|
+
* **Adicionar canonical novo**: 1) adiciona aqui, 2) adiciona entradas
|
|
1131
|
+
* em `actions-pt.ts` E `actions-en.ts`, 3) consumer plugin declara em
|
|
1132
|
+
* `intent.actionKeywords`. Sem step 3 o canonical é "registrado mas
|
|
1133
|
+
* não usado" — não quebra, mas é code-smell.
|
|
1134
|
+
*/
|
|
1135
|
+
type ActionCanonical = 'create' | 'delete' | 'select' | 'select-all' | 'deselect' | 'group' | 'ungroup' | 'undo' | 'redo' | 'zoom-in' | 'zoom-out' | 'zoom-reset' | 'copy' | 'paste' | 'cut' | 'duplicate' | 'move' | 'rotate' | 'resize' | 'flip' | 'align' | 'distribute' | 'bring-forward' | 'send-backward' | 'show' | 'hide' | 'toggle' | 'union' | 'intersect' | 'subtract' | 'exclude' | 'divide' | 'convert' | 'lock' | 'unlock' | 'export' | 'import';
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* **Action dictionary — English.**
|
|
1139
|
+
*
|
|
1140
|
+
* Maps EN verbs (base form + common synonyms) to the canonical token
|
|
1141
|
+
* shared with PT. Keep entries minimal and synonyms commonly seen in
|
|
1142
|
+
* UI / spoken English; avoid archaic forms.
|
|
1143
|
+
*
|
|
1144
|
+
* **Key convention**: lowercase, no diacritics. Same canonical as PT
|
|
1145
|
+
* (verified by `ActionCanonical` type).
|
|
1146
|
+
*/
|
|
1147
|
+
|
|
1148
|
+
declare const ACTION_DICTIONARY_EN: Readonly<Record<string, ActionCanonical>>;
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* **Dicionário de ações — Português.**
|
|
1152
|
+
*
|
|
1153
|
+
* Mapeia conjugações PT comuns (infinitivo + imperativo afirmativo)
|
|
1154
|
+
* para o termo canonical (`'create'`, `'delete'`, etc) compartilhado
|
|
1155
|
+
* com EN. Mantido separado pra cobrir variações regionais sem ruído.
|
|
1156
|
+
*
|
|
1157
|
+
* **Convenção de chave**: lowercase + SEM acento.
|
|
1158
|
+
* **Cobertura mínima**: pra cada verbo, infinitivo (`mover`) +
|
|
1159
|
+
* imperativo formal (`mova`) + imperativo informal (`movimente`).
|
|
1160
|
+
*/
|
|
1161
|
+
|
|
1162
|
+
declare const ACTION_DICTIONARY_PT: Readonly<Record<string, ActionCanonical>>;
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* **Merged dict** — PT + EN. EN tem precedência em colisão (mas
|
|
1166
|
+
* convencionamos não duplicar — colisões só acontecem em CSS keywords).
|
|
1167
|
+
*/
|
|
1168
|
+
declare const ACTION_DICTIONARY: Readonly<Record<string, ActionCanonical>>;
|
|
1169
|
+
/** Keys disponíveis pro fuzzy matcher. */
|
|
1170
|
+
declare const ACTION_KEYS: readonly string[];
|
|
1171
|
+
/**
|
|
1172
|
+
* Resolve uma palavra (lowercased + deacentuada) para ação canonical.
|
|
1173
|
+
* Retorna `null` quando não é verbo conhecido.
|
|
1174
|
+
*
|
|
1175
|
+
* **Multi-idioma**: consulta o merged dict (PT + EN), então funciona
|
|
1176
|
+
* pra qualquer input independente do idioma do usuário.
|
|
1177
|
+
*/
|
|
1178
|
+
declare function resolveActionCanonical(word: string): ActionCanonical | null;
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* **Stopwords — English.**
|
|
1182
|
+
*
|
|
1183
|
+
* Low-information words ignored by the parser to avoid noise in
|
|
1184
|
+
* keyword/action/slot matching. Includes articles, prepositions,
|
|
1185
|
+
* conjunctions, pronouns, conversational fillers and weak modal verbs.
|
|
1186
|
+
*
|
|
1187
|
+
* Does NOT include action verbs ("create", "delete") — those live in
|
|
1188
|
+
* `actions-en.ts`. Nor colors/shapes (those are slot values).
|
|
1189
|
+
*
|
|
1190
|
+
* **Key convention**: lowercase, no diacritics.
|
|
1191
|
+
*/
|
|
1192
|
+
declare const STOPWORDS_EN: ReadonlySet<string>;
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* **Stopwords — Português.**
|
|
1196
|
+
*
|
|
1197
|
+
* Palavras de baixa informação ignoradas pelo parser pra evitar ruído
|
|
1198
|
+
* no matching de keywords/actions/slots. Inclui artigos, preposições,
|
|
1199
|
+
* conjunções, pronomes, fillers conversacionais e verbos auxiliares
|
|
1200
|
+
* fracos que aparecem em comandos coloquiais.
|
|
1201
|
+
*
|
|
1202
|
+
* **Não inclui** verbos de ação ("criar", "deletar"); esses estão em
|
|
1203
|
+
* `actions-pt.ts`. Nem cores/formas (esses são slot values).
|
|
1204
|
+
*
|
|
1205
|
+
* **Convenção de chave**: lowercase + SEM acento (consistente com
|
|
1206
|
+
* o tokenizer — `deaccent()` é aplicado antes do lookup).
|
|
1207
|
+
*/
|
|
1208
|
+
declare const STOPWORDS_PT: ReadonlySet<string>;
|
|
1209
|
+
|
|
1210
|
+
/** **Merged set** — PT ∪ EN. */
|
|
1211
|
+
declare const STOPWORDS: ReadonlySet<string>;
|
|
1212
|
+
/**
|
|
1213
|
+
* `true` se o token é uma stopword (case-insensitive, sem acento).
|
|
1214
|
+
* O caller é responsável por já ter tokenizado/normalizado.
|
|
1215
|
+
*
|
|
1216
|
+
* **Multi-idioma**: cobre PT + EN no mesmo set.
|
|
1217
|
+
*/
|
|
1218
|
+
declare function isStopword(token: string): boolean;
|
|
1219
|
+
|
|
1220
|
+
/** Idiomas suportados oficialmente pela NLU. */
|
|
1221
|
+
type NluLanguage = 'pt' | 'en' | 'unknown';
|
|
1222
|
+
/**
|
|
1223
|
+
* Resultado da detecção — idioma + contagem de hits por dict pra debug.
|
|
1224
|
+
*/
|
|
1225
|
+
interface LanguageDetectResult {
|
|
1226
|
+
readonly language: NluLanguage;
|
|
1227
|
+
readonly hits: {
|
|
1228
|
+
readonly pt: number;
|
|
1229
|
+
readonly en: number;
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Detecta idioma dominante de uma sequência de tokens (já
|
|
1234
|
+
* tokenizados via `tokenize()`).
|
|
1235
|
+
*
|
|
1236
|
+
* **Retorna `'unknown'`** quando:
|
|
1237
|
+
* - Zero tokens
|
|
1238
|
+
* - Zero hits em ambos os idiomas (texto só de números/hex/símbolos)
|
|
1239
|
+
* - Empate exato (igual número de hits PT e EN)
|
|
1240
|
+
*
|
|
1241
|
+
* Caller decide o fallback (default PT? Persistido na UI?).
|
|
1242
|
+
*/
|
|
1243
|
+
declare function detectLanguage(tokens: readonly string[]): LanguageDetectResult;
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* **Number words — PT + EN.**
|
|
1247
|
+
*
|
|
1248
|
+
* Mapeia palavras de número escritas por extenso (PT/EN) para valor
|
|
1249
|
+
* numérico inteiro. Usado pelo `parseNumberToken` no slot-extractor
|
|
1250
|
+
* pra reconhecer comandos como "selecionar os três triangulos" →
|
|
1251
|
+
* count=3.
|
|
1252
|
+
*
|
|
1253
|
+
* **Cobertura**: 1-20 + dezenas (30, 40, ..., 100). Acima disso é
|
|
1254
|
+
* raro em comandos de editor SVG ("selecionar os 50 retangulos" é
|
|
1255
|
+
* mais natural com dígito).
|
|
1256
|
+
*
|
|
1257
|
+
* **Convenção de chave**: lowercase + SEM acento (tokenizer faz
|
|
1258
|
+
* `deaccent()`). Tres → `tres`, dois → `dois`, etc.
|
|
1259
|
+
*/
|
|
1260
|
+
declare const NUMBER_WORDS: Readonly<Record<string, number>>;
|
|
1261
|
+
/**
|
|
1262
|
+
* Resolve uma palavra de número (lowercased, deacentuada) pro valor
|
|
1263
|
+
* inteiro. Retorna `null` quando não é número conhecido.
|
|
1264
|
+
*/
|
|
1265
|
+
declare function resolveNumberWord(word: string): number | null;
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* **`NluDictionaryRegistry`** — D-046 review-10 (Sprint 2.B / H5).
|
|
1269
|
+
*
|
|
1270
|
+
* Service injetável que permite consumers ESTENDEREM o vocabulário NLU
|
|
1271
|
+
* em runtime, sem editar os arquivos source dos dicts built-in
|
|
1272
|
+
* (`colors-*.ts`, `shapes-*.ts`, `actions-*.ts`).
|
|
1273
|
+
*
|
|
1274
|
+
* **Motivação**: aplicações de domínio (fluxograma, BPMN, UML, mapas)
|
|
1275
|
+
* têm vocabulário próprio ("swimlane", "decision", "actor", "use-case").
|
|
1276
|
+
* Sem este registry, a única forma de cobrir é editar source ou
|
|
1277
|
+
* registrar 20+ intents customizados duplicados.
|
|
1278
|
+
*
|
|
1279
|
+
* **API**:
|
|
1280
|
+
* - `registerColor(name, hex)` — adiciona "vibrant-blue" → "#1e90ff"
|
|
1281
|
+
* - `registerShape(name, kind)` — adiciona "actor" → 'circle' alias
|
|
1282
|
+
* - `registerAction(word, canonical)` — adiciona "compor" → 'create'
|
|
1283
|
+
* - `resolveColor` / `resolveShape` / `resolveAction` — consultam
|
|
1284
|
+
* dinâmico ANTES dos built-in (override permitido)
|
|
1285
|
+
*
|
|
1286
|
+
* **Multi-editor (D-042)**: singleton root-provided, mas extensions
|
|
1287
|
+
* são compartilhadas entre todos os editors. Plugins por-editor
|
|
1288
|
+
* podem usar `track(disposable)` pra cleanup.
|
|
1289
|
+
*
|
|
1290
|
+
* **Estado atual** (Fase 1): este registry é OPCIONAL. O parser
|
|
1291
|
+
* (`slot-extractor`, `menu-intent-discovery`) ainda consulta dicts
|
|
1292
|
+
* estáticos diretamente. Integração com este registry fica como
|
|
1293
|
+
* Sprint futuro (refactor de `resolveColorName` pra usar override
|
|
1294
|
+
* dinâmico, etc).
|
|
1295
|
+
*/
|
|
1296
|
+
declare class NluDictionaryRegistry {
|
|
1297
|
+
private readonly _colors;
|
|
1298
|
+
private readonly _shapes;
|
|
1299
|
+
private readonly _actions;
|
|
1300
|
+
/** Snapshot reativo das cores customizadas. */
|
|
1301
|
+
readonly colors: Signal<ReadonlyMap<string, string>>;
|
|
1302
|
+
/** Snapshot reativo das shapes customizadas. */
|
|
1303
|
+
readonly shapes: Signal<ReadonlyMap<string, NluShapeKind>>;
|
|
1304
|
+
/** Snapshot reativo das actions customizadas. */
|
|
1305
|
+
readonly actions: Signal<ReadonlyMap<string, ActionCanonical>>;
|
|
1306
|
+
/**
|
|
1307
|
+
* Adiciona uma entrada de cor customizada. Retorna `Disposable` pra
|
|
1308
|
+
* remover (em geral trackeada por plugin via `ctx.track()`).
|
|
1309
|
+
*
|
|
1310
|
+
* @param name nome normalizado (lowercase, sem acento). Conflitos
|
|
1311
|
+
* com built-in fazem **override** silencioso (consumer tem prioridade).
|
|
1312
|
+
* @param hex CSS color (`#rrggbb`, `rgb(...)`, keyword) ou `'none'`/`'transparent'`.
|
|
1313
|
+
*/
|
|
1314
|
+
registerColor(name: string, hex: string): Disposable;
|
|
1315
|
+
/**
|
|
1316
|
+
* Adiciona uma entrada de shape customizada (alias semântico).
|
|
1317
|
+
*
|
|
1318
|
+
* @param name nome normalizado.
|
|
1319
|
+
* @param kind {@link NluShapeKind} canonical.
|
|
1320
|
+
*/
|
|
1321
|
+
registerShape(name: string, kind: NluShapeKind): Disposable;
|
|
1322
|
+
/**
|
|
1323
|
+
* Adiciona uma palavra de ação customizada → canonical existente.
|
|
1324
|
+
*
|
|
1325
|
+
* @param word palavra normalizada.
|
|
1326
|
+
* @param canonical {@link ActionCanonical} existente.
|
|
1327
|
+
*/
|
|
1328
|
+
registerAction(word: string, canonical: ActionCanonical): Disposable;
|
|
1329
|
+
/**
|
|
1330
|
+
* Resolve cor consultando PRIMEIRO o registry dinâmico (consumer
|
|
1331
|
+
* tem prioridade), DEPOIS o dict estático built-in. Retorna `null`
|
|
1332
|
+
* quando não encontra.
|
|
1333
|
+
*/
|
|
1334
|
+
resolveColor(name: string): string | null;
|
|
1335
|
+
/** Resolve shape no registry dinâmico (null se não encontrar). */
|
|
1336
|
+
resolveShape(name: string): NluShapeKind | null;
|
|
1337
|
+
/** Resolve action canonical no registry dinâmico (null se não). */
|
|
1338
|
+
resolveAction(word: string): ActionCanonical | null;
|
|
1339
|
+
/**
|
|
1340
|
+
* Helper genérico — registra entrada com validação básica e retorna
|
|
1341
|
+
* Disposable. Não-throw em conflito (override silencioso).
|
|
1342
|
+
*/
|
|
1343
|
+
private registerEntry;
|
|
1344
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NluDictionaryRegistry, never>;
|
|
1345
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NluDictionaryRegistry>;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* **Contrato de voz desacoplado** — D-046 voz híbrida.
|
|
1350
|
+
*
|
|
1351
|
+
* Define a abstração `VoiceProvider` num lugar **headless** (sem
|
|
1352
|
+
* Material, sem transformers.js) para que tanto a camada de UI
|
|
1353
|
+
* (`svg-engine/ai/nlu-ui`, provider Web Speech) quanto a camada WASM
|
|
1354
|
+
* (`svg-engine/ai/nlu-voice-wasm`, provider Whisper local) possam
|
|
1355
|
+
* compartilhar o mesmo contrato **sem dependência cruzada** entre elas.
|
|
1356
|
+
*
|
|
1357
|
+
* O orquestrador (`VoiceEngineService`, em `nlu-ui`) injeta o provider
|
|
1358
|
+
* Web Speech diretamente e o provider Whisper **opcionalmente** via
|
|
1359
|
+
* {@link VOICE_WHISPER_PROVIDER} — registrado pelo app só quando a voz
|
|
1360
|
+
* local é desejada (`provideWhisperVoiceEngine()`).
|
|
1361
|
+
*/
|
|
1362
|
+
/**
|
|
1363
|
+
* Engine de reconhecimento de voz escolhível pelo usuário:
|
|
1364
|
+
* - `'web-speech'` — Web Speech API nativa do navegador (rápida, mas
|
|
1365
|
+
* depende de STT em nuvem do vendor — pode falhar com `network`).
|
|
1366
|
+
* - `'whisper'` — Whisper local via WASM (100% offline, sem rede).
|
|
1367
|
+
* - `'auto'` — tenta Web Speech e, em falha, cai para o Whisper.
|
|
1368
|
+
*/
|
|
1369
|
+
type VoiceEngine = 'web-speech' | 'whisper' | 'auto';
|
|
1370
|
+
/**
|
|
1371
|
+
* Surface mínima comum de um provider de reconhecimento de voz.
|
|
1372
|
+
* Tanto `VoiceRecognitionService` (Web Speech) quanto
|
|
1373
|
+
* `WhisperVoiceService` (WASM) a satisfazem **estruturalmente** — não
|
|
1374
|
+
* é preciso `implements` nominal.
|
|
1375
|
+
*/
|
|
1376
|
+
interface VoiceProvider {
|
|
1377
|
+
/** `false` quando o ambiente não suporta esta engine. */
|
|
1378
|
+
readonly isSupported: Signal<boolean>;
|
|
1379
|
+
/** `true` enquanto captura/processa. */
|
|
1380
|
+
readonly listening: Signal<boolean>;
|
|
1381
|
+
/** Último código de erro (`'network'`, `'not-allowed'`, …) ou `null`. */
|
|
1382
|
+
readonly lastError: Signal<string | null>;
|
|
1383
|
+
/** `true` durante carregamento de modelo (só engines com modelo, ex. Whisper). */
|
|
1384
|
+
readonly modelLoading?: Signal<boolean>;
|
|
1385
|
+
/** Inicia a captura; resolve com a transcrição final. */
|
|
1386
|
+
listen(lang?: string, options?: {
|
|
1387
|
+
readonly timeoutMs?: number;
|
|
1388
|
+
}): Promise<string>;
|
|
1389
|
+
/** Encerra a captura ativa, se houver. */
|
|
1390
|
+
stop(): void;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Token DI **opcional** do provider Whisper local. Default `null`
|
|
1394
|
+
* (voz local não instalada). Apps que querem o Whisper offline
|
|
1395
|
+
* registram via `provideWhisperVoiceEngine()` de
|
|
1396
|
+
* `svg-engine/ai/nlu-voice-wasm`.
|
|
1397
|
+
*/
|
|
1398
|
+
declare const VOICE_WHISPER_PROVIDER: InjectionToken<VoiceProvider | null>;
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* **D-093 — contrato de provedor LLM (chat) desacoplado.**
|
|
1402
|
+
*
|
|
1403
|
+
* Define a abstração `AiChatProvider` num lugar **headless** (sem rede
|
|
1404
|
+
* amarrada, sem Material) — exatamente como o {@link VoiceProvider} fez
|
|
1405
|
+
* para voz. Qualquer backend (Ollama local, OpenAI, Anthropic, vLLM…)
|
|
1406
|
+
* que satisfaça este contrato pode alimentar o {@link LlmIntentResolverService}
|
|
1407
|
+
* sem que o resolver saiba qual é.
|
|
1408
|
+
*
|
|
1409
|
+
* O resolver injeta o provider **opcionalmente** via {@link AI_CHAT_PROVIDER}
|
|
1410
|
+
* (default `null` — LLM não instalado). Apps que querem a camada LLM
|
|
1411
|
+
* registram um provider concreto (ex.: `provideOllamaChat(...)`).
|
|
1412
|
+
*/
|
|
1413
|
+
/** Papel de uma mensagem no diálogo (formato estilo chat completions). */
|
|
1414
|
+
type AiChatRole = 'system' | 'user' | 'assistant';
|
|
1415
|
+
/** Uma mensagem do diálogo enviada ao modelo. */
|
|
1416
|
+
interface AiChatMessage {
|
|
1417
|
+
readonly role: AiChatRole;
|
|
1418
|
+
readonly content: string;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Opções por-chamada. Tudo opcional — o provider aplica seus defaults.
|
|
1422
|
+
*
|
|
1423
|
+
* **`model`** é o ponto-chave do requisito do usuário: permite **trocar
|
|
1424
|
+
* o modelo por requisição conforme a complexidade** do conteúdo (ex.:
|
|
1425
|
+
* `qwen2.5:3b` para o trivial, `qwen2.5:7b` para composições pesadas)
|
|
1426
|
+
* sem reconfigurar o provider.
|
|
1427
|
+
*/
|
|
1428
|
+
interface AiChatOptions {
|
|
1429
|
+
/** Sobrescreve o modelo do provider só nesta chamada (roteamento por complexidade). */
|
|
1430
|
+
readonly model?: string;
|
|
1431
|
+
/** Pede saída **JSON** estruturada (mapeia para `format:"json"` no Ollama). */
|
|
1432
|
+
readonly format?: 'json';
|
|
1433
|
+
/** Temperatura de amostragem (0 = determinístico). */
|
|
1434
|
+
readonly temperature?: number;
|
|
1435
|
+
/** Teto de tokens gerados (mapeia para `num_predict` no Ollama). */
|
|
1436
|
+
readonly maxTokens?: number;
|
|
1437
|
+
/** Cancela a chamada (timeout / troca de contexto). */
|
|
1438
|
+
readonly signal?: AbortSignal;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Surface mínima de um provedor de chat LLM. Satisfeito
|
|
1442
|
+
* **estruturalmente** — não exige `implements` nominal.
|
|
1443
|
+
*/
|
|
1444
|
+
interface AiChatProvider {
|
|
1445
|
+
/** `false` quando o provider não está utilizável (sem baseUrl, etc.). */
|
|
1446
|
+
readonly isConfigured: Signal<boolean>;
|
|
1447
|
+
/** Modelo default deste provider (o usado quando `opts.model` é omitido). */
|
|
1448
|
+
readonly defaultModel: Signal<string>;
|
|
1449
|
+
/**
|
|
1450
|
+
* **D-095 — modelos sugeridos** (curados/conhecidos), opcional. A UI funde
|
|
1451
|
+
* esta lista com os modelos descobertos ao vivo ({@link
|
|
1452
|
+
* AiChatProvider.listModels}) para popular o seletor — útil de fallback
|
|
1453
|
+
* quando a descoberta falha e como sugestão para consumidores. Backends sem
|
|
1454
|
+
* curadoria simplesmente não expõem (a UI cai nos descobertos + default).
|
|
1455
|
+
*/
|
|
1456
|
+
readonly suggestedModels?: Signal<readonly string[]>;
|
|
1457
|
+
/**
|
|
1458
|
+
* Envia o diálogo e resolve com o **texto** da resposta do assistente.
|
|
1459
|
+
* Lança em erro de rede / HTTP — o chamador (resolver) trata.
|
|
1460
|
+
*/
|
|
1461
|
+
chat(messages: readonly AiChatMessage[], opts?: AiChatOptions): Promise<string>;
|
|
1462
|
+
/**
|
|
1463
|
+
* **D-094 — descoberta de modelos** (opcional). Lista os modelos
|
|
1464
|
+
* disponíveis no backend para o usuário escolher (ex.: Ollama
|
|
1465
|
+
* `GET /api/tags`). O contrato é opcional: backends que não expõem um
|
|
1466
|
+
* catálogo (ou que só servem um modelo) simplesmente não implementam, e
|
|
1467
|
+
* a UI cai no {@link AiChatProvider.defaultModel}. Lança em erro de rede
|
|
1468
|
+
* / HTTP — o chamador trata e degrada para o default.
|
|
1469
|
+
*/
|
|
1470
|
+
listModels?(): Promise<readonly string[]>;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Token DI **opcional** do provedor LLM. Default `null` (camada LLM não
|
|
1474
|
+
* instalada → o {@link LlmIntentResolverService} reporta `isAvailable === false`
|
|
1475
|
+
* e o app continua só com o NLU rule-based). Apps registram um provider
|
|
1476
|
+
* concreto via, p.ex., `provideOllamaChat({ baseUrl, model })`.
|
|
1477
|
+
*/
|
|
1478
|
+
declare const AI_CHAT_PROVIDER: InjectionToken<AiChatProvider | null>;
|
|
1479
|
+
|
|
1480
|
+
/** Default Ollama endpoint (local). Override via {@link provideOllamaChat}. */
|
|
1481
|
+
declare const DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
|
|
1482
|
+
/** Default model — the small/fast one that fits a modest GPU (D-093 benchmark). */
|
|
1483
|
+
declare const DEFAULT_OLLAMA_MODEL = "qwen2.5:3b";
|
|
1484
|
+
/**
|
|
1485
|
+
* **D-095 — modelos curados (conhecidos) sugeridos no seletor.**
|
|
1486
|
+
*
|
|
1487
|
+
* Lista de fallback/curadoria que o `<svge-nlu-input>` funde com os modelos
|
|
1488
|
+
* **descobertos** ao vivo (`/api/tags`): garante que esses apareçam no seletor
|
|
1489
|
+
* mesmo quando a descoberta falha (servidor offline/sem CORS) e serve de
|
|
1490
|
+
* sugestão para consumidores da lib que ainda não puxaram nada.
|
|
1491
|
+
*
|
|
1492
|
+
* Inclui os modelos base (D-093) **e** os **`qwen2.5-coder`** (3b/7b/14b) —
|
|
1493
|
+
* estes últimos são **coder-tuned**, logo bem melhores para gerar SVG (que é
|
|
1494
|
+
* markup/código): fecham tags, respeitam `viewBox`/`path`/`defs`. Mantidos os
|
|
1495
|
+
* anteriores; os coder são **adicionais** (vide D-094 modo "SVG livre").
|
|
1496
|
+
*/
|
|
1497
|
+
declare const DEFAULT_OLLAMA_MODELS: readonly string[];
|
|
1498
|
+
/** Optional configuration for {@link OllamaChatProvider} / {@link provideOllamaChat}. */
|
|
1499
|
+
interface OllamaChatConfig {
|
|
1500
|
+
/** Base URL of the Ollama server, e.g. `http://192.168.1.21:11434`. */
|
|
1501
|
+
readonly baseUrl?: string;
|
|
1502
|
+
/** Default model id, e.g. `qwen2.5:3b`. Overridable per-call via `opts.model`. */
|
|
1503
|
+
readonly model?: string;
|
|
1504
|
+
/**
|
|
1505
|
+
* **D-095** — sobrescreve a lista curada de modelos sugeridos no seletor
|
|
1506
|
+
* ({@link DEFAULT_OLLAMA_MODELS}). Quando omitido, usa a curadoria padrão.
|
|
1507
|
+
*/
|
|
1508
|
+
readonly models?: readonly string[];
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* **D-093 — `AiChatProvider` para Ollama** (API nativa `/api/chat`).
|
|
1512
|
+
*
|
|
1513
|
+
* `fetch` puro contra um servidor Ollama. Sem dependência pesada (ao
|
|
1514
|
+
* contrário do Whisper), então mora no mesmo entry point `svg-engine/ai/nlu`
|
|
1515
|
+
* junto do contrato — o token {@link AI_CHAT_PROVIDER} mantém a troca de
|
|
1516
|
+
* backend desacoplada.
|
|
1517
|
+
*
|
|
1518
|
+
* **baseUrl/model são signals settáveis em runtime** — atende o requisito
|
|
1519
|
+
* de "trocar provider/model conforme a complexidade": o app pode chamar
|
|
1520
|
+
* `setModel('qwen2.5:7b')` para conteúdo pesado, ou passar `opts.model`
|
|
1521
|
+
* por chamada (sem mexer no default).
|
|
1522
|
+
*
|
|
1523
|
+
* **CORS**: o servidor Ollama precisa subir com `OLLAMA_ORIGINS` liberando
|
|
1524
|
+
* a origem do app (validado no D-093). Erros de rede/HTTP são propagados
|
|
1525
|
+
* via `throw` — o {@link LlmIntentResolverService} captura e degrada.
|
|
1526
|
+
*/
|
|
1527
|
+
declare class OllamaChatProvider implements AiChatProvider {
|
|
1528
|
+
private readonly _baseUrl;
|
|
1529
|
+
private readonly _model;
|
|
1530
|
+
private readonly _suggestedModels;
|
|
1531
|
+
/** Current base URL (reactive). */
|
|
1532
|
+
readonly baseUrl: i0.Signal<string>;
|
|
1533
|
+
/** Current default model (reactive). Satisfies {@link AiChatProvider.defaultModel}. */
|
|
1534
|
+
readonly defaultModel: i0.Signal<string>;
|
|
1535
|
+
/**
|
|
1536
|
+
* **D-095** — modelos curados sugeridos no seletor (reactive). Satisfaz
|
|
1537
|
+
* {@link AiChatProvider.suggestedModels}. Fundidos com os descobertos via
|
|
1538
|
+
* `/api/tags` pela UI; default = {@link DEFAULT_OLLAMA_MODELS}.
|
|
1539
|
+
*/
|
|
1540
|
+
readonly suggestedModels: i0.Signal<readonly string[]>;
|
|
1541
|
+
/** `true` once a non-empty base URL is set (it always is, by default). */
|
|
1542
|
+
readonly isConfigured: i0.Signal<boolean>;
|
|
1543
|
+
/** Apply a partial config (only the provided fields change). */
|
|
1544
|
+
configure(cfg: OllamaChatConfig): void;
|
|
1545
|
+
/** Switch the server URL at runtime. */
|
|
1546
|
+
setBaseUrl(url: string): void;
|
|
1547
|
+
/** Switch the default model at runtime (complexity routing). */
|
|
1548
|
+
setModel(model: string): void;
|
|
1549
|
+
chat(messages: readonly AiChatMessage[], opts?: AiChatOptions): Promise<string>;
|
|
1550
|
+
/**
|
|
1551
|
+
* **D-094** — lista os modelos instalados no servidor Ollama via
|
|
1552
|
+
* `GET /api/tags`. Alimenta o seletor de modelo da UI (o usuário escolhe
|
|
1553
|
+
* entre os modelos disponíveis em runtime). Devolve os nomes (`name`,
|
|
1554
|
+
* ex.: `qwen2.5:3b`) ordenados alfabeticamente e deduplicados. Propaga
|
|
1555
|
+
* erro de rede / HTTP — o chamador degrada para o {@link defaultModel}.
|
|
1556
|
+
*/
|
|
1557
|
+
listModels(): Promise<readonly string[]>;
|
|
1558
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<OllamaChatProvider, never>;
|
|
1559
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<OllamaChatProvider>;
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* **D-093** — DI helper that wires {@link OllamaChatProvider} as the active
|
|
1563
|
+
* {@link AI_CHAT_PROVIDER}. Add to an app/route `providers: []`:
|
|
1564
|
+
*
|
|
1565
|
+
* ```ts
|
|
1566
|
+
* providers: [
|
|
1567
|
+
* provideOllamaChat({ baseUrl: 'http://192.168.1.21:11434', model: 'qwen2.5:3b' }),
|
|
1568
|
+
* ]
|
|
1569
|
+
* ```
|
|
1570
|
+
*
|
|
1571
|
+
* The same instance is reachable as both {@link OllamaChatProvider} (for
|
|
1572
|
+
* runtime `setModel`/`setBaseUrl`) and {@link AI_CHAT_PROVIDER} (what the
|
|
1573
|
+
* resolver consumes).
|
|
1574
|
+
*/
|
|
1575
|
+
declare function provideOllamaChat(cfg?: OllamaChatConfig): Provider[];
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* **D-093 Fase 4** — teto de entradas no catálogo enviado ao modelo.
|
|
1579
|
+
*
|
|
1580
|
+
* Descoberta empírica (teste ao vivo 3b E 7b): com os 222 intents
|
|
1581
|
+
* registrados o prompt chega a ~4095 tokens — **~74s só de ingestão**
|
|
1582
|
+
* nessa GPU — e o modelo **perde o contrato de saída** (devolve um JSON
|
|
1583
|
+
* `{"card":{…}}` inventado em vez de `{"steps":[…]}`). Curar o catálogo
|
|
1584
|
+
* para um subconjunto relevante corta a latência E ajuda o modelo a
|
|
1585
|
+
* ancorar no formato. 24 cobre create-shape + cor/texto + os intents
|
|
1586
|
+
* textualmente relacionados ao pedido com folga.
|
|
1587
|
+
*/
|
|
1588
|
+
declare const DEFAULT_CATALOG_MAX_ENTRIES = 24;
|
|
1589
|
+
/**
|
|
1590
|
+
* **D-093 Fase 4** — intents **sempre** mantidos no catálogo curado
|
|
1591
|
+
* (casados por `id.includes(hint)`). São as primitivas de composição:
|
|
1592
|
+
* sem `create-shape` o modelo não tem como montar um "card de KPI" a
|
|
1593
|
+
* partir do zero. Curtos de propósito; ampliar só com primitiva nova.
|
|
1594
|
+
*/
|
|
1595
|
+
declare const CORE_INTENT_ID_HINTS: readonly string[];
|
|
1596
|
+
/**
|
|
1597
|
+
* Compact catalog entry fed to the model — one registered intent reduced
|
|
1598
|
+
* to id + description + slot names/kinds. Kept small on purpose (token
|
|
1599
|
+
* budget; the model only needs to pick an id and fill slots).
|
|
1600
|
+
*/
|
|
1601
|
+
interface LlmIntentCatalogEntry {
|
|
1602
|
+
readonly id: string;
|
|
1603
|
+
readonly description: string;
|
|
1604
|
+
/** `{ slotName: kind }`, e.g. `{ fill: 'color', width: 'number' }`. */
|
|
1605
|
+
readonly slots: Record<string, string>;
|
|
1606
|
+
}
|
|
1607
|
+
/** One resolved (validated) step of a plan — intent guaranteed to exist. */
|
|
1608
|
+
interface LlmResolvedStep {
|
|
1609
|
+
readonly intentId: string;
|
|
1610
|
+
readonly intent: NluIntent;
|
|
1611
|
+
readonly slots: Record<string, unknown>;
|
|
1612
|
+
}
|
|
1613
|
+
/** The validated plan returned by {@link LlmIntentResolverService.resolvePlan}. */
|
|
1614
|
+
interface LlmResolvedPlan {
|
|
1615
|
+
/** Steps whose `intentId` matched a registered intent (in order). */
|
|
1616
|
+
readonly steps: readonly LlmResolvedStep[];
|
|
1617
|
+
/** Model-reported confidence in `[0,1]` (defaults to 1 if absent). */
|
|
1618
|
+
readonly confidence: number;
|
|
1619
|
+
/** Raw model text (for debugging / UI "show reasoning"). */
|
|
1620
|
+
readonly raw: string;
|
|
1621
|
+
/** `intentId`s the model produced that do NOT exist (dropped, for telemetry). */
|
|
1622
|
+
readonly dropped: readonly string[];
|
|
1623
|
+
}
|
|
1624
|
+
/** Per-call options for the resolver. */
|
|
1625
|
+
interface LlmResolveOptions {
|
|
1626
|
+
/** Model override for THIS request (complexity routing: 3b vs 7b). */
|
|
1627
|
+
readonly model?: string;
|
|
1628
|
+
/** Abort the underlying request. */
|
|
1629
|
+
readonly signal?: AbortSignal;
|
|
1630
|
+
/** Cap generated tokens. */
|
|
1631
|
+
readonly maxTokens?: number;
|
|
1632
|
+
}
|
|
1633
|
+
/** Options for {@link LlmIntentResolverService.resolveAndExecute}. */
|
|
1634
|
+
interface LlmExecuteOptions extends LlmResolveOptions {
|
|
1635
|
+
/** Confirmation gate forwarded to {@link NaturalLanguageService.executeCandidate} (required for destructive steps). */
|
|
1636
|
+
readonly confirmGate?: NluExecuteOptions['confirmGate'];
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* **D-094 — modo SEM catálogo.** Resultado de {@link
|
|
1640
|
+
* LlmIntentResolverService.generateSvg}: o SVG completo extraído da resposta
|
|
1641
|
+
* do modelo + o texto cru (debug / "ver resposta").
|
|
1642
|
+
*/
|
|
1643
|
+
interface LlmRawSvgResult {
|
|
1644
|
+
/** Markup `<svg>…</svg>` extraído (fences/prosa removidos). `''` se nenhum. */
|
|
1645
|
+
readonly svg: string;
|
|
1646
|
+
/** Texto cru retornado pelo modelo (para debug / UI). */
|
|
1647
|
+
readonly raw: string;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* **D-094 — modo SEM catálogo.** Resultado de {@link
|
|
1651
|
+
* LlmIntentResolverService.generateAndInsertSvg}: o SVG gerado + o desfecho
|
|
1652
|
+
* da inserção no canvas (id do nó inserido ou um erro amigável).
|
|
1653
|
+
*/
|
|
1654
|
+
interface LlmRawSvgInsertResult {
|
|
1655
|
+
/** `true` quando o SVG foi parseado e inserido no documento. */
|
|
1656
|
+
readonly ok: boolean;
|
|
1657
|
+
/** Markup SVG gerado (mesmo quando a inserção falhou — para debug). */
|
|
1658
|
+
readonly svg: string;
|
|
1659
|
+
/** Texto cru do modelo. */
|
|
1660
|
+
readonly raw: string;
|
|
1661
|
+
/** Id do nó inserido (presente só quando `ok`). */
|
|
1662
|
+
readonly nodeId?: NodeId;
|
|
1663
|
+
/** Avisos de saneamento do importador (scripts/handlers removidos, etc.). */
|
|
1664
|
+
readonly warnings: readonly string[];
|
|
1665
|
+
/** Mensagem de erro amigável quando `ok` é `false`. */
|
|
1666
|
+
readonly error?: string;
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* **D-093 — `LlmIntentResolverService`** (resolver de intents via LLM).
|
|
1670
|
+
*
|
|
1671
|
+
* O **fallback inteligente** do NLU: quando o rule-based não resolve, o
|
|
1672
|
+
* texto livre é mandado ao LLM ({@link AI_CHAT_PROVIDER}), que devolve um
|
|
1673
|
+
* **plano** de comandos **já registrados** (intentId + slots). Cada passo
|
|
1674
|
+
* é validado contra o registry e executado pelo pipeline seguro
|
|
1675
|
+
* ({@link NaturalLanguageService.executeCandidate} — gate de destrutivos,
|
|
1676
|
+
* try/catch). **O LLM nunca inventa comando** — só escolhe dos existentes,
|
|
1677
|
+
* o que mantém a segurança.
|
|
1678
|
+
*
|
|
1679
|
+
* **Opcional por design**: se nenhum {@link AI_CHAT_PROVIDER} foi
|
|
1680
|
+
* registrado, `isAvailable === false` e o app segue só com o rule-based
|
|
1681
|
+
* (zero rede, specs offline). Root-scoped como o {@link NaturalLanguageService}
|
|
1682
|
+
* (registry global; execução per-scope via `NluContext.injector`).
|
|
1683
|
+
*/
|
|
1684
|
+
declare class LlmIntentResolverService {
|
|
1685
|
+
private readonly nlu;
|
|
1686
|
+
private readonly provider;
|
|
1687
|
+
/** `true` quando um provider LLM foi registrado (camada disponível). */
|
|
1688
|
+
get isAvailable(): boolean;
|
|
1689
|
+
/**
|
|
1690
|
+
* **D-094** — modelo default do provider (o usado quando nenhum override
|
|
1691
|
+
* é passado). `null` quando não há provider. Serve de fallback ao seletor
|
|
1692
|
+
* de modelo da UI quando o backend não lista modelos.
|
|
1693
|
+
*/
|
|
1694
|
+
get defaultModel(): string | null;
|
|
1695
|
+
/**
|
|
1696
|
+
* **D-094** — lista os modelos disponíveis no backend para o seletor de
|
|
1697
|
+
* modelo da UI. Quando o provider não implementa descoberta
|
|
1698
|
+
* ({@link AiChatProvider.listModels} opcional) ou não há provider,
|
|
1699
|
+
* devolve `[]` — a UI cai no {@link defaultModel}. Erros de rede são
|
|
1700
|
+
* propagados para o chamador decidir como degradar.
|
|
1701
|
+
*/
|
|
1702
|
+
listModels(): Promise<readonly string[]>;
|
|
1703
|
+
/**
|
|
1704
|
+
* **D-095** — modelos sugeridos (curados/conhecidos) do provider, para a UI
|
|
1705
|
+
* fundir com os descobertos ({@link listModels}). `[]` quando não há
|
|
1706
|
+
* provider ou ele não expõe curadoria ({@link AiChatProvider.suggestedModels}
|
|
1707
|
+
* é opcional). Síncrono (apenas lê um signal, sem rede).
|
|
1708
|
+
*/
|
|
1709
|
+
suggestedModels(): readonly string[];
|
|
1710
|
+
/**
|
|
1711
|
+
* Catálogo compacto dos intents registrados — enviado ao modelo no
|
|
1712
|
+
* system prompt. Deriva de `NaturalLanguageService.intents()`.
|
|
1713
|
+
*
|
|
1714
|
+
* **D-093 Fase 4 — curadoria por relevância**: quando há `text` E o
|
|
1715
|
+
* total de intents excede `maxEntries`, o catálogo é **pré-filtrado**
|
|
1716
|
+
* para um subconjunto relevante (primitivas core + top-K por
|
|
1717
|
+
* sobreposição de tokens com o pedido). Sem `text` — ou quando o
|
|
1718
|
+
* registry já cabe em `maxEntries` — devolve TODOS (comportamento
|
|
1719
|
+
* legado, specs offline intactos). Isso corta o prompt de ~4095 →
|
|
1720
|
+
* algumas centenas de tokens (latência) E ajuda o modelo a ancorar
|
|
1721
|
+
* no contrato de saída (vide nota de {@link DEFAULT_CATALOG_MAX_ENTRIES}).
|
|
1722
|
+
*
|
|
1723
|
+
* @param text pedido do usuário (opcional) — base da relevância.
|
|
1724
|
+
* @param opts `maxEntries` para sobrescrever o teto padrão.
|
|
1725
|
+
*/
|
|
1726
|
+
buildCatalog(text?: string, opts?: {
|
|
1727
|
+
maxEntries?: number;
|
|
1728
|
+
}): readonly LlmIntentCatalogEntry[];
|
|
1729
|
+
/**
|
|
1730
|
+
* Pede um plano ao LLM e valida cada passo contra o registry.
|
|
1731
|
+
* @throws se nenhum provider estiver registrado, ou se o modelo não
|
|
1732
|
+
* produzir JSON parseável.
|
|
1733
|
+
*/
|
|
1734
|
+
resolvePlan(text: string, opts?: LlmResolveOptions): Promise<LlmResolvedPlan>;
|
|
1735
|
+
/**
|
|
1736
|
+
* Resolve o plano e **executa** cada passo pelo pipeline seguro do NLU.
|
|
1737
|
+
* Passos não-destrutivos rodam direto; destrutivos exigem `confirmGate`
|
|
1738
|
+
* (senão são rejeitados, como em `executeCandidate`). Cada passo vira um
|
|
1739
|
+
* comando próprio no bus → **um passo de undo por etapa**.
|
|
1740
|
+
*/
|
|
1741
|
+
resolveAndExecute(text: string, ctx: NluContext, opts?: LlmExecuteOptions): Promise<readonly NluExecuteResult[]>;
|
|
1742
|
+
/**
|
|
1743
|
+
* **D-094 — modo SEM catálogo.** Pede ao LLM um **SVG completo** (sem
|
|
1744
|
+
* catálogo de intents, sem plano de passos) e extrai o `<svg>…</svg>` da
|
|
1745
|
+
* resposta. Ao contrário de {@link resolvePlan}, **não** força
|
|
1746
|
+
* `format:"json"` — a saída é markup SVG/XML cru. O `temperature:0` mantém
|
|
1747
|
+
* o resultado determinístico. O chamador trata erro de rede / SVG ausente.
|
|
1748
|
+
*
|
|
1749
|
+
* @throws se nenhum provider estiver registrado.
|
|
1750
|
+
*/
|
|
1751
|
+
generateSvg(text: string, opts?: LlmResolveOptions): Promise<LlmRawSvgResult>;
|
|
1752
|
+
/**
|
|
1753
|
+
* **D-094 — modo SEM catálogo.** Gera o SVG via {@link generateSvg},
|
|
1754
|
+
* parseia/saneia com o `svgImporter` (remove `<script>`, `on*`,
|
|
1755
|
+
* `javascript:` hrefs) e o **desenha no canvas** aditivamente, centralizado
|
|
1756
|
+
* na página ativa — exatamente o pipeline de `File ▸ Import ▸ SVG`
|
|
1757
|
+
* ({@link ImportPlacementService.placeDocumentCentered}, resolvido do
|
|
1758
|
+
* **escopo do editor** via `ctx.injector`). Nunca lança por SVG inválido:
|
|
1759
|
+
* devolve `{ ok:false, error }` para a UI mostrar.
|
|
1760
|
+
*/
|
|
1761
|
+
generateAndInsertSvg(text: string, ctx: NluContext, opts?: LlmResolveOptions): Promise<LlmRawSvgInsertResult>;
|
|
1762
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LlmIntentResolverService, never>;
|
|
1763
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<LlmIntentResolverService>;
|
|
1764
|
+
}
|
|
1765
|
+
/** Raw (pre-validation) step shape produced by the model. */
|
|
1766
|
+
interface RawPlanStep {
|
|
1767
|
+
readonly intentId: string;
|
|
1768
|
+
readonly slots: Record<string, unknown>;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Tolerant parse of the model's JSON. Strips markdown fences, slices to
|
|
1772
|
+
* the outermost `{...}`, and normalizes several shapes (top-level array,
|
|
1773
|
+
* single step, `{steps:[...]}`). Throws only when nothing parses.
|
|
1774
|
+
*/
|
|
1775
|
+
declare function parsePlan(raw: string): {
|
|
1776
|
+
steps: RawPlanStep[];
|
|
1777
|
+
confidence: number;
|
|
1778
|
+
};
|
|
1779
|
+
/**
|
|
1780
|
+
* **D-094** — extract the `<svg>…</svg>` element from a model reply. Strips
|
|
1781
|
+
* markdown fences (```svg / ```xml / ```html / bare ```), then slices from the
|
|
1782
|
+
* first `<svg` tag to the last `</svg>`. Returns `''` when no SVG is present
|
|
1783
|
+
* (the caller surfaces a friendly "no SVG returned" error). Tolerant of prose
|
|
1784
|
+
* before/after, which small models sometimes emit despite the instruction.
|
|
1785
|
+
*/
|
|
1786
|
+
declare function extractSvgBlob(raw: string): string;
|
|
1787
|
+
|
|
1788
|
+
/**
|
|
1789
|
+
* **D-093 (Fase 3)** — gatilho de escalonamento para o LLM.
|
|
1790
|
+
*
|
|
1791
|
+
* Problema (descoberto na Fase 2): o rule-based é **guloso** com verbos
|
|
1792
|
+
* de criação — "crie um card de KPI moderno com título e valor" casa
|
|
1793
|
+
* `create-shape` com 90% (gera 1 retângulo default) e **não** escala,
|
|
1794
|
+
* então pedidos complexos nunca chegam ao LLM.
|
|
1795
|
+
*
|
|
1796
|
+
* Heurística aqui: o pedido é **vago para o rule-based** quando tem
|
|
1797
|
+
* conteúdo suficiente mas a maior parte dele **não é reconhecida** pelos
|
|
1798
|
+
* dicionários (não é forma, cor, número/dimensão nem verbo de ação).
|
|
1799
|
+
* Nesse caso o `<svge-nlu-input>` roteia direto para o LLM — que usa o
|
|
1800
|
+
* texto inteiro — em vez de deixar o rule-based criar uma forma genérica.
|
|
1801
|
+
*
|
|
1802
|
+
* Protege os bons casos:
|
|
1803
|
+
* - "criar retângulo vermelho 100x50" → 4/4 reconhecidos → NÃO vago.
|
|
1804
|
+
* - "undo", "selecionar tudo" → poucos tokens → NÃO vago.
|
|
1805
|
+
* - "crie um card de KPI moderno com título e valor" → só "crie"
|
|
1806
|
+
* reconhecido (1/6) → **vago** → LLM.
|
|
1807
|
+
*/
|
|
1808
|
+
/** Mínimo de tokens significativos para considerar o roteamento (frases curtas ficam no rule-based). */
|
|
1809
|
+
declare const VAGUE_MIN_MEANINGFUL_TOKENS = 3;
|
|
1810
|
+
/** Abaixo desta fração de tokens reconhecidos, o pedido é "vago". */
|
|
1811
|
+
declare const VAGUE_RECOGNIZED_FRACTION = 0.5;
|
|
1812
|
+
/**
|
|
1813
|
+
* Substantivos **compostos** — coisas feitas de várias primitivas. Quando
|
|
1814
|
+
* aparecem, o rule-based reduz tudo a UMA forma genérica (o `create-shape`
|
|
1815
|
+
* casa "card"/"kpi" como alias de forma e gera 1 retângulo), então
|
|
1816
|
+
* roteamos direto ao LLM, que sabe decompor em vários comandos.
|
|
1817
|
+
* Deaccentuados/lowercase (forma do tokenizer).
|
|
1818
|
+
*/
|
|
1819
|
+
declare const COMPOSITE_KEYWORDS: ReadonlySet<string>;
|
|
1820
|
+
/**
|
|
1821
|
+
* Decide se `text` deve pular o rule-based e ir direto ao LLM.
|
|
1822
|
+
* Pure function — sem efeitos colaterais, testável offline.
|
|
1823
|
+
*
|
|
1824
|
+
* Vago quando QUALQUER:
|
|
1825
|
+
* 1. contém um substantivo composto ({@link COMPOSITE_KEYWORDS}) — ex.:
|
|
1826
|
+
* "card", "dashboard", "organograma" (o rule-based só faria 1 forma); OU
|
|
1827
|
+
* 2. tem conteúdo suficiente mas a maioria dos tokens **não é reconhecida**
|
|
1828
|
+
* pelos dicionários (forma/cor/número/ação) — ex.: "casa com telhado e
|
|
1829
|
+
* porta".
|
|
1830
|
+
*/
|
|
1831
|
+
declare function isVagueForRuleBased(text: string): boolean;
|
|
1832
|
+
|
|
1833
|
+
export { ACTION_DICTIONARY, ACTION_DICTIONARY_EN, ACTION_DICTIONARY_PT, ACTION_KEYS, AI_CHAT_PROVIDER, COLOR_DICTIONARY, COLOR_DICTIONARY_EN, COLOR_DICTIONARY_PT, COLOR_KEYS, COMPOSITE_KEYWORDS, CORE_INTENT_ID_HINTS, DEFAULT_CATALOG_MAX_ENTRIES, DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_MODEL, DEFAULT_OLLAMA_MODELS, HEX_COLOR_RE, LIGHTNESS_MODIFIERS, LIGHTNESS_MULTIPLIERS, LlmIntentResolverService, NUMBER_WORDS, NaturalLanguageService, NluDictionaryRegistry, OllamaChatProvider, SHAPE_DICTIONARY, SHAPE_DICTIONARY_EN, SHAPE_DICTIONARY_PT, SHAPE_KEYS, STOPWORDS, STOPWORDS_EN, STOPWORDS_PT, VAGUE_MIN_MEANINGFUL_TOKENS, VAGUE_RECOGNIZED_FRACTION, VOICE_WHISPER_PROVIDER, adaptiveMaxDistance, adjustHexLightness, bestMatch, builtinNluPlugin, darkenHex, deaccent, detectLanguage, discoverMenuIntents, discoverMenuIntentsReactive, extractSlots, extractSvgBlob, fuzzyMatchAll, fuzzyMatchAny, fuzzyMatchToken, hexToRgb, hslToRgb, isStopword, isVagueForRuleBased, levenshtein, lightenHex, menuContributionToIntent, normalize, parseColorPhrase, parseColorToken, parseDimensionToken, parseHslFunction, parseNumberToken, parsePlan, parseRgbFunction, provideOllamaChat, resolveActionCanonical, resolveColorName, resolveNumberWord, resolveShapeKind, rgbToHsl, tokenize, tokenizeWithoutStopwords };
|
|
1834
|
+
export type { ActionCanonical, AiChatMessage, AiChatOptions, AiChatProvider, AiChatRole, ColorPhraseMatch, DiscoverMenuIntentsReactiveResult, DiscoverMenuIntentsResult, ExtractContext, ExtractedSlots, FuzzyMatch, LanguageDetectResult, LlmExecuteOptions, LlmIntentCatalogEntry, LlmRawSvgInsertResult, LlmRawSvgResult, LlmResolveOptions, LlmResolvedPlan, LlmResolvedStep, NluCandidate, NluContext, NluExecuteOptions, NluExecuteResult, NluIntent, NluLanguage, NluMatchReason, NluParseOptions, NluShapeKind, NluSlotSchema, OllamaChatConfig, VoiceEngine, VoiceProvider };
|