rio-assist-widget 0.1.11 → 0.1.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rio-assist-widget",
3
- "version": "0.1.11",
3
+ "version": "0.1.14",
4
4
  "description": "Web Component do painel lateral Rio Insight, pronto para ser embutido em qualquer projeto.",
5
5
  "type": "module",
6
6
  "scripts": {
Binary file
@@ -61,6 +61,27 @@ export const conversationsPanelStyles = css`
61
61
  overflow: hidden;
62
62
  }
63
63
 
64
+ .recent-conversations-button {
65
+ display: inline-flex;
66
+ align-items: center;
67
+ gap: 12px;
68
+ padding: 6px 12px 6px 16px;
69
+ border: none;
70
+ background: transparent;
71
+ color: #1f2f36;
72
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
73
+ font-size: 18px;
74
+ font-weight: 700;
75
+ text-align: left;
76
+ width: calc(100% - 24px);
77
+ cursor: pointer;
78
+ }
79
+
80
+ .recent-conversations-button img {
81
+ width: 24px;
82
+ height: 24px;
83
+ }
84
+
64
85
  .conversation-search {
65
86
  display: flex;
66
87
  align-items: center;
@@ -9,6 +9,7 @@ const trashIconUrl = new URL('../../assets/icons/trash.png', import.meta.url).hr
9
9
  const searchIconUrl = new URL('../../assets/icons/searchIcon.png', import.meta.url).href;
10
10
  const plusFileSelectionUrl = new URL('../../assets/icons/plusFileSelection.png', import.meta.url)
11
11
  .href;
12
+ const hamburgerBlack = new URL('../../assets/icons/hamburgerBlack.png', import.meta.url).href;
12
13
 
13
14
  type ConversationsPanelVariant = 'drawer' | 'sidebar';
14
15
 
@@ -47,11 +48,11 @@ const renderConversationSurface = (
47
48
  component: RioAssistWidget,
48
49
  variant: ConversationsPanelVariant,
49
50
  ) => {
50
- const isSidebar = variant === 'sidebar';
51
-
52
- const newConversationCta = isSidebar
53
- ? html`
54
- <div
51
+ const isSidebar = variant === 'sidebar';
52
+
53
+ const newConversationCta = isSidebar
54
+ ? html`
55
+ <div
55
56
  class=${classMap({
56
57
  'new-conversation-cta': true,
57
58
  open: component.showNewConversationShortcut,
@@ -81,27 +82,27 @@ const renderConversationSurface = (
81
82
  >
82
83
  ${component.filteredConversations.map(
83
84
  (conversation) => {
84
- const menuOpen = component.conversationMenuId === conversation.id;
85
-
86
- return html`
87
- <div
88
- class="conversation-item"
89
- role="button"
90
- tabindex="0"
91
- title=${`Recuperar ${conversation.title}`}
92
- @click=${() => component.handleConversationSelect(conversation.id)}
93
- @keydown=${(event: KeyboardEvent) => {
94
- if (event.key === 'Enter' || event.key === ' ') {
95
- event.preventDefault();
96
- component.handleConversationSelect(conversation.id);
97
- }
98
- }}
99
- >
100
- <div class="conversation-item__text">
101
- ${conversation.title}
102
- </div>
103
- <button
104
- class="conversation-menu-button"
85
+ const menuOpen = component.conversationMenuId === conversation.id;
86
+
87
+ return html`
88
+ <div
89
+ class="conversation-item"
90
+ role="button"
91
+ tabindex="0"
92
+ title=${`Recuperar ${conversation.title}`}
93
+ @click=${() => component.handleConversationSelect(conversation.id)}
94
+ @keydown=${(event: KeyboardEvent) => {
95
+ if (event.key === 'Enter' || event.key === ' ') {
96
+ event.preventDefault();
97
+ component.handleConversationSelect(conversation.id);
98
+ }
99
+ }}
100
+ >
101
+ <div class="conversation-item__text">
102
+ ${conversation.title}
103
+ </div>
104
+ <button
105
+ class="conversation-menu-button"
105
106
  type="button"
106
107
  @click=${(event: Event) =>
107
108
  component.handleConversationMenuToggle(event, conversation.id)}
@@ -147,6 +148,15 @@ const renderConversationSurface = (
147
148
  return html`
148
149
  ${newConversationCta}
149
150
 
151
+ ${isSidebar
152
+ ? html`
153
+ <button class="recent-conversations-button" type="button" aria-label="Conversas recentes">
154
+ <img src=${hamburgerBlack} alt="" aria-hidden="true" />
155
+ <span>Conversas recentes</span>
156
+ </button>
157
+ `
158
+ : null}
159
+
150
160
  <div class="conversation-search">
151
161
  <img class="search-icon" src=${searchIconUrl} alt="" aria-hidden="true" />
152
162
  <input
@@ -157,16 +167,16 @@ const renderConversationSurface = (
157
167
  />
158
168
  </div>
159
169
 
160
- <div
161
- class=${classMap({
162
- 'conversation-list-wrapper': true,
163
- 'conversation-list-wrapper--sidebar': isSidebar,
164
- })}
165
- >
166
- ${component.conversationHistoryLoading
167
- ? html`<div class="conversation-loading">Carregando conversas...</div>`
168
- : null}
169
- ${list}
170
+ <div
171
+ class=${classMap({
172
+ 'conversation-list-wrapper': true,
173
+ 'conversation-list-wrapper--sidebar': isSidebar,
174
+ })}
175
+ >
176
+ ${component.conversationHistoryLoading
177
+ ? html`<div class="conversation-loading">Carregando conversas...</div>`
178
+ : null}
179
+ ${list}
170
180
  ${isSidebar
171
181
  ? html`
172
182
  <div
@@ -22,12 +22,13 @@ export const fullscreenStyles = css`
22
22
  padding: 6px 0 12px;
23
23
  }
24
24
 
25
- .fullscreen-shell__content {
26
- flex: 1;
27
- display: flex;
28
- flex-direction: column;
29
- overflow: hidden;
30
- }
25
+ .fullscreen-shell__content {
26
+ flex: 1;
27
+ display: flex;
28
+ flex-direction: column;
29
+ overflow: hidden;
30
+ position: relative;
31
+ }
31
32
 
32
33
  .rail-button {
33
34
  width: 38px;
@@ -153,16 +154,36 @@ export const fullscreenStyles = css`
153
154
  padding: 0;
154
155
  }
155
156
 
156
- .fullscreen-header__icon img {
157
- width: 24px;
158
- height: 24px;
159
- }
160
-
161
- .fullscreen-grid {
162
- flex: 1;
163
- display: grid;
164
- grid-template-columns: 300px minmax(0, 1fr);
165
- min-height: 0;
157
+ .fullscreen-header__icon img {
158
+ width: 24px;
159
+ height: 24px;
160
+ }
161
+
162
+ .fullscreen-exit-inline {
163
+ position: absolute;
164
+ top: 56px;
165
+ right: 14px;
166
+ width: 28px;
167
+ height: 28px;
168
+ border: none;
169
+ background: transparent;
170
+ padding: 0;
171
+ display: grid;
172
+ place-items: center;
173
+ cursor: pointer;
174
+ z-index: 1;
175
+ }
176
+
177
+ .fullscreen-exit-inline img {
178
+ width: 28px;
179
+ height: 28px;
180
+ }
181
+
182
+ .fullscreen-grid {
183
+ flex: 1;
184
+ display: grid;
185
+ grid-template-columns: 300px minmax(0, 1fr);
186
+ min-height: 0;
166
187
  background: linear-gradient(180deg, #eef3f6 0%, #fff 100%);
167
188
  }
168
189
 
@@ -1,6 +1,6 @@
1
1
  import { html } from 'lit';
2
2
  import { classMap } from 'lit/directives/class-map.js';
3
- import type { RioAssistWidget } from '../rio-assist/rio-assist';
3
+ import type { HeaderActionConfig, RioAssistWidget } from '../rio-assist/rio-assist';
4
4
  import { renderConversationsPanel } from '../conversations-panel/conversations-panel.template';
5
5
  import { renderChatSurface } from '../mini-panel/mini-panel.template';
6
6
 
@@ -8,14 +8,29 @@ const homeIconUrl = new URL('../../assets/icons/homeIcon.png', import.meta.url).
8
8
  const checkFrameIconUrl = new URL('../../assets/icons/checkFrame.png', import.meta.url).href;
9
9
  const infoFrameIconUrl = new URL('../../assets/icons/infoFrame.png', import.meta.url).href;
10
10
  const profileFrameIconUrl = new URL('../../assets/icons/profileFrame.png', import.meta.url).href;
11
+ const resizeScreenIconUrl = new URL('../../assets/icons/resizeScreen.png', import.meta.url).href;
12
+
13
+ const defaultHeaderActions: HeaderActionConfig[] = [
14
+ { id: 'status', iconUrl: checkFrameIconUrl, ariaLabel: 'Status' },
15
+ { id: 'info', iconUrl: infoFrameIconUrl, ariaLabel: 'Informacoes' },
16
+ { id: 'profile', iconUrl: profileFrameIconUrl, ariaLabel: 'Perfil de usuario' },
17
+ ];
11
18
 
12
19
  export const renderFullscreen = (component: RioAssistWidget) => {
13
20
  const chatSurface = renderChatSurface(component);
21
+ const headerActions = (component.headerActions?.length
22
+ ? component.headerActions
23
+ : defaultHeaderActions) as HeaderActionConfig[];
14
24
 
15
25
  return html`
16
26
  <section class="fullscreen-shell" role="dialog" aria-modal="true">
17
27
  <div class="fullscreen-shell__rail">
18
- <button type="button" class="rail-button" aria-label="Ir para home">
28
+ <button
29
+ type="button"
30
+ class="rail-button"
31
+ aria-label="Ir para home"
32
+ @click=${() => component.handleHomeNavigation()}
33
+ >
19
34
  <img src=${homeIconUrl} alt="" aria-hidden="true" />
20
35
  </button>
21
36
  <button
@@ -73,18 +88,30 @@ export const renderFullscreen = (component: RioAssistWidget) => {
73
88
  </div>
74
89
 
75
90
  <div class="fullscreen-header__actions">
76
- <button type="button" class="fullscreen-header__icon" aria-label="Status">
77
- <img src=${checkFrameIconUrl} alt="" aria-hidden="true" />
78
- </button>
79
- <button type="button" class="fullscreen-header__icon" aria-label="Informacoes">
80
- <img src=${infoFrameIconUrl} alt="" aria-hidden="true" />
81
- </button>
82
- <button type="button" class="fullscreen-header__icon" aria-label="Perfil de usuario">
83
- <img src=${profileFrameIconUrl} alt="" aria-hidden="true" />
84
- </button>
91
+ ${headerActions.map(
92
+ (action, index) => html`
93
+ <button
94
+ type="button"
95
+ class="fullscreen-header__icon"
96
+ aria-label=${action.ariaLabel ?? 'Acao do cabecalho'}
97
+ @click=${() => component.handleHeaderActionClick(action, index)}
98
+ >
99
+ <img src=${action.iconUrl} alt="" aria-hidden="true" />
100
+ </button>
101
+ `,
102
+ )}
85
103
  </div>
86
104
  </header>
87
105
 
106
+ <button
107
+ type="button"
108
+ class="fullscreen-exit-inline"
109
+ aria-label="Retornar para painel compacto"
110
+ @click=${() => component.exitFullscreen(true)}
111
+ >
112
+ <img src=${resizeScreenIconUrl} alt="" aria-hidden="true" />
113
+ </button>
114
+
88
115
  <div class="fullscreen-grid">
89
116
  ${renderConversationsPanel(component, { variant: 'sidebar' })}
90
117
  <div class="fullscreen-chat">
@@ -169,58 +169,58 @@ export const miniPanelStyles = css`
169
169
  gap: 12px;
170
170
  }
171
171
 
172
- .message {
173
- border-radius: 16px;
174
- border: 1px solid #e4eaee;
175
- padding: 10px 16px;
176
- max-width: 90%;
172
+ .message {
173
+ border-radius: 16px;
174
+ border: 1px solid #e4eaee;
175
+ padding: 10px 16px;
176
+ max-width: 90%;
177
177
  background: #fff;
178
178
  color: #1f2f36;
179
- font-size: 15px;
180
- }
181
-
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
- }
191
-
192
- .message__content pre {
193
- background: #0d161b;
194
- color: #f3f7fb;
195
- border-radius: 8px;
196
- padding: 10px;
197
- overflow-x: auto;
198
- margin: 6px 0;
199
- font-size: 14px;
200
- }
201
-
202
- .message__content code {
203
- background: #f1f4f7;
204
- padding: 2px 6px;
205
- border-radius: 6px;
206
- }
207
-
208
- .message__content blockquote {
209
- border-left: 3px solid #cfd6dc;
210
- margin: 6px 0;
211
- padding-left: 10px;
212
- color: #4b5a65;
213
- }
214
-
215
- .message__content ul,
216
- .message__content ol {
217
- padding-left: 20px;
218
- }
219
-
220
- .message--user {
221
- align-self: flex-end;
222
- background: #e5ebf0;
223
- border-color: #cfd6dc;
179
+ font-size: 15px;
180
+ }
181
+
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
+ }
191
+
192
+ .message__content pre {
193
+ background: #0d161b;
194
+ color: #f3f7fb;
195
+ border-radius: 8px;
196
+ padding: 10px;
197
+ overflow-x: auto;
198
+ margin: 6px 0;
199
+ font-size: 14px;
200
+ }
201
+
202
+ .message__content code {
203
+ background: #f1f4f7;
204
+ padding: 2px 6px;
205
+ border-radius: 6px;
206
+ }
207
+
208
+ .message__content blockquote {
209
+ border-left: 3px solid #cfd6dc;
210
+ margin: 6px 0;
211
+ padding-left: 10px;
212
+ color: #4b5a65;
213
+ }
214
+
215
+ .message__content ul,
216
+ .message__content ol {
217
+ padding-left: 20px;
218
+ }
219
+
220
+ .message--user {
221
+ align-self: flex-end;
222
+ background: #e5ebf0;
223
+ border-color: #cfd6dc;
224
224
  color: #1f2f36;
225
225
  padding: 8px 10px;
226
226
  }
@@ -287,18 +287,19 @@ export const miniPanelStyles = css`
287
287
  white-space: nowrap;
288
288
  }
289
289
 
290
- form {
291
- display: flex;
292
- align-items: center;
293
- gap: 12px;
294
- border: 1px solid #a4afbb;
295
- border-radius: 80px;
296
- padding: 10px 20px;
297
- background: #fff;
298
- width: 100%;
299
- max-width: 520px;
300
- margin-bottom: 0;
301
- max-height: 56px;
290
+ form {
291
+ display: flex;
292
+ align-items: center;
293
+ gap: 12px;
294
+ border: 1px solid #a4afbb;
295
+ border-radius: 80px;
296
+ padding: 8px 12px 8px 16px;
297
+ background: #fff;
298
+ width: 100%;
299
+ max-width: 520px;
300
+ margin-bottom: 0;
301
+ height: 56px;
302
+ box-sizing: border-box;
302
303
  }
303
304
 
304
305
  form input {
@@ -319,19 +320,20 @@ export const miniPanelStyles = css`
319
320
  }
320
321
 
321
322
  .input-button {
322
- width: 32px;
323
- height: 32px;
323
+ width: 40px;
324
+ height: 40px;
324
325
  border-radius: 50%;
325
326
  border: none;
326
327
  background: transparent;
327
328
  display: flex;
328
329
  align-items: center;
329
330
  justify-content: center;
331
+ padding: 0;
330
332
  }
331
333
 
332
334
  .input-button img {
333
- width: 32px;
334
- height: 32px;
335
+ width: 40px;
336
+ height: 40px;
335
337
  }
336
338
 
337
339
  .input-button:disabled {
@@ -9,6 +9,7 @@ const expandIconUrl = new URL('../../assets/icons/expandScreen.png', import.meta
9
9
  const iaCentralIconUrl = new URL('../../assets/icons/iaCentralIcon.png', import.meta.url).href;
10
10
  const plusFileSelectionUrl = new URL('../../assets/icons/plusFileSelection.png', import.meta.url).href;
11
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;
12
13
 
13
14
  export const renderChatSurface = (component: RioAssistWidget) => {
14
15
  const hasMessages = component.messages.length > 0;
@@ -103,6 +104,14 @@ export const renderChatSurface = (component: RioAssistWidget) => {
103
104
  }}
104
105
  ?disabled=${component.isLoading}
105
106
  />
107
+ <button
108
+ class="input-button submit-button"
109
+ type="submit"
110
+ aria-label="Enviar mensagem"
111
+ ?disabled=${component.isLoading}
112
+ >
113
+ <img src=${arrowButtonUrl} alt="" aria-hidden="true" />
114
+ </button>
106
115
  </form>
107
116
 
108
117
  <p class="footnote">
@@ -19,13 +19,20 @@ export type ChatMessage = {
19
19
  timestamp: number;
20
20
  };
21
21
 
22
- type ConversationItem = {
23
- id: string;
24
- title: string;
25
- updatedAt: string;
26
- };
27
-
28
- export class RioAssistWidget extends LitElement {
22
+ type ConversationItem = {
23
+ id: string;
24
+ title: string;
25
+ updatedAt: string;
26
+ };
27
+
28
+ export type HeaderActionConfig = {
29
+ id?: string;
30
+ iconUrl: string;
31
+ ariaLabel?: string;
32
+ onClick?: () => void;
33
+ };
34
+
35
+ export class RioAssistWidget extends LitElement {
29
36
  static styles = widgetStyles;
30
37
 
31
38
  static properties = {
@@ -51,6 +58,8 @@ export class RioAssistWidget extends LitElement {
51
58
  conversations: { state: true },
52
59
  conversationHistoryLoading: { type: Boolean, state: true },
53
60
  activeConversationTitle: { state: true },
61
+ headerActions: { attribute: false },
62
+ homeUrl: { type: String, attribute: 'data-home-url' },
54
63
  };
55
64
 
56
65
  open = false;
@@ -101,6 +110,10 @@ export class RioAssistWidget extends LitElement {
101
110
 
102
111
  activeConversationTitle: string | null = null;
103
112
 
113
+ headerActions: HeaderActionConfig[] = [];
114
+
115
+ homeUrl = '';
116
+
104
117
  private generateConversationId() {
105
118
  if (!this.conversationUserId) {
106
119
  this.conversationUserId = this.inferUserIdFromToken();
@@ -343,24 +356,70 @@ export class RioAssistWidget extends LitElement {
343
356
  }
344
357
  }
345
358
 
346
- handleConversationAction(action: 'rename' | 'delete', id: string) {
347
- this.conversationMenuId = null;
348
- const conversation = this.conversations.find((item) => item.id === id);
349
- if (!conversation) {
350
- return;
359
+ handleConversationAction(action: 'rename' | 'delete', id: string) {
360
+ this.conversationMenuId = null;
361
+ const conversation = this.conversations.find((item) => item.id === id);
362
+ if (!conversation) {
363
+ return;
351
364
  }
352
365
 
353
- const message = `${
354
- action === 'rename' ? 'Renomear' : 'Excluir'
355
- } "${conversation.title}"`;
356
- console.info(`[Mock] ${message}`);
357
- }
358
-
359
- handleCloseAction() {
360
- if (this.isFullscreen) {
361
- this.exitFullscreen(true);
362
- return;
363
- }
366
+ const message = `${
367
+ action === 'rename' ? 'Renomear' : 'Excluir'
368
+ } "${conversation.title}"`;
369
+ console.info(`[Mock] ${message}`);
370
+ }
371
+
372
+ handleHomeNavigation() {
373
+ const detail = { url: this.homeUrl || null };
374
+ const allowed = this.dispatchEvent(
375
+ new CustomEvent('rioassist:home', {
376
+ detail,
377
+ bubbles: true,
378
+ composed: true,
379
+ cancelable: true,
380
+ }),
381
+ );
382
+
383
+ if (!allowed) {
384
+ return;
385
+ }
386
+
387
+ if (this.homeUrl) {
388
+ window.location.assign(this.homeUrl);
389
+ }
390
+ }
391
+
392
+ handleHeaderActionClick(action: HeaderActionConfig, index: number) {
393
+ const detail = {
394
+ index,
395
+ id: action.id ?? null,
396
+ ariaLabel: action.ariaLabel ?? null,
397
+ iconUrl: action.iconUrl,
398
+ };
399
+
400
+ const allowed = this.dispatchEvent(
401
+ new CustomEvent('rioassist:header-action', {
402
+ detail,
403
+ bubbles: true,
404
+ composed: true,
405
+ cancelable: true,
406
+ }),
407
+ );
408
+
409
+ if (!allowed) {
410
+ return;
411
+ }
412
+
413
+ if (typeof action.onClick === 'function') {
414
+ action.onClick();
415
+ }
416
+ }
417
+
418
+ handleCloseAction() {
419
+ if (this.isFullscreen) {
420
+ this.exitFullscreen(true);
421
+ return;
422
+ }
364
423
 
365
424
  if (this.showConversations) {
366
425
  this.closeConversationsPanel();