rio-assist-widget 0.1.34 → 0.1.36

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/index.html CHANGED
@@ -34,5 +34,25 @@
34
34
 
35
35
  <script type="module" src="/src/main.ts"></script>
36
36
  <script type="module" src="/src/playground.ts"></script>
37
+ <script>
38
+ // Posiciona o botao flutuante proximo ao topo da viewport calculando bottom a partir da altura disponivel.
39
+ (function () {
40
+ const widget = document.querySelector('rio-assist-widget');
41
+ if (!widget) return;
42
+
43
+ const TOP_MARGIN = 96; // distancia desejada a partir do topo em px
44
+ const BUTTON_HEIGHT = 64;
45
+ const MIN_BOTTOM = 12;
46
+
47
+ const updateFloatingOffset = () => {
48
+ const viewport = window.innerHeight || document.documentElement.clientHeight || 0;
49
+ const bottom = Math.max(MIN_BOTTOM, viewport - TOP_MARGIN - BUTTON_HEIGHT);
50
+ widget.setAttribute('data-floating-offset', String(bottom));
51
+ };
52
+
53
+ updateFloatingOffset();
54
+ window.addEventListener('resize', updateFloatingOffset);
55
+ })();
56
+ </script>
37
57
  </body>
38
58
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rio-assist-widget",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Web Component do painel lateral Rio Insight, pronto para ser embutido em qualquer projeto.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -125,11 +125,15 @@ export const miniPanelStyles = css`
125
125
  overflow-y: auto;
126
126
  }
127
127
 
128
- .panel-content--empty {
129
- display: flex;
130
- align-items: center;
131
- justify-content: center;
132
- }
128
+ .panel-content--empty {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ }
133
+
134
+ .panel-content--consultant {
135
+ display: block;
136
+ }
133
137
 
134
138
  .hero-card {
135
139
  flex: 1;
@@ -152,21 +156,101 @@ export const miniPanelStyles = css`
152
156
  margin-bottom: 8px;
153
157
  }
154
158
 
155
- .hero-card h3 {
156
- font-family: 'Source Sans Pro', 'Inter', sans-serif;
157
- font-weight: 400;
158
- font-size: 32px;
159
- line-height: 1;
159
+ .hero-card h3 {
160
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
161
+ font-weight: 400;
162
+ font-size: 32px;
163
+ line-height: 1;
160
164
  white-space: nowrap;
161
- color: #2a3740;
162
- margin: 0;
163
- }
164
-
165
- .conversation {
166
- width: 100%;
167
- display: flex;
168
- flex-direction: column;
169
- gap: 12px;
165
+ color: #2a3740;
166
+ margin: 0;
167
+ }
168
+
169
+ .consultant-agent {
170
+ display: flex;
171
+ flex-direction: column;
172
+ align-items: center;
173
+ gap: 12px;
174
+ width: 100%;
175
+ }
176
+
177
+ .consultant-agent__button,
178
+ .consultant-agent__option {
179
+ display: inline-flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ height: auto;
183
+ padding: 8px 16px;
184
+ min-height: 34px;
185
+ border: 1px solid #30b4c0;
186
+ border-radius: 4px;
187
+ background: transparent;
188
+ color: #30b4c0;
189
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
190
+ font-weight: 600;
191
+ font-size: 14px;
192
+ line-height: 18px;
193
+ white-space: normal;
194
+ text-align: left;
195
+ word-break: break-word;
196
+ max-width: 100%;
197
+ box-sizing: border-box;
198
+ }
199
+
200
+ .consultant-agent__button:focus-visible,
201
+ .consultant-agent__option:focus-visible {
202
+ outline: 2px solid #30b4c0;
203
+ outline-offset: 2px;
204
+ }
205
+
206
+ .consultant-agent__intro {
207
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
208
+ font-weight: 400;
209
+ font-size: 18px;
210
+ line-height: 24px;
211
+ color: #2a3740;
212
+ text-align: center;
213
+ margin-top: 4px;
214
+ }
215
+
216
+ .consultant-agent__options {
217
+ display: flex;
218
+ flex-direction: column;
219
+ gap: 10px;
220
+ width: 100%;
221
+ align-items: flex-start;
222
+ }
223
+
224
+ .consultant-prompt {
225
+ display: flex;
226
+ flex-direction: column;
227
+ gap: 16px;
228
+ width: 100%;
229
+ align-items: flex-start;
230
+ }
231
+
232
+ .consultant-prompt__text {
233
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
234
+ font-weight: 400;
235
+ font-size: 18px;
236
+ line-height: 24px;
237
+ color: #2a3740;
238
+ text-align: left;
239
+ }
240
+
241
+ .consultant-prompt__options {
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 10px;
245
+ width: 100%;
246
+ align-items: flex-start;
247
+ }
248
+
249
+ .conversation {
250
+ width: 100%;
251
+ display: flex;
252
+ flex-direction: column;
253
+ gap: 12px;
170
254
  }
171
255
 
172
256
  .message {
@@ -179,15 +263,37 @@ export const miniPanelStyles = css`
179
263
  font-size: 15px;
180
264
  }
181
265
 
182
- .message__content {
183
- line-height: 1.35;
184
- }
185
-
186
- .message__content p,
187
- .message__content ul,
188
- .message__content ol {
189
- margin: 4px 0;
190
- }
266
+ .message__content {
267
+ line-height: 1.35;
268
+ }
269
+
270
+ .consultant-follow-up {
271
+ display: flex;
272
+ flex-direction: column;
273
+ gap: 10px;
274
+ }
275
+
276
+ .consultant-follow-up__text {
277
+ margin: 0;
278
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
279
+ font-weight: 400;
280
+ font-size: 15px;
281
+ color: #1f2f36;
282
+ }
283
+
284
+ .consultant-follow-up__options {
285
+ display: flex;
286
+ flex-direction: column;
287
+ gap: 8px;
288
+ align-items: flex-start;
289
+ width: 100%;
290
+ }
291
+
292
+ .message__content p,
293
+ .message__content ul,
294
+ .message__content ol {
295
+ margin: 4px 0;
296
+ }
191
297
 
192
298
  .message__content pre {
193
299
  background: #0d161b;
@@ -316,33 +422,33 @@ export const miniPanelStyles = css`
316
422
  margin-bottom: 6px;
317
423
  }
318
424
 
319
- .short-answer-toggle {
320
- display: inline-flex;
321
- align-items: center;
322
- gap: 12px;
323
- border: none;
324
- background: transparent;
325
- color: #1f2f36;
326
- border-radius: 999px;
327
- padding: 8px 12px 8px 10px;
328
- font-weight: 600;
329
- box-shadow: none;
330
- transition: color 0.2s ease;
331
- }
425
+ .short-answer-toggle {
426
+ display: inline-flex;
427
+ align-items: center;
428
+ gap: 12px;
429
+ border: none;
430
+ background: transparent;
431
+ color: #1f2f36;
432
+ border-radius: 999px;
433
+ padding: 8px 12px 8px 10px;
434
+ font-weight: 600;
435
+ box-shadow: none;
436
+ transition: color 0.2s ease;
437
+ }
332
438
 
333
439
  .short-answer-toggle:focus-visible {
334
440
  outline: 2px solid var(--accent-color, #008b9a);
335
441
  outline-offset: 2px;
336
442
  }
337
443
 
338
- .short-answer-toggle__track {
339
- width: 44px;
340
- height: 24px;
341
- border-radius: 999px;
342
- background: #c7d0d9;
343
- position: relative;
344
- transition: background-color 0.2s ease;
345
- }
444
+ .short-answer-toggle__track {
445
+ width: 44px;
446
+ height: 24px;
447
+ border-radius: 999px;
448
+ background: #c7d0d9;
449
+ position: relative;
450
+ transition: background-color 0.2s ease;
451
+ }
346
452
 
347
453
  .short-answer-toggle__track--on {
348
454
  background: #008b9a;
@@ -1,54 +1,122 @@
1
- import { html } from 'lit';
2
- import { unsafeHTML } from 'lit/directives/unsafe-html.js';
3
- import { classMap } from 'lit/directives/class-map.js';
4
- import type { RioAssistWidget } from '../rio-assist/rio-assist';
5
- import { renderConversationsPanel } from '../conversations-panel/conversations-panel.template';
1
+ import { html } from 'lit';
2
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import type { RioAssistWidget } from '../rio-assist/rio-assist';
5
+ import { renderConversationsPanel } from '../conversations-panel/conversations-panel.template';
6
+ import { renderConsultantAgentHero } from '../../consultant-agent/consultant-agent.template';
6
7
 
7
- const hamburgerIconUrl = new URL('../../assets/icons/hamburgerMenuIcon.png', import.meta.url).href;
8
- const expandIconUrl = new URL('../../assets/icons/expandScreen.png', import.meta.url).href;
9
- const iaCentralIconUrl = new URL('../../assets/icons/iaCentralIcon.png', import.meta.url).href;
10
- const plusFileSelectionUrl = new URL('../../assets/icons/plusFileSelection.png', import.meta.url).href;
11
- const closeIconUrl = new URL('../../assets/icons/closeIcon.png', import.meta.url).href;
12
- const arrowButtonUrl = new URL('../../assets/icons/arrowButton.png', import.meta.url).href;
8
+ const hamburgerIconUrl = new URL('../../assets/icons/hamburgerMenuIcon.png', import.meta.url).href;
9
+ const expandIconUrl = new URL('../../assets/icons/expandScreen.png', import.meta.url).href;
10
+ const iaCentralIconUrl = new URL('../../assets/icons/iaCentralIcon.png', import.meta.url).href;
11
+ const plusFileSelectionUrl = new URL('../../assets/icons/plusFileSelection.png', import.meta.url).href;
12
+ const closeIconUrl = new URL('../../assets/icons/closeIcon.png', import.meta.url).href;
13
+ const arrowButtonUrl = new URL('../../assets/icons/arrowButton.png', import.meta.url).href;
14
+
15
+ const renderConsultantFollowUp = (
16
+ component: RioAssistWidget,
17
+ payload: {
18
+ id: string;
19
+ topicLabel: string;
20
+ questions: string[];
21
+ },
22
+ ) => {
23
+ const buttonsVisible = component.activeConsultantFollowUpId === payload.id;
24
+
25
+ return html`
26
+ <div class="consultant-follow-up">
27
+ <p class="consultant-follow-up__text">
28
+ Certo! Reuni abaixo as principais dúvidas sobre ${payload.topicLabel}. Escolha uma delas ou
29
+ faça sua pergunta.
30
+ </p>
31
+ ${buttonsVisible
32
+ ? html`
33
+ <div class="consultant-follow-up__options">
34
+ ${payload.questions.map(
35
+ (question) => html`
36
+ <button
37
+ class="consultant-agent__option"
38
+ type="button"
39
+ @click=${() => component.handleConsultantFollowUpQuestion(question)}
40
+ >
41
+ ${question}
42
+ </button>
43
+ `,
44
+ )}
45
+ </div>
46
+ `
47
+ : null}
48
+ </div>
49
+ `;
50
+ };
13
51
 
14
52
  export const renderChatSurface = (component: RioAssistWidget) => {
15
- const hasMessages = component.messages.length > 0;
16
-
53
+ const hasMessages = component.messages.length > 0;
54
+ const showConsultantPrompt =
55
+ component.consultantAgentVisible &&
56
+ component.consultantAgentOptions.length > 0 &&
57
+ !hasMessages;
58
+
17
59
  const heroCard = html`
18
60
  <div class="hero-card">
19
61
  <img src=${iaCentralIconUrl} alt="IA assistente" class="hero-card__icon" />
20
62
  <h3>Como posso te ajudar hoje?</h3>
63
+ ${renderConsultantAgentHero(component)}
21
64
  </div>
22
65
  `;
23
-
24
- const conversation = html`
25
- <div class="conversation">
26
- ${component.messages.map(
27
- (message) => html`
28
- <div
29
- class=${classMap({
30
- message: true,
31
- 'message--user': message.role === 'user',
32
- 'message--assistant': message.role === 'assistant',
33
- })}
34
- >
35
- <div class="message__content">
36
- ${unsafeHTML(message.html ?? message.text)}
37
- </div>
38
- <time>
39
- ${new Date(message.timestamp).toLocaleTimeString('pt-BR', {
40
- hour: '2-digit',
41
- minute: '2-digit',
42
- })}
43
- </time>
44
- </div>
45
- `,
46
- )}
47
- ${component.isLoading
48
- ? html`
49
- <div class="message message--assistant typing">
50
- <span>${component.loadingLabel}</span>
51
- <span class="typing__dots" aria-hidden="true">
66
+
67
+ const consultantPrompt = html`
68
+ <div class="consultant-prompt">
69
+ <div class="consultant-prompt__text">
70
+ ${component.consultantAgentIntro}
71
+ </div>
72
+ <div class="consultant-prompt__options">
73
+ ${component.consultantAgentOptions.map(
74
+ (option) => html`
75
+ <button
76
+ class="consultant-agent__option"
77
+ type="button"
78
+ @click=${() => component.handleConsultantAgentOption(option)}
79
+ >
80
+ ${option.label}
81
+ </button>
82
+ `,
83
+ )}
84
+ </div>
85
+ </div>
86
+ `;
87
+
88
+ const conversation = html`
89
+ <div class="conversation">
90
+ ${component.messages.map((message) => {
91
+ const hasFollowUp = Boolean((message as any).consultantFollowUp);
92
+
93
+ return html`
94
+ <div
95
+ class=${classMap({
96
+ message: true,
97
+ 'message--user': message.role === 'user',
98
+ 'message--assistant': message.role === 'assistant',
99
+ })}
100
+ >
101
+ <div class="message__content">
102
+ ${hasFollowUp
103
+ ? renderConsultantFollowUp(component, (message as any).consultantFollowUp)
104
+ : unsafeHTML(message.html ?? message.text)}
105
+ </div>
106
+ <time>
107
+ ${new Date(message.timestamp).toLocaleTimeString('pt-BR', {
108
+ hour: '2-digit',
109
+ minute: '2-digit',
110
+ })}
111
+ </time>
112
+ </div>
113
+ `;
114
+ })}
115
+ ${component.isLoading
116
+ ? html`
117
+ <div class="message message--assistant typing">
118
+ <span>${component.loadingLabel}</span>
119
+ <span class="typing__dots" aria-hidden="true">
52
120
  <span>.</span><span>.</span><span>.</span>
53
121
  </span>
54
122
  </div>
@@ -60,24 +128,25 @@ export const renderChatSurface = (component: RioAssistWidget) => {
60
128
  return html`
61
129
  <div class="panel-body">
62
130
  <div
63
- class=${classMap({
64
- 'panel-content': true,
65
- 'panel-content--empty': !hasMessages,
66
- })}
67
- >
68
- ${hasMessages ? conversation : heroCard}
69
- </div>
131
+ class=${classMap({
132
+ 'panel-content': true,
133
+ 'panel-content--empty': !hasMessages && !showConsultantPrompt,
134
+ 'panel-content--consultant': showConsultantPrompt,
135
+ })}
136
+ >
137
+ ${hasMessages ? conversation : showConsultantPrompt ? consultantPrompt : heroCard}
138
+ </div>
70
139
 
71
140
  ${component.errorMessage
72
141
  ? html`<p class="error-banner">${component.errorMessage}</p>`
73
142
  : null}
74
143
 
75
- <div class="panel-footer">
76
- ${component.suggestions.length > 0
77
- ? html`
78
- <div class="suggestions-wrapper">
79
- <p class="suggestions-label">Sugestões de Perguntas</p>
80
- <div class="suggestions">
144
+ <div class="panel-footer">
145
+ ${component.showSuggestions && component.suggestions.length > 0
146
+ ? html`
147
+ <div class="suggestions-wrapper">
148
+ <p class="suggestions-label">Sugestões de Perguntas</p>
149
+ <div class="suggestions">
81
150
  ${component.suggestions.map(
82
151
  (suggestion) => html`
83
152
  <button
@@ -141,58 +210,58 @@ export const renderMiniPanel = (component: RioAssistWidget) => {
141
210
  <img src=${closeIconUrl} alt="" aria-hidden="true" />
142
211
  </button>
143
212
  </div>
144
- <div class="panel-header__actions">
145
- <button
146
- class="conversations-button"
147
- type="button"
148
- @click=${() => component.toggleConversationsPanel()}
213
+ <div class="panel-header__actions">
214
+ <button
215
+ class="conversations-button"
216
+ type="button"
217
+ @click=${() => component.toggleConversationsPanel()}
149
218
  >
150
219
  <img src=${hamburgerIconUrl} alt="" aria-hidden="true" />
151
220
  Minhas Conversas
152
221
  </button>
153
- <div class="panel-header__icons">
154
- <button
155
- type="button"
156
- class="short-answer-toggle short-answer-toggle--header"
157
- role="switch"
158
- aria-checked=${component.shortAnswerEnabled}
159
- @click=${() => component.toggleShortAnswers()}
160
- >
161
- <span
162
- class=${classMap({
163
- 'short-answer-toggle__track': true,
164
- 'short-answer-toggle__track--on': component.shortAnswerEnabled,
165
- })}
166
- aria-hidden="true"
167
- >
168
- <span class="short-answer-toggle__thumb"></span>
169
- </span>
170
- <span class="short-answer-toggle__label">Respostas rápidas</span>
171
- </button>
172
-
173
- ${component.hasActiveConversation
174
- ? html`
175
- <button
176
- class="panel-header__icon-button conversations-plus-button"
177
- type="button"
178
- aria-label="Nova conversa"
179
- @click=${() => component.handleCreateConversation()}
180
- >
181
- <img src=${plusFileSelectionUrl} alt="" aria-hidden="true" />
182
- </button>
183
- `
184
- : null}
185
- <button
186
- class="panel-header__icon-button"
187
- type="button"
188
- aria-label="Expandir painel"
189
- @click=${() => component.enterFullscreen()}
190
- >
191
- <img src=${expandIconUrl} alt="" aria-hidden="true" />
192
- </button>
193
- </div>
194
- </div>
195
- </header>
222
+ <div class="panel-header__icons">
223
+ <button
224
+ type="button"
225
+ class="short-answer-toggle short-answer-toggle--header"
226
+ role="switch"
227
+ aria-checked=${component.shortAnswerEnabled}
228
+ @click=${() => component.toggleShortAnswers()}
229
+ >
230
+ <span
231
+ class=${classMap({
232
+ 'short-answer-toggle__track': true,
233
+ 'short-answer-toggle__track--on': component.shortAnswerEnabled,
234
+ })}
235
+ aria-hidden="true"
236
+ >
237
+ <span class="short-answer-toggle__thumb"></span>
238
+ </span>
239
+ <span class="short-answer-toggle__label">Respostas rápidas</span>
240
+ </button>
241
+
242
+ ${component.hasActiveConversation
243
+ ? html`
244
+ <button
245
+ class="panel-header__icon-button conversations-plus-button"
246
+ type="button"
247
+ aria-label="Nova conversa"
248
+ @click=${() => component.handleCreateConversation()}
249
+ >
250
+ <img src=${plusFileSelectionUrl} alt="" aria-hidden="true" />
251
+ </button>
252
+ `
253
+ : null}
254
+ <button
255
+ class="panel-header__icon-button"
256
+ type="button"
257
+ aria-label="Expandir painel"
258
+ @click=${() => component.enterFullscreen()}
259
+ >
260
+ <img src=${expandIconUrl} alt="" aria-hidden="true" />
261
+ </button>
262
+ </div>
263
+ </div>
264
+ </header>
196
265
 
197
266
  ${chatSurface}
198
267
 
@@ -5,7 +5,7 @@ import { fullscreenStyles } from '../fullscreen/fullscreen.styles';
5
5
  import { conversationsPanelStyles } from '../conversations-panel/conversations-panel.styles';
6
6
 
7
7
  const baseStyles = css`
8
- @import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@700&display=swap');
8
+ @import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap');
9
9
 
10
10
  :host {
11
11
  position: fixed;