rio-assist-widget 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +57 -0
  2. package/dist/rio-assist.js +1087 -0
  3. package/index.html +46 -0
  4. package/package.json +27 -0
  5. package/playground-preview.png +0 -0
  6. package/src/assets/icons/checkFrame.png +0 -0
  7. package/src/assets/icons/edit.png +0 -0
  8. package/src/assets/icons/expandScreen.png +0 -0
  9. package/src/assets/icons/hamburgerMenuIcon.png +0 -0
  10. package/src/assets/icons/homeIcon.png +0 -0
  11. package/src/assets/icons/iaButtonIcon.png +0 -0
  12. package/src/assets/icons/iaCentralIcon.png +0 -0
  13. package/src/assets/icons/infoFrame.png +0 -0
  14. package/src/assets/icons/plusFileSelection.png +0 -0
  15. package/src/assets/icons/profileFrame.png +0 -0
  16. package/src/assets/icons/searchIcon.png +0 -0
  17. package/src/assets/icons/threePoints.png +0 -0
  18. package/src/assets/icons/trash.png +0 -0
  19. package/src/assets/icons/voiceRecoverIcon.png +0 -0
  20. package/src/components/conversations-panel/conversations-panel.styles.ts +243 -0
  21. package/src/components/conversations-panel/conversations-panel.template.ts +150 -0
  22. package/src/components/floating-button/floating-button.styles.ts +48 -0
  23. package/src/components/floating-button/floating-button.template.ts +16 -0
  24. package/src/components/fullscreen/fullscreen.styles.ts +159 -0
  25. package/src/components/fullscreen/fullscreen.template.ts +71 -0
  26. package/src/components/mini-panel/mini-panel.styles.ts +331 -0
  27. package/src/components/mini-panel/mini-panel.template.ts +167 -0
  28. package/src/components/rio-assist/index.ts +1 -0
  29. package/src/components/rio-assist/rio-assist.styles.ts +33 -0
  30. package/src/components/rio-assist/rio-assist.template.ts +21 -0
  31. package/src/components/rio-assist/rio-assist.ts +478 -0
  32. package/src/main.ts +72 -0
  33. package/src/playground.ts +23 -0
  34. package/src/services/rioWebsocket.ts +167 -0
  35. package/tsconfig.json +26 -0
  36. package/tsconfig.node.json +11 -0
  37. package/vite.config.ts +19 -0
  38. package/widget.png +0 -0
package/index.html ADDED
@@ -0,0 +1,46 @@
1
+ <!doctype html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>RIO Assist Widget</title>
7
+ <style>
8
+ body {
9
+ min-height: 100vh;
10
+ margin: 0;
11
+ font-family: 'Inter', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
12
+ background: linear-gradient(135deg, #0a1f33, #11486c);
13
+ color: #fff;
14
+ padding: 32px;
15
+ }
16
+
17
+ main {
18
+ max-width: 640px;
19
+ }
20
+
21
+ h1 {
22
+ margin-top: 0;
23
+ }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <main>
28
+ <h1>RIO Assist Widget</h1>
29
+ <p>
30
+ Playground de teste para o webcomponent. Clique no
31
+ botão flutuante no canto inferior direito para testar.
32
+ </p>
33
+ </main>
34
+
35
+ <rio-assist-widget
36
+ data-title="RIO Assist"
37
+ data-button-label="RIO Assist"
38
+ data-placeholder="Pergunte alguma coisa"
39
+ data-suggestions="Veículos com problemas|Valor das peças|Planos de manutenção"
40
+ data-accent-color="#c02267"
41
+ ></rio-assist-widget>
42
+
43
+ <script type="module" src="/src/main.ts"></script>
44
+ <script type="module" src="/src/playground.ts"></script>
45
+ </body>
46
+ </html>
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "rio-assist-widget",
3
+ "version": "0.1.0",
4
+ "description": "Web Component do painel lateral RIO Assist, pronto para ser embutido em qualquer projeto.",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "keywords": [
12
+ "web component",
13
+ "widget",
14
+ "rio assist",
15
+ "ai agent"
16
+ ],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "lit": "^3.1.2"
21
+ },
22
+ "devDependencies": {
23
+ "playwright-chromium": "^1.56.1",
24
+ "typescript": "^5.4.5",
25
+ "vite": "^5.2.0"
26
+ }
27
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,243 @@
1
+ import { css } from 'lit';
2
+
3
+ export const conversationsPanelStyles = css`
4
+ .conversations-panel {
5
+ position: absolute;
6
+ top: var(--header-height, 128px);
7
+ left: 0;
8
+ right: 0;
9
+ bottom: 0;
10
+ pointer-events: none;
11
+ display: flex;
12
+ flex-direction: column;
13
+ }
14
+
15
+ .conversations-panel--sidebar {
16
+ position: relative;
17
+ top: auto;
18
+ left: auto;
19
+ right: auto;
20
+ bottom: auto;
21
+ height: 100%;
22
+ width: 300px;
23
+ background: #eef2f6;
24
+ border-right: 1px solid #d4dee6;
25
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.02);
26
+ display: flex;
27
+ flex-direction: column;
28
+ min-height: 0;
29
+ }
30
+
31
+ .conversations-panel--open {
32
+ pointer-events: auto;
33
+ }
34
+
35
+ .conversations-panel__surface {
36
+ width: 100%;
37
+ height: 100%;
38
+ background: #d0d8de;
39
+ padding: 12px 32px 24px;
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: 16px;
43
+ transform: translateX(100%);
44
+ transition: transform 0.35s ease;
45
+ }
46
+
47
+ .conversations-panel--open .conversations-panel__surface {
48
+ transform: translateX(0);
49
+ }
50
+
51
+ .conversations-panel__surface--sidebar {
52
+ transform: none;
53
+ transition: none;
54
+ background: transparent;
55
+ padding: 12px 0 32px;
56
+ height: 100%;
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 12px;
60
+ min-height: 0;
61
+ overflow: hidden;
62
+ }
63
+
64
+ .conversation-search {
65
+ display: flex;
66
+ align-items: center;
67
+ border-radius: 4px;
68
+ border: 1px solid #b7c3cd;
69
+ padding: 0 14px;
70
+ background: rgba(255, 255, 255, 0.8);
71
+ gap: 10px;
72
+ width: 530px;
73
+ height: 34px;
74
+ box-sizing: border-box;
75
+ }
76
+
77
+ .conversations-panel--sidebar .conversation-search {
78
+ width: calc(100% - 32px);
79
+ height: 34px;
80
+ background: #fff;
81
+ border-radius: 6px;
82
+ border-color: #c8d4dc;
83
+ box-sizing: border-box;
84
+ margin: 0 16px;
85
+ }
86
+
87
+ .conversation-search input {
88
+ border: none;
89
+ background: transparent;
90
+ flex: 1;
91
+ height: 100%;
92
+ font-size: 14px;
93
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
94
+ font-style: italic;
95
+ color: #a4afbb;
96
+ outline: none;
97
+ }
98
+
99
+ .search-icon {
100
+ width: 16px;
101
+ height: 16px;
102
+ }
103
+
104
+ .conversation-list {
105
+ flex: 1;
106
+ overflow-y: auto;
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 8px;
110
+ min-height: 0;
111
+ }
112
+
113
+ .conversation-list-wrapper {
114
+ position: relative;
115
+ display: flex;
116
+ flex-direction: column;
117
+ flex: 1;
118
+ min-height: 0;
119
+ }
120
+
121
+ .conversation-list-wrapper--sidebar {
122
+ padding: 8px 0 0 0;
123
+ }
124
+
125
+ .conversations-panel--sidebar .conversation-list {
126
+ width: 100%;
127
+ padding-left: 16px;
128
+ padding-right: 20px;
129
+ overflow-y: auto;
130
+ min-height: 0;
131
+ }
132
+
133
+ .conversation-scrollbar {
134
+ position: absolute;
135
+ top: 8px;
136
+ right: 6px;
137
+ width: 6px;
138
+ height: calc(100% - 8px);
139
+ border-radius: 999px;
140
+ background: rgba(125, 143, 162, 0.15);
141
+ opacity: 0;
142
+ transition: opacity 0.2s ease;
143
+ pointer-events: none;
144
+ }
145
+
146
+ .conversation-scrollbar--visible {
147
+ opacity: 1;
148
+ }
149
+
150
+ .conversation-list-wrapper--sidebar:hover .conversation-scrollbar {
151
+ opacity: 1;
152
+ }
153
+
154
+ .conversation-scrollbar__thumb {
155
+ position: absolute;
156
+ width: 100%;
157
+ border-radius: 999px;
158
+ background: #7d8fa2;
159
+ min-height: 12px;
160
+ display: block;
161
+ }
162
+
163
+ .conversation-item {
164
+ display: flex;
165
+ align-items: center;
166
+ justify-content: space-between;
167
+ padding: 0 20px 0 6px;
168
+ border-radius: 8px;
169
+ color: #1f2f36;
170
+ font-size: 15px;
171
+ position: relative;
172
+ height: 40px;
173
+ }
174
+
175
+ .conversations-panel--sidebar .conversation-item {
176
+ border-radius: 8px;
177
+ background: transparent;
178
+ padding-right: 8px;
179
+ }
180
+
181
+ .conversations-panel--sidebar .conversation-item__text {
182
+ width: 236px;
183
+ min-height: 18px;
184
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
185
+ font-size: 14px;
186
+ font-weight: 400;
187
+ line-height: 18px;
188
+ }
189
+
190
+ .conversation-item__text {
191
+ flex: 1;
192
+ padding-right: 16px;
193
+ }
194
+
195
+ .conversation-menu-button {
196
+ width: 32px;
197
+ height: 32px;
198
+ border-radius: 50%;
199
+ border: none;
200
+ background: transparent;
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ }
205
+
206
+ .conversation-menu {
207
+ position: absolute;
208
+ top: calc(100% + 8px);
209
+ right: 12px;
210
+ background: #fff;
211
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
212
+ border-radius: 12px;
213
+ min-width: 140px;
214
+ padding: 8px 0;
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: 4px;
218
+ z-index: 3;
219
+ }
220
+
221
+ .conversation-menu--above {
222
+ top: auto;
223
+ bottom: calc(100% + 8px);
224
+ }
225
+
226
+ .conversation-menu button {
227
+ background: transparent;
228
+ border: none;
229
+ padding: 8px 14px;
230
+ display: flex;
231
+ align-items: center;
232
+ gap: 10px;
233
+ font-size: 13px;
234
+ color: #1f2f36;
235
+ text-align: left;
236
+ width: 100%;
237
+ }
238
+
239
+ .conversation-menu img {
240
+ width: 16px;
241
+ height: 16px;
242
+ }
243
+ `;
@@ -0,0 +1,150 @@
1
+ import { html } from 'lit';
2
+ import { classMap } from 'lit/directives/class-map.js';
3
+ import { styleMap } from 'lit/directives/style-map.js';
4
+ import type { RioAssistWidget } from '../rio-assist/rio-assist';
5
+
6
+ const threePointsIconUrl = new URL('../../assets/icons/threePoints.png', import.meta.url).href;
7
+ const editIconUrl = new URL('../../assets/icons/edit.png', import.meta.url).href;
8
+ const trashIconUrl = new URL('../../assets/icons/trash.png', import.meta.url).href;
9
+ const searchIconUrl = new URL('../../assets/icons/searchIcon.png', import.meta.url).href;
10
+
11
+ type ConversationsPanelVariant = 'drawer' | 'sidebar';
12
+
13
+ export const renderConversationsPanel = (
14
+ component: RioAssistWidget,
15
+ options: { variant?: ConversationsPanelVariant } = {},
16
+ ) => {
17
+ const variant = options.variant ?? 'drawer';
18
+ const isSidebar = variant === 'sidebar';
19
+ const isOpen = isSidebar || component.showConversations;
20
+
21
+ return html`
22
+ <div
23
+ class=${classMap({
24
+ 'conversations-panel': true,
25
+ 'conversations-panel--open': isOpen,
26
+ 'conversations-panel--sidebar': isSidebar,
27
+ })}
28
+ aria-hidden=${!isOpen}
29
+ @pointerdown=${(event: PointerEvent) =>
30
+ component.handleConversationsPanelPointer(event)}
31
+ >
32
+ <div
33
+ class=${classMap({
34
+ 'conversations-panel__surface': true,
35
+ 'conversations-panel__surface--sidebar': isSidebar,
36
+ })}
37
+ >
38
+ ${renderConversationSurface(component, variant)}
39
+ </div>
40
+ </div>
41
+ `;
42
+ };
43
+
44
+ const renderConversationSurface = (
45
+ component: RioAssistWidget,
46
+ variant: ConversationsPanelVariant,
47
+ ) => {
48
+ const isSidebar = variant === 'sidebar';
49
+
50
+ const list = html`
51
+ <div
52
+ class=${classMap({
53
+ 'conversation-list': true,
54
+ 'conversation-list--sidebar': isSidebar,
55
+ })}
56
+ @scroll=${isSidebar ? (event: Event) => component.handleConversationListScroll(event) : null}
57
+ >
58
+ ${component.filteredConversations.map(
59
+ (conversation) => {
60
+ const menuOpen = component.conversationMenuId === conversation.id;
61
+
62
+ return html`
63
+ <div class="conversation-item">
64
+ <div class="conversation-item__text">
65
+ ${conversation.title}
66
+ </div>
67
+ <button
68
+ class="conversation-menu-button"
69
+ type="button"
70
+ @click=${(event: Event) =>
71
+ component.handleConversationMenuToggle(event, conversation.id)}
72
+ >
73
+ <img src=${threePointsIconUrl} alt="" aria-hidden="true" />
74
+ </button>
75
+ ${menuOpen
76
+ ? html`
77
+ <div
78
+ class=${classMap({
79
+ 'conversation-menu': true,
80
+ 'conversation-menu--above':
81
+ component.conversationMenuPlacement === 'above',
82
+ })}
83
+ @click=${(event: Event) => event.stopPropagation()}
84
+ >
85
+ <button
86
+ type="button"
87
+ @click=${() =>
88
+ component.handleConversationAction('rename', conversation.id)}
89
+ >
90
+ <img src=${editIconUrl} alt="" aria-hidden="true" />
91
+ Renomear
92
+ </button>
93
+ <button
94
+ type="button"
95
+ @click=${() =>
96
+ component.handleConversationAction('delete', conversation.id)}
97
+ >
98
+ <img src=${trashIconUrl} alt="" aria-hidden="true" />
99
+ Excluir
100
+ </button>
101
+ </div>
102
+ `
103
+ : null}
104
+ </div>
105
+ `;
106
+ },
107
+ )}
108
+ </div>
109
+ `;
110
+
111
+ return html`
112
+ <div class="conversation-search">
113
+ <img class="search-icon" src=${searchIconUrl} alt="" aria-hidden="true" />
114
+ <input
115
+ type="text"
116
+ placeholder="Buscar nas conversas"
117
+ .value=${component.conversationSearch}
118
+ @input=${(event: InputEvent) => component.handleConversationSearch(event)}
119
+ />
120
+ </div>
121
+
122
+ <div
123
+ class=${classMap({
124
+ 'conversation-list-wrapper': true,
125
+ 'conversation-list-wrapper--sidebar': isSidebar,
126
+ })}
127
+ >
128
+ ${list}
129
+ ${isSidebar
130
+ ? html`
131
+ <div
132
+ class=${classMap({
133
+ 'conversation-scrollbar': true,
134
+ 'conversation-scrollbar--visible':
135
+ component.conversationScrollbar.visible,
136
+ })}
137
+ >
138
+ <span
139
+ class="conversation-scrollbar__thumb"
140
+ style=${styleMap({
141
+ height: `${component.conversationScrollbar.height}%`,
142
+ top: `${component.conversationScrollbar.top}%`,
143
+ })}
144
+ ></span>
145
+ </div>
146
+ `
147
+ : null}
148
+ </div>
149
+ `;
150
+ };
@@ -0,0 +1,48 @@
1
+ import { css } from 'lit';
2
+
3
+ export const floatingButtonStyles = css`
4
+ .floating-button {
5
+ position: absolute;
6
+ pointer-events: auto;
7
+ right: 0;
8
+ bottom: 32px;
9
+ width: 160px;
10
+ height: 64px;
11
+ padding: 0 26px 0 10px;
12
+ box-sizing: border-box;
13
+ display: inline-flex;
14
+ align-items: center;
15
+ gap: 8px;
16
+ color: #fff;
17
+ font-family: 'Source Sans Pro', 'Inter', sans-serif;
18
+ font-weight: 700;
19
+ font-size: 18px;
20
+ letter-spacing: -0.2px;
21
+ border-radius: 32px 0 0 32px;
22
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.25);
23
+ }
24
+
25
+ .floating-button img {
26
+ width: 40px;
27
+ height: 40px;
28
+ flex-shrink: 0;
29
+ display: block;
30
+ }
31
+
32
+ .floating-button span {
33
+ white-space: nowrap;
34
+ line-height: 1;
35
+ display: inline-block;
36
+ flex: 1;
37
+ text-align: left;
38
+ }
39
+
40
+ .floating-button:hover {
41
+ box-shadow: 0 16px 28px rgba(0, 0, 0, 0.3);
42
+ }
43
+
44
+ .canvas--fullscreen .floating-button {
45
+ opacity: 0;
46
+ pointer-events: none;
47
+ }
48
+ `;
@@ -0,0 +1,16 @@
1
+ import { html } from 'lit';
2
+ import type { RioAssistWidget } from '../rio-assist/rio-assist';
3
+
4
+ const buttonIconUrl = new URL('../../assets/icons/iaButtonIcon.png', import.meta.url).href;
5
+
6
+ export const renderFloatingButton = (component: RioAssistWidget) => html`
7
+ <button
8
+ class="floating-button"
9
+ style="background:${component.accentColor}"
10
+ @click=${() => component.togglePanel()}
11
+ aria-expanded=${component.open}
12
+ >
13
+ <img src=${buttonIconUrl} alt="" aria-hidden="true" />
14
+ <span>${component.buttonLabel}</span>
15
+ </button>
16
+ `;