rio-assist-widget 0.1.8 → 0.1.10
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 +1 -2
- package/dist/rio-assist.js +168 -113
- package/package.json +6 -2
- package/src/components/mini-panel/mini-panel.styles.ts +50 -17
- package/src/components/mini-panel/mini-panel.template.ts +4 -1
- package/src/components/rio-assist/rio-assist.ts +348 -107
- package/src/playground.ts +25 -24
- package/src/services/rioWebsocket.ts +120 -74
- package/src/types/markdown-it-task-lists.d.ts +1 -0
|
@@ -1,52 +1,69 @@
|
|
|
1
|
-
const WEBSOCKET_URL = 'wss://ws.volkswagen.latam-sandbox.rio.cloud';
|
|
2
|
-
const
|
|
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
|
-
|
|
4
|
+
export type RioIncomingMessage = {
|
|
5
|
+
text: string;
|
|
6
|
+
raw: string;
|
|
7
|
+
data: unknown;
|
|
8
|
+
action?: string;
|
|
9
|
+
};
|
|
9
10
|
|
|
10
11
|
export class RioWebsocketClient {
|
|
11
12
|
readonly token: string;
|
|
12
13
|
|
|
13
14
|
private socket: WebSocket | null = null;
|
|
14
15
|
|
|
15
|
-
private connectPromise: Promise<void> | null = null;
|
|
16
|
-
|
|
17
|
-
private readonly listeners = new Set<(message: RioIncomingMessage) => void>();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
+
}
|
|
22
25
|
|
|
23
26
|
matchesToken(value: string) {
|
|
24
27
|
return this.token === value;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
async sendMessage(message: string) {
|
|
28
|
-
const socket = await this.ensureConnection();
|
|
29
|
-
|
|
30
|
-
const payload = {
|
|
31
|
-
action: 'sendMessage',
|
|
32
|
-
message,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
30
|
+
async sendMessage(message: string) {
|
|
31
|
+
const socket = await this.ensureConnection();
|
|
32
|
+
|
|
33
|
+
const payload = {
|
|
34
|
+
action: 'sendMessage',
|
|
35
|
+
message,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
socket.send(JSON.stringify(payload));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async requestHistory(options: { conversationId?: string; limit?: number } = {}) {
|
|
42
|
+
const socket = await this.ensureConnection();
|
|
43
|
+
const payload: Record<string, unknown> = {
|
|
44
|
+
action: 'getHistory',
|
|
45
|
+
limit: options.limit ?? 50,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (options.conversationId) {
|
|
49
|
+
payload.conversationId = options.conversationId;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
socket.send(JSON.stringify(payload));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onMessage(listener: (message: RioIncomingMessage) => void) {
|
|
56
|
+
this.listeners.add(listener);
|
|
57
|
+
return () => this.listeners.delete(listener);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
close() {
|
|
61
|
+
this.stopHeartbeat();
|
|
62
|
+
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
63
|
+
this.socket.close();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.connectPromise = null;
|
|
50
67
|
this.socket = null;
|
|
51
68
|
this.listeners.clear();
|
|
52
69
|
}
|
|
@@ -65,27 +82,32 @@ export class RioWebsocketClient {
|
|
|
65
82
|
`${WEBSOCKET_URL}?token=${encodeURIComponent(this.token)}`,
|
|
66
83
|
);
|
|
67
84
|
|
|
68
|
-
this.socket.addEventListener('message', (event) => this.handleMessage(event));
|
|
69
|
-
this.socket.addEventListener('close', () => {
|
|
70
|
-
this.connectPromise = null;
|
|
71
|
-
this.socket = null;
|
|
72
|
-
|
|
85
|
+
this.socket.addEventListener('message', (event) => this.handleMessage(event));
|
|
86
|
+
this.socket.addEventListener('close', () => {
|
|
87
|
+
this.connectPromise = null;
|
|
88
|
+
this.socket = null;
|
|
89
|
+
this.stopHeartbeat();
|
|
90
|
+
});
|
|
73
91
|
|
|
74
92
|
this.connectPromise = new Promise((resolve, reject) => {
|
|
75
93
|
if (!this.socket) {
|
|
76
94
|
reject(new Error('Falha ao criar conexão WebSocket.'));
|
|
77
95
|
return;
|
|
78
96
|
}
|
|
79
|
-
|
|
80
|
-
const handleOpen = () => {
|
|
81
|
-
cleanup();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
|
|
98
|
+
const handleOpen = () => {
|
|
99
|
+
cleanup();
|
|
100
|
+
if (this.socket) {
|
|
101
|
+
this.startHeartbeat(this.socket);
|
|
102
|
+
}
|
|
103
|
+
resolve();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleError = () => {
|
|
107
|
+
cleanup();
|
|
108
|
+
this.stopHeartbeat();
|
|
109
|
+
this.socket?.close();
|
|
110
|
+
this.socket = null;
|
|
89
111
|
this.connectPromise = null;
|
|
90
112
|
reject(
|
|
91
113
|
new Error(
|
|
@@ -109,33 +131,57 @@ export class RioWebsocketClient {
|
|
|
109
131
|
throw new Error('Conexão WebSocket do Rio Insight não está pronta.');
|
|
110
132
|
}
|
|
111
133
|
|
|
112
|
-
return this.socket;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
return this.socket;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private startHeartbeat(socket: WebSocket) {
|
|
138
|
+
this.stopHeartbeat();
|
|
139
|
+
this.heartbeatId = window.setInterval(() => {
|
|
140
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
141
|
+
socket.send(JSON.stringify({ action: 'ping' }));
|
|
142
|
+
}
|
|
143
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private stopHeartbeat() {
|
|
147
|
+
if (this.heartbeatId !== null) {
|
|
148
|
+
window.clearInterval(this.heartbeatId);
|
|
149
|
+
this.heartbeatId = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private async handleMessage(event: MessageEvent) {
|
|
154
|
+
const raw = await this.readMessage(event.data);
|
|
155
|
+
let parsed: unknown = null;
|
|
156
|
+
let text = raw;
|
|
157
|
+
let action: string | undefined;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
parsed = JSON.parse(raw);
|
|
161
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
162
|
+
const maybeAction =
|
|
163
|
+
(parsed as any).action ?? (parsed as any).type ?? (parsed as any).event;
|
|
164
|
+
|
|
165
|
+
if (typeof maybeAction === 'string') {
|
|
166
|
+
action = maybeAction;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const maybeText =
|
|
170
|
+
(parsed as any).message ??
|
|
171
|
+
(parsed as any).response ??
|
|
172
|
+
(parsed as any).text ??
|
|
173
|
+
(parsed as any).content;
|
|
128
174
|
|
|
129
175
|
if (typeof maybeText === 'string') {
|
|
130
176
|
text = maybeText;
|
|
131
177
|
}
|
|
132
178
|
}
|
|
133
|
-
} catch {
|
|
134
|
-
parsed = null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
this.listeners.forEach((listener) => listener({ text, raw, data: parsed }));
|
|
138
|
-
}
|
|
179
|
+
} catch {
|
|
180
|
+
parsed = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.listeners.forEach((listener) => listener({ text, raw, data: parsed, action }));
|
|
184
|
+
}
|
|
139
185
|
|
|
140
186
|
private async readMessage(
|
|
141
187
|
data: MessageEvent['data'],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module 'markdown-it-task-lists';
|