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.
- package/README.md +11 -182
- package/esm2020/lib/ui-header.component.mjs +20 -26
- package/esm2020/lib/ui-header.module.mjs +4 -10
- package/esm2020/public-api.mjs +1 -17
- package/fesm2015/uitec-ng-header.mjs +24 -1808
- package/fesm2015/uitec-ng-header.mjs.map +1 -1
- package/fesm2020/uitec-ng-header.mjs +24 -1771
- package/fesm2020/uitec-ng-header.mjs.map +1 -1
- package/lib/ui-header.component.d.ts +8 -11
- package/lib/ui-header.module.d.ts +1 -2
- package/package.json +2 -3
- package/public-api.d.ts +0 -15
- package/esm2020/lib/assistant/components/assistant-action-card/ui-assistant-action-card.component.mjs +0 -102
- package/esm2020/lib/assistant/components/assistant-button/ui-assistant-button.component.mjs +0 -18
- package/esm2020/lib/assistant/components/assistant-composer/ui-assistant-composer.component.mjs +0 -79
- package/esm2020/lib/assistant/components/assistant-layout/ui-assistant-layout.component.mjs +0 -140
- package/esm2020/lib/assistant/components/assistant-messages/ui-assistant-messages.component.mjs +0 -144
- package/esm2020/lib/assistant/components/assistant-panel/ui-assistant-panel.component.mjs +0 -156
- package/esm2020/lib/assistant/components/assistant-text/ui-assistant-text.component.mjs +0 -26
- package/esm2020/lib/assistant/models/uiny-api.models.mjs +0 -12
- package/esm2020/lib/assistant/models/uiny.models.mjs +0 -2
- package/esm2020/lib/assistant/services/ui-assistant.service.mjs +0 -137
- package/esm2020/lib/assistant/services/uiny-api.service.mjs +0 -149
- package/esm2020/lib/assistant/services/uiny-chat.service.mjs +0 -368
- package/esm2020/lib/assistant/services/uiny-session.service.mjs +0 -74
- package/esm2020/lib/assistant/ui-assistant.module.mjs +0 -59
- package/esm2020/lib/assistant/uiny.config.mjs +0 -53
- package/esm2020/lib/assistant/utils/uiny-api.adapter.mjs +0 -154
- package/esm2020/lib/assistant/utils/uiny-contexto.mjs +0 -43
- package/esm2020/lib/assistant/utils/uiny-markdown.mjs +0 -122
- package/lib/assistant/components/assistant-action-card/ui-assistant-action-card.component.d.ts +0 -33
- package/lib/assistant/components/assistant-button/ui-assistant-button.component.d.ts +0 -9
- package/lib/assistant/components/assistant-composer/ui-assistant-composer.component.d.ts +0 -24
- package/lib/assistant/components/assistant-layout/ui-assistant-layout.component.d.ts +0 -51
- package/lib/assistant/components/assistant-messages/ui-assistant-messages.component.d.ts +0 -38
- package/lib/assistant/components/assistant-panel/ui-assistant-panel.component.d.ts +0 -53
- package/lib/assistant/components/assistant-text/ui-assistant-text.component.d.ts +0 -14
- package/lib/assistant/models/uiny-api.models.d.ts +0 -109
- package/lib/assistant/models/uiny.models.d.ts +0 -104
- package/lib/assistant/services/ui-assistant.service.d.ts +0 -55
- package/lib/assistant/services/uiny-api.service.d.ts +0 -38
- package/lib/assistant/services/uiny-chat.service.d.ts +0 -82
- package/lib/assistant/services/uiny-session.service.d.ts +0 -38
- package/lib/assistant/ui-assistant.module.d.ts +0 -19
- package/lib/assistant/uiny.config.d.ts +0 -31
- package/lib/assistant/utils/uiny-api.adapter.d.ts +0 -22
- package/lib/assistant/utils/uiny-contexto.d.ts +0 -7
- 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,
|
|
3
|
-
import * as
|
|
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
|
|
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
|
|
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:
|
|
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,
|
|
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:
|
|
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",
|
|
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)=\"
|
|
832
|
-
}], ctorParameters: function () { return [{ type: i1.AuthService }, { type: i1.BaseUrlService }, { type: i1.LayoutService }, { type: i1.IframeService }, { type:
|
|
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 {
|
|
309
|
+
export { UiHeaderComponent, UiHeaderModule, UiHeaderService };
|
|
2057
310
|
//# sourceMappingURL=uitec-ng-header.mjs.map
|