uitec-ng-assistant 1.0.1 → 1.0.3
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 +213 -207
- package/esm2020/lib/components/assistant-action-card/ui-assistant-action-card.component.mjs +2 -2
- package/esm2020/lib/components/assistant-button/ui-assistant-button.component.mjs +2 -2
- package/esm2020/lib/components/assistant-messages/ui-assistant-messages.component.mjs +2 -2
- package/esm2020/lib/components/assistant-panel/ui-assistant-panel.component.mjs +7 -7
- package/esm2020/lib/components/assistant-text/ui-assistant-text.component.mjs +2 -2
- package/esm2020/lib/models/uiny.models.mjs +1 -1
- package/esm2020/lib/services/ui-assistant.service.mjs +4 -1
- package/esm2020/lib/uiny.config.mjs +1 -24
- package/fesm2015/uitec-ng-assistant.mjs +19 -38
- package/fesm2015/uitec-ng-assistant.mjs.map +1 -1
- package/fesm2020/uitec-ng-assistant.mjs +18 -38
- package/fesm2020/uitec-ng-assistant.mjs.map +1 -1
- package/lib/models/uiny.models.d.ts +11 -0
- package/lib/services/ui-assistant.service.d.ts +1 -0
- package/lib/uiny.config.d.ts +0 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,207 +1,213 @@
|
|
|
1
|
-
# uitec-ng-assistant
|
|
2
|
-
|
|
3
|
-
**Uiny**, assistente virtual da Uishop.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
ng build ui-assistant # gera dist/ui-assistant
|
|
7
|
-
ng test ui-assistant --watch=false --browsers=ChromeHeadless --karma-config=karma.conf.js
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
A lib não depende do `uitec-ng-header`. São duas peças:
|
|
11
|
-
|
|
12
|
-
- `<ui-assistant-button>`: o botão que abre e fecha a Uiny. Pode ficar em qualquer lugar (no header, na sidebar, flutuando).
|
|
13
|
-
- `<ui-assistant-layout>`: envolve a aplicação e abre o painel **à direita, ocupando espaço real**. A aplicação (header e sidebar fixos inclusive) passa a usar a largura restante à esquerda, sem ficar coberta.
|
|
14
|
-
|
|
15
|
-
A Uiny só fica disponível (e o botão só aparece) quando **todas** as condições valem:
|
|
16
|
-
|
|
17
|
-
1. existe um `<ui-assistant-layout>` envolvendo a aplicação;
|
|
18
|
-
2. o `AuthService` do `uitec-ng-core` tem usuário com token e empresa atual;
|
|
19
|
-
3. a URL da API está configurada.
|
|
20
|
-
|
|
21
|
-
> Até a `uitec-ng-header@1.1.0` a Uiny ficava dentro do header (`[showAssistant]`). A partir da `1.1.1` o header não tem mais nada da Uiny: importe desta lib e coloque o botão no slot `#right` (seção 3).
|
|
22
|
-
|
|
23
|
-
### 1. Configurar a URL da API e o sistema
|
|
24
|
-
|
|
25
|
-
`AppModule`:
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
import { UiAssistantModule } from 'uitec-ng-assistant';
|
|
29
|
-
|
|
30
|
-
@NgModule({
|
|
31
|
-
imports: [
|
|
32
|
-
UiAssistantModule.forRoot({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
<app-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
###
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
1
|
+
# uitec-ng-assistant
|
|
2
|
+
|
|
3
|
+
**Uiny**, assistente virtual da Uishop.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
ng build ui-assistant # gera dist/ui-assistant
|
|
7
|
+
ng test ui-assistant --watch=false --browsers=ChromeHeadless --karma-config=karma.conf.js
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
A lib não depende do `uitec-ng-header`. São duas peças:
|
|
11
|
+
|
|
12
|
+
- `<ui-assistant-button>`: o botão que abre e fecha a Uiny. Pode ficar em qualquer lugar (no header, na sidebar, flutuando).
|
|
13
|
+
- `<ui-assistant-layout>`: envolve a aplicação e abre o painel **à direita, ocupando espaço real**. A aplicação (header e sidebar fixos inclusive) passa a usar a largura restante à esquerda, sem ficar coberta.
|
|
14
|
+
|
|
15
|
+
A Uiny só fica disponível (e o botão só aparece) quando **todas** as condições valem:
|
|
16
|
+
|
|
17
|
+
1. existe um `<ui-assistant-layout>` envolvendo a aplicação;
|
|
18
|
+
2. o `AuthService` do `uitec-ng-core` tem usuário com token e empresa atual;
|
|
19
|
+
3. a URL da API está configurada.
|
|
20
|
+
|
|
21
|
+
> Até a `uitec-ng-header@1.1.0` a Uiny ficava dentro do header (`[showAssistant]`). A partir da `1.1.1` o header não tem mais nada da Uiny: importe desta lib e coloque o botão no slot `#right` (seção 3).
|
|
22
|
+
|
|
23
|
+
### 1. Configurar a URL da API e o sistema
|
|
24
|
+
|
|
25
|
+
`AppModule`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { UiAssistantModule } from 'uitec-ng-assistant';
|
|
29
|
+
|
|
30
|
+
@NgModule({
|
|
31
|
+
imports: [
|
|
32
|
+
UiAssistantModule.forRoot({
|
|
33
|
+
sistemaOrigem: 'studio', // 'erp' no ERP
|
|
34
|
+
sugestoes: {
|
|
35
|
+
textos: ['Deixe as cores do meu site mais modernas'],
|
|
36
|
+
alteracoes: true,
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Nos módulos que declaram o layout e o header (ex.: `SharedModule`), importe `UiAssistantModule` sem `forRoot`:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
imports: [UiHeaderModule, UiAssistantModule]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A URL vem de `environment.domain.uiny`. Inclua o `/api/v1`:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// environment.ts
|
|
53
|
+
domain: {
|
|
54
|
+
api: 'http://localhost:8000/api',
|
|
55
|
+
uiny: 'http://localhost:8100/api/v1', // uishop-ia
|
|
56
|
+
}
|
|
57
|
+
// environment.prod.ts
|
|
58
|
+
domain: { uiny: 'https://<host-da-uiny>/api/v1' }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`forRoot({ apiUrl })` sobrescreve o environment se precisar. Opções: `larguraPainel` (340–400, padrão 380) e `larguraMinimaLadoALado` (padrão 1200).
|
|
62
|
+
|
|
63
|
+
**Avatar:** como as outras imagens das libs (logo, coroa), o arquivo fica nos `assets` de cada aplicação. Coloque em `src/assets/images/uiny/avatar.svg` no Studio, no ERP e na demo, ou informe outro caminho em `forRoot({ avatarUrl })`. Se a imagem não carregar, o painel mostra o ícone padrão.
|
|
64
|
+
|
|
65
|
+
A origem do Angular precisa estar em `CORS_ORIGINS` do uishop-ia.
|
|
66
|
+
|
|
67
|
+
### 2. Autenticação
|
|
68
|
+
|
|
69
|
+
Nada novo para configurar. A Uiny lê o token de `AuthService.currentUserValue.token`, a mesma fonte do `JwtInterceptor`, e envia `Authorization: Bearer` **somente** para a URL da Uiny.
|
|
70
|
+
|
|
71
|
+
As chamadas da Uiny usam um `HttpClient` próprio, sem os interceptors da aplicação. Motivo: o `ErrorInterceptor` troca o erro por uma string e perde o status. Sem o status não dá para distinguir sessão expirada, ação expirada e conflito. Além disso, ele mostraria toasts globais, e o `SpinnerInterceptor` cobriria a tela a cada mensagem.
|
|
72
|
+
|
|
73
|
+
A Uiny fecha o painel, cancela as requisições e descarta conversa, ações e catálogo em memória quando:
|
|
74
|
+
|
|
75
|
+
- acontece logout (`currentUser` vira `null`);
|
|
76
|
+
- muda o usuário, a empresa atual ou o token (inclusive no re-login do mesmo usuário);
|
|
77
|
+
- a API responde 401 (a Uiny fica oculta até a sessão mudar).
|
|
78
|
+
|
|
79
|
+
Nada vai para `localStorage`/`sessionStorage`. Sem internet (`navigator.onLine`), o painel mostra o aviso e bloqueia o envio.
|
|
80
|
+
|
|
81
|
+
### 3. Integrar ao layout (Studio e ERP)
|
|
82
|
+
|
|
83
|
+
**O botão sozinho não desloca a aplicação.** Envolva o conteúdo do `content.component.html`:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<ui-assistant-layout (assistantEvent)="aoEventoUiny($event)">
|
|
87
|
+
<div [ngClass]="layout.config.settings.layout_type" [class]="layout.config.settings.layout_version">
|
|
88
|
+
<div class="page-wrapper" id="canvas-bookmark" [ngClass]="layoutClass">
|
|
89
|
+
<app-header *ngIf="userObs | async"></app-header>
|
|
90
|
+
...
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
<ui-suporte></ui-suporte> <!-- ERP: dentro do layout, para acompanhar a coluna -->
|
|
94
|
+
</ui-assistant-layout>
|
|
95
|
+
<app-spinner *ngIf="spinnerService.spinnerObservable | async"></app-spinner>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
E coloque o botão onde quiser. No header, use o slot `#right` (ele é renderizado dentro da lista de ícones, por isso o `<li>`):
|
|
99
|
+
|
|
100
|
+
```html
|
|
101
|
+
<ui-header ...>
|
|
102
|
+
<ng-template #right>
|
|
103
|
+
<li><ui-assistant-button></ui-assistant-button></li>
|
|
104
|
+
</ng-template>
|
|
105
|
+
</ui-header>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Sem o botão, o painel continua acessível por código: `UiAssistantService.abrir()`, `fechar()` e `alternar()`.
|
|
109
|
+
|
|
110
|
+
Como funciona:
|
|
111
|
+
|
|
112
|
+
- **Fechado:** wrappers neutros, a janela rola normalmente. Nada muda para o usuário.
|
|
113
|
+
- **Aberto (≥ 1200 px):** linha flex `[aplicação | painel]` com a altura da viewport. A coluna da aplicação recebe `contain: layout paint`, então os elementos `position: fixed` do tema Cuba (`.page-header`, `.sidebar-wrapper`) passam a se posicionar dentro dela, e ela ganha rolagem própria. A posição de rolagem é transferida na abertura e devolvida no fechamento. A cada `NavigationEnd`, a coluna volta ao topo (o `scrollPositionRestoration` do router só atua na janela).
|
|
114
|
+
- **Aberto (< 1200 px):** o painel ocupa a largura toda e a aplicação fica recolhida (`inert`) até o usuário tocar em "Voltar".
|
|
115
|
+
- Transição de largura de 240 ms, desligada com `prefers-reduced-motion`.
|
|
116
|
+
|
|
117
|
+
Efeitos colaterais conhecidos com o painel aberto:
|
|
118
|
+
|
|
119
|
+
- Listeners de `window.scroll` não disparam, porque quem rola é a coluna.
|
|
120
|
+
- As media queries do tema continuam medindo a viewport: o layout desktop do Cuba é mantido na coluna, que tem no mínimo 800 px.
|
|
121
|
+
- Modais e overlays anexados ao `body` continuam cobrindo a tela toda, como hoje.
|
|
122
|
+
|
|
123
|
+
O `uitec-ng-header` usa `width: 100%` em `.not-sidebar` (antes `100vw`) para acompanhar a coluna.
|
|
124
|
+
|
|
125
|
+
### 4. Contexto da tela
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
constructor(private uiny: UiAssistantService) { }
|
|
129
|
+
|
|
130
|
+
ngOnInit() {
|
|
131
|
+
this.uiny.definirContexto({
|
|
132
|
+
tela: 'loja-leiaute/editar', // até 60 caracteres [a-z0-9_-/.]
|
|
133
|
+
idLoja: this.loja.id_loja,
|
|
134
|
+
idLojaLeiaute: this.lojaLeiaute.id_loja_leiaute,
|
|
135
|
+
// também: idTema, idProduto, idPedido
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
ngOnDestroy() { this.uiny.definirContexto(null); }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Os campos seguem o `ContextoTela` do backend, que recusa campos extras com 422. O `sistema` vem do `forRoot`. `atualizarContexto({ idLoja })` altera só os campos informados, e valores inválidos são descartados. O contexto é só uma indicação: quem autoriza é o backend. Com `idLoja`, o backend confere no Studio se a loja é da empresa (403 se não for) e restringe a conversa a ela.
|
|
142
|
+
|
|
143
|
+
**Trocar de loja encerra a conversa atual.** A lista "Conversas anteriores" mostra só as do mesmo sistema e da mesma loja. Trocar de empresa limpa tudo (seção 2).
|
|
144
|
+
|
|
145
|
+
### 5. Eventos de ações concluídas
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
aoEventoUiny(e: UinyEvento) {
|
|
149
|
+
switch (e.tipo) {
|
|
150
|
+
case 'acao_concluida': // a API devolveu a ação como "executada"
|
|
151
|
+
break;
|
|
152
|
+
case 'tema_atualizado': // instrução "atualizar_preview_tema" do backend
|
|
153
|
+
if (e.idLojaLeiaute === this.lojaLeiaute?.id_loja_leiaute) this.recarregarPreview();
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Também disponível como `UiAssistantService.eventos$`. Eventos só saem de respostas novas, nunca do histórico. Instruções fora da lista são descartadas. O componente não navega e não executa nada recebido da API.
|
|
160
|
+
|
|
161
|
+
### Exemplos
|
|
162
|
+
|
|
163
|
+
**Studio** (`sistemaOrigem: 'studio'`): contexto com `idLoja` e `idLojaLeiaute` na tela do editor; recarregar a prévia em `tema_atualizado`.
|
|
164
|
+
|
|
165
|
+
**ERP** (`sistemaOrigem: 'erp'`): contexto com `tela` (ex.: `produto/form`) e `idProduto` ou `idPedido` quando houver. As ferramentas atuais do ERP são só de consulta.
|
|
166
|
+
|
|
167
|
+
### Confirmação de ações
|
|
168
|
+
|
|
169
|
+
O card mostra o `resumo` e os `detalhes` da ação (na troca de cor: tema, loja, cor, valor atual e novo, com amostra), o prazo e os botões **Confirmar** e **Cancelar**:
|
|
170
|
+
|
|
171
|
+
- A confirmação só acontece pelo clique. O card envia `POST /acoes/{id}/confirmar` com corpo `{}`: nenhum argumento sai do frontend.
|
|
172
|
+
- Cliques repetidos são ignorados enquanto a ação não está `pendente`.
|
|
173
|
+
- Sucesso só aparece com `situacao: executada` na resposta. Situação desconhecida vira "Resultado a verificar".
|
|
174
|
+
- 409 `ACAO_INVALIDADA`: o card antigo vira "Precisa de nova confirmação" e a nova proposta aparece com seus próprios botões.
|
|
175
|
+
- 409 de expirada, cancelada ou em execução, e 502/504: o card usa a situação que vem no corpo do erro.
|
|
176
|
+
- Timeout ou erro de rede depois de confirmar: **nada é repetido**. O card fica "Resultado a verificar" com o botão "Verificar resultado", que relê o histórico da conversa.
|
|
177
|
+
- Ação vencida pelo relógio local não é enviada.
|
|
178
|
+
|
|
179
|
+
Pedidos de esclarecimento (`estado: esclarecimento` com bloco `opcoes`) mostram as opções como botões na última mensagem; o clique envia a opção como texto.
|
|
180
|
+
|
|
181
|
+
### Segurança e acessibilidade
|
|
182
|
+
|
|
183
|
+
- O texto é renderizado como um subconjunto de Markdown por interpolação (sem `innerHTML`, sem bypass). Links só `http`, `https` e `mailto`, abertos com `noopener noreferrer`.
|
|
184
|
+
- Mensagens de 5xx não são exibidas cruas. O id de correlação aparece para o suporte.
|
|
185
|
+
- Painel `aside` não modal no desktop, com rótulo acessível. O foco vai para o campo ao abrir e volta ao botão ao fechar. Escape fecha. Há uma região `aria-live` discreta para "respondendo", "nova resposta" e "alteração concluída".
|
|
186
|
+
- Enter envia e Shift+Enter quebra a linha. Composição (IME) é respeitada. Envio vazio ou duplicado é bloqueado. O limite é de 2000 caracteres (`UINY_MENSAGEM_MAX_CARACTERES` do backend).
|
|
187
|
+
|
|
188
|
+
### API pública
|
|
189
|
+
|
|
190
|
+
`UiAssistantModule`, `UiAssistantService`, `UinyChatService`, `UinyApiService`, `UinySessionService`, os componentes `ui-assistant-*`, os tipos `UinyConfig`, `UinyContexto`, `UinyEvento` e `Uiny*Api`, e `UINY_CONFIG`.
|
|
191
|
+
|
|
192
|
+
### Contrato
|
|
193
|
+
|
|
194
|
+
Fonte: `C:\git\uishop\uishop-ia` (README, `config/web.php`, `services/uiny/ApresentadorConversa.php`, `ContextoTela.php`, `ConfirmacaoService.php`, `config/Constants.php`). Tipos em `lib/models/uiny-api.models.ts`, conversão em `lib/utils/uiny-api.adapter.ts`.
|
|
195
|
+
|
|
196
|
+
| Rota (`/api/v1`) | Uso no frontend |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `POST /conversas` `{ contexto }` | criada no primeiro envio |
|
|
199
|
+
| `POST /conversas/{id}/mensagens` `{ texto, contexto }` | resposta com `conversa`, `estado`, `mensagem`, `acao_pendente` |
|
|
200
|
+
| `GET /conversas?page=1&per_page=20` | "Conversas anteriores", filtrada pelo sistema e pela loja atuais |
|
|
201
|
+
| `GET /conversas/{id}?page=1&per_page=100` | abrir conversa anterior e "Verificar resultado" |
|
|
202
|
+
| `POST /acoes/{id}/confirmar` e `/cancelar` | corpo `{}` |
|
|
203
|
+
|
|
204
|
+
Pontos em aberto:
|
|
205
|
+
|
|
206
|
+
- **Sugestões iniciais:** a API não expõe o catálogo de ferramentas, então cada sistema passa as suas em `forRoot({ sistemaOrigem, sugestoes: { textos, alteracoes } })`. Sugira só o que as ferramentas do uishop-ia daquele sistema conseguem fazer (README do backend); sem `sugestoes`, o painel abre sem elas.
|
|
207
|
+
- **Blocos de `apresentacao`:** só `opcoes` é renderizado. `lista_produtos`, `resumo_vendas`, `lista_lojas`, `tema` e `alteracao_cor` aparecem apenas pelo texto da mensagem.
|
|
208
|
+
- **Histórico longo:** abrir uma conversa carrega as 100 mensagens mais recentes; não há "carregar anteriores".
|
|
209
|
+
- **401:** o backend sugere redirecionar para o login. A Uiny apenas se esconde, para não derrubar a sessão da aplicação por causa da idade máxima de token, que é mais curta na Uiny.
|
|
210
|
+
|
|
211
|
+
### Demonstração
|
|
212
|
+
|
|
213
|
+
App `cuba`, rota `#/assistente` ("Uiny (assistente)" no menu). A API é simulada por `src/app/site/pages/assistente/demo/uiny-demo.backend.ts`, no formato do contrato real, que só responde a `https://uiny-demo.local/api/v1` e repassa o resto ao `HttpXhrBackend`. O usuário é trocado só em memória. A página tem controles para mostrar ou esconder o botão no header, login/logout, troca de loja, resultado da confirmação e atraso. Para usar a API real na demo, defina `environment.domain.uiny` (a demo passa a usá-la no lugar da simulada) e entre no Studio ou no ERP em `localhost` para ter o cookie de sessão.
|
|
@@ -86,10 +86,10 @@ export class UiAssistantActionCardComponent {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
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 });
|
|
89
|
-
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(--theme-default
|
|
89
|
+
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(--theme-default, #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.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.UiIconComponent, selector: "ui-icon", inputs: ["icon", "size", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
90
90
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantActionCardComponent, decorators: [{
|
|
91
91
|
type: Component,
|
|
92
|
-
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(--theme-default
|
|
92
|
+
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(--theme-default, #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"] }]
|
|
93
93
|
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }]; }, propDecorators: { acao: [{
|
|
94
94
|
type: Input
|
|
95
95
|
}], confirmar: [{
|
|
@@ -14,9 +14,9 @@ export class UiAssistantButtonComponent {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
UiAssistantButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantButtonComponent, deps: [{ token: i1.UiAssistantService }], target: i0.ɵɵFactoryTarget.Component });
|
|
17
|
-
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(var(--bs-primary-rgb),.28);border-radius:999px;background:rgba(var(--bs-primary-rgb),.08);color:var(--theme-default
|
|
17
|
+
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(var(--bs-primary-rgb),.28);border-radius:999px;background:rgba(var(--bs-primary-rgb),.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(var(--bs-primary-rgb),.14);border-color:rgba(var(--bs-primary-rgb),.45)}.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: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18
18
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: UiAssistantButtonComponent, decorators: [{
|
|
19
19
|
type: Component,
|
|
20
|
-
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(var(--bs-primary-rgb),.28);border-radius:999px;background:rgba(var(--bs-primary-rgb),.08);color:var(--theme-default
|
|
20
|
+
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(var(--bs-primary-rgb),.28);border-radius:999px;background:rgba(var(--bs-primary-rgb),.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(var(--bs-primary-rgb),.14);border-color:rgba(var(--bs-primary-rgb),.45)}.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"] }]
|
|
21
21
|
}], ctorParameters: function () { return [{ type: i1.UiAssistantService }]; } });
|
|
22
22
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidWktYXNzaXN0YW50LWJ1dHRvbi5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy91aS1hc3Npc3RhbnQvc3JjL2xpYi9jb21wb25lbnRzL2Fzc2lzdGFudC1idXR0b24vdWktYXNzaXN0YW50LWJ1dHRvbi5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy91aS1hc3Npc3RhbnQvc3JjL2xpYi9jb21wb25lbnRzL2Fzc2lzdGFudC1idXR0b24vdWktYXNzaXN0YW50LWJ1dHRvbi5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsU0FBUyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ25FLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHFDQUFxQyxDQUFDOzs7O0FBRXpFOzs7R0FHRztBQU9ILE1BQU0sT0FBTywwQkFBMEI7SUFDbkMsWUFBbUIsU0FBNkI7UUFBN0IsY0FBUyxHQUFULFNBQVMsQ0FBb0I7UUFDNUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO0lBQ3ZDLENBQUM7O3dIQUhRLDBCQUEwQjs0R0FBMUIsMEJBQTBCLDJEQ2J2Qyxnb0NBYUE7NEZEQWEsMEJBQTBCO2tCQU50QyxTQUFTOytCQUNJLHFCQUFxQixtQkFHZCx1QkFBdUIsQ0FBQyxNQUFNIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIENvbXBvbmVudCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgVWlBc3Npc3RhbnRTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vc2VydmljZXMvdWktYXNzaXN0YW50LnNlcnZpY2UnO1xuXG4vKipcbiAqIEJvdMOjbyBxdWUgYWJyZS9mZWNoYSBhIFVpbnkuIFPDsyBhcGFyZWNlIHF1YW5kbyBhIGFzc2lzdGVudGUgZXN0w6EgZGlzcG9uw612ZWwuXG4gKiBQb2RlIGZpY2FyIGVtIHF1YWxxdWVyIGx1Z2FyIGRhIGFwbGljYcOnw6NvIChleC46IG5vIHNsb3QgI3JpZ2h0IGRvIHVpLWhlYWRlcikuXG4gKi9cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAndWktYXNzaXN0YW50LWJ1dHRvbicsXG4gICAgdGVtcGxhdGVVcmw6ICcuL3VpLWFzc2lzdGFudC1idXR0b24uY29tcG9uZW50Lmh0bWwnLFxuICAgIHN0eWxlVXJsczogWycuL3VpLWFzc2lzdGFudC1idXR0b24uY29tcG9uZW50LnNjc3MnXSxcbiAgICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaFxufSlcbmV4cG9ydCBjbGFzcyBVaUFzc2lzdGFudEJ1dHRvbkNvbXBvbmVudCB7XG4gICAgY29uc3RydWN0b3IocHVibGljIGFzc2lzdGFudDogVWlBc3Npc3RhbnRTZXJ2aWNlKSB7XG4gICAgICAgIHRoaXMuYXNzaXN0YW50LmRpYWdub3N0aWNhckJvdGFvKCk7XG4gICAgfVxufVxuIiwiPGJ1dHRvbiAqbmdJZj1cImFzc2lzdGFudC5kaXNwb25pdmVsJCB8IGFzeW5jXCIgI2JvdGFvIHR5cGU9XCJidXR0b25cIiBjbGFzcz1cInVpbnktdHJpZ2dlclwiXG4gICAgW2NsYXNzLmlzLW9wZW5dPVwiYXNzaXN0YW50LmFiZXJ0byQgfCBhc3luY1wiXG4gICAgW2F0dHIuYXJpYS1leHBhbmRlZF09XCIoYXNzaXN0YW50LmFiZXJ0byQgfCBhc3luYykgPyAndHJ1ZScgOiAnZmFsc2UnXCJcbiAgICBhcmlhLWNvbnRyb2xzPVwidWlueS1wYWluZWxcIlxuICAgIFthdHRyLmFyaWEtbGFiZWxdPVwiKGFzc2lzdGFudC5hYmVydG8kIHwgYXN5bmMpID8gJ0ZlY2hhciBhIFVpbnksIGFzc2lzdGVudGUgZGEgVWlzaG9wJyA6ICdBYnJpciBhIFVpbnksIGFzc2lzdGVudGUgZGEgVWlzaG9wJ1wiXG4gICAgW3RpdGxlXT1cIihhc3Npc3RhbnQuYWJlcnRvJCB8IGFzeW5jKSA/ICdGZWNoYXIgYSBVaW55JyA6ICdGYWxhciBjb20gYSBVaW55J1wiXG4gICAgKGNsaWNrKT1cImFzc2lzdGFudC5hbHRlcm5hcihib3RhbylcIj5cbiAgICA8c3ZnIGNsYXNzPVwidWlueS10cmlnZ2VyX19tYXJrXCIgdmlld0JveD1cIjAgMCAyNCAyNFwiIHdpZHRoPVwiMThcIiBoZWlnaHQ9XCIxOFwiIGFyaWEtaGlkZGVuPVwidHJ1ZVwiIGZvY3VzYWJsZT1cImZhbHNlXCI+XG4gICAgICAgIDxwYXRoIGQ9XCJNMTIgMi41Yy41IDMuOSAyLjYgNi45IDYuNCA4LjIuNS4yLjUuOSAwIDEuMS0zLjggMS4zLTUuOSA0LjMtNi40IDguMi0uMS42LS45LjYtMSAwLS41LTMuOS0yLjYtNi45LTYuNC04LjItLjUtLjItLjUtLjkgMC0xLjEgMy44LTEuMyA1LjktNC4zIDYuNC04LjIuMS0uNi45LS42IDEgMFpcIiBmaWxsPVwiY3VycmVudENvbG9yXCIvPlxuICAgICAgICA8cGF0aCBkPVwiTTE5IDIuOGMuMiAxLjMuOSAyLjIgMi4xIDIuNi4yLjEuMi4zIDAgLjQtMS4yLjQtMS45IDEuMy0yLjEgMi42IDAgLjItLjMuMi0uMyAwLS4yLTEuMy0uOS0yLjItMi4xLTIuNi0uMi0uMS0uMi0uMyAwLS40IDEuMi0uNCAxLjktMS4zIDIuMS0yLjYgMC0uMi4zLS4yLjMgMFpcIiBmaWxsPVwiY3VycmVudENvbG9yXCIgb3BhY2l0eT1cIi43XCIvPlxuICAgIDwvc3ZnPlxuICAgIDxzcGFuIGNsYXNzPVwidWlueS10cmlnZ2VyX19sYWJlbFwiPlVpbnk8L3NwYW4+XG48L2J1dHRvbj5cbiJdfQ==
|