uitec-ng-header 1.1.0 → 1.2.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.
Files changed (48) hide show
  1. package/README.md +11 -182
  2. package/esm2020/lib/ui-header.component.mjs +20 -26
  3. package/esm2020/lib/ui-header.module.mjs +4 -10
  4. package/esm2020/public-api.mjs +1 -17
  5. package/fesm2015/uitec-ng-header.mjs +24 -1808
  6. package/fesm2015/uitec-ng-header.mjs.map +1 -1
  7. package/fesm2020/uitec-ng-header.mjs +24 -1771
  8. package/fesm2020/uitec-ng-header.mjs.map +1 -1
  9. package/lib/ui-header.component.d.ts +8 -11
  10. package/lib/ui-header.module.d.ts +1 -2
  11. package/package.json +2 -3
  12. package/public-api.d.ts +0 -15
  13. package/esm2020/lib/assistant/components/assistant-action-card/ui-assistant-action-card.component.mjs +0 -102
  14. package/esm2020/lib/assistant/components/assistant-button/ui-assistant-button.component.mjs +0 -18
  15. package/esm2020/lib/assistant/components/assistant-composer/ui-assistant-composer.component.mjs +0 -79
  16. package/esm2020/lib/assistant/components/assistant-layout/ui-assistant-layout.component.mjs +0 -140
  17. package/esm2020/lib/assistant/components/assistant-messages/ui-assistant-messages.component.mjs +0 -144
  18. package/esm2020/lib/assistant/components/assistant-panel/ui-assistant-panel.component.mjs +0 -156
  19. package/esm2020/lib/assistant/components/assistant-text/ui-assistant-text.component.mjs +0 -26
  20. package/esm2020/lib/assistant/models/uiny-api.models.mjs +0 -12
  21. package/esm2020/lib/assistant/models/uiny.models.mjs +0 -2
  22. package/esm2020/lib/assistant/services/ui-assistant.service.mjs +0 -137
  23. package/esm2020/lib/assistant/services/uiny-api.service.mjs +0 -149
  24. package/esm2020/lib/assistant/services/uiny-chat.service.mjs +0 -368
  25. package/esm2020/lib/assistant/services/uiny-session.service.mjs +0 -74
  26. package/esm2020/lib/assistant/ui-assistant.module.mjs +0 -59
  27. package/esm2020/lib/assistant/uiny.config.mjs +0 -53
  28. package/esm2020/lib/assistant/utils/uiny-api.adapter.mjs +0 -154
  29. package/esm2020/lib/assistant/utils/uiny-contexto.mjs +0 -43
  30. package/esm2020/lib/assistant/utils/uiny-markdown.mjs +0 -122
  31. package/lib/assistant/components/assistant-action-card/ui-assistant-action-card.component.d.ts +0 -33
  32. package/lib/assistant/components/assistant-button/ui-assistant-button.component.d.ts +0 -9
  33. package/lib/assistant/components/assistant-composer/ui-assistant-composer.component.d.ts +0 -24
  34. package/lib/assistant/components/assistant-layout/ui-assistant-layout.component.d.ts +0 -51
  35. package/lib/assistant/components/assistant-messages/ui-assistant-messages.component.d.ts +0 -38
  36. package/lib/assistant/components/assistant-panel/ui-assistant-panel.component.d.ts +0 -53
  37. package/lib/assistant/components/assistant-text/ui-assistant-text.component.d.ts +0 -14
  38. package/lib/assistant/models/uiny-api.models.d.ts +0 -109
  39. package/lib/assistant/models/uiny.models.d.ts +0 -104
  40. package/lib/assistant/services/ui-assistant.service.d.ts +0 -55
  41. package/lib/assistant/services/uiny-api.service.d.ts +0 -38
  42. package/lib/assistant/services/uiny-chat.service.d.ts +0 -82
  43. package/lib/assistant/services/uiny-session.service.d.ts +0 -38
  44. package/lib/assistant/ui-assistant.module.d.ts +0 -19
  45. package/lib/assistant/uiny.config.d.ts +0 -31
  46. package/lib/assistant/utils/uiny-api.adapter.d.ts +0 -22
  47. package/lib/assistant/utils/uiny-contexto.d.ts +0 -7
  48. package/lib/assistant/utils/uiny-markdown.d.ts +0 -37
@@ -1,22 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, Optional, Inject, isDevMode, Component, ChangeDetectionStrategy, EventEmitter, Input, Output, ContentChild, ViewChild, HostListener, NgModule } from '@angular/core';
3
- import * as i1$2 from '@angular/common';
2
+ import { Injectable, EventEmitter, Component, Input, Output, Inject, ContentChild, NgModule } from '@angular/core';
3
+ import * as i2 from '@angular/common';
4
4
  import { DOCUMENT, CommonModule } from '@angular/common';
5
5
  import * as i1 from 'uitec-ng-core';
6
6
  import { UsuarioMenuDTO } from 'uitec-ng-core';
7
- import { BehaviorSubject, combineLatest, of, merge, fromEvent, defer, throwError, TimeoutError, Subject, Subscription } from 'rxjs';
8
- import { startWith, map, distinctUntilChanged, shareReplay, timeout, catchError, filter, switchMap, takeUntil, finalize } from 'rxjs/operators';
9
- import * as i1$1 from '@angular/common/http';
10
- import { HttpClient, HttpParams, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
11
- import * as i2 from 'uitec-ng-icon';
7
+ import * as i3 from 'uitec-ng-icon';
12
8
  import { UiIconModule } from 'uitec-ng-icon';
13
- import * as i5 from 'primeng/tooltip';
9
+ import * as i4$1 from 'primeng/tooltip';
14
10
  import { TooltipModule } from 'primeng/tooltip';
15
11
  import * as i4 from 'uitec-ng-user-top';
16
12
  import { UiUserTopModule } from 'uitec-ng-user-top';
17
13
  import { ReactiveFormsModule } from '@angular/forms';
18
- import * as i2$1 from '@angular/router';
19
- import { NavigationEnd } from '@angular/router';
20
14
 
21
15
  class UiHeaderService {
22
16
  constructor() { }
@@ -30,602 +24,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
30
24
  }]
31
25
  }], ctorParameters: function () { return []; } });
32
26
 
33
- const UINY_CONFIG = new InjectionToken('UINY_CONFIG');
34
- const UINY_AVATAR_PADRAO = 'assets/images/uiny/avatar.svg';
35
- const UINY_LARGURA_PADRAO = 380;
36
- const UINY_LARGURA_MIN = 340;
37
- const UINY_LARGURA_MAX = 400;
38
- const UINY_LADO_A_LADO_PADRAO = 1200;
39
- /** Mesmo limite padrão do backend (UINY_MENSAGEM_MAX_CARACTERES no uishop-ia). */
40
- const UINY_MENSAGEM_MAX_CARACTERES = 2000;
41
- /** O backend limita cada mensagem a UINY_TEMPO_MAXIMO_SEGUNDOS (90 s); damos uma folga. */
42
- const UINY_TIMEOUT_MENSAGEM_MS = 100000;
43
- const UINY_TIMEOUT_ACAO_MS = 60000;
44
- const UINY_TIMEOUT_CONSULTA_MS = 20000;
45
- /**
46
- * Resolve a URL-base: forRoot({ apiUrl }) tem prioridade; depois APP_ENVIRONMENT.domain.uiny.
47
- * Sem barra no final. Retorna null quando não configurada (o recurso fica indisponível).
48
- */
49
- function resolverUrlUiny(config, env) {
50
- const url = config?.apiUrl || env?.domain?.uiny || null;
51
- if (!url || typeof url !== 'string')
52
- return null;
53
- if (!/^https?:\/\//i.test(url))
54
- return null;
55
- return url.replace(/\/+$/, '');
56
- }
57
- function larguraPainel(config) {
58
- const largura = config?.larguraPainel || UINY_LARGURA_PADRAO;
59
- return Math.min(UINY_LARGURA_MAX, Math.max(UINY_LARGURA_MIN, largura));
60
- }
61
- /**
62
- * Sugestões do primeiro acesso. A API não expõe o catálogo de ferramentas, então a lista
63
- * acompanha as ferramentas implementadas no uishop-ia (README, "Ferramentas implementadas").
64
- * Ao adicionar ou remover ferramentas no backend, atualize aqui.
65
- */
66
- const UINY_SUGESTOES = {
67
- studio: {
68
- textos: [
69
- 'Quais lojas e temas eu tenho?',
70
- 'Quais são as cores do meu tema?',
71
- 'Troque a cor principal do tema para #e8590c',
72
- ],
73
- // studio_alterar_cor_tema exige confirmação
74
- alteracoes: true,
75
- },
76
- erp: {
77
- textos: [
78
- 'Quais produtos estão sem estoque?',
79
- 'Como foram minhas vendas neste mês?',
80
- ],
81
- alteracoes: false,
82
- },
83
- };
84
-
85
- /** Mesma regra de ContextoTela no backend. */
86
- const TELA_VALIDA = /^[a-z0-9_\-\/.]{1,60}$/i;
87
- const CAMPOS_ID = ['idLoja', 'idLojaLeiaute', 'idTema', 'idProduto', 'idPedido'];
88
- function idPositivo(valor) {
89
- return typeof valor === 'number' && Number.isInteger(valor) && valor > 0 && valor <= 2147483647 ? valor : null;
90
- }
91
- /** Normaliza o contexto informado pelo consumidor: só campos conhecidos e valores válidos. */
92
- function normalizarContexto(contexto) {
93
- const resultado = {};
94
- if (!contexto)
95
- return resultado;
96
- const tela = typeof contexto.tela === 'string' ? contexto.tela.trim() : '';
97
- if (tela && TELA_VALIDA.test(tela))
98
- resultado.tela = tela;
99
- CAMPOS_ID.forEach(campo => {
100
- const id = idPositivo(contexto[campo]);
101
- if (id)
102
- resultado[campo] = id;
103
- });
104
- return resultado;
105
- }
106
- /** Converte para o formato de ContextoTela (campos extras geram 422 no backend). */
107
- function contextoParaApi(sistema, contexto) {
108
- const api = { sistema };
109
- if (contexto.tela)
110
- api.tela = contexto.tela;
111
- if (contexto.idLoja)
112
- api.id_loja = contexto.idLoja;
113
- if (contexto.idLojaLeiaute)
114
- api.id_loja_leiaute = contexto.idLojaLeiaute;
115
- if (contexto.idTema)
116
- api.id_tema = contexto.idTema;
117
- if (contexto.idProduto)
118
- api.id_produto = contexto.idProduto;
119
- if (contexto.idPedido)
120
- api.id_pedido = contexto.idPedido;
121
- return api;
122
- }
123
- function mesmoContexto(a, b) {
124
- return (a.tela ?? null) === (b.tela ?? null)
125
- && CAMPOS_ID.every(campo => (a[campo] ?? null) === (b[campo] ?? null));
126
- }
127
-
128
- /** Situação da API (Constants::ACAO_*) -> estado da tela. */
129
- const ESTADOS = {
130
- pendente: 'pendente',
131
- executando: 'executando',
132
- executada: 'concluida',
133
- cancelada: 'cancelada',
134
- expirada: 'expirada',
135
- invalidada: 'invalidada',
136
- falhou: 'falha',
137
- indeterminada: 'indeterminada',
138
- };
139
- const UUID = /^[0-9a-f-]{36}$/i;
140
- const MAX_DETALHES = 12;
141
- const MAX_VALOR = 200;
142
- /** Datas do backend: "Y-m-d H:i:s" (America/Sao_Paulo) ou ISO. */
143
- function lerData(valor) {
144
- if (typeof valor !== 'string' || !valor)
145
- return null;
146
- const iso = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$/.test(valor) ? valor.replace(' ', 'T') + '-03:00' : valor;
147
- const data = new Date(iso);
148
- return isNaN(data.getTime()) ? null : data;
149
- }
150
- function idValido(valor) {
151
- return typeof valor === 'string' && UUID.test(valor) ? valor : null;
152
- }
153
- function inteiro(valor) {
154
- const n = typeof valor === 'string' && valor.trim() !== '' ? Number(valor) : valor;
155
- return typeof n === 'number' && Number.isInteger(n) && n > 0 ? n : null;
156
- }
157
- function texto(valor, max = 20000) {
158
- if (typeof valor !== 'string')
159
- return '';
160
- return valor.length > max ? valor.slice(0, max) : valor;
161
- }
162
- function adaptarConversa(api) {
163
- const id = idValido(api?.id);
164
- if (!id)
165
- return null;
166
- return {
167
- id,
168
- titulo: texto(api.titulo, 120) || null,
169
- sistemaOrigem: api.sistema_origem,
170
- idLoja: inteiro(api.id_loja),
171
- atualizadaEm: lerData(api.dt_alteracao),
172
- };
173
- }
174
- function adaptarMensagem(api) {
175
- const id = idValido(api?.id);
176
- if (!id || (api.papel !== 'usuario' && api.papel !== 'assistente'))
177
- return null;
178
- return {
179
- id,
180
- papel: api.papel,
181
- estado: typeof api.estado === 'string' ? api.estado : null,
182
- texto: texto(api.texto),
183
- idAcao: idValido(api.acao_pendente?.id),
184
- opcoes: adaptarOpcoes(api.apresentacao),
185
- criadaEm: lerData(api.dt_criacao),
186
- };
187
- }
188
- /** Bloco "opcoes" do esclarecimento: só strings curtas. */
189
- function adaptarOpcoes(apresentacao) {
190
- if (!Array.isArray(apresentacao))
191
- return [];
192
- const bloco = apresentacao.find(b => b && typeof b === 'object' && b.tipo === 'opcoes');
193
- const opcoes = bloco?.dados?.opcoes;
194
- if (!Array.isArray(opcoes))
195
- return [];
196
- return opcoes.filter(o => typeof o === 'string' && o.trim()).map(o => o.trim().slice(0, 80)).slice(0, 6);
197
- }
198
- function adaptarAcao(api) {
199
- const id = idValido(api?.id);
200
- if (!id)
201
- return null;
202
- return {
203
- id,
204
- ferramenta: texto(api.ferramenta, 80),
205
- resumo: texto(api.resumo, 2000),
206
- detalhes: adaptarDetalhes(api.detalhes),
207
- // situação desconhecida: não afirmamos sucesso nem falha
208
- estado: ESTADOS[api.situacao] ?? 'indeterminada',
209
- expiraEm: lerData(api.dt_expiracao),
210
- };
211
- }
212
- /**
213
- * "detalhes" vem da PropostaAlteracao da ferramenta. Só aproveitamos pares rótulo/valor
214
- * com valores primitivos; nada é interpretado como HTML. Identificadores internos
215
- * (tipo, id_*) não são exibidos.
216
- */
217
- function adaptarDetalhes(detalhes) {
218
- const itens = [];
219
- const adicionar = (rotulo, valor) => {
220
- if (itens.length >= MAX_DETALHES)
221
- return;
222
- if (typeof rotulo !== 'string' || !rotulo.trim())
223
- return;
224
- if (typeof valor !== 'string' && typeof valor !== 'number' && typeof valor !== 'boolean')
225
- return;
226
- const v = typeof valor === 'boolean' ? (valor ? 'Sim' : 'Não') : String(valor);
227
- if (!v.trim())
228
- return;
229
- itens.push({ rotulo: humanizar(rotulo).slice(0, 80), valor: v.slice(0, MAX_VALOR) });
230
- };
231
- if (Array.isArray(detalhes)) {
232
- detalhes.forEach(item => {
233
- if (item && typeof item === 'object')
234
- adicionar(item.rotulo ?? item.campo, item.valor);
235
- });
236
- return itens;
237
- }
238
- if (!detalhes || typeof detalhes !== 'object')
239
- return itens;
240
- const d = detalhes;
241
- if (d.tipo === 'alteracao_cor') {
242
- // tools/studio/AlterarCorTemaStudio.php
243
- adicionar('Tema', d.tema);
244
- adicionar('Loja', d.loja);
245
- adicionar('Cor', d.rotulo);
246
- adicionar('Valor atual', d.valor_atual ?? 'sem valor definido');
247
- adicionar('Novo valor', d.valor_novo);
248
- return itens;
249
- }
250
- Object.keys(d)
251
- .filter(chave => chave !== 'tipo' && !/^id(_|$)/.test(chave))
252
- .forEach(chave => adicionar(chave, d[chave]));
253
- return itens;
254
- }
255
- function humanizar(chave) {
256
- if (/[A-Z ]/.test(chave))
257
- return chave;
258
- const t = chave.replace(/[_-]+/g, ' ').trim();
259
- return t.charAt(0).toUpperCase() + t.slice(1);
260
- }
261
- /**
262
- * Converte instrucoes_interface (Constants::INSTRUCOES_INTERFACE) em eventos tipados.
263
- * Tipos fora da lista e campos inesperados são descartados. Nada é executado nem navegado aqui.
264
- */
265
- function adaptarInstrucoes(instrucoes) {
266
- if (!Array.isArray(instrucoes))
267
- return [];
268
- const eventos = [];
269
- for (const item of instrucoes.slice(0, 10)) {
270
- if (!item || typeof item !== 'object')
271
- continue;
272
- const bruto = item;
273
- if (bruto.tipo === 'atualizar_preview_tema') {
274
- const idLojaLeiaute = inteiro(bruto.id_loja_leiaute);
275
- if (idLojaLeiaute)
276
- eventos.push({ tipo: 'tema_atualizado', idLojaLeiaute });
277
- }
278
- }
279
- return eventos;
280
- }
281
-
282
- /**
283
- * Deriva a sessão do AuthService do uitec-ng-core (mesma fonte do JwtInterceptor).
284
- * Não lê cookies nem localStorage diretamente e não guarda o token.
285
- */
286
- class UinySessionService {
287
- constructor(authService, zone) {
288
- this.authService = authService;
289
- this.zone = zone;
290
- /** Chave recusada pela API (401). Fica sem sessão até o AuthService mudar. */
291
- this.chaveRecusada$ = new BehaviorSubject(null);
292
- this.sequencia = 0;
293
- this.ultimaChave = null;
294
- this.ultimaSessao = null;
295
- const usuario$ = this.authService.currentUser.pipe(startWith(this.authService.currentUserValue));
296
- const acesso$ = this.authService.currentAccess.pipe(startWith(this.authService.currentAccessValue));
297
- this.sessao$ = combineLatest([usuario$, acesso$, this.chaveRecusada$]).pipe(map(([usuario, acesso, recusada]) => {
298
- const chave = this.chave(usuario, acesso);
299
- if (!chave || chave === recusada)
300
- return null;
301
- return this.sessaoDaChave(chave, usuario, acesso);
302
- }), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: false }));
303
- this.online$ = typeof window === 'undefined'
304
- ? of(true)
305
- : merge(fromEvent(window, 'online').pipe(map(() => true)), fromEvent(window, 'offline').pipe(map(() => false))).pipe(startWith(typeof navigator === 'undefined' ? true : navigator.onLine !== false), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: false }));
306
- }
307
- /** Token da sessão atual, lido do AuthService no momento da requisição. */
308
- token() {
309
- const usuario = this.authService.currentUserValue;
310
- return usuario?.token || null;
311
- }
312
- /** A API recusou a sessão (401): esconde a Uiny até o usuário autenticar de novo. */
313
- recusarSessaoAtual() {
314
- const chave = this.chave(this.authService.currentUserValue, this.acessoAtual(this.authService.currentUserValue, this.authService.currentAccessValue));
315
- if (chave)
316
- this.zone.run(() => this.chaveRecusada$.next(chave));
317
- }
318
- acessoAtual(usuario, acesso) {
319
- if (!usuario)
320
- return null;
321
- return acesso || usuario.acessos?.find(a => a.in_atual) || null;
322
- }
323
- chave(usuario, acesso) {
324
- const atual = this.acessoAtual(usuario, acesso);
325
- const idUsuario = usuario?.usuario?.id_usuario;
326
- if (!usuario?.token || !idUsuario || !atual?.id_empresa)
327
- return null;
328
- return `${idUsuario}|${atual.id_empresa}|${usuario.token}`;
329
- }
330
- sessaoDaChave(chave, usuario, acesso) {
331
- if (chave === this.ultimaChave && this.ultimaSessao)
332
- return this.ultimaSessao;
333
- const atual = this.acessoAtual(usuario, acesso);
334
- this.ultimaChave = chave;
335
- this.ultimaSessao = {
336
- id: ++this.sequencia,
337
- idUsuario: Number(usuario.usuario.id_usuario),
338
- idEmpresa: Number(atual.id_empresa),
339
- };
340
- return this.ultimaSessao;
341
- }
342
- }
343
- UinySessionService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinySessionService, deps: [{ token: i1.AuthService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
344
- UinySessionService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinySessionService, providedIn: 'root' });
345
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinySessionService, decorators: [{
346
- type: Injectable,
347
- args: [{ providedIn: 'root' }]
348
- }], ctorParameters: function () { return [{ type: i1.AuthService }, { type: i0.NgZone }]; } });
349
-
350
- const MENSAGENS_PADRAO = {
351
- sessao: 'Sua sessão expirou. Entre novamente na Uishop para continuar com a Uiny.',
352
- rede: 'Não foi possível conectar à Uiny. Verifique sua internet e tente de novo.',
353
- timeout: 'A Uiny demorou demais para responder. Tente de novo em instantes.',
354
- conflito: 'Não foi possível concluir agora. Atualize e tente de novo.',
355
- nao_encontrado: 'Não encontramos o que você pediu. Ele pode ter sido removido.',
356
- invalido: 'Não foi possível processar esse pedido.',
357
- limite: 'Você enviou muitas mensagens em pouco tempo. Aguarde um pouco e tente de novo.',
358
- servidor: 'A Uiny está com instabilidade no momento. Tente de novo em instantes.',
359
- configuracao: 'A Uiny não está configurada neste sistema.',
360
- };
361
- /**
362
- * Cliente HTTP da API da Uiny (uishop-ia, /api/v1).
363
- *
364
- * Usa um HttpClient sobre o HttpBackend, sem os interceptors da aplicação:
365
- * - o ErrorInterceptor do uitec-ng-core troca o erro por uma string (perde status e o corpo,
366
- * necessários para sessão expirada, ação expirada/invalidada e conflitos) e abre um toast global;
367
- * - o SpinnerInterceptor cobriria a tela a cada mensagem.
368
- * O token é o mesmo usado pelo JwtInterceptor (AuthService.currentUserValue.token) e só é
369
- * anexado a requisições cuja URL começa com a URL-base configurada da Uiny.
370
- */
371
- class UinyApiService {
372
- constructor(httpBackend, session, config, env) {
373
- this.session = session;
374
- this.http = new HttpClient(httpBackend);
375
- this.baseUrl = resolverUrlUiny(config, env);
376
- }
377
- get configurada() {
378
- return !!this.baseUrl;
379
- }
380
- listarConversas() {
381
- return defer(() => this.request('GET', '/conversas', null, UINY_TIMEOUT_CONSULTA_MS, new HttpParams().set('page', 1).set('per_page', 20)));
382
- }
383
- criarConversa(contexto) {
384
- return defer(() => this.request('POST', '/conversas', { contexto }, UINY_TIMEOUT_CONSULTA_MS));
385
- }
386
- /** Página mais recente do histórico (até 100 mensagens), em ordem cronológica. */
387
- historico(idConversa) {
388
- return defer(() => this.request('GET', `/conversas/${this.id(idConversa)}`, null, UINY_TIMEOUT_CONSULTA_MS, new HttpParams().set('page', 1).set('per_page', 100)));
389
- }
390
- enviarMensagem(idConversa, texto, contexto) {
391
- return defer(() => this.request('POST', `/conversas/${this.id(idConversa)}/mensagens`, { texto, contexto }, UINY_TIMEOUT_MENSAGEM_MS));
392
- }
393
- /** Confirma pelo id da ação persistida. Nenhum argumento é enviado. Nunca é repetida automaticamente. */
394
- confirmarAcao(idAcao) {
395
- return defer(() => this.request('POST', `/acoes/${this.id(idAcao)}/confirmar`, {}, UINY_TIMEOUT_ACAO_MS));
396
- }
397
- cancelarAcao(idAcao) {
398
- return defer(() => this.request('POST', `/acoes/${this.id(idAcao)}/cancelar`, {}, UINY_TIMEOUT_CONSULTA_MS));
399
- }
400
- id(valor) {
401
- const id = idValido(valor);
402
- if (!id)
403
- throw this.erro('invalido', 0);
404
- return id;
405
- }
406
- request(method, path, body, timeoutMs, params) {
407
- if (!this.baseUrl) {
408
- return throwError(() => this.erro('configuracao', 0));
409
- }
410
- const url = this.baseUrl + path;
411
- const token = this.session.token();
412
- if (!token) {
413
- return throwError(() => this.erro('sessao', 401));
414
- }
415
- let headers = new HttpHeaders({ 'Accept': 'application/json' });
416
- // Garantia explícita: o token só segue para a URL-base da Uiny.
417
- if (url.startsWith(this.baseUrl + '/')) {
418
- headers = headers.set('Authorization', `Bearer ${token}`);
419
- }
420
- if (method === 'POST') {
421
- headers = headers.set('Content-Type', 'application/json');
422
- }
423
- return this.http.request(method, url, { body: body ?? undefined, headers, params, responseType: 'json' }).pipe(timeout(timeoutMs), catchError(err => throwError(() => this.normalizarErro(err))));
424
- }
425
- normalizarErro(err) {
426
- if (err instanceof TimeoutError) {
427
- return this.erro('timeout', 0);
428
- }
429
- if (!(err instanceof HttpErrorResponse)) {
430
- return this.erro('servidor', 0);
431
- }
432
- const corpo = err.error && typeof err.error === 'object' ? err.error : null;
433
- const correlacao = this.textoSeguro(corpo?.id_correlacao, 64) || this.textoSeguro(err.headers?.get('X-Correlation-Id'), 64);
434
- const codigo = this.textoSeguro(corpo?.erro?.codigo, 60);
435
- const mensagemApi = this.textoSeguro(corpo?.erro?.mensagem, 300);
436
- let tipo;
437
- if (err.status === 0)
438
- tipo = 'rede';
439
- else if (err.status === 401)
440
- tipo = 'sessao';
441
- else if (err.status === 404)
442
- tipo = 'nao_encontrado';
443
- else if (err.status === 409)
444
- tipo = 'conflito';
445
- else if (err.status === 429)
446
- tipo = 'limite';
447
- else if (err.status === 400 || err.status === 403 || err.status === 422)
448
- tipo = 'invalido';
449
- else
450
- tipo = 'servidor';
451
- if (tipo === 'sessao') {
452
- this.session.recusarSessaoAtual();
453
- }
454
- // 502/503/504 trazem mensagens próprias e seguras (INTEGRACAO_*, PROVEDOR_IA_INDISPONIVEL).
455
- // 500 (INTERNO) e falhas de rede usam sempre o texto padrão.
456
- const usarMensagemApi = tipo !== 'rede' && tipo !== 'sessao' && err.status !== 500;
457
- const mensagem = (usarMensagemApi && mensagemApi) || MENSAGENS_PADRAO[tipo];
458
- return { tipo, status: err.status, codigo, mensagem, correlacao, corpo };
459
- }
460
- erro(tipo, status) {
461
- return { tipo, status, codigo: null, mensagem: MENSAGENS_PADRAO[tipo], correlacao: null };
462
- }
463
- textoSeguro(valor, max) {
464
- if (typeof valor !== 'string')
465
- return null;
466
- const texto = valor.trim();
467
- if (!texto || /bearer\s/i.test(texto))
468
- return null;
469
- return texto.length > max ? texto.slice(0, max) + '…' : texto;
470
- }
471
- }
472
- UinyApiService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinyApiService, deps: [{ token: i1$1.HttpBackend }, { token: UinySessionService }, { token: UINY_CONFIG, optional: true }, { token: 'APP_ENVIRONMENT', optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
473
- UinyApiService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinyApiService, providedIn: 'root' });
474
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinyApiService, decorators: [{
475
- type: Injectable,
476
- args: [{ providedIn: 'root' }]
477
- }], ctorParameters: function () { return [{ type: i1$1.HttpBackend }, { type: UinySessionService }, { type: undefined, decorators: [{
478
- type: Optional
479
- }, {
480
- type: Inject,
481
- args: [UINY_CONFIG]
482
- }] }, { type: undefined, decorators: [{
483
- type: Optional
484
- }, {
485
- type: Inject,
486
- args: ['APP_ENVIRONMENT']
487
- }] }]; } });
488
-
489
- /**
490
- * Estado compartilhado da Uiny: quem habilitou (o header), quem hospeda o painel
491
- * (o ui-assistant-layout), se está aberto, o contexto da tela e os eventos de saída.
492
- *
493
- * O painel só fica disponível quando TODAS as condições valem:
494
- * - algum ui-header está com [showAssistant]="true";
495
- * - existe um ui-assistant-layout na página para receber o painel;
496
- * - há sessão autenticada no AuthService (e a API não a recusou);
497
- * - a URL da API está configurada.
498
- */
499
- class UiAssistantService {
500
- constructor(session, api, config) {
501
- this.session = session;
502
- this.api = api;
503
- this.config = config;
504
- this.habilitadores$ = new BehaviorSubject(new Set());
505
- this.hosts$ = new BehaviorSubject(0);
506
- this.abertoInterno$ = new BehaviorSubject(false);
507
- this.contextoInterno$ = new BehaviorSubject({});
508
- this.eventosInternos$ = new Subject();
509
- this.gatilho = null;
510
- this.avisouSemHost = false;
511
- this.contexto$ = this.contextoInterno$.pipe(distinctUntilChanged(mesmoContexto));
512
- /** Ações concluídas e instruções de interface vindas da API, já validadas. */
513
- this.eventos$ = this.eventosInternos$.asObservable();
514
- this.disponivel$ = combineLatest([this.habilitadores$, this.hosts$, this.session.sessao$]).pipe(map(([habilitadores, hosts, sessao]) => habilitadores.size > 0 && hosts > 0 && !!sessao && this.api.configurada && !!this.config), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: false }));
515
- this.aberto$ = combineLatest([this.abertoInterno$, this.disponivel$]).pipe(map(([aberto, disponivel]) => aberto && disponivel), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: false }));
516
- // Indisponível (logout, sessão recusada, header sem a flag): fecha sem devolver foco.
517
- this.disponivel$.subscribe(disponivel => {
518
- if (!disponivel && this.abertoInterno$.value) {
519
- this.gatilho = null;
520
- this.abertoInterno$.next(false);
521
- }
522
- });
523
- combineLatest([this.habilitadores$, this.hosts$]).subscribe(([habilitadores, hosts]) => this.diagnosticar(habilitadores.size > 0, hosts));
524
- }
525
- get aberto() {
526
- return this.abertoInterno$.value;
527
- }
528
- get sistemaOrigem() {
529
- return this.config?.sistemaOrigem ?? null;
530
- }
531
- abrir(gatilho) {
532
- if (gatilho)
533
- this.gatilho = gatilho;
534
- this.abertoInterno$.next(true);
535
- }
536
- /** Fecha o painel e devolve o foco ao botão que o abriu, se ele ainda existir. */
537
- fechar(devolverFoco = true) {
538
- if (!this.abertoInterno$.value)
539
- return;
540
- this.abertoInterno$.next(false);
541
- const gatilho = this.gatilho;
542
- this.gatilho = null;
543
- if (devolverFoco && gatilho) {
544
- // espera o layout reexibir a aplicação (no celular ela fica recolhida)
545
- setTimeout(() => gatilho.isConnected && gatilho.focus());
546
- }
547
- }
548
- alternar(gatilho) {
549
- this.abertoInterno$.value ? this.fechar() : this.abrir(gatilho);
550
- }
551
- /** Substitui o contexto da tela atual. Campos fora do tipo UinyContexto são ignorados. */
552
- definirContexto(contexto) {
553
- this.contextoInterno$.next(normalizarContexto(contexto));
554
- }
555
- /** Altera apenas os campos informados do contexto. */
556
- atualizarContexto(parcial) {
557
- this.contextoInterno$.next(normalizarContexto({ ...this.contextoInterno$.value, ...parcial }));
558
- }
559
- get contexto() {
560
- return this.contextoInterno$.value;
561
- }
562
- /** @internal usado pelo ui-header */
563
- definirHabilitado(origem, habilitado) {
564
- const atual = new Set(this.habilitadores$.value);
565
- habilitado ? atual.add(origem) : atual.delete(origem);
566
- if (atual.size !== this.habilitadores$.value.size)
567
- this.habilitadores$.next(atual);
568
- }
569
- /** @internal usado pelo ui-assistant-layout */
570
- registrarHost() {
571
- this.hosts$.next(this.hosts$.value + 1);
572
- let removido = false;
573
- return () => {
574
- if (removido)
575
- return;
576
- removido = true;
577
- this.hosts$.next(Math.max(0, this.hosts$.value - 1));
578
- };
579
- }
580
- /** @internal usado pelo serviço de conversa */
581
- emitir(evento) {
582
- this.eventosInternos$.next(evento);
583
- }
584
- diagnosticar(habilitado, hosts) {
585
- if (!isDevMode() || !habilitado)
586
- return;
587
- if (!this.config) {
588
- console.error('[Uiny] showAssistant está ativo, mas UiAssistantModule.forRoot({ sistemaOrigem }) não foi importado.');
589
- }
590
- else if (!this.api.configurada) {
591
- console.error('[Uiny] URL da API não configurada. Informe APP_ENVIRONMENT.domain.uiny ou forRoot({ apiUrl }).');
592
- }
593
- else if (hosts === 0 && !this.avisouSemHost) {
594
- // o layout pode ser criado logo depois do header; avisa só se continuar faltando
595
- this.avisouSemHost = true;
596
- setTimeout(() => {
597
- if (this.hosts$.value === 0) {
598
- console.warn('[Uiny] showAssistant está ativo, mas nenhum <ui-assistant-layout> envolve a aplicação. O botão fica oculto.');
599
- }
600
- }, 2000);
601
- }
602
- }
603
- }
604
- UiAssistantService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantService, deps: [{ token: UinySessionService }, { token: UinyApiService }, { token: UINY_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
605
- UiAssistantService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantService, providedIn: 'root' });
606
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantService, decorators: [{
607
- type: Injectable,
608
- args: [{ providedIn: 'root' }]
609
- }], ctorParameters: function () { return [{ type: UinySessionService }, { type: UinyApiService }, { type: undefined, decorators: [{
610
- type: Optional
611
- }, {
612
- type: Inject,
613
- args: [UINY_CONFIG]
614
- }] }]; } });
615
-
616
- /** Botão que abre/fecha a Uiny. Só aparece quando a assistente está disponível. */
617
- class UiAssistantButtonComponent {
618
- constructor(assistant) {
619
- this.assistant = assistant;
620
- }
621
- }
622
- UiAssistantButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantButtonComponent, deps: [{ token: UiAssistantService }], target: i0.ɵɵFactoryTarget.Component });
623
- UiAssistantButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiAssistantButtonComponent, selector: "ui-assistant-button", ngImport: i0, template: "<button *ngIf=\"assistant.disponivel$ | async\" #botao type=\"button\" class=\"uiny-trigger\"\n [class.is-open]=\"assistant.aberto$ | async\"\n [attr.aria-expanded]=\"(assistant.aberto$ | async) ? 'true' : 'false'\"\n aria-controls=\"uiny-painel\"\n [attr.aria-label]=\"(assistant.aberto$ | async) ? 'Fechar a Uiny, assistente da Uishop' : 'Abrir a Uiny, assistente da Uishop'\"\n [title]=\"(assistant.aberto$ | async) ? 'Fechar a Uiny' : 'Falar com a Uiny'\"\n (click)=\"assistant.alternar(botao)\">\n <svg class=\"uiny-trigger__mark\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M12 2.5c.5 3.9 2.6 6.9 6.4 8.2.5.2.5.9 0 1.1-3.8 1.3-5.9 4.3-6.4 8.2-.1.6-.9.6-1 0-.5-3.9-2.6-6.9-6.4-8.2-.5-.2-.5-.9 0-1.1 3.8-1.3 5.9-4.3 6.4-8.2.1-.6.9-.6 1 0Z\" fill=\"currentColor\"/>\n <path d=\"M19 2.8c.2 1.3.9 2.2 2.1 2.6.2.1.2.3 0 .4-1.2.4-1.9 1.3-2.1 2.6 0 .2-.3.2-.3 0-.2-1.3-.9-2.2-2.1-2.6-.2-.1-.2-.3 0-.4 1.2-.4 1.9-1.3 2.1-2.6 0-.2.3-.2.3 0Z\" fill=\"currentColor\" opacity=\".7\"/>\n </svg>\n <span class=\"uiny-trigger__label\">Uiny</span>\n</button>\n", styles: [":host{display:inline-flex}.uiny-trigger{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 14px 0 10px;border:1px solid rgba(115,102,255,.28);border-radius:999px;background:rgba(115,102,255,.08);color:var(--theme-default, #7366ff);font-size:13px;font-weight:600;line-height:1;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.uiny-trigger:hover{background:rgba(115,102,255,.14);border-color:#7366ff73}.uiny-trigger:focus-visible{outline:2px solid var(--theme-default, #7366ff);outline-offset:2px}.uiny-trigger.is-open{background:var(--theme-default, #7366ff);border-color:var(--theme-default, #7366ff);color:#fff}.uiny-trigger__mark{flex:0 0 auto}@media (max-width: 575.98px){.uiny-trigger{width:34px;padding:0;justify-content:center}.uiny-trigger__label{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}}@media (prefers-reduced-motion: reduce){.uiny-trigger{transition:none}}\n"], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
624
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantButtonComponent, decorators: [{
625
- type: Component,
626
- args: [{ selector: 'ui-assistant-button', changeDetection: ChangeDetectionStrategy.OnPush, template: "<button *ngIf=\"assistant.disponivel$ | async\" #botao type=\"button\" class=\"uiny-trigger\"\n [class.is-open]=\"assistant.aberto$ | async\"\n [attr.aria-expanded]=\"(assistant.aberto$ | async) ? 'true' : 'false'\"\n aria-controls=\"uiny-painel\"\n [attr.aria-label]=\"(assistant.aberto$ | async) ? 'Fechar a Uiny, assistente da Uishop' : 'Abrir a Uiny, assistente da Uishop'\"\n [title]=\"(assistant.aberto$ | async) ? 'Fechar a Uiny' : 'Falar com a Uiny'\"\n (click)=\"assistant.alternar(botao)\">\n <svg class=\"uiny-trigger__mark\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M12 2.5c.5 3.9 2.6 6.9 6.4 8.2.5.2.5.9 0 1.1-3.8 1.3-5.9 4.3-6.4 8.2-.1.6-.9.6-1 0-.5-3.9-2.6-6.9-6.4-8.2-.5-.2-.5-.9 0-1.1 3.8-1.3 5.9-4.3 6.4-8.2.1-.6.9-.6 1 0Z\" fill=\"currentColor\"/>\n <path d=\"M19 2.8c.2 1.3.9 2.2 2.1 2.6.2.1.2.3 0 .4-1.2.4-1.9 1.3-2.1 2.6 0 .2-.3.2-.3 0-.2-1.3-.9-2.2-2.1-2.6-.2-.1-.2-.3 0-.4 1.2-.4 1.9-1.3 2.1-2.6 0-.2.3-.2.3 0Z\" fill=\"currentColor\" opacity=\".7\"/>\n </svg>\n <span class=\"uiny-trigger__label\">Uiny</span>\n</button>\n", styles: [":host{display:inline-flex}.uiny-trigger{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 14px 0 10px;border:1px solid rgba(115,102,255,.28);border-radius:999px;background:rgba(115,102,255,.08);color:var(--theme-default, #7366ff);font-size:13px;font-weight:600;line-height:1;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,color .15s ease}.uiny-trigger:hover{background:rgba(115,102,255,.14);border-color:#7366ff73}.uiny-trigger:focus-visible{outline:2px solid var(--theme-default, #7366ff);outline-offset:2px}.uiny-trigger.is-open{background:var(--theme-default, #7366ff);border-color:var(--theme-default, #7366ff);color:#fff}.uiny-trigger__mark{flex:0 0 auto}@media (max-width: 575.98px){.uiny-trigger{width:34px;padding:0;justify-content:center}.uiny-trigger__label{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}}@media (prefers-reduced-motion: reduce){.uiny-trigger{transition:none}}\n"] }]
627
- }], ctorParameters: function () { return [{ type: UiAssistantService }]; } });
628
-
629
27
  class MyAccountComponent {
630
28
  constructor(authService, urlService) {
631
29
  this.authService = authService;
@@ -650,7 +48,7 @@ class MyAccountComponent {
650
48
  }
651
49
  }
652
50
  MyAccountComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MyAccountComponent, deps: [{ token: i1.AuthService }, { token: i1.BaseUrlService }], target: i0.ɵɵFactoryTarget.Component });
653
- MyAccountComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: MyAccountComponent, selector: "my-account", inputs: { menus: "menus" }, outputs: { onNavigate: "onNavigate" }, ngImport: i0, template: "<ui-user-top [urlImage]=\"urlService.imagemUsuario({model: userObs?.usuario})\" [usuario]=\"userObs?.usuario\"\r\n [acesso]=\"accessObs\"></ui-user-top>\r\n\r\n<ul class=\"profile-dropdown onhover-show-div\">\r\n <li *ngFor=\"let col of menus\">\r\n <a (click)=\"clickMenu(col)\">\r\n <ui-icon [icon]=\"col.ds_icone\" size=\"20\"></ui-icon>\r\n <span>{{col.no_funcionalidade}}</span>\r\n </a>\r\n </li>\r\n <li>\r\n <a href=\"javascript:void(0)\" (click)=\"logout()\" class=\"text-danger\">\r\n <ui-icon icon=\"login\" size=\"20\"></ui-icon>\r\n <span>Sair</span>\r\n </a>\r\n </li>\r\n</ul>\r\n", styles: ["a{display:flex!important;align-items:center!important}ui-icon{display:flex;padding-right:1rem}ui-image{margin-top:4px}\n"], dependencies: [{ kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }, { kind: "component", type: i4.UiUserTopComponent, selector: "ui-user-top", inputs: ["urlImage", "usuario", "acesso"] }] });
51
+ MyAccountComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: MyAccountComponent, selector: "my-account", inputs: { menus: "menus" }, outputs: { onNavigate: "onNavigate" }, ngImport: i0, template: "<ui-user-top [urlImage]=\"urlService.imagemUsuario({model: userObs?.usuario})\" [usuario]=\"userObs?.usuario\"\r\n [acesso]=\"accessObs\"></ui-user-top>\r\n\r\n<ul class=\"profile-dropdown onhover-show-div\">\r\n <li *ngFor=\"let col of menus\">\r\n <a (click)=\"clickMenu(col)\">\r\n <ui-icon [icon]=\"col.ds_icone\" size=\"20\"></ui-icon>\r\n <span>{{col.no_funcionalidade}}</span>\r\n </a>\r\n </li>\r\n <li>\r\n <a href=\"javascript:void(0)\" (click)=\"logout()\" class=\"text-danger\">\r\n <ui-icon icon=\"login\" size=\"20\"></ui-icon>\r\n <span>Sair</span>\r\n </a>\r\n </li>\r\n</ul>\r\n", styles: ["a{display:flex!important;align-items:center!important}ui-icon{display:flex;padding-right:1rem}ui-image{margin-top:4px}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i3.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }, { kind: "component", type: i4.UiUserTopComponent, selector: "ui-user-top", inputs: ["urlImage", "usuario", "acesso"] }] });
654
52
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MyAccountComponent, decorators: [{
655
53
  type: Component,
656
54
  args: [{ selector: 'my-account', template: "<ui-user-top [urlImage]=\"urlService.imagemUsuario({model: userObs?.usuario})\" [usuario]=\"userObs?.usuario\"\r\n [acesso]=\"accessObs\"></ui-user-top>\r\n\r\n<ul class=\"profile-dropdown onhover-show-div\">\r\n <li *ngFor=\"let col of menus\">\r\n <a (click)=\"clickMenu(col)\">\r\n <ui-icon [icon]=\"col.ds_icone\" size=\"20\"></ui-icon>\r\n <span>{{col.no_funcionalidade}}</span>\r\n </a>\r\n </li>\r\n <li>\r\n <a href=\"javascript:void(0)\" (click)=\"logout()\" class=\"text-danger\">\r\n <ui-icon icon=\"login\" size=\"20\"></ui-icon>\r\n <span>Sair</span>\r\n </a>\r\n </li>\r\n</ul>\r\n", styles: ["a{display:flex!important;align-items:center!important}ui-icon{display:flex;padding-right:1rem}ui-image{margin-top:4px}\n"] }]
@@ -661,30 +59,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
661
59
  }] } });
662
60
 
663
61
  class UiHeaderComponent {
664
- constructor(authService, urlService, layoutService, iframeService, assistantService, document) {
62
+ constructor(authService, urlService, layoutService, iframeService, document) {
665
63
  this.authService = authService;
666
64
  this.urlService = urlService;
667
65
  this.layoutService = layoutService;
668
66
  this.iframeService = iframeService;
669
- this.assistantService = assistantService;
670
67
  this.document = document;
671
68
  this.sidebar = true;
69
+ this.fullWidth = false;
672
70
  this.redirectHome = true;
673
71
  this.darkDefault = "light";
674
- /** Exibe o botão da Uiny (assistente virtual). Exige um <ui-assistant-layout> envolvendo a aplicação. */
675
- this.showAssistant = false;
676
72
  this.collapseSidebarChange = new EventEmitter();
677
73
  this.fullScreenChange = new EventEmitter();
678
74
  this.headerBackdropChange = new EventEmitter();
679
75
  this.onNavigate = new EventEmitter();
680
- }
681
- ngOnChanges(changes) {
682
- if (changes['showAssistant']) {
683
- this.assistantService.definirHabilitado(this, !!this.showAssistant);
684
- }
685
- }
686
- ngOnDestroy() {
687
- this.assistantService.definirHabilitado(this, false);
76
+ this.onUpgrade = new EventEmitter();
688
77
  }
689
78
  ngOnInit() {
690
79
  this.elem = document.documentElement;
@@ -750,6 +139,10 @@ class UiHeaderComponent {
750
139
  this.statuSidebar = this.collapseSidebar;
751
140
  localStorage.setItem('local', this.statuSidebar);
752
141
  }
142
+ fecharBackdrop() {
143
+ this.headerBackdrop = null;
144
+ this.headerBackdropChange.emit(false);
145
+ }
753
146
  sidebarToggle() {
754
147
  this.collapseSidebar = !this.collapseSidebar;
755
148
  this.setStatusSideBar();
@@ -824,12 +217,12 @@ class UiHeaderComponent {
824
217
  }
825
218
  }
826
219
  }
827
- UiHeaderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiHeaderComponent, deps: [{ token: i1.AuthService }, { token: i1.BaseUrlService }, { token: i1.LayoutService }, { token: i1.IframeService }, { token: UiAssistantService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component });
828
- UiHeaderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiHeaderComponent, selector: "ui-header", inputs: { collapseSidebar: "collapseSidebar", fullScreen: "fullScreen", headerBackdrop: "headerBackdrop", isFree: "isFree", menus: "menus", sidebar: "sidebar", redirectHome: "redirectHome", darkDefault: "darkDefault", showAssistant: "showAssistant" }, outputs: { collapseSidebarChange: "collapseSidebarChange", fullScreenChange: "fullScreenChange", headerBackdropChange: "headerBackdropChange", onNavigate: "onNavigate" }, queries: [{ propertyName: "left", first: true, predicate: ["left"], descendants: true }, { propertyName: "center", first: true, predicate: ["center"], descendants: true }, { propertyName: "right", first: true, predicate: ["right"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Page Header Backdrop -->\r\n<div *ngIf=\"headerBackdrop\" (click)=\"navService.hideAllPopUps()\" class=\"header-backgrop\"></div>\r\n\r\n<!-- Page Header Start -->\r\n<div class=\"page-header\" [class.close_icon]=\"collapseSidebar\" [class.not-sidebar]=\"!sidebar\">\r\n <div class=\"header-wrapper d-flex m-0 justify-content-between\">\r\n <div class=\"header-logo-wrapper col-auto d-lg-none px-0\">\r\n <div *ngIf=\"sidebar\" class=\"toggle-sidebar\" (click)=\"sidebarToggle()\">\r\n <ui-icon icon=\"menu\" class=\"status_toggle middle\"></ui-icon>\r\n </div>\r\n <a (click)=\"home()\" [class.cursor-pointer]=\"redirectHome\">\r\n <img class=\"img-fluid for-dark image-logo d-md-none\" src=\"assets/images/logo/logo_dark.png\" alt=\"\" />\r\n </a>\r\n </div>\r\n\r\n <div class=\"left-header horizontal-wrapper ps-0\">\r\n <ng-container [ngTemplateOutlet]=\"left\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-center\">\r\n <ng-container [ngTemplateOutlet]=\"center\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-right p-0\">\r\n <ul class=\"nav-menus\">\r\n <!-- <li class=\"language-nav\" (click)=\"languageToggle()\">\r\n <header-languages></header-languages>\r\n </li> -->\r\n <ng-container *ngIf=\"isFree; then liUpgrade; else liEmpty\"></ng-container>\r\n\r\n <ng-template #liUpgrade>\r\n <li>\r\n <button\r\n class=\"d-flex align-items-center justify-content-center border-0 rounded-pill gap-2 b-color\"\r\n (click)=\"openModal()\">\r\n <img src=\"assets/images/coroa.svg\" class=\"w-auto\" alt=\"icone-coroa\" />\r\n Fa\u00E7a Upgrade\r\n </button>\r\n </li>\r\n </ng-template>\r\n <ng-template #liEmpty>\r\n <li></li>\r\n </ng-template>\r\n\r\n <ng-container [ngTemplateOutlet]=\"right\"></ng-container>\r\n\r\n <li *ngIf=\"showAssistant && (assistantService.disponivel$ | async)\" class=\"uiny-nav\">\r\n <ui-assistant-button></ui-assistant-button>\r\n </li>\r\n\r\n <li>\r\n <div class=\"mode cursor-pointer\" (click)=\"layoutToggle()\">\r\n <ui-icon pTooltip=\"Ativar Modo Escuro\" tooltipPosition=\"bottom\" icon=\"dark_mode\" size=\"24\"\r\n *ngIf=\"!dark\"></ui-icon>\r\n <ui-icon pTooltip=\"Ativar Modo Claro\" tooltipPosition=\"bottom\" icon=\"sunny\" size=\"24\"\r\n *ngIf=\"dark\"></ui-icon>\r\n\r\n </div>\r\n </li>\r\n\r\n <li class=\"maximize\">\r\n <a pTooltip=\"Maximizar\" tooltipPosition=\"bottom\" class=\"text-dark cursor-pointer\"\r\n href=\"javascript:void(0)\" (click)=\"toggleFullScreen()\">\r\n <ui-icon icon=\"fullscreen\" size=\"24\"></ui-icon>\r\n </a>\r\n </li>\r\n <li class=\"profile-nav onhover-dropdown p-0 ps-2\">\r\n <my-account [menus]=\"menus\" (onNavigate)=\"onNavigate.emit($event)\"></my-account>\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n</div>\r\n<!-- Page Header Ends -->\r\n", styles: ["@charset \"UTF-8\";::ng-deep .ambiente{position:fixed;top:0;right:0;left:0;height:2px;z-index:999;text-align:center}::ng-deep .ambiente span{content:\"\";position:absolute;height:20px;width:100px;right:0;margin:auto;border-radius:0 0 0 20px;text-align:center;color:#fff;font-size:12px;padding:0}::ng-deep .ambiente.ambiente-develop{background-color:#09b409}::ng-deep .ambiente.ambiente-develop span{background-color:#09b409}::ng-deep .ambiente.ambiente-homolog{background-color:#ff5d22}::ng-deep .ambiente.ambiente-homolog span{background-color:#ff5d22}::ng-deep .image-logo{width:auto;height:20px}::ng-deep .header-logo-wrapper.col-auto.d-lg-none.px-0{height:20px}::ng-deep ui-icon.status_toggle.middle{color:#7366ff}::ng-deep ui-icon.icons-menu{height:20px}::ng-deep .mode.d-none.d-md-block{height:20px}::ng-deep .header-backgrop{position:fixed;inset:0;z-index:8}::ng-deep .header-backgrop .page-header{z-index:11!important}::ng-deep .b-color{color:#e9e7ff;background:#7366ff;height:36px;width:136px}::ng-deep .b-color img{height:15px}::ng-deep .page-wrapper .page-header.not-sidebar{margin-left:0!important;width:100%!important}::ng-deep .page-wrapper .page-header .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li{padding:0 10px!important;margin-right:0}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}@media (min-width: 577px){::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper{display:block!important}::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper img{display:block!important}}@media (max-width: 576px){::ng-deep .hide-mobile{display:none!important}}@media only screen and (max-width: 575.98px){::ng-deep .nav-menus{margin-right:0!important}::ng-deep .page-wrapper .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li{padding:0 10px!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li:last-child{padding-right:0!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}}::ng-deep body .dark-only li.maximize .text-dark span{color:#ffffffb3!important}\n"], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: UiAssistantButtonComponent, selector: "ui-assistant-button" }, { kind: "component", type: MyAccountComponent, selector: "my-account", inputs: ["menus"], outputs: ["onNavigate"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }] });
220
+ UiHeaderComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiHeaderComponent, deps: [{ token: i1.AuthService }, { token: i1.BaseUrlService }, { token: i1.LayoutService }, { token: i1.IframeService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component });
221
+ UiHeaderComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiHeaderComponent, selector: "ui-header", inputs: { collapseSidebar: "collapseSidebar", fullScreen: "fullScreen", headerBackdrop: "headerBackdrop", isFree: "isFree", menus: "menus", sidebar: "sidebar", fullWidth: "fullWidth", redirectHome: "redirectHome", darkDefault: "darkDefault" }, outputs: { collapseSidebarChange: "collapseSidebarChange", fullScreenChange: "fullScreenChange", headerBackdropChange: "headerBackdropChange", onNavigate: "onNavigate", onUpgrade: "onUpgrade" }, queries: [{ propertyName: "left", first: true, predicate: ["left"], descendants: true }, { propertyName: "center", first: true, predicate: ["center"], descendants: true }, { propertyName: "right", first: true, predicate: ["right"], descendants: true }], ngImport: i0, template: "<!-- Page Header Backdrop -->\r\n<div *ngIf=\"headerBackdrop\" (click)=\"fecharBackdrop()\" class=\"header-backgrop\"></div>\r\n\r\n<!-- Page Header Start -->\r\n<div class=\"page-header\" [class.close_icon]=\"collapseSidebar\" [class.not-sidebar]=\"!sidebar\"\r\n [class.full-width]=\"fullWidth\">\r\n <div class=\"header-wrapper d-flex m-0 justify-content-between\">\r\n <div class=\"header-logo-wrapper col-auto d-lg-none px-0\">\r\n <div *ngIf=\"sidebar\" class=\"toggle-sidebar\" (click)=\"sidebarToggle()\">\r\n <ui-icon icon=\"menu\" class=\"status_toggle middle\"></ui-icon>\r\n </div>\r\n <a (click)=\"home()\" [class.cursor-pointer]=\"redirectHome\">\r\n <img class=\"img-fluid for-dark image-logo d-md-none\" src=\"assets/images/logo/logo_dark.png\" alt=\"\" />\r\n </a>\r\n </div>\r\n\r\n <div class=\"left-header horizontal-wrapper ps-0\">\r\n <ng-container [ngTemplateOutlet]=\"left\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-center\">\r\n <ng-container [ngTemplateOutlet]=\"center\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-right p-0\">\r\n <ul class=\"nav-menus\">\r\n <!-- <li class=\"language-nav\" (click)=\"languageToggle()\">\r\n <header-languages></header-languages>\r\n </li> -->\r\n <ng-container *ngIf=\"isFree; then liUpgrade; else liEmpty\"></ng-container>\r\n\r\n <ng-template #liUpgrade>\r\n <li>\r\n <button\r\n class=\"d-flex align-items-center justify-content-center border-0 rounded-pill gap-2 b-color\"\r\n (click)=\"onUpgrade.emit()\">\r\n <img src=\"assets/images/coroa.svg\" class=\"w-auto\" alt=\"icone-coroa\" />\r\n Fa\u00E7a Upgrade\r\n </button>\r\n </li>\r\n </ng-template>\r\n <ng-template #liEmpty>\r\n <li></li>\r\n </ng-template>\r\n\r\n <ng-container [ngTemplateOutlet]=\"right\"></ng-container>\r\n\r\n <li>\r\n <div class=\"mode cursor-pointer\" (click)=\"layoutToggle()\">\r\n <ui-icon pTooltip=\"Ativar Modo Escuro\" tooltipPosition=\"bottom\" icon=\"dark_mode\" size=\"24\"\r\n *ngIf=\"!dark\"></ui-icon>\r\n <ui-icon pTooltip=\"Ativar Modo Claro\" tooltipPosition=\"bottom\" icon=\"sunny\" size=\"24\"\r\n *ngIf=\"dark\"></ui-icon>\r\n\r\n </div>\r\n </li>\r\n\r\n <li class=\"maximize\">\r\n <a pTooltip=\"Maximizar\" tooltipPosition=\"bottom\" class=\"text-dark cursor-pointer\"\r\n href=\"javascript:void(0)\" (click)=\"toggleFullScreen()\">\r\n <ui-icon icon=\"fullscreen\" size=\"24\"></ui-icon>\r\n </a>\r\n </li>\r\n <li class=\"profile-nav onhover-dropdown p-0 ps-2\">\r\n <my-account [menus]=\"menus\" (onNavigate)=\"onNavigate.emit($event)\"></my-account>\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n</div>\r\n<!-- Page Header Ends -->\r\n", styles: ["@charset \"UTF-8\";::ng-deep .ambiente{position:fixed;top:0;right:0;left:0;height:2px;z-index:999;text-align:center}::ng-deep .ambiente span{content:\"\";position:absolute;height:20px;width:100px;right:0;margin:auto;border-radius:0 0 0 20px;text-align:center;color:#fff;font-size:12px;padding:0}::ng-deep .ambiente.ambiente-develop{background-color:#09b409}::ng-deep .ambiente.ambiente-develop span{background-color:#09b409}::ng-deep .ambiente.ambiente-homolog{background-color:#ff5d22}::ng-deep .ambiente.ambiente-homolog span{background-color:#ff5d22}::ng-deep .header-backgrop{position:fixed;inset:0;z-index:8}::ng-deep .header-backgrop .page-header{z-index:11!important}::ng-deep .image-logo{width:auto;height:20px}::ng-deep .header-logo-wrapper.col-auto.d-lg-none.px-0{height:20px}::ng-deep ui-icon.status_toggle.middle{color:#7366ff}::ng-deep ui-icon.icons-menu{height:20px}::ng-deep .mode.d-none.d-md-block{height:20px}::ng-deep .b-color{color:#e9e7ff;background:var(--theme-default, #7366ff);height:36px;width:136px}::ng-deep .b-color img{height:15px}::ng-deep .page-wrapper .page-header.not-sidebar,::ng-deep .page-wrapper .page-header.full-width{margin-left:0!important;width:100%!important}::ng-deep .page-wrapper .page-header .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li{padding:0 10px!important;margin-right:0}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}@media (min-width: 577px){::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper{display:block!important}::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper img{display:block!important}}@media (max-width: 576px){::ng-deep .hide-mobile{display:none!important}}@media only screen and (max-width: 575.98px){::ng-deep .nav-menus{margin-right:0!important}::ng-deep .page-wrapper .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li{padding:0 10px!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li:last-child{padding-right:0!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}}::ng-deep body .dark-only li.maximize .text-dark span{color:#ffffffb3!important}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i3.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }, { kind: "directive", type: i4$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: MyAccountComponent, selector: "my-account", inputs: ["menus"], outputs: ["onNavigate"] }] });
829
222
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiHeaderComponent, decorators: [{
830
223
  type: Component,
831
- args: [{ selector: 'ui-header', template: "<!-- Page Header Backdrop -->\r\n<div *ngIf=\"headerBackdrop\" (click)=\"navService.hideAllPopUps()\" class=\"header-backgrop\"></div>\r\n\r\n<!-- Page Header Start -->\r\n<div class=\"page-header\" [class.close_icon]=\"collapseSidebar\" [class.not-sidebar]=\"!sidebar\">\r\n <div class=\"header-wrapper d-flex m-0 justify-content-between\">\r\n <div class=\"header-logo-wrapper col-auto d-lg-none px-0\">\r\n <div *ngIf=\"sidebar\" class=\"toggle-sidebar\" (click)=\"sidebarToggle()\">\r\n <ui-icon icon=\"menu\" class=\"status_toggle middle\"></ui-icon>\r\n </div>\r\n <a (click)=\"home()\" [class.cursor-pointer]=\"redirectHome\">\r\n <img class=\"img-fluid for-dark image-logo d-md-none\" src=\"assets/images/logo/logo_dark.png\" alt=\"\" />\r\n </a>\r\n </div>\r\n\r\n <div class=\"left-header horizontal-wrapper ps-0\">\r\n <ng-container [ngTemplateOutlet]=\"left\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-center\">\r\n <ng-container [ngTemplateOutlet]=\"center\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-right p-0\">\r\n <ul class=\"nav-menus\">\r\n <!-- <li class=\"language-nav\" (click)=\"languageToggle()\">\r\n <header-languages></header-languages>\r\n </li> -->\r\n <ng-container *ngIf=\"isFree; then liUpgrade; else liEmpty\"></ng-container>\r\n\r\n <ng-template #liUpgrade>\r\n <li>\r\n <button\r\n class=\"d-flex align-items-center justify-content-center border-0 rounded-pill gap-2 b-color\"\r\n (click)=\"openModal()\">\r\n <img src=\"assets/images/coroa.svg\" class=\"w-auto\" alt=\"icone-coroa\" />\r\n Fa\u00E7a Upgrade\r\n </button>\r\n </li>\r\n </ng-template>\r\n <ng-template #liEmpty>\r\n <li></li>\r\n </ng-template>\r\n\r\n <ng-container [ngTemplateOutlet]=\"right\"></ng-container>\r\n\r\n <li *ngIf=\"showAssistant && (assistantService.disponivel$ | async)\" class=\"uiny-nav\">\r\n <ui-assistant-button></ui-assistant-button>\r\n </li>\r\n\r\n <li>\r\n <div class=\"mode cursor-pointer\" (click)=\"layoutToggle()\">\r\n <ui-icon pTooltip=\"Ativar Modo Escuro\" tooltipPosition=\"bottom\" icon=\"dark_mode\" size=\"24\"\r\n *ngIf=\"!dark\"></ui-icon>\r\n <ui-icon pTooltip=\"Ativar Modo Claro\" tooltipPosition=\"bottom\" icon=\"sunny\" size=\"24\"\r\n *ngIf=\"dark\"></ui-icon>\r\n\r\n </div>\r\n </li>\r\n\r\n <li class=\"maximize\">\r\n <a pTooltip=\"Maximizar\" tooltipPosition=\"bottom\" class=\"text-dark cursor-pointer\"\r\n href=\"javascript:void(0)\" (click)=\"toggleFullScreen()\">\r\n <ui-icon icon=\"fullscreen\" size=\"24\"></ui-icon>\r\n </a>\r\n </li>\r\n <li class=\"profile-nav onhover-dropdown p-0 ps-2\">\r\n <my-account [menus]=\"menus\" (onNavigate)=\"onNavigate.emit($event)\"></my-account>\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n</div>\r\n<!-- Page Header Ends -->\r\n", styles: ["@charset \"UTF-8\";::ng-deep .ambiente{position:fixed;top:0;right:0;left:0;height:2px;z-index:999;text-align:center}::ng-deep .ambiente span{content:\"\";position:absolute;height:20px;width:100px;right:0;margin:auto;border-radius:0 0 0 20px;text-align:center;color:#fff;font-size:12px;padding:0}::ng-deep .ambiente.ambiente-develop{background-color:#09b409}::ng-deep .ambiente.ambiente-develop span{background-color:#09b409}::ng-deep .ambiente.ambiente-homolog{background-color:#ff5d22}::ng-deep .ambiente.ambiente-homolog span{background-color:#ff5d22}::ng-deep .image-logo{width:auto;height:20px}::ng-deep .header-logo-wrapper.col-auto.d-lg-none.px-0{height:20px}::ng-deep ui-icon.status_toggle.middle{color:#7366ff}::ng-deep ui-icon.icons-menu{height:20px}::ng-deep .mode.d-none.d-md-block{height:20px}::ng-deep .header-backgrop{position:fixed;inset:0;z-index:8}::ng-deep .header-backgrop .page-header{z-index:11!important}::ng-deep .b-color{color:#e9e7ff;background:#7366ff;height:36px;width:136px}::ng-deep .b-color img{height:15px}::ng-deep .page-wrapper .page-header.not-sidebar{margin-left:0!important;width:100%!important}::ng-deep .page-wrapper .page-header .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li{padding:0 10px!important;margin-right:0}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}@media (min-width: 577px){::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper{display:block!important}::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper img{display:block!important}}@media (max-width: 576px){::ng-deep .hide-mobile{display:none!important}}@media only screen and (max-width: 575.98px){::ng-deep .nav-menus{margin-right:0!important}::ng-deep .page-wrapper .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li{padding:0 10px!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li:last-child{padding-right:0!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}}::ng-deep body .dark-only li.maximize .text-dark span{color:#ffffffb3!important}\n"] }]
832
- }], ctorParameters: function () { return [{ type: i1.AuthService }, { type: i1.BaseUrlService }, { type: i1.LayoutService }, { type: i1.IframeService }, { type: UiAssistantService }, { type: undefined, decorators: [{
224
+ args: [{ selector: 'ui-header', template: "<!-- Page Header Backdrop -->\r\n<div *ngIf=\"headerBackdrop\" (click)=\"fecharBackdrop()\" class=\"header-backgrop\"></div>\r\n\r\n<!-- Page Header Start -->\r\n<div class=\"page-header\" [class.close_icon]=\"collapseSidebar\" [class.not-sidebar]=\"!sidebar\"\r\n [class.full-width]=\"fullWidth\">\r\n <div class=\"header-wrapper d-flex m-0 justify-content-between\">\r\n <div class=\"header-logo-wrapper col-auto d-lg-none px-0\">\r\n <div *ngIf=\"sidebar\" class=\"toggle-sidebar\" (click)=\"sidebarToggle()\">\r\n <ui-icon icon=\"menu\" class=\"status_toggle middle\"></ui-icon>\r\n </div>\r\n <a (click)=\"home()\" [class.cursor-pointer]=\"redirectHome\">\r\n <img class=\"img-fluid for-dark image-logo d-md-none\" src=\"assets/images/logo/logo_dark.png\" alt=\"\" />\r\n </a>\r\n </div>\r\n\r\n <div class=\"left-header horizontal-wrapper ps-0\">\r\n <ng-container [ngTemplateOutlet]=\"left\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-center\">\r\n <ng-container [ngTemplateOutlet]=\"center\"></ng-container>\r\n </div>\r\n\r\n <div class=\"nav-right p-0\">\r\n <ul class=\"nav-menus\">\r\n <!-- <li class=\"language-nav\" (click)=\"languageToggle()\">\r\n <header-languages></header-languages>\r\n </li> -->\r\n <ng-container *ngIf=\"isFree; then liUpgrade; else liEmpty\"></ng-container>\r\n\r\n <ng-template #liUpgrade>\r\n <li>\r\n <button\r\n class=\"d-flex align-items-center justify-content-center border-0 rounded-pill gap-2 b-color\"\r\n (click)=\"onUpgrade.emit()\">\r\n <img src=\"assets/images/coroa.svg\" class=\"w-auto\" alt=\"icone-coroa\" />\r\n Fa\u00E7a Upgrade\r\n </button>\r\n </li>\r\n </ng-template>\r\n <ng-template #liEmpty>\r\n <li></li>\r\n </ng-template>\r\n\r\n <ng-container [ngTemplateOutlet]=\"right\"></ng-container>\r\n\r\n <li>\r\n <div class=\"mode cursor-pointer\" (click)=\"layoutToggle()\">\r\n <ui-icon pTooltip=\"Ativar Modo Escuro\" tooltipPosition=\"bottom\" icon=\"dark_mode\" size=\"24\"\r\n *ngIf=\"!dark\"></ui-icon>\r\n <ui-icon pTooltip=\"Ativar Modo Claro\" tooltipPosition=\"bottom\" icon=\"sunny\" size=\"24\"\r\n *ngIf=\"dark\"></ui-icon>\r\n\r\n </div>\r\n </li>\r\n\r\n <li class=\"maximize\">\r\n <a pTooltip=\"Maximizar\" tooltipPosition=\"bottom\" class=\"text-dark cursor-pointer\"\r\n href=\"javascript:void(0)\" (click)=\"toggleFullScreen()\">\r\n <ui-icon icon=\"fullscreen\" size=\"24\"></ui-icon>\r\n </a>\r\n </li>\r\n <li class=\"profile-nav onhover-dropdown p-0 ps-2\">\r\n <my-account [menus]=\"menus\" (onNavigate)=\"onNavigate.emit($event)\"></my-account>\r\n </li>\r\n </ul>\r\n </div>\r\n </div>\r\n</div>\r\n<!-- Page Header Ends -->\r\n", styles: ["@charset \"UTF-8\";::ng-deep .ambiente{position:fixed;top:0;right:0;left:0;height:2px;z-index:999;text-align:center}::ng-deep .ambiente span{content:\"\";position:absolute;height:20px;width:100px;right:0;margin:auto;border-radius:0 0 0 20px;text-align:center;color:#fff;font-size:12px;padding:0}::ng-deep .ambiente.ambiente-develop{background-color:#09b409}::ng-deep .ambiente.ambiente-develop span{background-color:#09b409}::ng-deep .ambiente.ambiente-homolog{background-color:#ff5d22}::ng-deep .ambiente.ambiente-homolog span{background-color:#ff5d22}::ng-deep .header-backgrop{position:fixed;inset:0;z-index:8}::ng-deep .header-backgrop .page-header{z-index:11!important}::ng-deep .image-logo{width:auto;height:20px}::ng-deep .header-logo-wrapper.col-auto.d-lg-none.px-0{height:20px}::ng-deep ui-icon.status_toggle.middle{color:#7366ff}::ng-deep ui-icon.icons-menu{height:20px}::ng-deep .mode.d-none.d-md-block{height:20px}::ng-deep .b-color{color:#e9e7ff;background:var(--theme-default, #7366ff);height:36px;width:136px}::ng-deep .b-color img{height:15px}::ng-deep .page-wrapper .page-header.not-sidebar,::ng-deep .page-wrapper .page-header.full-width{margin-left:0!important;width:100%!important}::ng-deep .page-wrapper .page-header .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li{padding:0 10px!important;margin-right:0}::ng-deep .page-wrapper .page-header .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}@media (min-width: 577px){::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper{display:block!important}::ng-deep .page-wrapper .page-header.not-sidebar .header-logo-wrapper img{display:block!important}}@media (max-width: 576px){::ng-deep .hide-mobile{display:none!important}}@media only screen and (max-width: 575.98px){::ng-deep .nav-menus{margin-right:0!important}::ng-deep .page-wrapper .header-wrapper{flex-direction:row}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li{padding:0 10px!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li:last-child{padding-right:0!important}::ng-deep .page-wrapper .header-wrapper .nav-right>ul li .profile-media img{width:100%!important}}::ng-deep body .dark-only li.maximize .text-dark span{color:#ffffffb3!important}\n"] }]
225
+ }], ctorParameters: function () { return [{ type: i1.AuthService }, { type: i1.BaseUrlService }, { type: i1.LayoutService }, { type: i1.IframeService }, { type: undefined, decorators: [{
833
226
  type: Inject,
834
227
  args: [DOCUMENT]
835
228
  }] }]; }, propDecorators: { collapseSidebar: [{
@@ -844,12 +237,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
844
237
  type: Input
845
238
  }], sidebar: [{
846
239
  type: Input
240
+ }], fullWidth: [{
241
+ type: Input
847
242
  }], redirectHome: [{
848
243
  type: Input
849
244
  }], darkDefault: [{
850
245
  type: Input
851
- }], showAssistant: [{
852
- type: Input
853
246
  }], collapseSidebarChange: [{
854
247
  type: Output
855
248
  }], fullScreenChange: [{
@@ -858,6 +251,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
858
251
  type: Output
859
252
  }], onNavigate: [{
860
253
  type: Output
254
+ }], onUpgrade: [{
255
+ type: Output
861
256
  }], left: [{
862
257
  type: ContentChild,
863
258
  args: ['left']
@@ -869,1132 +264,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
869
264
  args: ['right']
870
265
  }] } });
871
266
 
872
- const ROTULOS = {
873
- pendente: 'Aguardando sua confirmação',
874
- executando: 'Executando…',
875
- concluida: 'Concluída',
876
- cancelada: 'Cancelada',
877
- expirada: 'Expirada',
878
- invalidada: 'Precisa de nova confirmação',
879
- falha: 'Não concluída',
880
- indeterminada: 'Resultado a verificar',
881
- };
882
- const DESCRICOES = {
883
- concluida: 'A alteração foi aplicada.',
884
- cancelada: 'Nada foi alterado.',
885
- expirada: 'O prazo para confirmar terminou. Nada foi alterado; peça de novo se ainda quiser.',
886
- invalidada: 'Os dados mudaram desde a proposta. Peça a alteração de novo para revisar.',
887
- falha: 'A alteração não foi aplicada.',
888
- };
889
- /**
890
- * Card de ação pendente. A confirmação só acontece pelo clique em "Confirmar":
891
- * o card emite apenas o id da ação persistida, sem argumentos.
892
- */
893
- class UiAssistantActionCardComponent {
894
- constructor(cdr, zone) {
895
- this.cdr = cdr;
896
- this.zone = zone;
897
- this.confirmar = new EventEmitter();
898
- this.cancelar = new EventEmitter();
899
- this.verificar = new EventEmitter();
900
- this.agora = Date.now();
901
- }
902
- ngOnChanges() {
903
- this.agendar();
904
- }
905
- ngOnDestroy() {
906
- clearInterval(this.timer);
907
- }
908
- /** O servidor decide a expiração; aqui só evitamos oferecer um clique que já vai falhar. */
909
- get expiradaLocalmente() {
910
- return this.acao?.estado === 'pendente' && !!this.acao.expiraEm && this.acao.expiraEm.getTime() <= this.agora;
911
- }
912
- get estado() {
913
- return this.expiradaLocalmente ? 'expirada' : this.acao?.estado;
914
- }
915
- get rotulo() {
916
- return ROTULOS[this.estado] ?? '';
917
- }
918
- get descricao() {
919
- return this.acao?.aviso || DESCRICOES[this.estado] || null;
920
- }
921
- get podeDecidir() {
922
- return this.estado === 'pendente' && !this.acao.enviando;
923
- }
924
- get minutosRestantes() {
925
- if (this.estado !== 'pendente' || !this.acao.expiraEm)
926
- return null;
927
- return Math.max(1, Math.ceil((this.acao.expiraEm.getTime() - this.agora) / 60000));
928
- }
929
- ehCor(valor) {
930
- return /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(valor);
931
- }
932
- aoConfirmar() {
933
- if (this.podeDecidir)
934
- this.confirmar.emit(this.acao.id);
935
- }
936
- aoCancelar() {
937
- if (this.podeDecidir)
938
- this.cancelar.emit(this.acao.id);
939
- }
940
- agendar() {
941
- clearInterval(this.timer);
942
- this.agora = Date.now();
943
- if (this.acao?.estado !== 'pendente' || !this.acao.expiraEm)
944
- return;
945
- this.zone.runOutsideAngular(() => {
946
- this.timer = setInterval(() => this.zone.run(() => {
947
- this.agora = Date.now();
948
- if (this.expiradaLocalmente)
949
- clearInterval(this.timer);
950
- this.cdr.markForCheck();
951
- }), 15000);
952
- });
953
- }
954
- }
955
- UiAssistantActionCardComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantActionCardComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
956
- UiAssistantActionCardComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiAssistantActionCardComponent, selector: "ui-assistant-action-card", inputs: { acao: "acao" }, outputs: { confirmar: "confirmar", cancelar: "cancelar", verificar: "verificar" }, usesOnChanges: true, ngImport: i0, template: "<article class=\"uiny-acao\" [ngClass]=\"'uiny-acao--' + estado\" [attr.aria-busy]=\"acao.enviando ? 'true' : null\"\n [attr.aria-labelledby]=\"'uiny-acao-' + acao.id\">\n <header class=\"uiny-acao__topo\">\n <span class=\"uiny-acao__selo\">\n <span *ngIf=\"estado === 'executando' || acao.enviando\" class=\"uiny-acao__spinner\" aria-hidden=\"true\"></span>\n <ui-icon *ngIf=\"estado === 'concluida'\" icon=\"check_circle\" size=\"16\"></ui-icon>\n <ui-icon *ngIf=\"estado === 'falha' || estado === 'indeterminada'\" icon=\"error\" size=\"16\"></ui-icon>\n <ui-icon *ngIf=\"estado === 'pendente' && !acao.enviando\" icon=\"pending_actions\" size=\"16\"></ui-icon>\n {{ rotulo }}\n </span>\n <span *ngIf=\"minutosRestantes\" class=\"uiny-acao__prazo\">expira em {{ minutosRestantes }} min</span>\n </header>\n\n <p class=\"uiny-acao__resumo\" [id]=\"'uiny-acao-' + acao.id\">{{ acao.resumo }}</p>\n\n <dl *ngIf=\"acao.detalhes?.length\" class=\"uiny-acao__detalhes\">\n <div *ngFor=\"let d of acao.detalhes\" class=\"uiny-acao__item\">\n <dt>{{ d.rotulo }}</dt>\n <dd>\n <span *ngIf=\"ehCor(d.valor)\" class=\"uiny-acao__cor\" [style.background-color]=\"d.valor\" aria-hidden=\"true\"></span>\n {{ d.valor }}\n </dd>\n </div>\n </dl>\n\n <p *ngIf=\"descricao\" class=\"uiny-acao__descricao\" role=\"status\">{{ descricao }}</p>\n\n <div *ngIf=\"estado === 'pendente'\" class=\"uiny-acao__botoes\">\n <button type=\"button\" class=\"uiny-acao__botao uiny-acao__botao--secundario\" [disabled]=\"!podeDecidir\" (click)=\"aoCancelar()\">\n Cancelar\n </button>\n <button type=\"button\" class=\"uiny-acao__botao uiny-acao__botao--primario\" [disabled]=\"!podeDecidir\" (click)=\"aoConfirmar()\">\n Confirmar\n </button>\n </div>\n\n <div *ngIf=\"estado === 'indeterminada'\" class=\"uiny-acao__botoes\">\n <button type=\"button\" class=\"uiny-acao__botao uiny-acao__botao--secundario\" [disabled]=\"acao.enviando\" (click)=\"verificar.emit(acao.id)\">\n Verificar resultado\n </button>\n </div>\n</article>\n", styles: [":host{display:block;margin-top:8px}.uiny-acao{padding:12px 14px;border:1px solid var(--uiny-borda, #ececf3);border-left:3px solid var(--uiny-primaria, #7366ff);border-radius:12px;background:var(--uiny-fundo, #fff);box-shadow:0 2px 10px #1f1b4d0d}.uiny-acao--concluida{border-left-color:#1aa467}.uiny-acao--falha,.uiny-acao--indeterminada{border-left-color:var(--uiny-erro, #d9364f)}.uiny-acao--cancelada,.uiny-acao--expirada,.uiny-acao--invalidada{border-left-color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__topo{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}.uiny-acao__selo{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--uiny-primaria, #7366ff)}.uiny-acao--concluida .uiny-acao__selo{color:#1aa467}.uiny-acao--falha .uiny-acao__selo,.uiny-acao--indeterminada .uiny-acao__selo{color:var(--uiny-erro, #d9364f)}.uiny-acao--cancelada .uiny-acao__selo,.uiny-acao--expirada .uiny-acao__selo,.uiny-acao--invalidada .uiny-acao__selo{color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__prazo{font-size:11px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__resumo{margin:0;font-weight:600;line-height:1.45;color:var(--uiny-texto, #2b2b2b);overflow-wrap:anywhere}.uiny-acao__detalhes{margin:10px 0 0;padding:8px 10px;border-radius:9px;background:var(--uiny-fundo-suave, #f7f7fb);font-size:13px}.uiny-acao__item{display:flex;justify-content:space-between;gap:12px}.uiny-acao__item+.uiny-acao__item{margin-top:4px}.uiny-acao__item dt{font-weight:400;color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__item dd{display:inline-flex;align-items:center;gap:6px;margin:0;font-weight:600;text-align:right;overflow-wrap:anywhere}.uiny-acao__cor{flex:0 0 auto;width:14px;height:14px;border:1px solid rgba(0,0,0,.12);border-radius:4px}.uiny-acao__descricao{margin:8px 0 0;font-size:12.5px;line-height:1.45;color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__botoes{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}.uiny-acao__botao{min-height:34px;padding:0 14px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer}.uiny-acao__botao:disabled{opacity:.5;cursor:not-allowed}.uiny-acao__botao:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-acao__botao--primario{border:1px solid var(--uiny-primaria, #7366ff);background:var(--uiny-primaria, #7366ff);color:#fff}.uiny-acao__botao--secundario{border:1px solid var(--uiny-borda, #ececf3);background:transparent;color:var(--uiny-texto, #2b2b2b)}.uiny-acao__spinner{width:12px;height:12px;border:2px solid var(--uiny-borda, #ececf3);border-top-color:var(--uiny-primaria, #7366ff);border-radius:50%;animation:uiny-girar .8s linear infinite}@keyframes uiny-girar{to{transform:rotate(360deg)}}@media (prefers-reduced-motion: reduce){.uiny-acao__spinner{animation-duration:2.4s}}\n"], dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
957
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantActionCardComponent, decorators: [{
958
- type: Component,
959
- args: [{ selector: 'ui-assistant-action-card', changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"uiny-acao\" [ngClass]=\"'uiny-acao--' + estado\" [attr.aria-busy]=\"acao.enviando ? 'true' : null\"\n [attr.aria-labelledby]=\"'uiny-acao-' + acao.id\">\n <header class=\"uiny-acao__topo\">\n <span class=\"uiny-acao__selo\">\n <span *ngIf=\"estado === 'executando' || acao.enviando\" class=\"uiny-acao__spinner\" aria-hidden=\"true\"></span>\n <ui-icon *ngIf=\"estado === 'concluida'\" icon=\"check_circle\" size=\"16\"></ui-icon>\n <ui-icon *ngIf=\"estado === 'falha' || estado === 'indeterminada'\" icon=\"error\" size=\"16\"></ui-icon>\n <ui-icon *ngIf=\"estado === 'pendente' && !acao.enviando\" icon=\"pending_actions\" size=\"16\"></ui-icon>\n {{ rotulo }}\n </span>\n <span *ngIf=\"minutosRestantes\" class=\"uiny-acao__prazo\">expira em {{ minutosRestantes }} min</span>\n </header>\n\n <p class=\"uiny-acao__resumo\" [id]=\"'uiny-acao-' + acao.id\">{{ acao.resumo }}</p>\n\n <dl *ngIf=\"acao.detalhes?.length\" class=\"uiny-acao__detalhes\">\n <div *ngFor=\"let d of acao.detalhes\" class=\"uiny-acao__item\">\n <dt>{{ d.rotulo }}</dt>\n <dd>\n <span *ngIf=\"ehCor(d.valor)\" class=\"uiny-acao__cor\" [style.background-color]=\"d.valor\" aria-hidden=\"true\"></span>\n {{ d.valor }}\n </dd>\n </div>\n </dl>\n\n <p *ngIf=\"descricao\" class=\"uiny-acao__descricao\" role=\"status\">{{ descricao }}</p>\n\n <div *ngIf=\"estado === 'pendente'\" class=\"uiny-acao__botoes\">\n <button type=\"button\" class=\"uiny-acao__botao uiny-acao__botao--secundario\" [disabled]=\"!podeDecidir\" (click)=\"aoCancelar()\">\n Cancelar\n </button>\n <button type=\"button\" class=\"uiny-acao__botao uiny-acao__botao--primario\" [disabled]=\"!podeDecidir\" (click)=\"aoConfirmar()\">\n Confirmar\n </button>\n </div>\n\n <div *ngIf=\"estado === 'indeterminada'\" class=\"uiny-acao__botoes\">\n <button type=\"button\" class=\"uiny-acao__botao uiny-acao__botao--secundario\" [disabled]=\"acao.enviando\" (click)=\"verificar.emit(acao.id)\">\n Verificar resultado\n </button>\n </div>\n</article>\n", styles: [":host{display:block;margin-top:8px}.uiny-acao{padding:12px 14px;border:1px solid var(--uiny-borda, #ececf3);border-left:3px solid var(--uiny-primaria, #7366ff);border-radius:12px;background:var(--uiny-fundo, #fff);box-shadow:0 2px 10px #1f1b4d0d}.uiny-acao--concluida{border-left-color:#1aa467}.uiny-acao--falha,.uiny-acao--indeterminada{border-left-color:var(--uiny-erro, #d9364f)}.uiny-acao--cancelada,.uiny-acao--expirada,.uiny-acao--invalidada{border-left-color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__topo{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}.uiny-acao__selo{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:600;color:var(--uiny-primaria, #7366ff)}.uiny-acao--concluida .uiny-acao__selo{color:#1aa467}.uiny-acao--falha .uiny-acao__selo,.uiny-acao--indeterminada .uiny-acao__selo{color:var(--uiny-erro, #d9364f)}.uiny-acao--cancelada .uiny-acao__selo,.uiny-acao--expirada .uiny-acao__selo,.uiny-acao--invalidada .uiny-acao__selo{color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__prazo{font-size:11px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__resumo{margin:0;font-weight:600;line-height:1.45;color:var(--uiny-texto, #2b2b2b);overflow-wrap:anywhere}.uiny-acao__detalhes{margin:10px 0 0;padding:8px 10px;border-radius:9px;background:var(--uiny-fundo-suave, #f7f7fb);font-size:13px}.uiny-acao__item{display:flex;justify-content:space-between;gap:12px}.uiny-acao__item+.uiny-acao__item{margin-top:4px}.uiny-acao__item dt{font-weight:400;color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__item dd{display:inline-flex;align-items:center;gap:6px;margin:0;font-weight:600;text-align:right;overflow-wrap:anywhere}.uiny-acao__cor{flex:0 0 auto;width:14px;height:14px;border:1px solid rgba(0,0,0,.12);border-radius:4px}.uiny-acao__descricao{margin:8px 0 0;font-size:12.5px;line-height:1.45;color:var(--uiny-texto-suave, #6b6b80)}.uiny-acao__botoes{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}.uiny-acao__botao{min-height:34px;padding:0 14px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer}.uiny-acao__botao:disabled{opacity:.5;cursor:not-allowed}.uiny-acao__botao:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-acao__botao--primario{border:1px solid var(--uiny-primaria, #7366ff);background:var(--uiny-primaria, #7366ff);color:#fff}.uiny-acao__botao--secundario{border:1px solid var(--uiny-borda, #ececf3);background:transparent;color:var(--uiny-texto, #2b2b2b)}.uiny-acao__spinner{width:12px;height:12px;border:2px solid var(--uiny-borda, #ececf3);border-top-color:var(--uiny-primaria, #7366ff);border-radius:50%;animation:uiny-girar .8s linear infinite}@keyframes uiny-girar{to{transform:rotate(360deg)}}@media (prefers-reduced-motion: reduce){.uiny-acao__spinner{animation-duration:2.4s}}\n"] }]
960
- }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }]; }, propDecorators: { acao: [{
961
- type: Input
962
- }], confirmar: [{
963
- type: Output
964
- }], cancelar: [{
965
- type: Output
966
- }], verificar: [{
967
- type: Output
968
- }] } });
969
-
970
- const ALTURA_MAXIMA = 160;
971
- /** Campo de mensagem: Enter envia, Shift+Enter quebra linha, cresce até ALTURA_MAXIMA. */
972
- class UiAssistantComposerComponent {
973
- constructor() {
974
- this.desabilitado = false;
975
- this.enviando = false;
976
- /** Explica por que o envio está bloqueado (sem internet, carregando...). */
977
- this.motivo = null;
978
- this.enviar = new EventEmitter();
979
- this.max = UINY_MENSAGEM_MAX_CARACTERES;
980
- this.texto = '';
981
- }
982
- get vazio() {
983
- return !this.texto.trim();
984
- }
985
- get bloqueado() {
986
- return this.desabilitado || this.enviando || this.vazio || this.texto.length > this.max;
987
- }
988
- get mostrarContador() {
989
- return this.texto.length > this.max - 400;
990
- }
991
- focar() {
992
- this.campo?.nativeElement.focus({ preventScroll: true });
993
- }
994
- limpar() {
995
- this.texto = '';
996
- if (this.campo) {
997
- this.campo.nativeElement.value = '';
998
- this.ajustarAltura();
999
- }
1000
- }
1001
- aoDigitar(valor) {
1002
- this.texto = valor;
1003
- this.ajustarAltura();
1004
- }
1005
- aoTeclar(evento) {
1006
- if (evento.key !== 'Enter' || evento.shiftKey)
1007
- return;
1008
- // durante a composição (IME, acentos em alguns teclados) Enter confirma o caractere
1009
- if (evento.isComposing || evento.keyCode === 229)
1010
- return;
1011
- evento.preventDefault();
1012
- this.submeter();
1013
- }
1014
- submeter() {
1015
- if (this.bloqueado)
1016
- return;
1017
- this.enviar.emit(this.texto.trim());
1018
- }
1019
- ajustarAltura() {
1020
- const el = this.campo.nativeElement;
1021
- el.style.height = 'auto';
1022
- el.style.height = Math.min(el.scrollHeight, ALTURA_MAXIMA) + 'px';
1023
- el.style.overflowY = el.scrollHeight > ALTURA_MAXIMA ? 'auto' : 'hidden';
1024
- }
1025
- }
1026
- UiAssistantComposerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1027
- UiAssistantComposerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiAssistantComposerComponent, selector: "ui-assistant-composer", inputs: { desabilitado: "desabilitado", enviando: "enviando", motivo: "motivo" }, outputs: { enviar: "enviar" }, viewQueries: [{ propertyName: "campo", first: true, predicate: ["campo"], descendants: true, static: true }], ngImport: i0, template: "<form class=\"uiny-composer\" (submit)=\"$event.preventDefault(); submeter()\">\n <label for=\"uiny-campo\" class=\"uiny-sr\">Mensagem para a Uiny</label>\n <div class=\"uiny-composer__caixa\" [class.is-desabilitado]=\"desabilitado\">\n <textarea #campo id=\"uiny-campo\" class=\"uiny-composer__campo\" rows=\"1\"\n placeholder=\"Pergunte algo para a Uiny\"\n [value]=\"texto\"\n [attr.aria-describedby]=\"motivo ? 'uiny-campo-motivo' : 'uiny-campo-dica'\"\n [attr.maxlength]=\"max + 200\"\n (input)=\"aoDigitar(campo.value)\"\n (keydown)=\"aoTeclar($event)\"></textarea>\n\n <button type=\"submit\" class=\"uiny-composer__enviar\" [disabled]=\"bloqueado\"\n [attr.aria-label]=\"enviando ? 'Aguardando a resposta da Uiny' : 'Enviar mensagem'\"\n [title]=\"enviando ? 'Aguardando a resposta' : 'Enviar (Enter)'\">\n <span *ngIf=\"enviando\" class=\"uiny-composer__spinner\" aria-hidden=\"true\"></span>\n <ui-icon *ngIf=\"!enviando\" icon=\"arrow_upward\" size=\"20\"></ui-icon>\n </button>\n </div>\n\n <div class=\"uiny-composer__rodape\">\n <span *ngIf=\"motivo\" id=\"uiny-campo-motivo\" class=\"uiny-composer__motivo\">{{ motivo }}</span>\n <span *ngIf=\"!motivo\" id=\"uiny-campo-dica\">Enter envia \u00B7 Shift+Enter quebra a linha</span>\n <span *ngIf=\"mostrarContador\" class=\"uiny-composer__contador\" [class.is-excedido]=\"texto.length > max\">\n {{ texto.length }}/{{ max }}\n </span>\n </div>\n</form>\n", styles: [":host{display:block;padding:10px 12px 12px;border-top:1px solid var(--uiny-borda, #ececf3);background:var(--uiny-fundo, #fff)}.uiny-composer__caixa{display:flex;align-items:flex-end;gap:8px;padding:6px 6px 6px 12px;border:1px solid var(--uiny-borda, #ececf3);border-radius:14px;background:var(--uiny-fundo-suave, #f7f7fb);transition:border-color .15s ease,box-shadow .15s ease}.uiny-composer__caixa:focus-within{border-color:var(--uiny-primaria, #7366ff);box-shadow:0 0 0 3px #7366ff26}.uiny-composer__caixa.is-desabilitado{opacity:.75}.uiny-composer__campo{flex:1 1 auto;min-width:0;max-height:160px;padding:6px 0;border:0;outline:0;background:transparent;color:var(--uiny-texto, #2b2b2b);font:inherit;font-size:14px;line-height:1.45;resize:none;overflow-y:hidden}.uiny-composer__campo::placeholder{color:var(--uiny-texto-suave, #6b6b80)}.uiny-composer__enviar{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:0;border-radius:10px;background:var(--uiny-primaria, #7366ff);color:#fff;cursor:pointer;transition:opacity .15s ease}.uiny-composer__enviar:disabled{opacity:.35;cursor:not-allowed}.uiny-composer__enviar:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-composer__spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:uiny-girar .8s linear infinite}.uiny-composer__rodape{display:flex;justify-content:space-between;gap:8px;margin-top:6px;padding:0 4px;font-size:11px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-composer__motivo{color:var(--uiny-offline-texto, #8a5300)}.uiny-composer__contador.is-excedido{color:var(--uiny-erro, #d9364f);font-weight:600}.uiny-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}@keyframes uiny-girar{to{transform:rotate(360deg)}}@media (prefers-reduced-motion: reduce){.uiny-composer__caixa,.uiny-composer__enviar{transition:none}.uiny-composer__spinner{animation-duration:2.4s}}\n"], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1028
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantComposerComponent, decorators: [{
1029
- type: Component,
1030
- args: [{ selector: 'ui-assistant-composer', changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"uiny-composer\" (submit)=\"$event.preventDefault(); submeter()\">\n <label for=\"uiny-campo\" class=\"uiny-sr\">Mensagem para a Uiny</label>\n <div class=\"uiny-composer__caixa\" [class.is-desabilitado]=\"desabilitado\">\n <textarea #campo id=\"uiny-campo\" class=\"uiny-composer__campo\" rows=\"1\"\n placeholder=\"Pergunte algo para a Uiny\"\n [value]=\"texto\"\n [attr.aria-describedby]=\"motivo ? 'uiny-campo-motivo' : 'uiny-campo-dica'\"\n [attr.maxlength]=\"max + 200\"\n (input)=\"aoDigitar(campo.value)\"\n (keydown)=\"aoTeclar($event)\"></textarea>\n\n <button type=\"submit\" class=\"uiny-composer__enviar\" [disabled]=\"bloqueado\"\n [attr.aria-label]=\"enviando ? 'Aguardando a resposta da Uiny' : 'Enviar mensagem'\"\n [title]=\"enviando ? 'Aguardando a resposta' : 'Enviar (Enter)'\">\n <span *ngIf=\"enviando\" class=\"uiny-composer__spinner\" aria-hidden=\"true\"></span>\n <ui-icon *ngIf=\"!enviando\" icon=\"arrow_upward\" size=\"20\"></ui-icon>\n </button>\n </div>\n\n <div class=\"uiny-composer__rodape\">\n <span *ngIf=\"motivo\" id=\"uiny-campo-motivo\" class=\"uiny-composer__motivo\">{{ motivo }}</span>\n <span *ngIf=\"!motivo\" id=\"uiny-campo-dica\">Enter envia \u00B7 Shift+Enter quebra a linha</span>\n <span *ngIf=\"mostrarContador\" class=\"uiny-composer__contador\" [class.is-excedido]=\"texto.length > max\">\n {{ texto.length }}/{{ max }}\n </span>\n </div>\n</form>\n", styles: [":host{display:block;padding:10px 12px 12px;border-top:1px solid var(--uiny-borda, #ececf3);background:var(--uiny-fundo, #fff)}.uiny-composer__caixa{display:flex;align-items:flex-end;gap:8px;padding:6px 6px 6px 12px;border:1px solid var(--uiny-borda, #ececf3);border-radius:14px;background:var(--uiny-fundo-suave, #f7f7fb);transition:border-color .15s ease,box-shadow .15s ease}.uiny-composer__caixa:focus-within{border-color:var(--uiny-primaria, #7366ff);box-shadow:0 0 0 3px #7366ff26}.uiny-composer__caixa.is-desabilitado{opacity:.75}.uiny-composer__campo{flex:1 1 auto;min-width:0;max-height:160px;padding:6px 0;border:0;outline:0;background:transparent;color:var(--uiny-texto, #2b2b2b);font:inherit;font-size:14px;line-height:1.45;resize:none;overflow-y:hidden}.uiny-composer__campo::placeholder{color:var(--uiny-texto-suave, #6b6b80)}.uiny-composer__enviar{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border:0;border-radius:10px;background:var(--uiny-primaria, #7366ff);color:#fff;cursor:pointer;transition:opacity .15s ease}.uiny-composer__enviar:disabled{opacity:.35;cursor:not-allowed}.uiny-composer__enviar:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-composer__spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:uiny-girar .8s linear infinite}.uiny-composer__rodape{display:flex;justify-content:space-between;gap:8px;margin-top:6px;padding:0 4px;font-size:11px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-composer__motivo{color:var(--uiny-offline-texto, #8a5300)}.uiny-composer__contador.is-excedido{color:var(--uiny-erro, #d9364f);font-weight:600}.uiny-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}@keyframes uiny-girar{to{transform:rotate(360deg)}}@media (prefers-reduced-motion: reduce){.uiny-composer__caixa,.uiny-composer__enviar{transition:none}.uiny-composer__spinner{animation-duration:2.4s}}\n"] }]
1031
- }], propDecorators: { desabilitado: [{
1032
- type: Input
1033
- }], enviando: [{
1034
- type: Input
1035
- }], motivo: [{
1036
- type: Input
1037
- }], enviar: [{
1038
- type: Output
1039
- }], campo: [{
1040
- type: ViewChild,
1041
- args: ['campo', { static: true }]
1042
- }] } });
1043
-
1044
- const ESTADO_INICIAL = {
1045
- conversa: null,
1046
- mensagens: [],
1047
- acoes: {},
1048
- enviando: false,
1049
- carregandoConversa: false,
1050
- erro: null,
1051
- aviso: null,
1052
- conversas: null,
1053
- carregandoConversas: false,
1054
- textoComFalha: null,
1055
- };
1056
- /**
1057
- * Estado da conversa, só em memória. Nada vai para localStorage/sessionStorage.
1058
- *
1059
- * Limpeza: qualquer mudança de sessão (logout, outro usuário, outra empresa, novo token,
1060
- * 401 da API) ou a Uiny ficar indisponível cancela as requisições em andamento e
1061
- * descarta todo o estado.
1062
- */
1063
- class UinyChatService {
1064
- constructor(api, session, assistant) {
1065
- this.api = api;
1066
- this.session = session;
1067
- this.assistant = assistant;
1068
- this.estado$ = new BehaviorSubject(ESTADO_INICIAL);
1069
- /** Emite para abortar as requisições da sessão/conversa atual. */
1070
- this.cancelarConversa$ = new Subject();
1071
- this.cancelarSessao$ = new Subject();
1072
- this.anuncios$ = new Subject();
1073
- this.subs = new Subscription();
1074
- this.sessao = null;
1075
- this.idLocal = 0;
1076
- /** Loja usada ao criar a conversa atual: o backend restringe a conversa a ela. */
1077
- this.escopoLoja = null;
1078
- this.estado = this.estado$.asObservable();
1079
- /** Textos curtos para a região aria-live do painel. */
1080
- this.anuncios = this.anuncios$.asObservable();
1081
- this.subs.add(this.session.sessao$.subscribe(sessao => {
1082
- if (sessao?.id !== this.sessao?.id) {
1083
- this.sessao = sessao;
1084
- this.limparTudo();
1085
- }
1086
- }));
1087
- this.subs.add(this.assistant.disponivel$.pipe(filter(d => !d)).subscribe(() => this.limparTudo()));
1088
- // Trocar de loja muda o escopo da conversa: não reaproveitamos a anterior.
1089
- this.subs.add(this.assistant.contexto$.pipe(map(c => c.idLoja ?? null), distinctUntilChanged()).subscribe(idLoja => {
1090
- if (this.valor.conversa && this.escopoLoja !== idLoja) {
1091
- this.encerrarConversa('Você mudou de loja. Começamos uma nova conversa para manter os assuntos separados.');
1092
- }
1093
- }));
1094
- }
1095
- ngOnDestroy() {
1096
- this.cancelarSessao$.next();
1097
- this.subs.unsubscribe();
1098
- }
1099
- get valor() {
1100
- return this.estado$.value;
1101
- }
1102
- get ocupado() {
1103
- const v = this.valor;
1104
- return v.enviando || Object.values(v.acoes).some(a => a.enviando || a.estado === 'executando');
1105
- }
1106
- enviar(textoBruto) {
1107
- const texto = (textoBruto || '').trim();
1108
- const sistema = this.assistant.sistemaOrigem;
1109
- if (!texto || !sistema || !this.sessao || this.valor.enviando)
1110
- return false;
1111
- if (texto.length > UINY_MENSAGEM_MAX_CARACTERES) {
1112
- this.patch({ erro: this.erroLocal('invalido', `A mensagem pode ter até ${UINY_MENSAGEM_MAX_CARACTERES} caracteres.`) });
1113
- return false;
1114
- }
1115
- const contexto = contextoParaApi(sistema, this.assistant.contexto);
1116
- const local = {
1117
- id: this.novoIdLocal(), papel: 'usuario', estado: null, texto, idAcao: null, opcoes: [], criadaEm: new Date(),
1118
- };
1119
- this.patch({
1120
- mensagens: [...this.valor.mensagens.filter(m => !m.erro), local],
1121
- enviando: true,
1122
- erro: null,
1123
- aviso: null,
1124
- textoComFalha: null,
1125
- });
1126
- this.anuncios$.next('A Uiny está respondendo…');
1127
- const conversa$ = this.valor.conversa
1128
- ? of(this.valor.conversa)
1129
- : this.api.criarConversa(contexto).pipe(map(r => {
1130
- const conversa = adaptarConversa(r?.conversa);
1131
- if (!conversa)
1132
- throw this.erroLocal('servidor', 'Não foi possível iniciar a conversa.');
1133
- this.escopoLoja = contexto.id_loja ?? null;
1134
- this.patch({ conversa, conversas: null });
1135
- return conversa;
1136
- }));
1137
- conversa$.pipe(switchMap(conversa => this.api.enviarMensagem(conversa.id, texto, contexto)), takeUntil(this.cancelarConversa$), finalize(() => this.valor.enviando && this.patch({ enviando: false }))).subscribe({
1138
- next: resposta => {
1139
- this.aplicarResposta(resposta, true);
1140
- this.patch({ enviando: false });
1141
- this.anuncios$.next(resposta?.mensagem ? 'Nova resposta da Uiny.' : '');
1142
- },
1143
- error: err => {
1144
- const erro = this.normalizar(err);
1145
- // erros da API podem trazer a mensagem da Uiny (ex.: falha do provedor registrada)
1146
- if (erro.corpo?.mensagem)
1147
- this.aplicarResposta(erro.corpo, false);
1148
- this.patch({
1149
- mensagens: this.valor.mensagens.map(m => m.id === local.id ? { ...m, erro: true } : m),
1150
- enviando: false,
1151
- erro,
1152
- textoComFalha: erro.tipo === 'sessao' ? null : texto,
1153
- });
1154
- this.anuncios$.next(erro.mensagem);
1155
- },
1156
- });
1157
- return true;
1158
- }
1159
- /** Começa do zero. Bloqueado enquanto há envio ou ação em execução. */
1160
- novaConversa() {
1161
- if (this.ocupado)
1162
- return false;
1163
- this.encerrarConversa(null);
1164
- return true;
1165
- }
1166
- listarConversas() {
1167
- if (!this.sessao || this.valor.carregandoConversas)
1168
- return;
1169
- this.patch({ carregandoConversas: true, erro: null });
1170
- this.api.listarConversas().pipe(takeUntil(this.cancelarSessao$), finalize(() => this.patch({ carregandoConversas: false }))).subscribe({
1171
- next: r => this.patch({
1172
- conversas: (Array.isArray(r?.data) ? r.data : []).map(adaptarConversa).filter(c => !!c && this.doContextoAtual(c)),
1173
- }),
1174
- error: err => this.patch({ erro: this.normalizar(err), conversas: [] }),
1175
- });
1176
- }
1177
- abrirConversa(id) {
1178
- const resumo = this.valor.conversas?.find(c => c.id === id);
1179
- if (!resumo || this.ocupado || !this.doContextoAtual(resumo))
1180
- return;
1181
- this.encerrarConversa(null);
1182
- this.patch({ carregandoConversa: true });
1183
- this.api.historico(id).pipe(takeUntil(this.cancelarConversa$), finalize(() => this.patch({ carregandoConversa: false }))).subscribe({
1184
- next: r => {
1185
- const conversa = adaptarConversa(r?.conversa) ?? resumo;
1186
- if (!this.doContextoAtual(conversa)) {
1187
- this.patch({ aviso: 'Essa conversa pertence a outro contexto e não pode ser continuada aqui.' });
1188
- return;
1189
- }
1190
- this.escopoLoja = conversa.idLoja;
1191
- const mensagensApi = Array.isArray(r?.data) ? r.data : [];
1192
- this.patch({
1193
- conversa,
1194
- mensagens: mensagensApi.map(adaptarMensagem).filter(Boolean),
1195
- acoes: this.mesclarAcoes(mensagensApi.map(m => m?.acao_pendente)),
1196
- });
1197
- // Histórico não reemite instruções de interface: elas já foram tratadas quando chegaram.
1198
- },
1199
- error: err => this.patch({ erro: this.normalizar(err) }),
1200
- });
1201
- }
1202
- /**
1203
- * Confirma pelo id da ação persistida no backend. Só é chamado pelo clique no card;
1204
- * nenhum texto da conversa dispara isto. Nunca há repetição automática.
1205
- */
1206
- confirmar(idAcao) {
1207
- const acao = this.valor.acoes[idAcao];
1208
- if (!acao || acao.enviando || acao.estado !== 'pendente')
1209
- return;
1210
- if (acao.expiraEm && acao.expiraEm.getTime() <= Date.now()) {
1211
- this.atualizarAcao(idAcao, { estado: 'expirada', aviso: 'O prazo para confirmar terminou. Peça a alteração de novo.' });
1212
- return;
1213
- }
1214
- this.atualizarAcao(idAcao, { enviando: true, estado: 'executando', aviso: null });
1215
- this.anuncios$.next('Executando a alteração…');
1216
- this.api.confirmarAcao(idAcao).pipe(takeUntil(this.cancelarSessao$)).subscribe({
1217
- next: resposta => this.concluirDecisao(idAcao, resposta, null, true),
1218
- error: err => {
1219
- const erro = this.normalizar(err);
1220
- if (erro.corpo?.acao_pendente || erro.corpo?.acao_anterior) {
1221
- // 409 (expirada, invalidada com nova proposta, em execução) ou 502/504: o corpo traz o estado real
1222
- this.concluirDecisao(idAcao, erro.corpo, erro, true);
1223
- }
1224
- else if (erro.tipo === 'timeout' || erro.tipo === 'rede' || erro.tipo === 'servidor') {
1225
- // A operação pode ter sido executada. Não repetimos: o usuário verifica o estado.
1226
- this.atualizarAcao(idAcao, {
1227
- enviando: false,
1228
- estado: 'indeterminada',
1229
- aviso: 'Não recebemos a resposta da confirmação. Verifique o resultado antes de pedir de novo.',
1230
- });
1231
- }
1232
- else if (erro.tipo === 'conflito' || erro.tipo === 'nao_encontrado') {
1233
- this.atualizarAcao(idAcao, { enviando: false, estado: 'indeterminada', aviso: erro.mensagem });
1234
- this.verificarAcao(idAcao);
1235
- }
1236
- else {
1237
- this.atualizarAcao(idAcao, { enviando: false, estado: 'pendente', aviso: erro.mensagem });
1238
- }
1239
- this.anuncios$.next(erro.mensagem);
1240
- },
1241
- });
1242
- }
1243
- cancelar(idAcao) {
1244
- const acao = this.valor.acoes[idAcao];
1245
- if (!acao || acao.enviando || acao.estado !== 'pendente')
1246
- return;
1247
- this.atualizarAcao(idAcao, { enviando: true, aviso: null });
1248
- this.api.cancelarAcao(idAcao).pipe(takeUntil(this.cancelarSessao$)).subscribe({
1249
- next: resposta => this.concluirDecisao(idAcao, resposta, null, false),
1250
- error: err => {
1251
- const erro = this.normalizar(err);
1252
- if (erro.corpo?.acao_pendente) {
1253
- this.concluirDecisao(idAcao, erro.corpo, erro, false);
1254
- }
1255
- else {
1256
- this.atualizarAcao(idAcao, { enviando: false, aviso: erro.mensagem });
1257
- }
1258
- },
1259
- });
1260
- }
1261
- /** Consulta a situação real da ação relendo o histórico da conversa. */
1262
- verificarAcao(idAcao) {
1263
- const acao = this.valor.acoes[idAcao];
1264
- const idConversa = this.valor.conversa?.id;
1265
- if (!acao || !idConversa)
1266
- return;
1267
- this.atualizarAcao(idAcao, { enviando: true });
1268
- this.api.historico(idConversa).pipe(takeUntil(this.cancelarSessao$)).subscribe({
1269
- next: r => {
1270
- const mensagens = Array.isArray(r?.data) ? r.data : [];
1271
- const atual = mensagens.map(m => adaptarAcao(m?.acao_pendente)).find(a => a?.id === idAcao);
1272
- if (atual) {
1273
- this.atualizarAcao(idAcao, { ...atual, enviando: false, aviso: atual.estado === 'indeterminada' ? 'Confira no sistema se a alteração foi aplicada.' : null });
1274
- this.patch({ mensagens: this.mesclar(this.valor.mensagens, mensagens.map(adaptarMensagem).filter(Boolean)) });
1275
- }
1276
- else {
1277
- this.atualizarAcao(idAcao, {
1278
- enviando: false,
1279
- aviso: 'Não foi possível consultar o estado desta ação. Confira na tela correspondente se a alteração foi aplicada.',
1280
- });
1281
- }
1282
- },
1283
- error: err => this.atualizarAcao(idAcao, { enviando: false, aviso: this.normalizar(err).mensagem }),
1284
- });
1285
- }
1286
- limparErro() {
1287
- this.patch({ erro: null, aviso: null });
1288
- }
1289
- /* ---------------- internos ---------------- */
1290
- /** Aplica uma resposta de confirmar/cancelar (de sucesso ou de erro com corpo). */
1291
- concluirDecisao(idAcao, resposta, erro, confirmacao) {
1292
- const anterior = this.valor.acoes[idAcao];
1293
- this.aplicarResposta(resposta, !erro);
1294
- const atual = this.valor.acoes[idAcao];
1295
- // sem a ação na resposta, o resultado é desconhecido
1296
- if (!atual || atual === anterior) {
1297
- this.atualizarAcao(idAcao, {
1298
- enviando: false,
1299
- estado: confirmacao ? 'indeterminada' : anterior?.estado,
1300
- aviso: erro?.mensagem ?? 'A API não informou o resultado. Confira se a alteração foi aplicada.',
1301
- });
1302
- }
1303
- else {
1304
- this.atualizarAcao(idAcao, { enviando: false, aviso: erro?.mensagem ?? null });
1305
- }
1306
- const final = this.valor.acoes[idAcao];
1307
- // Sucesso só quando a API devolve a ação como executada.
1308
- if (confirmacao && final?.estado === 'concluida') {
1309
- this.assistant.emitir({ tipo: 'acao_concluida', idAcao, ferramenta: final.ferramenta });
1310
- this.anuncios$.next('Alteração concluída.');
1311
- }
1312
- else if (final?.estado === 'falha') {
1313
- this.anuncios$.next('A alteração não foi concluída.');
1314
- }
1315
- }
1316
- /** Resposta padrão (ApresentadorConversa::resposta): conversa, mensagem e ação pendente. */
1317
- aplicarResposta(resposta, emitirInstrucoes) {
1318
- if (!resposta || typeof resposta !== 'object')
1319
- return;
1320
- const mensagem = adaptarMensagem(resposta.mensagem);
1321
- this.patch({
1322
- conversa: adaptarConversa(resposta.conversa) ?? this.valor.conversa,
1323
- mensagens: mensagem ? this.mesclar(this.valor.mensagens, [mensagem]) : this.valor.mensagens,
1324
- acoes: this.mesclarAcoes([resposta.acao_anterior, resposta.mensagem?.acao_pendente, resposta.acao_pendente]),
1325
- });
1326
- if (emitirInstrucoes && resposta.mensagem?.papel === 'assistente') {
1327
- adaptarInstrucoes(resposta.mensagem.instrucoes_interface).forEach(e => this.assistant.emitir(e));
1328
- }
1329
- }
1330
- mesclar(atuais, novas) {
1331
- const ids = new Set(atuais.map(m => m.id));
1332
- return [...atuais, ...novas.filter(m => !ids.has(m.id))];
1333
- }
1334
- mesclarAcoes(novas) {
1335
- const acoes = { ...this.valor.acoes };
1336
- novas.map(adaptarAcao).filter(Boolean).forEach(a => {
1337
- // uma ação já resolvida localmente não volta a "pendente" por uma resposta antiga
1338
- const anterior = acoes[a.id];
1339
- acoes[a.id] = anterior && anterior.estado !== 'pendente' && anterior.estado !== 'executando' && a.estado === 'pendente'
1340
- ? anterior
1341
- : { ...a, enviando: anterior?.enviando, aviso: anterior?.aviso };
1342
- });
1343
- return acoes;
1344
- }
1345
- atualizarAcao(id, parcial) {
1346
- const atual = this.valor.acoes[id];
1347
- if (!atual)
1348
- return;
1349
- this.patch({ acoes: { ...this.valor.acoes, [id]: { ...atual, ...parcial, id } } });
1350
- }
1351
- doContextoAtual(conversa) {
1352
- const sistema = this.assistant.sistemaOrigem;
1353
- const idLoja = this.assistant.contexto.idLoja ?? null;
1354
- return conversa.sistemaOrigem === sistema && (conversa.idLoja ?? null) === idLoja;
1355
- }
1356
- encerrarConversa(aviso) {
1357
- this.cancelarConversa$.next();
1358
- this.escopoLoja = null;
1359
- this.patch({
1360
- conversa: null,
1361
- mensagens: [],
1362
- acoes: {},
1363
- enviando: false,
1364
- carregandoConversa: false,
1365
- erro: null,
1366
- aviso,
1367
- conversas: null,
1368
- textoComFalha: null,
1369
- });
1370
- }
1371
- limparTudo() {
1372
- this.cancelarConversa$.next();
1373
- this.cancelarSessao$.next();
1374
- this.escopoLoja = null;
1375
- this.estado$.next(ESTADO_INICIAL);
1376
- }
1377
- patch(parcial) {
1378
- this.estado$.next({ ...this.estado$.value, ...parcial });
1379
- }
1380
- novoIdLocal() {
1381
- return 'local-' + (++this.idLocal);
1382
- }
1383
- normalizar(err) {
1384
- if (err && typeof err === 'object' && typeof err.tipo === 'string' && typeof err.mensagem === 'string')
1385
- return err;
1386
- return this.erroLocal('servidor', 'Algo deu errado ao falar com a Uiny. Tente de novo.');
1387
- }
1388
- erroLocal(tipo, mensagem) {
1389
- return { tipo, status: 0, codigo: null, mensagem, correlacao: null };
1390
- }
1391
- }
1392
- UinyChatService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinyChatService, deps: [{ token: UinyApiService }, { token: UinySessionService }, { token: UiAssistantService }], target: i0.ɵɵFactoryTarget.Injectable });
1393
- UinyChatService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinyChatService, providedIn: 'root' });
1394
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UinyChatService, decorators: [{
1395
- type: Injectable,
1396
- args: [{ providedIn: 'root' }]
1397
- }], ctorParameters: function () { return [{ type: UinyApiService }, { type: UinySessionService }, { type: UiAssistantService }]; } });
1398
-
1399
- /*
1400
- * Subconjunto seguro de Markdown para as respostas da Uiny.
1401
- *
1402
- * O texto vira uma árvore de tokens renderizada pelo template com interpolação
1403
- * ({{ }}), sem innerHTML e sem bypass do sanitizador do Angular. HTML presente no
1404
- * texto aparece literalmente. Links só com http, https ou mailto.
1405
- *
1406
- * Suporta: parágrafos, quebras de linha, listas (- * 1.), títulos (#, ##, ###),
1407
- * blocos de código (```), `código`, **negrito**, *itálico*, [texto](url) e URLs soltas.
1408
- */
1409
- const PROTOCOLOS = ['http:', 'https:', 'mailto:'];
1410
- function urlPermitida(url) {
1411
- const bruto = (url || '').trim();
1412
- if (!bruto || bruto.length > 2000)
1413
- return null;
1414
- try {
1415
- const u = new URL(bruto);
1416
- return PROTOCOLOS.includes(u.protocol) ? u.href : null;
1417
- }
1418
- catch {
1419
- return null;
1420
- }
1421
- }
1422
- const INLINE = /(`[^`\n]+`)|(\*\*[^*\n]+\*\*)|(\[[^\]\n]{1,300}\]\([^()\s]{1,2000}\))|(\bhttps?:\/\/[^\s<>()]*[^\s<>().,;:!?'"])|(\*[^*\s][^*\n]*\*)/g;
1423
- function parseInline(texto) {
1424
- const saida = [];
1425
- let ultimo = 0;
1426
- INLINE.lastIndex = 0;
1427
- let m;
1428
- while ((m = INLINE.exec(texto)) !== null) {
1429
- if (m.index > ultimo)
1430
- saida.push({ t: 'text', v: texto.slice(ultimo, m.index) });
1431
- const token = m[0];
1432
- if (m[1]) {
1433
- saida.push({ t: 'code', v: token.slice(1, -1) });
1434
- }
1435
- else if (m[2]) {
1436
- saida.push({ t: 'strong', v: token.slice(2, -2) });
1437
- }
1438
- else if (m[3]) {
1439
- const fim = token.indexOf('](');
1440
- const rotulo = token.slice(1, fim);
1441
- const href = urlPermitida(token.slice(fim + 2, -1));
1442
- saida.push(href ? { t: 'link', v: rotulo, href } : { t: 'text', v: rotulo });
1443
- }
1444
- else if (m[4]) {
1445
- const href = urlPermitida(token);
1446
- saida.push(href ? { t: 'link', v: token, href } : { t: 'text', v: token });
1447
- }
1448
- else {
1449
- saida.push({ t: 'em', v: token.slice(1, -1) });
1450
- }
1451
- ultimo = m.index + token.length;
1452
- }
1453
- if (ultimo < texto.length)
1454
- saida.push({ t: 'text', v: texto.slice(ultimo) });
1455
- return saida;
1456
- }
1457
- function parseMarkdown(texto) {
1458
- const linhas = (texto || '').replace(/\r\n?/g, '\n').split('\n');
1459
- const blocos = [];
1460
- let paragrafo = [];
1461
- let lista = null;
1462
- const fecharParagrafo = () => {
1463
- if (paragrafo.length)
1464
- blocos.push({ t: 'p', linhas: paragrafo });
1465
- paragrafo = [];
1466
- };
1467
- const fecharLista = () => {
1468
- if (lista)
1469
- blocos.push(lista.t === 'ul' ? { t: 'ul', itens: lista.itens } : { t: 'ol', itens: lista.itens, inicio: lista.inicio });
1470
- lista = null;
1471
- };
1472
- for (let i = 0; i < linhas.length; i++) {
1473
- const linha = linhas[i];
1474
- if (/^\s*```/.test(linha)) {
1475
- fecharParagrafo();
1476
- fecharLista();
1477
- const codigo = [];
1478
- i++;
1479
- while (i < linhas.length && !/^\s*```/.test(linhas[i]))
1480
- codigo.push(linhas[i++]);
1481
- blocos.push({ t: 'code', v: codigo.join('\n') });
1482
- continue;
1483
- }
1484
- if (!linha.trim()) {
1485
- fecharParagrafo();
1486
- fecharLista();
1487
- continue;
1488
- }
1489
- const titulo = /^\s{0,3}#{1,3}\s+(.*)$/.exec(linha);
1490
- if (titulo) {
1491
- fecharParagrafo();
1492
- fecharLista();
1493
- blocos.push({ t: 'h', inlines: parseInline(titulo[1]) });
1494
- continue;
1495
- }
1496
- const ul = /^\s*[-*•]\s+(.*)$/.exec(linha);
1497
- const ol = /^\s*(\d{1,3})[.)]\s+(.*)$/.exec(linha);
1498
- if (ul || ol) {
1499
- fecharParagrafo();
1500
- const tipo = ul ? 'ul' : 'ol';
1501
- if (!lista || lista.t !== tipo) {
1502
- fecharLista();
1503
- lista = { t: tipo, itens: [], inicio: ol ? Number(ol[1]) : 1 };
1504
- }
1505
- lista.itens.push(parseInline(ul ? ul[1] : ol[2]));
1506
- continue;
1507
- }
1508
- if (lista && /^\s{2,}\S/.test(linha)) {
1509
- // continuação do último item da lista
1510
- lista.itens[lista.itens.length - 1].push({ t: 'text', v: ' ' }, ...parseInline(linha.trim()));
1511
- continue;
1512
- }
1513
- fecharLista();
1514
- paragrafo.push(parseInline(linha));
1515
- }
1516
- fecharParagrafo();
1517
- fecharLista();
1518
- return blocos;
1519
- }
1520
-
1521
- /**
1522
- * Exibe o texto da Uiny com um subconjunto de Markdown. Tudo é renderizado por
1523
- * interpolação: HTML vindo da API aparece como texto, nunca é interpretado.
1524
- */
1525
- class UiAssistantTextComponent {
1526
- constructor() {
1527
- this.texto = '';
1528
- this.blocos = [];
1529
- }
1530
- ngOnChanges() {
1531
- this.blocos = parseMarkdown(this.texto || '');
1532
- }
1533
- }
1534
- UiAssistantTextComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1535
- UiAssistantTextComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiAssistantTextComponent, selector: "ui-assistant-text", inputs: { texto: "texto" }, usesOnChanges: true, ngImport: i0, template: "<ng-template #inlines let-lista>\n <ng-container *ngFor=\"let i of lista\" [ngSwitch]=\"i.t\">\n <strong *ngSwitchCase=\"'strong'\">{{ i.v }}</strong>\n <em *ngSwitchCase=\"'em'\">{{ i.v }}</em>\n <code *ngSwitchCase=\"'code'\">{{ i.v }}</code>\n <a *ngSwitchCase=\"'link'\" [href]=\"i.href\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">{{ i.v }}</a>\n <ng-container *ngSwitchDefault>{{ i.v }}</ng-container>\n </ng-container>\n</ng-template>\n\n<ng-container *ngFor=\"let b of blocos\" [ngSwitch]=\"b.t\">\n <p *ngSwitchCase=\"'p'\">\n <ng-container *ngFor=\"let linha of $any(b).linhas; let ultima = last\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: linha }\"></ng-container><br *ngIf=\"!ultima\">\n </ng-container>\n </p>\n <p *ngSwitchCase=\"'h'\" class=\"uiny-texto__titulo\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: $any(b).inlines }\"></ng-container>\n </p>\n <ul *ngSwitchCase=\"'ul'\">\n <li *ngFor=\"let item of $any(b).itens\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: item }\"></ng-container>\n </li>\n </ul>\n <ol *ngSwitchCase=\"'ol'\" [attr.start]=\"$any(b).inicio !== 1 ? $any(b).inicio : null\">\n <li *ngFor=\"let item of $any(b).itens\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: item }\"></ng-container>\n </li>\n </ol>\n <pre *ngSwitchCase=\"'code'\"><code>{{ $any(b).v }}</code></pre>\n</ng-container>\n", styles: [":host{display:block;line-height:1.55;overflow-wrap:anywhere}p,ul,ol,pre{margin:0 0 8px}p:last-child,ul:last-child,ol:last-child,pre:last-child{margin-bottom:0}ul,ol{padding-left:20px}li+li{margin-top:3px}.uiny-texto__titulo{font-weight:700;margin-top:4px}strong{font-weight:700}code{padding:1px 5px;border-radius:5px;background:var(--uiny-fundo-suave, #f7f7fb);font-size:12.5px}pre{padding:10px 12px;border-radius:10px;background:var(--uiny-fundo-suave, #f7f7fb);overflow-x:auto;white-space:pre}pre code{padding:0;background:none}a{color:var(--uiny-primaria, #7366ff);text-decoration:underline;text-underline-offset:2px}\n"], dependencies: [{ kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$2.NgSwitchDefault, selector: "[ngSwitchDefault]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1536
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantTextComponent, decorators: [{
1537
- type: Component,
1538
- args: [{ selector: 'ui-assistant-text', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #inlines let-lista>\n <ng-container *ngFor=\"let i of lista\" [ngSwitch]=\"i.t\">\n <strong *ngSwitchCase=\"'strong'\">{{ i.v }}</strong>\n <em *ngSwitchCase=\"'em'\">{{ i.v }}</em>\n <code *ngSwitchCase=\"'code'\">{{ i.v }}</code>\n <a *ngSwitchCase=\"'link'\" [href]=\"i.href\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">{{ i.v }}</a>\n <ng-container *ngSwitchDefault>{{ i.v }}</ng-container>\n </ng-container>\n</ng-template>\n\n<ng-container *ngFor=\"let b of blocos\" [ngSwitch]=\"b.t\">\n <p *ngSwitchCase=\"'p'\">\n <ng-container *ngFor=\"let linha of $any(b).linhas; let ultima = last\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: linha }\"></ng-container><br *ngIf=\"!ultima\">\n </ng-container>\n </p>\n <p *ngSwitchCase=\"'h'\" class=\"uiny-texto__titulo\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: $any(b).inlines }\"></ng-container>\n </p>\n <ul *ngSwitchCase=\"'ul'\">\n <li *ngFor=\"let item of $any(b).itens\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: item }\"></ng-container>\n </li>\n </ul>\n <ol *ngSwitchCase=\"'ol'\" [attr.start]=\"$any(b).inicio !== 1 ? $any(b).inicio : null\">\n <li *ngFor=\"let item of $any(b).itens\">\n <ng-container *ngTemplateOutlet=\"inlines; context: { $implicit: item }\"></ng-container>\n </li>\n </ol>\n <pre *ngSwitchCase=\"'code'\"><code>{{ $any(b).v }}</code></pre>\n</ng-container>\n", styles: [":host{display:block;line-height:1.55;overflow-wrap:anywhere}p,ul,ol,pre{margin:0 0 8px}p:last-child,ul:last-child,ol:last-child,pre:last-child{margin-bottom:0}ul,ol{padding-left:20px}li+li{margin-top:3px}.uiny-texto__titulo{font-weight:700;margin-top:4px}strong{font-weight:700}code{padding:1px 5px;border-radius:5px;background:var(--uiny-fundo-suave, #f7f7fb);font-size:12.5px}pre{padding:10px 12px;border-radius:10px;background:var(--uiny-fundo-suave, #f7f7fb);overflow-x:auto;white-space:pre}pre code{padding:0;background:none}a{color:var(--uiny-primaria, #7366ff);text-decoration:underline;text-underline-offset:2px}\n"] }]
1539
- }], propDecorators: { texto: [{
1540
- type: Input
1541
- }] } });
1542
-
1543
- /** Distância do fim (px) até onde ainda consideramos que o usuário está "no fim". */
1544
- const PERTO_DO_FIM = 96;
1545
- class UiAssistantMessagesComponent {
1546
- constructor(cdr, zone) {
1547
- this.cdr = cdr;
1548
- this.mensagens = [];
1549
- this.acoes = {};
1550
- this.enviando = false;
1551
- this.carregando = false;
1552
- this.sugestoes = [];
1553
- this.capacidades = [];
1554
- this.temAlteracoes = false;
1555
- this.podeEnviar = true;
1556
- this.sugestao = new EventEmitter();
1557
- this.confirmar = new EventEmitter();
1558
- this.cancelar = new EventEmitter();
1559
- this.verificar = new EventEmitter();
1560
- this.novas = false;
1561
- this.pertoDoFim = true;
1562
- this.rolarPendente = true;
1563
- // o listener roda fora da zona: só reentra quando o indicador muda
1564
- zone.runOutsideAngular(() => setTimeout(() => {
1565
- const el = this.area?.nativeElement;
1566
- if (!el)
1567
- return;
1568
- const aoRolar = () => {
1569
- const perto = this.calcularPertoDoFim();
1570
- if (perto === this.pertoDoFim)
1571
- return;
1572
- this.pertoDoFim = perto;
1573
- if (perto && this.novas)
1574
- zone.run(() => {
1575
- this.novas = false;
1576
- this.cdr.markForCheck();
1577
- });
1578
- };
1579
- el.addEventListener('scroll', aoRolar, { passive: true });
1580
- this.removerScroll = () => el.removeEventListener('scroll', aoRolar);
1581
- }));
1582
- }
1583
- ngOnChanges(changes) {
1584
- const mudou = changes['mensagens'] || changes['enviando'] || changes['acoes'];
1585
- if (!mudou)
1586
- return;
1587
- const mensagensMudaram = !!changes['mensagens'];
1588
- const anterior = changes['mensagens']?.previousValue || [];
1589
- const atual = this.mensagens || [];
1590
- const conversaTrocada = mensagensMudaram && (!anterior.length || !atual.length || anterior[0]?.id !== atual[0]?.id);
1591
- const ultima = atual[atual.length - 1];
1592
- // quem acabou de enviar quer ver a própria mensagem
1593
- const ultimaDoUsuario = mensagensMudaram && ultima?.papel === 'usuario' && ultima.id.startsWith('local-');
1594
- if (conversaTrocada || ultimaDoUsuario || this.pertoDoFim) {
1595
- this.rolarPendente = true;
1596
- }
1597
- else if (mensagensMudaram && atual.length > anterior.length) {
1598
- // o usuário está lendo mensagens antigas: preserva a posição
1599
- this.novas = true;
1600
- }
1601
- }
1602
- ngAfterViewChecked() {
1603
- if (!this.rolarPendente)
1604
- return;
1605
- this.rolarPendente = false;
1606
- this.irParaOFim(false);
1607
- }
1608
- ngOnDestroy() {
1609
- this.removerScroll?.();
1610
- }
1611
- irParaOFim(suave = true) {
1612
- const el = this.area?.nativeElement;
1613
- if (!el)
1614
- return;
1615
- const reduzir = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
1616
- if (suave && !reduzir && el.scrollTo) {
1617
- el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });
1618
- }
1619
- else {
1620
- el.scrollTop = el.scrollHeight;
1621
- }
1622
- this.pertoDoFim = true;
1623
- if (this.novas) {
1624
- this.novas = false;
1625
- this.cdr.markForCheck();
1626
- }
1627
- }
1628
- acaoDa(mensagem) {
1629
- return mensagem.idAcao ? this.acoes?.[mensagem.idAcao] ?? null : null;
1630
- }
1631
- /** Ações devolvidas pela API sem mensagem que as referencie: aparecem no fim. */
1632
- get acoesSemMensagem() {
1633
- const referenciadas = new Set((this.mensagens || []).map(m => m.idAcao).filter(Boolean));
1634
- return Object.values(this.acoes || {}).filter(a => !referenciadas.has(a.id));
1635
- }
1636
- trackMensagem(_, m) {
1637
- return m.id;
1638
- }
1639
- trackAcao(_, a) {
1640
- return a.id;
1641
- }
1642
- calcularPertoDoFim() {
1643
- const el = this.area.nativeElement;
1644
- return el.scrollHeight - el.scrollTop - el.clientHeight <= PERTO_DO_FIM;
1645
- }
1646
- }
1647
- UiAssistantMessagesComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantMessagesComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1648
- UiAssistantMessagesComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiAssistantMessagesComponent, selector: "ui-assistant-messages", inputs: { mensagens: "mensagens", acoes: "acoes", enviando: "enviando", carregando: "carregando", sugestoes: "sugestoes", capacidades: "capacidades", temAlteracoes: "temAlteracoes", podeEnviar: "podeEnviar" }, outputs: { sugestao: "sugestao", confirmar: "confirmar", cancelar: "cancelar", verificar: "verificar" }, viewQueries: [{ propertyName: "area", first: true, predicate: ["area"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #area class=\"uiny-msgs\" role=\"log\" aria-live=\"off\" aria-label=\"Mensagens da conversa com a Uiny\" tabindex=\"0\">\r\n\r\n <div *ngIf=\"carregando\" class=\"uiny-msgs__carregando\" role=\"status\">\r\n <span class=\"uiny-spinner\" aria-hidden=\"true\"></span> Carregando a conversa\u2026\r\n </div>\r\n\r\n <div *ngIf=\"!carregando && !mensagens?.length\" class=\"uiny-boas-vindas\">\r\n <p class=\"uiny-boas-vindas__titulo\">Ol\u00E1! Eu sou a Uiny.</p>\r\n <p class=\"uiny-boas-vindas__texto\">Sou a assistente virtual da Uishop. Conte o que voc\u00EA precisa e eu ajudo por aqui.</p>\r\n <p *ngIf=\"temAlteracoes\" class=\"uiny-boas-vindas__nota\">\r\n <ui-icon icon=\"verified_user\" size=\"16\"></ui-icon>\r\n <span>Antes de alterar qualquer informa\u00E7\u00E3o, eu mostro o que vai mudar e s\u00F3 sigo com a sua confirma\u00E7\u00E3o.</span>\r\n </p>\r\n\r\n <div *ngIf=\"sugestoes?.length\" class=\"uiny-sugestoes\" aria-label=\"Sugest\u00F5es\">\r\n <button *ngFor=\"let s of sugestoes\" type=\"button\" class=\"uiny-sugestao\" [disabled]=\"!podeEnviar\" (click)=\"sugestao.emit(s)\">\r\n {{ s }}\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"capacidades?.length\" class=\"uiny-capacidades\">\r\n <p class=\"uiny-capacidades__titulo\">Posso ajudar com:</p>\r\n <ul>\r\n <li *ngFor=\"let c of capacidades\">{{ c }}</li>\r\n </ul>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let m of mensagens; trackBy: trackMensagem\" class=\"uiny-msg\"\r\n [class.uiny-msg--usuario]=\"m.papel === 'usuario'\"\r\n [class.uiny-msg--assistente]=\"m.papel !== 'usuario'\"\r\n [class.uiny-msg--falha]=\"m.erro\">\r\n <span class=\"uiny-sr\">{{ m.papel === 'usuario' ? 'Voc\u00EA:' : 'Uiny:' }}</span>\r\n <div class=\"uiny-msg__balao\">\r\n <p *ngIf=\"m.papel === 'usuario'\" class=\"uiny-msg__simples\">{{ m.texto }}</p>\r\n <ui-assistant-text *ngIf=\"m.papel !== 'usuario'\" [texto]=\"m.texto\"></ui-assistant-text>\r\n </div>\r\n\r\n <ui-assistant-action-card *ngIf=\"acaoDa(m) as acao\" [acao]=\"acao\"\r\n (confirmar)=\"confirmar.emit($event)\" (cancelar)=\"cancelar.emit($event)\" (verificar)=\"verificar.emit($event)\">\r\n </ui-assistant-action-card>\r\n\r\n <!-- esclarecimento: op\u00E7\u00F5es s\u00F3 na \u00FAltima mensagem, enviadas como texto -->\r\n <div *ngIf=\"m.opcoes?.length && m === mensagens[mensagens.length - 1]\" class=\"uiny-sugestoes uiny-msg__opcoes\" aria-label=\"Op\u00E7\u00F5es de resposta\">\r\n <button *ngFor=\"let o of m.opcoes\" type=\"button\" class=\"uiny-sugestao\" [disabled]=\"!podeEnviar || enviando\" (click)=\"sugestao.emit(o)\">\r\n {{ o }}\r\n </button>\r\n </div>\r\n\r\n <div class=\"uiny-msg__meta\">\r\n <span *ngIf=\"m.erro\" class=\"uiny-msg__nao-enviada\">\r\n <ui-icon icon=\"error\" size=\"14\"></ui-icon> N\u00E3o enviada\r\n </span>\r\n <time *ngIf=\"m.criadaEm && !m.erro\" [attr.datetime]=\"m.criadaEm.toISOString()\">{{ m.criadaEm | date:'HH:mm' }}</time>\r\n </div>\r\n </div>\r\n\r\n <ui-assistant-action-card *ngFor=\"let acao of acoesSemMensagem; trackBy: trackAcao\" class=\"uiny-msgs__acao-solta\" [acao]=\"acao\"\r\n (confirmar)=\"confirmar.emit($event)\" (cancelar)=\"cancelar.emit($event)\" (verificar)=\"verificar.emit($event)\">\r\n </ui-assistant-action-card>\r\n\r\n <div *ngIf=\"enviando\" class=\"uiny-digitando\">\r\n <span class=\"uiny-digitando__pontos\" aria-hidden=\"true\"><i></i><i></i><i></i></span>\r\n <span class=\"uiny-digitando__texto\">A Uiny est\u00E1 pensando\u2026</span>\r\n </div>\r\n</div>\r\n\r\n<button *ngIf=\"novas\" type=\"button\" class=\"uiny-novas\" (click)=\"irParaOFim()\">\r\n <ui-icon icon=\"arrow_downward\" size=\"16\"></ui-icon> Novas mensagens\r\n</button>\r\n", styles: [":host{position:relative;display:flex;flex-direction:column;min-height:0}.uiny-msgs{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;padding:16px 14px 8px;scrollbar-width:thin}.uiny-msgs:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:-2px}.uiny-msgs__carregando{display:flex;align-items:center;gap:8px;color:var(--uiny-texto-suave, #6b6b80);font-size:13px}.uiny-spinner{width:14px;height:14px;border:2px solid var(--uiny-borda, #ececf3);border-top-color:var(--uiny-primaria, #7366ff);border-radius:50%;animation:uiny-girar .8s linear infinite}.uiny-boas-vindas{padding:8px 2px 4px}.uiny-boas-vindas__titulo{margin:0 0 4px;font-size:18px;font-weight:700;color:var(--uiny-texto, #2b2b2b)}.uiny-boas-vindas__texto{margin:0 0 12px;color:var(--uiny-texto-suave, #6b6b80);line-height:1.5}.uiny-boas-vindas__nota{display:flex;gap:6px;margin:0 0 14px;padding:8px 10px;border-radius:10px;background:var(--uiny-fundo-suave, #f7f7fb);color:var(--uiny-texto-suave, #6b6b80);font-size:12.5px;line-height:1.45}.uiny-boas-vindas__nota ui-icon{flex:0 0 auto;color:var(--uiny-primaria, #7366ff)}.uiny-sugestoes{display:flex;flex-direction:column;align-items:flex-start;gap:6px}.uiny-sugestao{max-width:100%;padding:8px 12px;border:1px solid var(--uiny-borda, #ececf3);border-radius:12px;background:var(--uiny-fundo, #fff);color:var(--uiny-texto, #2b2b2b);font-size:13px;text-align:left;line-height:1.35;cursor:pointer;transition:border-color .15s ease,background-color .15s ease}.uiny-sugestao:hover:not(:disabled){border-color:var(--uiny-primaria, #7366ff);background:var(--uiny-aviso-fundo, #f1effe)}.uiny-sugestao:disabled{opacity:.5;cursor:not-allowed}.uiny-sugestao:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-capacidades{font-size:13px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-capacidades ul{margin:0;padding-left:18px}.uiny-capacidades li+li{margin-top:2px}.uiny-capacidades__titulo{margin:0 0 4px;font-weight:600;color:var(--uiny-texto, #2b2b2b)}.uiny-msg{display:flex;flex-direction:column;margin-top:14px}.uiny-msg:first-of-type{margin-top:0}.uiny-msg--usuario{align-items:flex-end}.uiny-msg--usuario .uiny-msg__balao{max-width:85%;padding:9px 13px;border-radius:16px 16px 4px;background:var(--uiny-primaria, #7366ff);color:#fff}.uiny-msg--assistente{align-items:stretch}.uiny-msg--assistente .uiny-msg__balao{max-width:100%;color:var(--uiny-texto, #2b2b2b)}.uiny-msg--falha .uiny-msg__balao{opacity:.65}.uiny-msg__simples{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.45}.uiny-msg__meta{margin-top:3px;font-size:11px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-msg__meta time{opacity:.8}.uiny-msg__nao-enviada{display:inline-flex;align-items:center;gap:3px;color:var(--uiny-erro, #d9364f)}.uiny-msg__opcoes{margin-top:8px}.uiny-msgs__acao-solta{display:block;margin-top:14px}.uiny-digitando{display:flex;align-items:center;gap:8px;margin-top:14px;color:var(--uiny-texto-suave, #6b6b80);font-size:13px}.uiny-digitando__pontos{display:inline-flex;gap:3px}.uiny-digitando__pontos i{width:6px;height:6px;border-radius:50%;background:var(--uiny-primaria, #7366ff);animation:uiny-pulsar 1.2s ease-in-out infinite}.uiny-digitando__pontos i:nth-child(2){animation-delay:.15s}.uiny-digitando__pontos i:nth-child(3){animation-delay:.3s}.uiny-novas{position:absolute;left:50%;bottom:10px;transform:translate(-50%);display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border:0;border-radius:999px;background:var(--uiny-primaria, #7366ff);color:#fff;font-size:12px;font-weight:600;box-shadow:0 6px 16px #1f1b4d33;cursor:pointer}.uiny-novas:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}@keyframes uiny-girar{to{transform:rotate(360deg)}}@keyframes uiny-pulsar{0%,80%,to{opacity:.25;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.uiny-digitando__pontos i{animation:none;opacity:.6}.uiny-spinner{animation-duration:2.4s}.uiny-sugestao{transition:none}}\n"], dependencies: [{ kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }, { kind: "component", type: UiAssistantTextComponent, selector: "ui-assistant-text", inputs: ["texto"] }, { kind: "component", type: UiAssistantActionCardComponent, selector: "ui-assistant-action-card", inputs: ["acao"], outputs: ["confirmar", "cancelar", "verificar"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1649
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantMessagesComponent, decorators: [{
1650
- type: Component,
1651
- args: [{ selector: 'ui-assistant-messages', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #area class=\"uiny-msgs\" role=\"log\" aria-live=\"off\" aria-label=\"Mensagens da conversa com a Uiny\" tabindex=\"0\">\r\n\r\n <div *ngIf=\"carregando\" class=\"uiny-msgs__carregando\" role=\"status\">\r\n <span class=\"uiny-spinner\" aria-hidden=\"true\"></span> Carregando a conversa\u2026\r\n </div>\r\n\r\n <div *ngIf=\"!carregando && !mensagens?.length\" class=\"uiny-boas-vindas\">\r\n <p class=\"uiny-boas-vindas__titulo\">Ol\u00E1! Eu sou a Uiny.</p>\r\n <p class=\"uiny-boas-vindas__texto\">Sou a assistente virtual da Uishop. Conte o que voc\u00EA precisa e eu ajudo por aqui.</p>\r\n <p *ngIf=\"temAlteracoes\" class=\"uiny-boas-vindas__nota\">\r\n <ui-icon icon=\"verified_user\" size=\"16\"></ui-icon>\r\n <span>Antes de alterar qualquer informa\u00E7\u00E3o, eu mostro o que vai mudar e s\u00F3 sigo com a sua confirma\u00E7\u00E3o.</span>\r\n </p>\r\n\r\n <div *ngIf=\"sugestoes?.length\" class=\"uiny-sugestoes\" aria-label=\"Sugest\u00F5es\">\r\n <button *ngFor=\"let s of sugestoes\" type=\"button\" class=\"uiny-sugestao\" [disabled]=\"!podeEnviar\" (click)=\"sugestao.emit(s)\">\r\n {{ s }}\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"capacidades?.length\" class=\"uiny-capacidades\">\r\n <p class=\"uiny-capacidades__titulo\">Posso ajudar com:</p>\r\n <ul>\r\n <li *ngFor=\"let c of capacidades\">{{ c }}</li>\r\n </ul>\r\n </div>\r\n </div>\r\n\r\n <div *ngFor=\"let m of mensagens; trackBy: trackMensagem\" class=\"uiny-msg\"\r\n [class.uiny-msg--usuario]=\"m.papel === 'usuario'\"\r\n [class.uiny-msg--assistente]=\"m.papel !== 'usuario'\"\r\n [class.uiny-msg--falha]=\"m.erro\">\r\n <span class=\"uiny-sr\">{{ m.papel === 'usuario' ? 'Voc\u00EA:' : 'Uiny:' }}</span>\r\n <div class=\"uiny-msg__balao\">\r\n <p *ngIf=\"m.papel === 'usuario'\" class=\"uiny-msg__simples\">{{ m.texto }}</p>\r\n <ui-assistant-text *ngIf=\"m.papel !== 'usuario'\" [texto]=\"m.texto\"></ui-assistant-text>\r\n </div>\r\n\r\n <ui-assistant-action-card *ngIf=\"acaoDa(m) as acao\" [acao]=\"acao\"\r\n (confirmar)=\"confirmar.emit($event)\" (cancelar)=\"cancelar.emit($event)\" (verificar)=\"verificar.emit($event)\">\r\n </ui-assistant-action-card>\r\n\r\n <!-- esclarecimento: op\u00E7\u00F5es s\u00F3 na \u00FAltima mensagem, enviadas como texto -->\r\n <div *ngIf=\"m.opcoes?.length && m === mensagens[mensagens.length - 1]\" class=\"uiny-sugestoes uiny-msg__opcoes\" aria-label=\"Op\u00E7\u00F5es de resposta\">\r\n <button *ngFor=\"let o of m.opcoes\" type=\"button\" class=\"uiny-sugestao\" [disabled]=\"!podeEnviar || enviando\" (click)=\"sugestao.emit(o)\">\r\n {{ o }}\r\n </button>\r\n </div>\r\n\r\n <div class=\"uiny-msg__meta\">\r\n <span *ngIf=\"m.erro\" class=\"uiny-msg__nao-enviada\">\r\n <ui-icon icon=\"error\" size=\"14\"></ui-icon> N\u00E3o enviada\r\n </span>\r\n <time *ngIf=\"m.criadaEm && !m.erro\" [attr.datetime]=\"m.criadaEm.toISOString()\">{{ m.criadaEm | date:'HH:mm' }}</time>\r\n </div>\r\n </div>\r\n\r\n <ui-assistant-action-card *ngFor=\"let acao of acoesSemMensagem; trackBy: trackAcao\" class=\"uiny-msgs__acao-solta\" [acao]=\"acao\"\r\n (confirmar)=\"confirmar.emit($event)\" (cancelar)=\"cancelar.emit($event)\" (verificar)=\"verificar.emit($event)\">\r\n </ui-assistant-action-card>\r\n\r\n <div *ngIf=\"enviando\" class=\"uiny-digitando\">\r\n <span class=\"uiny-digitando__pontos\" aria-hidden=\"true\"><i></i><i></i><i></i></span>\r\n <span class=\"uiny-digitando__texto\">A Uiny est\u00E1 pensando\u2026</span>\r\n </div>\r\n</div>\r\n\r\n<button *ngIf=\"novas\" type=\"button\" class=\"uiny-novas\" (click)=\"irParaOFim()\">\r\n <ui-icon icon=\"arrow_downward\" size=\"16\"></ui-icon> Novas mensagens\r\n</button>\r\n", styles: [":host{position:relative;display:flex;flex-direction:column;min-height:0}.uiny-msgs{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;padding:16px 14px 8px;scrollbar-width:thin}.uiny-msgs:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:-2px}.uiny-msgs__carregando{display:flex;align-items:center;gap:8px;color:var(--uiny-texto-suave, #6b6b80);font-size:13px}.uiny-spinner{width:14px;height:14px;border:2px solid var(--uiny-borda, #ececf3);border-top-color:var(--uiny-primaria, #7366ff);border-radius:50%;animation:uiny-girar .8s linear infinite}.uiny-boas-vindas{padding:8px 2px 4px}.uiny-boas-vindas__titulo{margin:0 0 4px;font-size:18px;font-weight:700;color:var(--uiny-texto, #2b2b2b)}.uiny-boas-vindas__texto{margin:0 0 12px;color:var(--uiny-texto-suave, #6b6b80);line-height:1.5}.uiny-boas-vindas__nota{display:flex;gap:6px;margin:0 0 14px;padding:8px 10px;border-radius:10px;background:var(--uiny-fundo-suave, #f7f7fb);color:var(--uiny-texto-suave, #6b6b80);font-size:12.5px;line-height:1.45}.uiny-boas-vindas__nota ui-icon{flex:0 0 auto;color:var(--uiny-primaria, #7366ff)}.uiny-sugestoes{display:flex;flex-direction:column;align-items:flex-start;gap:6px}.uiny-sugestao{max-width:100%;padding:8px 12px;border:1px solid var(--uiny-borda, #ececf3);border-radius:12px;background:var(--uiny-fundo, #fff);color:var(--uiny-texto, #2b2b2b);font-size:13px;text-align:left;line-height:1.35;cursor:pointer;transition:border-color .15s ease,background-color .15s ease}.uiny-sugestao:hover:not(:disabled){border-color:var(--uiny-primaria, #7366ff);background:var(--uiny-aviso-fundo, #f1effe)}.uiny-sugestao:disabled{opacity:.5;cursor:not-allowed}.uiny-sugestao:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-capacidades{font-size:13px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-capacidades ul{margin:0;padding-left:18px}.uiny-capacidades li+li{margin-top:2px}.uiny-capacidades__titulo{margin:0 0 4px;font-weight:600;color:var(--uiny-texto, #2b2b2b)}.uiny-msg{display:flex;flex-direction:column;margin-top:14px}.uiny-msg:first-of-type{margin-top:0}.uiny-msg--usuario{align-items:flex-end}.uiny-msg--usuario .uiny-msg__balao{max-width:85%;padding:9px 13px;border-radius:16px 16px 4px;background:var(--uiny-primaria, #7366ff);color:#fff}.uiny-msg--assistente{align-items:stretch}.uiny-msg--assistente .uiny-msg__balao{max-width:100%;color:var(--uiny-texto, #2b2b2b)}.uiny-msg--falha .uiny-msg__balao{opacity:.65}.uiny-msg__simples{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.45}.uiny-msg__meta{margin-top:3px;font-size:11px;color:var(--uiny-texto-suave, #6b6b80)}.uiny-msg__meta time{opacity:.8}.uiny-msg__nao-enviada{display:inline-flex;align-items:center;gap:3px;color:var(--uiny-erro, #d9364f)}.uiny-msg__opcoes{margin-top:8px}.uiny-msgs__acao-solta{display:block;margin-top:14px}.uiny-digitando{display:flex;align-items:center;gap:8px;margin-top:14px;color:var(--uiny-texto-suave, #6b6b80);font-size:13px}.uiny-digitando__pontos{display:inline-flex;gap:3px}.uiny-digitando__pontos i{width:6px;height:6px;border-radius:50%;background:var(--uiny-primaria, #7366ff);animation:uiny-pulsar 1.2s ease-in-out infinite}.uiny-digitando__pontos i:nth-child(2){animation-delay:.15s}.uiny-digitando__pontos i:nth-child(3){animation-delay:.3s}.uiny-novas{position:absolute;left:50%;bottom:10px;transform:translate(-50%);display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border:0;border-radius:999px;background:var(--uiny-primaria, #7366ff);color:#fff;font-size:12px;font-weight:600;box-shadow:0 6px 16px #1f1b4d33;cursor:pointer}.uiny-novas:focus-visible{outline:2px solid var(--uiny-primaria, #7366ff);outline-offset:2px}.uiny-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}@keyframes uiny-girar{to{transform:rotate(360deg)}}@keyframes uiny-pulsar{0%,80%,to{opacity:.25;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.uiny-digitando__pontos i{animation:none;opacity:.6}.uiny-spinner{animation-duration:2.4s}.uiny-sugestao{transition:none}}\n"] }]
1652
- }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }]; }, propDecorators: { mensagens: [{
1653
- type: Input
1654
- }], acoes: [{
1655
- type: Input
1656
- }], enviando: [{
1657
- type: Input
1658
- }], carregando: [{
1659
- type: Input
1660
- }], sugestoes: [{
1661
- type: Input
1662
- }], capacidades: [{
1663
- type: Input
1664
- }], temAlteracoes: [{
1665
- type: Input
1666
- }], podeEnviar: [{
1667
- type: Input
1668
- }], sugestao: [{
1669
- type: Output
1670
- }], confirmar: [{
1671
- type: Output
1672
- }], cancelar: [{
1673
- type: Output
1674
- }], verificar: [{
1675
- type: Output
1676
- }], area: [{
1677
- type: ViewChild,
1678
- args: ['area', { static: true }]
1679
- }] } });
1680
-
1681
- class UiAssistantPanelComponent {
1682
- constructor(chat, assistant, session, cdr, config) {
1683
- this.chat = chat;
1684
- this.assistant = assistant;
1685
- this.session = session;
1686
- this.cdr = cdr;
1687
- /** Painel em largura total (celular/tablet): o botão de fechar vira "Voltar". */
1688
- this.empilhado = false;
1689
- this.online = true;
1690
- this.anuncio = '';
1691
- this.vendoHistorico = false;
1692
- this.mensagens = [];
1693
- this.acoes = {};
1694
- this.sugestoes = [];
1695
- this.capacidades = [];
1696
- this.temAlteracoes = false;
1697
- /** A imagem não carregou: usa o ícone padrão. */
1698
- this.avatarFalhou = false;
1699
- this.subs = new Subscription();
1700
- this.avatarUrl = config?.avatarUrl || UINY_AVATAR_PADRAO;
1701
- }
1702
- ngOnInit() {
1703
- this.subs.add(this.chat.estado.subscribe(estado => {
1704
- this.estado = estado;
1705
- this.mensagens = estado.mensagens;
1706
- this.acoes = estado.acoes;
1707
- this.cdr.markForCheck();
1708
- }));
1709
- this.subs.add(this.session.online$.subscribe(online => {
1710
- this.online = online;
1711
- this.cdr.markForCheck();
1712
- }));
1713
- this.subs.add(this.chat.anuncios.subscribe(texto => this.anunciar(texto)));
1714
- this.montarBoasVindas();
1715
- }
1716
- ngAfterViewInit() {
1717
- // espera a transição começar para não rolar a página no focus
1718
- setTimeout(() => this.composer?.focar(), 60);
1719
- }
1720
- ngOnDestroy() {
1721
- clearTimeout(this.timerAnuncio);
1722
- this.subs.unsubscribe();
1723
- }
1724
- aoEscape(evento) {
1725
- if (evento.defaultPrevented)
1726
- return;
1727
- if (this.vendoHistorico) {
1728
- this.vendoHistorico = false;
1729
- return;
1730
- }
1731
- evento.preventDefault();
1732
- this.assistant.fechar();
1733
- }
1734
- get podeEnviar() {
1735
- return this.online && !this.estado?.enviando && !this.estado?.carregandoConversa;
1736
- }
1737
- get motivoBloqueio() {
1738
- if (!this.online)
1739
- return 'Sem conexão com a internet.';
1740
- if (this.estado?.carregandoConversa)
1741
- return 'Carregando a conversa…';
1742
- return null;
1743
- }
1744
- fechar() {
1745
- this.assistant.fechar();
1746
- }
1747
- enviar(texto) {
1748
- if (!this.podeEnviar)
1749
- return;
1750
- if (this.chat.enviar(texto)) {
1751
- this.composer?.limpar();
1752
- this.vendoHistorico = false;
1753
- }
1754
- }
1755
- reenviar() {
1756
- const texto = this.estado?.textoComFalha;
1757
- if (texto && this.podeEnviar)
1758
- this.chat.enviar(texto);
1759
- }
1760
- usarSugestao(texto) {
1761
- this.enviar(texto);
1762
- }
1763
- novaConversa() {
1764
- if (this.chat.novaConversa()) {
1765
- this.vendoHistorico = false;
1766
- this.anunciar('Nova conversa iniciada.');
1767
- setTimeout(() => this.composer?.focar());
1768
- }
1769
- }
1770
- alternarHistorico() {
1771
- this.vendoHistorico = !this.vendoHistorico;
1772
- if (this.vendoHistorico)
1773
- this.chat.listarConversas();
1774
- }
1775
- abrirConversa(id) {
1776
- this.vendoHistorico = false;
1777
- this.chat.abrirConversa(id);
1778
- setTimeout(() => this.composer?.focar());
1779
- }
1780
- trackConversa(_, c) {
1781
- return c.id;
1782
- }
1783
- montarBoasVindas() {
1784
- const sugestoes = UINY_SUGESTOES[this.assistant.sistemaOrigem] ?? { textos: [], alteracoes: false };
1785
- this.sugestoes = sugestoes.textos;
1786
- this.capacidades = [];
1787
- this.temAlteracoes = sugestoes.alteracoes;
1788
- }
1789
- anunciar(texto) {
1790
- // limpa antes para leitores de tela repetirem anúncios iguais
1791
- this.anuncio = '';
1792
- this.cdr.markForCheck();
1793
- clearTimeout(this.timerAnuncio);
1794
- if (!texto)
1795
- return;
1796
- this.timerAnuncio = setTimeout(() => {
1797
- this.anuncio = texto;
1798
- this.cdr.markForCheck();
1799
- }, 50);
1800
- }
1801
- }
1802
- UiAssistantPanelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantPanelComponent, deps: [{ token: UinyChatService }, { token: UiAssistantService }, { token: UinySessionService }, { token: i0.ChangeDetectorRef }, { token: UINY_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Component });
1803
- UiAssistantPanelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiAssistantPanelComponent, selector: "ui-assistant-panel", inputs: { empilhado: "empilhado" }, host: { listeners: { "keydown.escape": "aoEscape($event)" } }, viewQueries: [{ propertyName: "composer", first: true, predicate: UiAssistantComposerComponent, descendants: true }], ngImport: i0, template: "<section class=\"uiny\" aria-labelledby=\"uiny-titulo\">\r\n <header class=\"uiny__topo\">\r\n <div class=\"uiny__marca\">\r\n <img *ngIf=\"!avatarFalhou\" class=\"uiny__avatar uiny__avatar--imagem\" [src]=\"avatarUrl\" alt=\"\"\r\n width=\"34\" height=\"34\" (error)=\"avatarFalhou = true\">\r\n <span *ngIf=\"avatarFalhou\" class=\"uiny__avatar\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" focusable=\"false\">\r\n <path d=\"M12 2.5c.5 3.9 2.6 6.9 6.4 8.2.5.2.5.9 0 1.1-3.8 1.3-5.9 4.3-6.4 8.2-.1.6-.9.6-1 0-.5-3.9-2.6-6.9-6.4-8.2-.5-.2-.5-.9 0-1.1 3.8-1.3 5.9-4.3 6.4-8.2.1-.6.9-.6 1 0Z\" fill=\"currentColor\"/>\r\n </svg>\r\n </span>\r\n <div class=\"uiny__identidade\">\r\n <h2 id=\"uiny-titulo\" class=\"uiny__nome\">Uiny</h2>\r\n <p class=\"uiny__sub\">Assistente da Uishop</p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"uiny__comandos\">\r\n <button type=\"button\" class=\"uiny__icone\" [class.is-ativo]=\"vendoHistorico\"\r\n [attr.aria-pressed]=\"vendoHistorico ? 'true' : 'false'\"\r\n aria-label=\"Conversas anteriores\" title=\"Conversas anteriores\"\r\n (click)=\"alternarHistorico()\">\r\n <ui-icon icon=\"history\" size=\"20\"></ui-icon>\r\n </button>\r\n <button type=\"button\" class=\"uiny__icone\" aria-label=\"Nova conversa\" title=\"Nova conversa\"\r\n [disabled]=\"chat.ocupado\" (click)=\"novaConversa()\">\r\n <ui-icon icon=\"edit_square\" size=\"20\"></ui-icon>\r\n </button>\r\n <button type=\"button\" class=\"uiny__icone\" (click)=\"fechar()\"\r\n [attr.aria-label]=\"empilhado ? 'Voltar para a aplica\u00E7\u00E3o' : 'Fechar a Uiny'\"\r\n [title]=\"empilhado ? 'Voltar para a aplica\u00E7\u00E3o' : 'Fechar (Esc)'\">\r\n <ui-icon [icon]=\"empilhado ? 'arrow_back' : 'close'\" size=\"20\"></ui-icon>\r\n </button>\r\n </div>\r\n </header>\r\n\r\n <div *ngIf=\"!online\" class=\"uiny__faixa uiny__faixa--offline\" role=\"status\">\r\n <ui-icon icon=\"wifi_off\" size=\"18\"></ui-icon>\r\n <span>Sem conex\u00E3o com a internet. Voc\u00EA poder\u00E1 enviar mensagens quando ela voltar.</span>\r\n </div>\r\n\r\n <div *ngIf=\"estado?.aviso\" class=\"uiny__faixa uiny__faixa--info\" role=\"status\">\r\n <ui-icon icon=\"info\" size=\"18\"></ui-icon>\r\n <span>{{ estado.aviso }}</span>\r\n <button type=\"button\" class=\"uiny__faixa-fechar\" aria-label=\"Dispensar aviso\" (click)=\"chat.limparErro()\">\r\n <ui-icon icon=\"close\" size=\"16\"></ui-icon>\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"vendoHistorico; else conversaAtual\" class=\"uiny__historico\">\r\n <div class=\"uiny__historico-topo\">\r\n <h3 class=\"uiny__historico-titulo\">Conversas anteriores</h3>\r\n <button type=\"button\" class=\"uiny__link\" (click)=\"vendoHistorico = false\">Voltar \u00E0 conversa</button>\r\n </div>\r\n\r\n <div *ngIf=\"estado?.carregandoConversas\" class=\"uiny__vazio\" role=\"status\">\r\n <span class=\"uiny__spinner\" aria-hidden=\"true\"></span> Carregando conversas\u2026\r\n </div>\r\n\r\n <p *ngIf=\"!estado?.carregandoConversas && estado?.conversas?.length === 0\" class=\"uiny__vazio\">\r\n Nenhuma conversa anterior neste contexto.\r\n </p>\r\n\r\n <ul *ngIf=\"estado?.conversas?.length\" class=\"uiny__lista-conversas\">\r\n <li *ngFor=\"let c of estado.conversas; trackBy: trackConversa\">\r\n <button type=\"button\" class=\"uiny__conversa\" [class.is-atual]=\"c.id === estado.conversa?.id\"\r\n [attr.aria-current]=\"c.id === estado.conversa?.id ? 'true' : null\"\r\n [disabled]=\"chat.ocupado\" (click)=\"abrirConversa(c.id)\">\r\n <span class=\"uiny__conversa-titulo\">{{ c.titulo || 'Conversa sem t\u00EDtulo' }}</span>\r\n <span *ngIf=\"c.atualizadaEm\" class=\"uiny__conversa-data\">{{ c.atualizadaEm | date:'dd/MM/yyyy HH:mm' }}</span>\r\n </button>\r\n </li>\r\n </ul>\r\n </div>\r\n\r\n <ng-template #conversaAtual>\r\n <ui-assistant-messages class=\"uiny__mensagens\"\r\n [mensagens]=\"mensagens\"\r\n [acoes]=\"acoes\"\r\n [enviando]=\"estado?.enviando\"\r\n [carregando]=\"estado?.carregandoConversa\"\r\n [sugestoes]=\"sugestoes\"\r\n [capacidades]=\"capacidades\"\r\n [temAlteracoes]=\"temAlteracoes\"\r\n [podeEnviar]=\"podeEnviar\"\r\n (sugestao)=\"usarSugestao($event)\"\r\n (confirmar)=\"chat.confirmar($event)\"\r\n (cancelar)=\"chat.cancelar($event)\"\r\n (verificar)=\"chat.verificarAcao($event)\">\r\n </ui-assistant-messages>\r\n </ng-template>\r\n\r\n <div *ngIf=\"estado?.erro\" class=\"uiny__erro\" role=\"alert\">\r\n <ui-icon icon=\"error\" size=\"18\"></ui-icon>\r\n <div class=\"uiny__erro-texto\">\r\n <span>{{ estado.erro.mensagem }}</span>\r\n <small *ngIf=\"estado.erro.correlacao\">C\u00F3digo para o suporte: {{ estado.erro.correlacao }}</small>\r\n </div>\r\n <button *ngIf=\"estado.textoComFalha && podeEnviar\" type=\"button\" class=\"uiny__link\" (click)=\"reenviar()\">\r\n Tentar de novo\r\n </button>\r\n <button type=\"button\" class=\"uiny__faixa-fechar\" aria-label=\"Dispensar erro\" (click)=\"chat.limparErro()\">\r\n <ui-icon icon=\"close\" size=\"16\"></ui-icon>\r\n </button>\r\n </div>\r\n\r\n <ui-assistant-composer class=\"uiny__composer\"\r\n [desabilitado]=\"!podeEnviar\"\r\n [enviando]=\"estado?.enviando\"\r\n [motivo]=\"motivoBloqueio\"\r\n (enviar)=\"enviar($event)\">\r\n </ui-assistant-composer>\r\n\r\n <div class=\"uiny-sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ anuncio }}</div>\r\n</section>\r\n", styles: [":host{--uiny-primaria: var(--theme-default, #7366ff);--uiny-fundo: #ffffff;--uiny-fundo-suave: #f7f7fb;--uiny-borda: #ececf3;--uiny-texto: #2b2b2b;--uiny-texto-suave: #6b6b80;--uiny-erro: #d9364f;--uiny-erro-fundo: #fdeef1;--uiny-aviso-fundo: #f1effe;--uiny-offline-fundo: #fff4e5;--uiny-offline-texto: #8a5300;display:block;height:100%;color:var(--uiny-texto);font-size:14px}:host-context(.dark-only){--uiny-fundo: #262932;--uiny-fundo-suave: #1d1e26;--uiny-borda: #374558;--uiny-texto: rgba(255, 255, 255, .85);--uiny-texto-suave: #98a6ad;--uiny-erro-fundo: rgba(217, 54, 79, .16);--uiny-aviso-fundo: rgba(115, 102, 255, .16);--uiny-offline-fundo: rgba(255, 170, 0, .14);--uiny-offline-texto: #ffc76b}.uiny{display:flex;flex-direction:column;height:100%;background:var(--uiny-fundo);border-left:1px solid var(--uiny-borda);box-shadow:-4px 0 24px #1f1b4d0f}.uiny__topo{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 12px 14px 16px;border-bottom:1px solid var(--uiny-borda)}.uiny__marca{display:flex;align-items:center;gap:10px;min-width:0}.uiny__avatar{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:11px;color:#fff;background:linear-gradient(135deg,var(--uiny-primaria) 0%,#a26cf8 100%)}.uiny__avatar--imagem{object-fit:cover;background:var(--uiny-fundo-suave)}.uiny__identidade{min-width:0}.uiny__nome{margin:0;font-size:16px;font-weight:700;line-height:1.2;color:var(--uiny-texto)}.uiny__sub{margin:0;font-size:12px;line-height:1.3;color:var(--uiny-texto-suave)}.uiny__comandos{display:flex;align-items:center;gap:2px}.uiny__icone{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border:0;border-radius:9px;background:transparent;color:var(--uiny-texto-suave);cursor:pointer}.uiny__icone:hover:not(:disabled),.uiny__icone.is-ativo{background:var(--uiny-fundo-suave);color:var(--uiny-primaria)}.uiny__icone:disabled{opacity:.4;cursor:not-allowed}button:focus-visible{outline:2px solid var(--uiny-primaria);outline-offset:2px}.uiny__faixa,.uiny__erro{flex:0 0 auto;display:flex;align-items:flex-start;gap:8px;margin:10px 12px 0;padding:9px 10px;border-radius:10px;font-size:13px;line-height:1.4}.uiny__faixa>ui-icon,.uiny__erro>ui-icon{flex:0 0 auto;margin-top:1px}.uiny__faixa>span,.uiny__faixa .uiny__erro-texto,.uiny__erro>span,.uiny__erro .uiny__erro-texto{flex:1 1 auto;min-width:0}.uiny__faixa--offline{background:var(--uiny-offline-fundo);color:var(--uiny-offline-texto)}.uiny__faixa--info{background:var(--uiny-aviso-fundo);color:var(--uiny-texto)}.uiny__erro{margin:0 12px 8px;background:var(--uiny-erro-fundo);color:var(--uiny-erro);align-items:center}.uiny__erro .uiny__erro-texto{display:flex;flex-direction:column;gap:2px}.uiny__erro .uiny__erro-texto small{opacity:.8;font-size:11px}.uiny__faixa-fechar{flex:0 0 auto;display:inline-flex;padding:2px;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer;opacity:.7}.uiny__faixa-fechar:hover{opacity:1}.uiny__link{flex:0 0 auto;padding:0;border:0;background:none;color:var(--uiny-primaria);font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap}.uiny__link:hover{text-decoration:underline}.uiny__mensagens{flex:1 1 auto;min-height:0}.uiny__historico{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 12px}.uiny__historico-topo{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;padding:0 4px}.uiny__historico-titulo{margin:0;font-size:13px;font-weight:700;color:var(--uiny-texto)}.uiny__lista-conversas{margin:0;padding:0;list-style:none}.uiny__conversa{display:flex;flex-direction:column;gap:2px;width:100%;padding:10px 12px;border:1px solid transparent;border-radius:10px;background:transparent;color:var(--uiny-texto);text-align:left;cursor:pointer}.uiny__conversa:hover:not(:disabled){background:var(--uiny-fundo-suave)}.uiny__conversa.is-atual{border-color:var(--uiny-borda);background:var(--uiny-fundo-suave)}.uiny__conversa:disabled{opacity:.5;cursor:not-allowed}.uiny__conversa-titulo{overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.uiny__conversa-data{font-size:12px;color:var(--uiny-texto-suave)}.uiny__vazio{display:flex;align-items:center;gap:8px;margin:18px 4px;color:var(--uiny-texto-suave);font-size:13px}.uiny__spinner{width:14px;height:14px;border:2px solid var(--uiny-borda);border-top-color:var(--uiny-primaria);border-radius:50%;animation:uiny-girar .8s linear infinite}.uiny__composer{flex:0 0 auto}.uiny-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}@keyframes uiny-girar{to{transform:rotate(360deg)}}@media (prefers-reduced-motion: reduce){.uiny__spinner{animation-duration:2.4s}}\n"], dependencies: [{ kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }, { kind: "component", type: UiAssistantMessagesComponent, selector: "ui-assistant-messages", inputs: ["mensagens", "acoes", "enviando", "carregando", "sugestoes", "capacidades", "temAlteracoes", "podeEnviar"], outputs: ["sugestao", "confirmar", "cancelar", "verificar"] }, { kind: "component", type: UiAssistantComposerComponent, selector: "ui-assistant-composer", inputs: ["desabilitado", "enviando", "motivo"], outputs: ["enviar"] }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1804
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantPanelComponent, decorators: [{
1805
- type: Component,
1806
- args: [{ selector: 'ui-assistant-panel', changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"uiny\" aria-labelledby=\"uiny-titulo\">\r\n <header class=\"uiny__topo\">\r\n <div class=\"uiny__marca\">\r\n <img *ngIf=\"!avatarFalhou\" class=\"uiny__avatar uiny__avatar--imagem\" [src]=\"avatarUrl\" alt=\"\"\r\n width=\"34\" height=\"34\" (error)=\"avatarFalhou = true\">\r\n <span *ngIf=\"avatarFalhou\" class=\"uiny__avatar\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" focusable=\"false\">\r\n <path d=\"M12 2.5c.5 3.9 2.6 6.9 6.4 8.2.5.2.5.9 0 1.1-3.8 1.3-5.9 4.3-6.4 8.2-.1.6-.9.6-1 0-.5-3.9-2.6-6.9-6.4-8.2-.5-.2-.5-.9 0-1.1 3.8-1.3 5.9-4.3 6.4-8.2.1-.6.9-.6 1 0Z\" fill=\"currentColor\"/>\r\n </svg>\r\n </span>\r\n <div class=\"uiny__identidade\">\r\n <h2 id=\"uiny-titulo\" class=\"uiny__nome\">Uiny</h2>\r\n <p class=\"uiny__sub\">Assistente da Uishop</p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"uiny__comandos\">\r\n <button type=\"button\" class=\"uiny__icone\" [class.is-ativo]=\"vendoHistorico\"\r\n [attr.aria-pressed]=\"vendoHistorico ? 'true' : 'false'\"\r\n aria-label=\"Conversas anteriores\" title=\"Conversas anteriores\"\r\n (click)=\"alternarHistorico()\">\r\n <ui-icon icon=\"history\" size=\"20\"></ui-icon>\r\n </button>\r\n <button type=\"button\" class=\"uiny__icone\" aria-label=\"Nova conversa\" title=\"Nova conversa\"\r\n [disabled]=\"chat.ocupado\" (click)=\"novaConversa()\">\r\n <ui-icon icon=\"edit_square\" size=\"20\"></ui-icon>\r\n </button>\r\n <button type=\"button\" class=\"uiny__icone\" (click)=\"fechar()\"\r\n [attr.aria-label]=\"empilhado ? 'Voltar para a aplica\u00E7\u00E3o' : 'Fechar a Uiny'\"\r\n [title]=\"empilhado ? 'Voltar para a aplica\u00E7\u00E3o' : 'Fechar (Esc)'\">\r\n <ui-icon [icon]=\"empilhado ? 'arrow_back' : 'close'\" size=\"20\"></ui-icon>\r\n </button>\r\n </div>\r\n </header>\r\n\r\n <div *ngIf=\"!online\" class=\"uiny__faixa uiny__faixa--offline\" role=\"status\">\r\n <ui-icon icon=\"wifi_off\" size=\"18\"></ui-icon>\r\n <span>Sem conex\u00E3o com a internet. Voc\u00EA poder\u00E1 enviar mensagens quando ela voltar.</span>\r\n </div>\r\n\r\n <div *ngIf=\"estado?.aviso\" class=\"uiny__faixa uiny__faixa--info\" role=\"status\">\r\n <ui-icon icon=\"info\" size=\"18\"></ui-icon>\r\n <span>{{ estado.aviso }}</span>\r\n <button type=\"button\" class=\"uiny__faixa-fechar\" aria-label=\"Dispensar aviso\" (click)=\"chat.limparErro()\">\r\n <ui-icon icon=\"close\" size=\"16\"></ui-icon>\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"vendoHistorico; else conversaAtual\" class=\"uiny__historico\">\r\n <div class=\"uiny__historico-topo\">\r\n <h3 class=\"uiny__historico-titulo\">Conversas anteriores</h3>\r\n <button type=\"button\" class=\"uiny__link\" (click)=\"vendoHistorico = false\">Voltar \u00E0 conversa</button>\r\n </div>\r\n\r\n <div *ngIf=\"estado?.carregandoConversas\" class=\"uiny__vazio\" role=\"status\">\r\n <span class=\"uiny__spinner\" aria-hidden=\"true\"></span> Carregando conversas\u2026\r\n </div>\r\n\r\n <p *ngIf=\"!estado?.carregandoConversas && estado?.conversas?.length === 0\" class=\"uiny__vazio\">\r\n Nenhuma conversa anterior neste contexto.\r\n </p>\r\n\r\n <ul *ngIf=\"estado?.conversas?.length\" class=\"uiny__lista-conversas\">\r\n <li *ngFor=\"let c of estado.conversas; trackBy: trackConversa\">\r\n <button type=\"button\" class=\"uiny__conversa\" [class.is-atual]=\"c.id === estado.conversa?.id\"\r\n [attr.aria-current]=\"c.id === estado.conversa?.id ? 'true' : null\"\r\n [disabled]=\"chat.ocupado\" (click)=\"abrirConversa(c.id)\">\r\n <span class=\"uiny__conversa-titulo\">{{ c.titulo || 'Conversa sem t\u00EDtulo' }}</span>\r\n <span *ngIf=\"c.atualizadaEm\" class=\"uiny__conversa-data\">{{ c.atualizadaEm | date:'dd/MM/yyyy HH:mm' }}</span>\r\n </button>\r\n </li>\r\n </ul>\r\n </div>\r\n\r\n <ng-template #conversaAtual>\r\n <ui-assistant-messages class=\"uiny__mensagens\"\r\n [mensagens]=\"mensagens\"\r\n [acoes]=\"acoes\"\r\n [enviando]=\"estado?.enviando\"\r\n [carregando]=\"estado?.carregandoConversa\"\r\n [sugestoes]=\"sugestoes\"\r\n [capacidades]=\"capacidades\"\r\n [temAlteracoes]=\"temAlteracoes\"\r\n [podeEnviar]=\"podeEnviar\"\r\n (sugestao)=\"usarSugestao($event)\"\r\n (confirmar)=\"chat.confirmar($event)\"\r\n (cancelar)=\"chat.cancelar($event)\"\r\n (verificar)=\"chat.verificarAcao($event)\">\r\n </ui-assistant-messages>\r\n </ng-template>\r\n\r\n <div *ngIf=\"estado?.erro\" class=\"uiny__erro\" role=\"alert\">\r\n <ui-icon icon=\"error\" size=\"18\"></ui-icon>\r\n <div class=\"uiny__erro-texto\">\r\n <span>{{ estado.erro.mensagem }}</span>\r\n <small *ngIf=\"estado.erro.correlacao\">C\u00F3digo para o suporte: {{ estado.erro.correlacao }}</small>\r\n </div>\r\n <button *ngIf=\"estado.textoComFalha && podeEnviar\" type=\"button\" class=\"uiny__link\" (click)=\"reenviar()\">\r\n Tentar de novo\r\n </button>\r\n <button type=\"button\" class=\"uiny__faixa-fechar\" aria-label=\"Dispensar erro\" (click)=\"chat.limparErro()\">\r\n <ui-icon icon=\"close\" size=\"16\"></ui-icon>\r\n </button>\r\n </div>\r\n\r\n <ui-assistant-composer class=\"uiny__composer\"\r\n [desabilitado]=\"!podeEnviar\"\r\n [enviando]=\"estado?.enviando\"\r\n [motivo]=\"motivoBloqueio\"\r\n (enviar)=\"enviar($event)\">\r\n </ui-assistant-composer>\r\n\r\n <div class=\"uiny-sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ anuncio }}</div>\r\n</section>\r\n", styles: [":host{--uiny-primaria: var(--theme-default, #7366ff);--uiny-fundo: #ffffff;--uiny-fundo-suave: #f7f7fb;--uiny-borda: #ececf3;--uiny-texto: #2b2b2b;--uiny-texto-suave: #6b6b80;--uiny-erro: #d9364f;--uiny-erro-fundo: #fdeef1;--uiny-aviso-fundo: #f1effe;--uiny-offline-fundo: #fff4e5;--uiny-offline-texto: #8a5300;display:block;height:100%;color:var(--uiny-texto);font-size:14px}:host-context(.dark-only){--uiny-fundo: #262932;--uiny-fundo-suave: #1d1e26;--uiny-borda: #374558;--uiny-texto: rgba(255, 255, 255, .85);--uiny-texto-suave: #98a6ad;--uiny-erro-fundo: rgba(217, 54, 79, .16);--uiny-aviso-fundo: rgba(115, 102, 255, .16);--uiny-offline-fundo: rgba(255, 170, 0, .14);--uiny-offline-texto: #ffc76b}.uiny{display:flex;flex-direction:column;height:100%;background:var(--uiny-fundo);border-left:1px solid var(--uiny-borda);box-shadow:-4px 0 24px #1f1b4d0f}.uiny__topo{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:14px 12px 14px 16px;border-bottom:1px solid var(--uiny-borda)}.uiny__marca{display:flex;align-items:center;gap:10px;min-width:0}.uiny__avatar{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:11px;color:#fff;background:linear-gradient(135deg,var(--uiny-primaria) 0%,#a26cf8 100%)}.uiny__avatar--imagem{object-fit:cover;background:var(--uiny-fundo-suave)}.uiny__identidade{min-width:0}.uiny__nome{margin:0;font-size:16px;font-weight:700;line-height:1.2;color:var(--uiny-texto)}.uiny__sub{margin:0;font-size:12px;line-height:1.3;color:var(--uiny-texto-suave)}.uiny__comandos{display:flex;align-items:center;gap:2px}.uiny__icone{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;border:0;border-radius:9px;background:transparent;color:var(--uiny-texto-suave);cursor:pointer}.uiny__icone:hover:not(:disabled),.uiny__icone.is-ativo{background:var(--uiny-fundo-suave);color:var(--uiny-primaria)}.uiny__icone:disabled{opacity:.4;cursor:not-allowed}button:focus-visible{outline:2px solid var(--uiny-primaria);outline-offset:2px}.uiny__faixa,.uiny__erro{flex:0 0 auto;display:flex;align-items:flex-start;gap:8px;margin:10px 12px 0;padding:9px 10px;border-radius:10px;font-size:13px;line-height:1.4}.uiny__faixa>ui-icon,.uiny__erro>ui-icon{flex:0 0 auto;margin-top:1px}.uiny__faixa>span,.uiny__faixa .uiny__erro-texto,.uiny__erro>span,.uiny__erro .uiny__erro-texto{flex:1 1 auto;min-width:0}.uiny__faixa--offline{background:var(--uiny-offline-fundo);color:var(--uiny-offline-texto)}.uiny__faixa--info{background:var(--uiny-aviso-fundo);color:var(--uiny-texto)}.uiny__erro{margin:0 12px 8px;background:var(--uiny-erro-fundo);color:var(--uiny-erro);align-items:center}.uiny__erro .uiny__erro-texto{display:flex;flex-direction:column;gap:2px}.uiny__erro .uiny__erro-texto small{opacity:.8;font-size:11px}.uiny__faixa-fechar{flex:0 0 auto;display:inline-flex;padding:2px;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer;opacity:.7}.uiny__faixa-fechar:hover{opacity:1}.uiny__link{flex:0 0 auto;padding:0;border:0;background:none;color:var(--uiny-primaria);font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap}.uiny__link:hover{text-decoration:underline}.uiny__mensagens{flex:1 1 auto;min-height:0}.uiny__historico{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 12px}.uiny__historico-topo{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;padding:0 4px}.uiny__historico-titulo{margin:0;font-size:13px;font-weight:700;color:var(--uiny-texto)}.uiny__lista-conversas{margin:0;padding:0;list-style:none}.uiny__conversa{display:flex;flex-direction:column;gap:2px;width:100%;padding:10px 12px;border:1px solid transparent;border-radius:10px;background:transparent;color:var(--uiny-texto);text-align:left;cursor:pointer}.uiny__conversa:hover:not(:disabled){background:var(--uiny-fundo-suave)}.uiny__conversa.is-atual{border-color:var(--uiny-borda);background:var(--uiny-fundo-suave)}.uiny__conversa:disabled{opacity:.5;cursor:not-allowed}.uiny__conversa-titulo{overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.uiny__conversa-data{font-size:12px;color:var(--uiny-texto-suave)}.uiny__vazio{display:flex;align-items:center;gap:8px;margin:18px 4px;color:var(--uiny-texto-suave);font-size:13px}.uiny__spinner{width:14px;height:14px;border:2px solid var(--uiny-borda);border-top-color:var(--uiny-primaria);border-radius:50%;animation:uiny-girar .8s linear infinite}.uiny__composer{flex:0 0 auto}.uiny-sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}@keyframes uiny-girar{to{transform:rotate(360deg)}}@media (prefers-reduced-motion: reduce){.uiny__spinner{animation-duration:2.4s}}\n"] }]
1807
- }], ctorParameters: function () { return [{ type: UinyChatService }, { type: UiAssistantService }, { type: UinySessionService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
1808
- type: Optional
1809
- }, {
1810
- type: Inject,
1811
- args: [UINY_CONFIG]
1812
- }] }]; }, propDecorators: { empilhado: [{
1813
- type: Input
1814
- }], composer: [{
1815
- type: ViewChild,
1816
- args: [UiAssistantComposerComponent]
1817
- }], aoEscape: [{
1818
- type: HostListener,
1819
- args: ['keydown.escape', ['$event']]
1820
- }] } });
1821
-
1822
- const DURACAO_TRANSICAO_MS = 240;
1823
- /**
1824
- * Contêiner que hospeda a Uiny ao lado da aplicação.
1825
- *
1826
- * Envolve TODO o layout da aplicação (inclusive o header e a sidebar fixos):
1827
- *
1828
- * <ui-assistant-layout (assistantEvent)="...">
1829
- * <div class="page-wrapper">...</div>
1830
- * </ui-assistant-layout>
1831
- *
1832
- * Fechado, não altera nada: a página rola normalmente pela janela.
1833
- * Aberto no desktop, vira uma linha flex [aplicação | painel]: a coluna da aplicação passa
1834
- * a ser o bloco de contenção dos elementos position:fixed (contain: layout) e ganha rolagem
1835
- * própria. Assim o header e a sidebar fixos ocupam a coluna da esquerda, sem serem cobertos.
1836
- * Em telas estreitas o painel ocupa a largura toda e a aplicação fica recolhida até voltar.
1837
- */
1838
- class UiAssistantLayoutComponent {
1839
- constructor(assistant, cdr, zone, config, router) {
1840
- this.assistant = assistant;
1841
- this.cdr = cdr;
1842
- this.zone = zone;
1843
- this.router = router;
1844
- /** Ações concluídas e instruções de interface (tema atualizado, atualizar listagem, abrir prévia). */
1845
- this.assistantEvent = new EventEmitter();
1846
- /** Painel montado e layout em modo contido (inclui a animação de fechamento). */
1847
- this.contido = false;
1848
- /** Painel com a largura final (classe que dispara a transição). */
1849
- this.expandido = false;
1850
- /** Tela estreita: painel em largura total, aplicação recolhida. */
1851
- this.empilhado = false;
1852
- this.disponivel = false;
1853
- this.subs = new Subscription();
1854
- this.midia = null;
1855
- this.scrollJanela = 0;
1856
- this.largura = larguraPainel(config);
1857
- const ladoALado = config?.larguraMinimaLadoALado || UINY_LADO_A_LADO_PADRAO;
1858
- if (typeof window !== 'undefined' && window.matchMedia) {
1859
- this.midia = window.matchMedia(`(max-width: ${ladoALado - 0.02}px)`);
1860
- this.empilhado = this.midia.matches;
1861
- }
1862
- }
1863
- ngOnInit() {
1864
- this.removerHost = this.assistant.registrarHost();
1865
- this.subs.add(this.assistant.disponivel$.subscribe(d => {
1866
- this.disponivel = d;
1867
- this.cdr.markForCheck();
1868
- }));
1869
- this.subs.add(this.assistant.aberto$.subscribe(aberto => aberto ? this.abrir() : this.fechar()));
1870
- this.subs.add(this.assistant.eventos$.subscribe(e => this.assistantEvent.emit(e)));
1871
- // Com o layout contido a janela não rola: replica o scroll ao topo da navegação.
1872
- if (this.router) {
1873
- this.subs.add(this.router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe(() => {
1874
- if (this.contido && this.rolagem)
1875
- this.rolagem.nativeElement.scrollTop = 0;
1876
- }));
1877
- }
1878
- if (this.midia) {
1879
- const aoMudar = (e) => this.zone.run(() => {
1880
- this.empilhado = e.matches;
1881
- this.cdr.markForCheck();
1882
- });
1883
- this.midia.addEventListener ? this.midia.addEventListener('change', aoMudar) : this.midia.addListener(aoMudar);
1884
- this.subs.add(() => this.midia.removeEventListener
1885
- ? this.midia.removeEventListener('change', aoMudar)
1886
- : this.midia.removeListener(aoMudar));
1887
- }
1888
- }
1889
- ngOnDestroy() {
1890
- clearTimeout(this.timerFechamento);
1891
- this.subs.unsubscribe();
1892
- this.removerHost?.();
1893
- if (this.contido)
1894
- this.restaurarJanela();
1895
- }
1896
- abrir() {
1897
- clearTimeout(this.timerFechamento);
1898
- if (!this.contido) {
1899
- this.scrollJanela = window.scrollY || 0;
1900
- this.contido = true;
1901
- this.cdr.detectChanges();
1902
- // a rolagem passa da janela para a coluna da aplicação, na mesma posição
1903
- this.rolagem.nativeElement.scrollTop = this.scrollJanela;
1904
- }
1905
- // próximo frame: a largura sai de 0 e a transição acontece
1906
- requestAnimationFrame(() => {
1907
- this.expandido = true;
1908
- this.cdr.detectChanges();
1909
- });
1910
- }
1911
- fechar() {
1912
- if (!this.contido)
1913
- return;
1914
- this.expandido = false;
1915
- this.cdr.detectChanges();
1916
- clearTimeout(this.timerFechamento);
1917
- this.timerFechamento = setTimeout(() => {
1918
- this.restaurarJanela();
1919
- this.cdr.detectChanges();
1920
- }, this.semAnimacao() ? 0 : DURACAO_TRANSICAO_MS);
1921
- }
1922
- restaurarJanela() {
1923
- const topo = this.empilhado ? this.scrollJanela : (this.rolagem?.nativeElement.scrollTop ?? 0);
1924
- this.contido = false;
1925
- this.cdr.detectChanges();
1926
- window.scrollTo(0, topo);
1927
- }
1928
- semAnimacao() {
1929
- return typeof window !== 'undefined' && !!window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1930
- }
1931
- }
1932
- UiAssistantLayoutComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantLayoutComponent, deps: [{ token: UiAssistantService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: UINY_CONFIG, optional: true }, { token: i2$1.Router, optional: true }], target: i0.ɵɵFactoryTarget.Component });
1933
- UiAssistantLayoutComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: UiAssistantLayoutComponent, selector: "ui-assistant-layout", outputs: { assistantEvent: "assistantEvent" }, viewQueries: [{ propertyName: "rolagem", first: true, predicate: ["rolagem"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"uiny-layout\"\n [class.uiny-layout--contido]=\"contido\"\n [class.uiny-layout--expandido]=\"expandido\"\n [class.uiny-layout--empilhado]=\"empilhado\"\n [style.--uiny-largura]=\"largura + 'px'\">\n\n <div class=\"uiny-layout__app\"\n [attr.inert]=\"contido && expandido && empilhado ? '' : null\"\n [attr.aria-hidden]=\"contido && expandido && empilhado ? 'true' : null\">\n <div #rolagem class=\"uiny-layout__rolagem\">\n <ng-content></ng-content>\n </div>\n </div>\n\n <aside *ngIf=\"disponivel && contido\" id=\"uiny-painel\" class=\"uiny-layout__painel\"\n aria-label=\"Uiny, assistente da Uishop\"\n [attr.aria-hidden]=\"expandido ? null : 'true'\"\n [attr.inert]=\"expandido ? null : ''\">\n <div class=\"uiny-layout__painel-conteudo\">\n <ui-assistant-panel [empilhado]=\"empilhado\"></ui-assistant-panel>\n </div>\n </aside>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.uiny-layout,.uiny-layout__app,.uiny-layout__rolagem{display:block}.uiny-layout--contido{display:flex;width:100%;height:100vh;height:100dvh;overflow:hidden}.uiny-layout--contido .uiny-layout__app{flex:1 1 0;min-width:0;height:100%;position:relative;contain:layout paint;overflow:hidden}.uiny-layout--contido .uiny-layout__rolagem{height:100%;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}.uiny-layout__painel{flex:0 0 auto;width:0;height:100%;overflow:hidden;position:relative;z-index:2;transition:width .24s cubic-bezier(.2,.8,.2,1)}.uiny-layout__painel-conteudo{width:var(--uiny-largura, 380px);height:100%}.uiny-layout--expandido .uiny-layout__painel{width:var(--uiny-largura, 380px)}.uiny-layout--empilhado .uiny-layout__painel,.uiny-layout--empilhado .uiny-layout__painel-conteudo{width:100%}.uiny-layout--empilhado .uiny-layout__painel{transition:opacity .18s ease;opacity:0}.uiny-layout--empilhado.uiny-layout--expandido .uiny-layout__painel{width:100%;opacity:1}.uiny-layout--empilhado.uiny-layout--expandido .uiny-layout__app{display:none}@media (prefers-reduced-motion: reduce){.uiny-layout__painel,.uiny-layout--empilhado .uiny-layout__painel{transition:none}}@media print{.uiny-layout__painel{display:none}}\n"], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: UiAssistantPanelComponent, selector: "ui-assistant-panel", inputs: ["empilhado"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1934
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantLayoutComponent, decorators: [{
1935
- type: Component,
1936
- args: [{ selector: 'ui-assistant-layout', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"uiny-layout\"\n [class.uiny-layout--contido]=\"contido\"\n [class.uiny-layout--expandido]=\"expandido\"\n [class.uiny-layout--empilhado]=\"empilhado\"\n [style.--uiny-largura]=\"largura + 'px'\">\n\n <div class=\"uiny-layout__app\"\n [attr.inert]=\"contido && expandido && empilhado ? '' : null\"\n [attr.aria-hidden]=\"contido && expandido && empilhado ? 'true' : null\">\n <div #rolagem class=\"uiny-layout__rolagem\">\n <ng-content></ng-content>\n </div>\n </div>\n\n <aside *ngIf=\"disponivel && contido\" id=\"uiny-painel\" class=\"uiny-layout__painel\"\n aria-label=\"Uiny, assistente da Uishop\"\n [attr.aria-hidden]=\"expandido ? null : 'true'\"\n [attr.inert]=\"expandido ? null : ''\">\n <div class=\"uiny-layout__painel-conteudo\">\n <ui-assistant-panel [empilhado]=\"empilhado\"></ui-assistant-panel>\n </div>\n </aside>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.uiny-layout,.uiny-layout__app,.uiny-layout__rolagem{display:block}.uiny-layout--contido{display:flex;width:100%;height:100vh;height:100dvh;overflow:hidden}.uiny-layout--contido .uiny-layout__app{flex:1 1 0;min-width:0;height:100%;position:relative;contain:layout paint;overflow:hidden}.uiny-layout--contido .uiny-layout__rolagem{height:100%;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}.uiny-layout__painel{flex:0 0 auto;width:0;height:100%;overflow:hidden;position:relative;z-index:2;transition:width .24s cubic-bezier(.2,.8,.2,1)}.uiny-layout__painel-conteudo{width:var(--uiny-largura, 380px);height:100%}.uiny-layout--expandido .uiny-layout__painel{width:var(--uiny-largura, 380px)}.uiny-layout--empilhado .uiny-layout__painel,.uiny-layout--empilhado .uiny-layout__painel-conteudo{width:100%}.uiny-layout--empilhado .uiny-layout__painel{transition:opacity .18s ease;opacity:0}.uiny-layout--empilhado.uiny-layout--expandido .uiny-layout__painel{width:100%;opacity:1}.uiny-layout--empilhado.uiny-layout--expandido .uiny-layout__app{display:none}@media (prefers-reduced-motion: reduce){.uiny-layout__painel,.uiny-layout--empilhado .uiny-layout__painel{transition:none}}@media print{.uiny-layout__painel{display:none}}\n"] }]
1937
- }], ctorParameters: function () { return [{ type: UiAssistantService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: undefined, decorators: [{
1938
- type: Optional
1939
- }, {
1940
- type: Inject,
1941
- args: [UINY_CONFIG]
1942
- }] }, { type: i2$1.Router, decorators: [{
1943
- type: Optional
1944
- }] }]; }, propDecorators: { assistantEvent: [{
1945
- type: Output
1946
- }], rolagem: [{
1947
- type: ViewChild,
1948
- args: ['rolagem', { static: true }]
1949
- }] } });
1950
-
1951
- const COMPONENTES = [
1952
- UiAssistantButtonComponent,
1953
- UiAssistantLayoutComponent,
1954
- UiAssistantPanelComponent,
1955
- UiAssistantMessagesComponent,
1956
- UiAssistantTextComponent,
1957
- UiAssistantComposerComponent,
1958
- UiAssistantActionCardComponent,
1959
- ];
1960
- class UiAssistantModule {
1961
- /** Importe uma vez no AppModule do consumidor. */
1962
- static forRoot(config) {
1963
- return {
1964
- ngModule: UiAssistantModule,
1965
- providers: [{ provide: UINY_CONFIG, useValue: config }]
1966
- };
1967
- }
1968
- }
1969
- UiAssistantModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1970
- UiAssistantModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantModule, declarations: [UiAssistantButtonComponent,
1971
- UiAssistantLayoutComponent,
1972
- UiAssistantPanelComponent,
1973
- UiAssistantMessagesComponent,
1974
- UiAssistantTextComponent,
1975
- UiAssistantComposerComponent,
1976
- UiAssistantActionCardComponent], imports: [CommonModule,
1977
- UiIconModule], exports: [UiAssistantButtonComponent,
1978
- UiAssistantLayoutComponent,
1979
- UiAssistantPanelComponent,
1980
- UiAssistantMessagesComponent,
1981
- UiAssistantTextComponent,
1982
- UiAssistantComposerComponent,
1983
- UiAssistantActionCardComponent] });
1984
- UiAssistantModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantModule, imports: [CommonModule,
1985
- UiIconModule] });
1986
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantModule, decorators: [{
1987
- type: NgModule,
1988
- args: [{
1989
- declarations: COMPONENTES,
1990
- imports: [
1991
- CommonModule,
1992
- UiIconModule
1993
- ],
1994
- exports: COMPONENTES
1995
- }]
1996
- }] });
1997
-
1998
267
  class UiHeaderModule {
1999
268
  }
2000
269
  UiHeaderModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiHeaderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -2003,15 +272,12 @@ UiHeaderModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version:
2003
272
  ReactiveFormsModule,
2004
273
  UiIconModule,
2005
274
  UiUserTopModule,
2006
- TooltipModule,
2007
- UiAssistantModule], exports: [UiHeaderComponent,
2008
- UiAssistantModule] });
275
+ TooltipModule], exports: [UiHeaderComponent] });
2009
276
  UiHeaderModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiHeaderModule, imports: [CommonModule,
2010
277
  ReactiveFormsModule,
2011
278
  UiIconModule,
2012
279
  UiUserTopModule,
2013
- TooltipModule,
2014
- UiAssistantModule, UiAssistantModule] });
280
+ TooltipModule] });
2015
281
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiHeaderModule, decorators: [{
2016
282
  type: NgModule,
2017
283
  args: [{
@@ -2024,27 +290,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
2024
290
  ReactiveFormsModule,
2025
291
  UiIconModule,
2026
292
  UiUserTopModule,
2027
- TooltipModule,
2028
- UiAssistantModule
293
+ TooltipModule
2029
294
  ],
2030
295
  exports: [
2031
296
  UiHeaderComponent,
2032
- UiAssistantModule,
2033
297
  ]
2034
298
  }]
2035
299
  }] });
2036
300
 
2037
- /*
2038
- * Contrato HTTP da API da Uiny (projeto uishop-ia, prefixo /api/v1).
2039
- *
2040
- * Fonte: C:\git\uishop\uishop-ia
2041
- * - Rotas: config/web.php; README.md (seção Endpoints).
2042
- * - Formatos: services/uiny/ApresentadorConversa.php, ContextoTela.php, ConfirmacaoService.php.
2043
- * - Valores: config/Constants.php; erros: services/excecoes/ErroUiny.php.
2044
- *
2045
- * Os ids de conversa, mensagem e ação são UUIDs (string).
2046
- */
2047
-
2048
301
  /*
2049
302
  * Public API Surface of ui-header
2050
303
  */
@@ -2053,5 +306,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
2053
306
  * Generated bundle index. Do not edit.
2054
307
  */
2055
308
 
2056
- export { UINY_AVATAR_PADRAO, UINY_CONFIG, UINY_LADO_A_LADO_PADRAO, UINY_LARGURA_MAX, UINY_LARGURA_MIN, UINY_LARGURA_PADRAO, UINY_MENSAGEM_MAX_CARACTERES, UINY_SUGESTOES, UINY_TIMEOUT_ACAO_MS, UINY_TIMEOUT_CONSULTA_MS, UINY_TIMEOUT_MENSAGEM_MS, UiAssistantActionCardComponent, UiAssistantButtonComponent, UiAssistantComposerComponent, UiAssistantLayoutComponent, UiAssistantMessagesComponent, UiAssistantModule, UiAssistantPanelComponent, UiAssistantService, UiAssistantTextComponent, UiHeaderComponent, UiHeaderModule, UiHeaderService, UinyApiService, UinyChatService, UinySessionService, larguraPainel, resolverUrlUiny };
309
+ export { UiHeaderComponent, UiHeaderModule, UiHeaderService };
2057
310
  //# sourceMappingURL=uitec-ng-header.mjs.map