rio-assist-widget 0.1.28 → 0.1.32
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 +62 -62
- package/dist/rio-assist.js +164 -104
- package/index.html +38 -38
- package/package.json +40 -40
- package/src/components/conversations-panel/conversations-panel.template.ts +27 -27
- package/src/components/fullscreen/fullscreen.styles.ts +75 -65
- package/src/components/fullscreen/fullscreen.template.ts +34 -0
- package/src/components/mini-panel/mini-panel.styles.ts +52 -51
- package/src/components/mini-panel/mini-panel.template.ts +167 -189
- package/src/components/rio-assist/rio-assist.styles.ts +101 -92
- package/src/components/rio-assist/rio-assist.template.ts +106 -75
- package/src/components/rio-assist/rio-assist.ts +373 -347
- package/src/main.ts +15 -15
- package/src/playground.ts +31 -31
- package/src/services/rioWebsocket.ts +142 -142
package/src/main.ts
CHANGED
|
@@ -13,21 +13,21 @@ export type RioAssistOptions = {
|
|
|
13
13
|
|
|
14
14
|
const DEFAULT_OPTIONS: Required<Omit<RioAssistOptions, 'target'>> = {
|
|
15
15
|
title: 'Rio Insight',
|
|
16
|
-
buttonLabel: 'Rio Insight',
|
|
17
|
-
placeholder: 'Pergunte alguma coisa',
|
|
18
|
-
suggestions: [
|
|
19
|
-
'Resumo da Frota',
|
|
20
|
-
'Frota Disponível',
|
|
21
|
-
'Chamados Abertos',
|
|
22
|
-
'Parados + Causas',
|
|
23
|
-
'Aguardando Peças',
|
|
24
|
-
'Principais Gargalos',
|
|
25
|
-
'Tempo por Concessionária',
|
|
26
|
-
'Tempo de Ciclo',
|
|
27
|
-
'Preventiva x Corretiva',
|
|
28
|
-
],
|
|
29
|
-
accentColor: '#008B9A',
|
|
30
|
-
apiBaseUrl: '',
|
|
16
|
+
buttonLabel: 'Rio Insight',
|
|
17
|
+
placeholder: 'Pergunte alguma coisa',
|
|
18
|
+
suggestions: [
|
|
19
|
+
'Resumo da Frota',
|
|
20
|
+
'Frota Disponível',
|
|
21
|
+
'Chamados Abertos',
|
|
22
|
+
'Parados + Causas',
|
|
23
|
+
'Aguardando Peças',
|
|
24
|
+
'Principais Gargalos',
|
|
25
|
+
'Tempo por Concessionária',
|
|
26
|
+
'Tempo de Ciclo',
|
|
27
|
+
'Preventiva x Corretiva',
|
|
28
|
+
],
|
|
29
|
+
accentColor: '#008B9A',
|
|
30
|
+
apiBaseUrl: '',
|
|
31
31
|
rioToken: '',
|
|
32
32
|
};
|
|
33
33
|
|
package/src/playground.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import './main';
|
|
2
|
-
|
|
3
|
-
const rioToken = import.meta.env.VITE_RIO_TOKEN || 'SEU_TOKEN_RIO_AQUI';
|
|
4
|
-
const apiBaseUrl = import.meta.env.VITE_RIO_API_BASE_URL || '';
|
|
5
|
-
|
|
6
|
-
const boot = () => {
|
|
7
|
-
window.RioAssist?.init({
|
|
8
|
-
title: 'Rio Insight',
|
|
9
|
-
buttonLabel: 'Rio Insight',
|
|
10
|
-
accentColor: '#c02267',
|
|
11
|
-
rioToken,
|
|
12
|
-
apiBaseUrl,
|
|
13
|
-
suggestions: [
|
|
14
|
-
'Resumo da Frota',
|
|
15
|
-
'Frota Disponível',
|
|
16
|
-
'Chamados Abertos',
|
|
17
|
-
'Parados + Causas',
|
|
18
|
-
'Aguardando Peças',
|
|
19
|
-
'Principais Gargalos',
|
|
20
|
-
'Tempo por Concessionária',
|
|
21
|
-
'Tempo de Ciclo',
|
|
22
|
-
'Preventiva x Corretiva',
|
|
23
|
-
],
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
if (window.RioAssist) {
|
|
28
|
-
boot();
|
|
29
|
-
} else {
|
|
30
|
-
window.addEventListener('rio-assist-ready', boot, { once: true });
|
|
31
|
-
}
|
|
1
|
+
import './main';
|
|
2
|
+
|
|
3
|
+
const rioToken = import.meta.env.VITE_RIO_TOKEN || 'SEU_TOKEN_RIO_AQUI';
|
|
4
|
+
const apiBaseUrl = import.meta.env.VITE_RIO_API_BASE_URL || '';
|
|
5
|
+
|
|
6
|
+
const boot = () => {
|
|
7
|
+
window.RioAssist?.init({
|
|
8
|
+
title: 'Rio Insight',
|
|
9
|
+
buttonLabel: 'Rio Insight',
|
|
10
|
+
accentColor: '#c02267',
|
|
11
|
+
rioToken,
|
|
12
|
+
apiBaseUrl,
|
|
13
|
+
suggestions: [
|
|
14
|
+
'Resumo da Frota',
|
|
15
|
+
'Frota Disponível',
|
|
16
|
+
'Chamados Abertos',
|
|
17
|
+
'Parados + Causas',
|
|
18
|
+
'Aguardando Peças',
|
|
19
|
+
'Principais Gargalos',
|
|
20
|
+
'Tempo por Concessionária',
|
|
21
|
+
'Tempo de Ciclo',
|
|
22
|
+
'Preventiva x Corretiva',
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (window.RioAssist) {
|
|
28
|
+
boot();
|
|
29
|
+
} else {
|
|
30
|
+
window.addEventListener('rio-assist-ready', boot, { once: true });
|
|
31
|
+
}
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
const WEBSOCKET_URL = 'wss://ws.volkswagen.latam-sandbox.rio.cloud';
|
|
2
|
-
const HEARTBEAT_INTERVAL_MS = 5 * 60_000; // keep-alive before the 10min idle timeout
|
|
1
|
+
const WEBSOCKET_URL = 'wss://ws.volkswagen.latam-sandbox.rio.cloud';
|
|
2
|
+
const HEARTBEAT_INTERVAL_MS = 5 * 60_000; // keep-alive before the 10min idle timeout
|
|
3
3
|
|
|
4
|
-
export type RioIncomingMessage = {
|
|
5
|
-
text: string;
|
|
6
|
-
raw: string;
|
|
7
|
-
data: unknown;
|
|
8
|
-
action?: string;
|
|
9
|
-
};
|
|
4
|
+
export type RioIncomingMessage = {
|
|
5
|
+
text: string;
|
|
6
|
+
raw: string;
|
|
7
|
+
data: unknown;
|
|
8
|
+
action?: string;
|
|
9
|
+
};
|
|
10
10
|
|
|
11
11
|
export class RioWebsocketClient {
|
|
12
12
|
readonly token: string;
|
|
13
13
|
|
|
14
14
|
private socket: WebSocket | null = null;
|
|
15
15
|
|
|
16
|
-
private connectPromise: Promise<void> | null = null;
|
|
17
|
-
|
|
18
|
-
private readonly listeners = new Set<(message: RioIncomingMessage) => void>();
|
|
19
|
-
|
|
20
|
-
private heartbeatId: number | null = null;
|
|
21
|
-
|
|
22
|
-
constructor(token: string) {
|
|
23
|
-
this.token = token;
|
|
24
|
-
}
|
|
16
|
+
private connectPromise: Promise<void> | null = null;
|
|
17
|
+
|
|
18
|
+
private readonly listeners = new Set<(message: RioIncomingMessage) => void>();
|
|
19
|
+
|
|
20
|
+
private heartbeatId: number | null = null;
|
|
21
|
+
|
|
22
|
+
constructor(token: string) {
|
|
23
|
+
this.token = token;
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
matchesToken(value: string) {
|
|
27
27
|
return this.token === value;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async sendMessage(message: string, conversationId?: string | null) {
|
|
31
|
-
const socket = await this.ensureConnection();
|
|
32
|
-
|
|
33
|
-
const payload = {
|
|
34
|
-
action: 'sendMessage',
|
|
35
|
-
message,
|
|
36
|
-
conversationId: conversationId ?? null,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
console.info('[RioAssist][ws] enviando payload de mensagem', payload);
|
|
40
|
-
socket.send(JSON.stringify(payload));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async requestHistory(options: { conversationId?: string | null; limit?: number } = {}) {
|
|
44
|
-
const socket = await this.ensureConnection();
|
|
45
|
-
const payload: Record<string, unknown> = {
|
|
46
|
-
action: 'getHistory',
|
|
47
|
-
limit: options.limit ?? 50,
|
|
48
|
-
conversationId: options.conversationId ?? null,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
socket.send(JSON.stringify(payload));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async renameConversation(conversationId: string, newTitle: string) {
|
|
55
|
-
const socket = await this.ensureConnection();
|
|
56
|
-
const payload = {
|
|
57
|
-
action: 'renameConversation',
|
|
58
|
-
conversationId,
|
|
59
|
-
newTitle,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
console.info('[RioAssist][ws] enviando renameConversation', payload);
|
|
63
|
-
socket.send(JSON.stringify(payload));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async deleteConversation(conversationId: string) {
|
|
67
|
-
const socket = await this.ensureConnection();
|
|
68
|
-
const payload = {
|
|
69
|
-
action: 'deleteConversation',
|
|
70
|
-
conversationId,
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
console.info('[RioAssist][ws] enviando deleteConversation', payload);
|
|
74
|
-
socket.send(JSON.stringify(payload));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
onMessage(listener: (message: RioIncomingMessage) => void) {
|
|
78
|
-
this.listeners.add(listener);
|
|
79
|
-
return () => this.listeners.delete(listener);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
close() {
|
|
83
|
-
this.stopHeartbeat();
|
|
84
|
-
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
85
|
-
this.socket.close();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
this.connectPromise = null;
|
|
30
|
+
async sendMessage(message: string, conversationId?: string | null) {
|
|
31
|
+
const socket = await this.ensureConnection();
|
|
32
|
+
|
|
33
|
+
const payload = {
|
|
34
|
+
action: 'sendMessage',
|
|
35
|
+
message,
|
|
36
|
+
conversationId: conversationId ?? null,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
console.info('[RioAssist][ws] enviando payload de mensagem', payload);
|
|
40
|
+
socket.send(JSON.stringify(payload));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async requestHistory(options: { conversationId?: string | null; limit?: number } = {}) {
|
|
44
|
+
const socket = await this.ensureConnection();
|
|
45
|
+
const payload: Record<string, unknown> = {
|
|
46
|
+
action: 'getHistory',
|
|
47
|
+
limit: options.limit ?? 50,
|
|
48
|
+
conversationId: options.conversationId ?? null,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
socket.send(JSON.stringify(payload));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async renameConversation(conversationId: string, newTitle: string) {
|
|
55
|
+
const socket = await this.ensureConnection();
|
|
56
|
+
const payload = {
|
|
57
|
+
action: 'renameConversation',
|
|
58
|
+
conversationId,
|
|
59
|
+
newTitle,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
console.info('[RioAssist][ws] enviando renameConversation', payload);
|
|
63
|
+
socket.send(JSON.stringify(payload));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async deleteConversation(conversationId: string) {
|
|
67
|
+
const socket = await this.ensureConnection();
|
|
68
|
+
const payload = {
|
|
69
|
+
action: 'deleteConversation',
|
|
70
|
+
conversationId,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
console.info('[RioAssist][ws] enviando deleteConversation', payload);
|
|
74
|
+
socket.send(JSON.stringify(payload));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onMessage(listener: (message: RioIncomingMessage) => void) {
|
|
78
|
+
this.listeners.add(listener);
|
|
79
|
+
return () => this.listeners.delete(listener);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
close() {
|
|
83
|
+
this.stopHeartbeat();
|
|
84
|
+
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
85
|
+
this.socket.close();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.connectPromise = null;
|
|
89
89
|
this.socket = null;
|
|
90
90
|
this.listeners.clear();
|
|
91
91
|
}
|
|
@@ -104,32 +104,32 @@ export class RioWebsocketClient {
|
|
|
104
104
|
`${WEBSOCKET_URL}?token=${encodeURIComponent(this.token)}`,
|
|
105
105
|
);
|
|
106
106
|
|
|
107
|
-
this.socket.addEventListener('message', (event) => this.handleMessage(event));
|
|
108
|
-
this.socket.addEventListener('close', () => {
|
|
109
|
-
this.connectPromise = null;
|
|
110
|
-
this.socket = null;
|
|
111
|
-
this.stopHeartbeat();
|
|
112
|
-
});
|
|
107
|
+
this.socket.addEventListener('message', (event) => this.handleMessage(event));
|
|
108
|
+
this.socket.addEventListener('close', () => {
|
|
109
|
+
this.connectPromise = null;
|
|
110
|
+
this.socket = null;
|
|
111
|
+
this.stopHeartbeat();
|
|
112
|
+
});
|
|
113
113
|
|
|
114
114
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
115
115
|
if (!this.socket) {
|
|
116
116
|
reject(new Error('Falha ao criar conexão WebSocket.'));
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
const handleOpen = () => {
|
|
121
|
-
cleanup();
|
|
122
|
-
if (this.socket) {
|
|
123
|
-
this.startHeartbeat(this.socket);
|
|
124
|
-
}
|
|
125
|
-
resolve();
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const handleError = () => {
|
|
129
|
-
cleanup();
|
|
130
|
-
this.stopHeartbeat();
|
|
131
|
-
this.socket?.close();
|
|
132
|
-
this.socket = null;
|
|
119
|
+
|
|
120
|
+
const handleOpen = () => {
|
|
121
|
+
cleanup();
|
|
122
|
+
if (this.socket) {
|
|
123
|
+
this.startHeartbeat(this.socket);
|
|
124
|
+
}
|
|
125
|
+
resolve();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const handleError = () => {
|
|
129
|
+
cleanup();
|
|
130
|
+
this.stopHeartbeat();
|
|
131
|
+
this.socket?.close();
|
|
132
|
+
this.socket = null;
|
|
133
133
|
this.connectPromise = null;
|
|
134
134
|
reject(
|
|
135
135
|
new Error(
|
|
@@ -153,57 +153,57 @@ export class RioWebsocketClient {
|
|
|
153
153
|
throw new Error('Conexão WebSocket do Rio Insight não está pronta.');
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
return this.socket;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private startHeartbeat(socket: WebSocket) {
|
|
160
|
-
this.stopHeartbeat();
|
|
161
|
-
this.heartbeatId = window.setInterval(() => {
|
|
162
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
163
|
-
socket.send(JSON.stringify({ action: 'ping' }));
|
|
164
|
-
}
|
|
165
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
private stopHeartbeat() {
|
|
169
|
-
if (this.heartbeatId !== null) {
|
|
170
|
-
window.clearInterval(this.heartbeatId);
|
|
171
|
-
this.heartbeatId = null;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private async handleMessage(event: MessageEvent) {
|
|
176
|
-
const raw = await this.readMessage(event.data);
|
|
177
|
-
let parsed: unknown = null;
|
|
178
|
-
let text = raw;
|
|
179
|
-
let action: string | undefined;
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
parsed = JSON.parse(raw);
|
|
183
|
-
if (typeof parsed === 'object' && parsed !== null) {
|
|
184
|
-
const maybeAction =
|
|
185
|
-
(parsed as any).action ?? (parsed as any).type ?? (parsed as any).event;
|
|
186
|
-
|
|
187
|
-
if (typeof maybeAction === 'string') {
|
|
188
|
-
action = maybeAction;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const maybeText =
|
|
192
|
-
(parsed as any).message ??
|
|
193
|
-
(parsed as any).response ??
|
|
194
|
-
(parsed as any).text ??
|
|
195
|
-
(parsed as any).content;
|
|
156
|
+
return this.socket;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private startHeartbeat(socket: WebSocket) {
|
|
160
|
+
this.stopHeartbeat();
|
|
161
|
+
this.heartbeatId = window.setInterval(() => {
|
|
162
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
163
|
+
socket.send(JSON.stringify({ action: 'ping' }));
|
|
164
|
+
}
|
|
165
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private stopHeartbeat() {
|
|
169
|
+
if (this.heartbeatId !== null) {
|
|
170
|
+
window.clearInterval(this.heartbeatId);
|
|
171
|
+
this.heartbeatId = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private async handleMessage(event: MessageEvent) {
|
|
176
|
+
const raw = await this.readMessage(event.data);
|
|
177
|
+
let parsed: unknown = null;
|
|
178
|
+
let text = raw;
|
|
179
|
+
let action: string | undefined;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
parsed = JSON.parse(raw);
|
|
183
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
184
|
+
const maybeAction =
|
|
185
|
+
(parsed as any).action ?? (parsed as any).type ?? (parsed as any).event;
|
|
186
|
+
|
|
187
|
+
if (typeof maybeAction === 'string') {
|
|
188
|
+
action = maybeAction;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const maybeText =
|
|
192
|
+
(parsed as any).message ??
|
|
193
|
+
(parsed as any).response ??
|
|
194
|
+
(parsed as any).text ??
|
|
195
|
+
(parsed as any).content;
|
|
196
196
|
|
|
197
197
|
if (typeof maybeText === 'string') {
|
|
198
198
|
text = maybeText;
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
} catch {
|
|
202
|
-
parsed = null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
this.listeners.forEach((listener) => listener({ text, raw, data: parsed, action }));
|
|
206
|
-
}
|
|
201
|
+
} catch {
|
|
202
|
+
parsed = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.listeners.forEach((listener) => listener({ text, raw, data: parsed, action }));
|
|
206
|
+
}
|
|
207
207
|
|
|
208
208
|
private async readMessage(
|
|
209
209
|
data: MessageEvent['data'],
|