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/src/main.ts ADDED
@@ -0,0 +1,72 @@
1
+ import './components/rio-assist';
2
+
3
+ export type RioAssistOptions = {
4
+ target?: HTMLElement;
5
+ title?: string;
6
+ buttonLabel?: string;
7
+ placeholder?: string;
8
+ suggestions?: string[];
9
+ accentColor?: string;
10
+ apiBaseUrl?: string;
11
+ rioToken?: string;
12
+ };
13
+
14
+ const DEFAULT_OPTIONS: Required<Omit<RioAssistOptions, 'target'>> = {
15
+ title: 'RIO Assist',
16
+ buttonLabel: 'RIO Assist',
17
+ placeholder: 'Pergunte alguma coisa',
18
+ suggestions: [
19
+ 'Veículos com problemas',
20
+ 'Valor das peças',
21
+ 'Planos de manutenção',
22
+ ],
23
+ accentColor: '#008B9A',
24
+ apiBaseUrl: '',
25
+ rioToken: '',
26
+ };
27
+
28
+ const widgetTagName = 'rio-assist-widget';
29
+
30
+ function ensureElement(options: RioAssistOptions = {}) {
31
+ const {
32
+ target = document.body,
33
+ ...rest
34
+ } = options;
35
+
36
+ let widget = document.querySelector(widgetTagName) as HTMLElement | null;
37
+
38
+ if (!widget) {
39
+ widget = document.createElement(widgetTagName);
40
+ target.appendChild(widget);
41
+ }
42
+
43
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...rest };
44
+
45
+ Object.entries(mergedOptions).forEach(([key, value]) => {
46
+ if (value === undefined) {
47
+ return;
48
+ }
49
+
50
+ widget?.setAttribute(
51
+ `data-${key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`,
52
+ Array.isArray(value) ? value.join('|') : String(value),
53
+ );
54
+ });
55
+ }
56
+
57
+ declare global {
58
+ interface Window {
59
+ RioAssist?: {
60
+ init: (options?: RioAssistOptions) => void;
61
+ };
62
+ }
63
+ }
64
+
65
+ if (typeof window !== 'undefined') {
66
+ window.RioAssist = window.RioAssist ?? {
67
+ init: ensureElement,
68
+ };
69
+ window.dispatchEvent(new Event('rio-assist-ready'));
70
+ }
71
+
72
+
@@ -0,0 +1,23 @@
1
+ import './main';
2
+
3
+ const boot = () => {
4
+ window.RioAssist?.init({
5
+ title: 'RIO Assist',
6
+ buttonLabel: 'RIO Assist',
7
+ accentColor: '#c02267',
8
+ rioToken: 'SEU_TOKEN_RIO_AQUI',
9
+ suggestions: [
10
+ 'Veículos com problemas',
11
+ 'Valor das peças',
12
+ 'Planos de manutenção',
13
+ ],
14
+ });
15
+ };
16
+
17
+ if (window.RioAssist) {
18
+ boot();
19
+ } else {
20
+ window.addEventListener('rio-assist-ready', boot, { once: true });
21
+ }
22
+
23
+
@@ -0,0 +1,167 @@
1
+ const WEBSOCKET_URL = 'wss://ws.volkswagen.latam-sandbox.rio.cloud';
2
+ const DEFAULT_AGENT_MODEL = 'claude-3-sonnet';
3
+
4
+ export type RioIncomingMessage = {
5
+ text: string;
6
+ raw: string;
7
+ data: unknown;
8
+ };
9
+
10
+ export class RioWebsocketClient {
11
+ readonly token: string;
12
+
13
+ private socket: WebSocket | null = null;
14
+
15
+ private connectPromise: Promise<void> | null = null;
16
+
17
+ private readonly listeners = new Set<(message: RioIncomingMessage) => void>();
18
+
19
+ constructor(token: string) {
20
+ this.token = token;
21
+ }
22
+
23
+ matchesToken(value: string) {
24
+ return this.token === value;
25
+ }
26
+
27
+ async sendMessage(message: string) {
28
+ const socket = await this.ensureConnection();
29
+
30
+ const payload = {
31
+ action: 'sendMessage',
32
+ message,
33
+ agentModel: DEFAULT_AGENT_MODEL,
34
+ };
35
+
36
+ socket.send(JSON.stringify(payload));
37
+ }
38
+
39
+ onMessage(listener: (message: RioIncomingMessage) => void) {
40
+ this.listeners.add(listener);
41
+ return () => this.listeners.delete(listener);
42
+ }
43
+
44
+ close() {
45
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
46
+ this.socket.close();
47
+ }
48
+
49
+ this.connectPromise = null;
50
+ this.socket = null;
51
+ this.listeners.clear();
52
+ }
53
+
54
+ private async ensureConnection(): Promise<WebSocket> {
55
+ if (
56
+ this.socket &&
57
+ (this.socket.readyState === WebSocket.OPEN ||
58
+ this.socket.readyState === WebSocket.CONNECTING)
59
+ ) {
60
+ await this.connectPromise;
61
+ return this.socket;
62
+ }
63
+
64
+ this.socket = new WebSocket(
65
+ `${WEBSOCKET_URL}?token=${encodeURIComponent(this.token)}`,
66
+ );
67
+
68
+ this.socket.addEventListener('message', (event) => this.handleMessage(event));
69
+ this.socket.addEventListener('close', () => {
70
+ this.connectPromise = null;
71
+ this.socket = null;
72
+ });
73
+
74
+ this.connectPromise = new Promise((resolve, reject) => {
75
+ if (!this.socket) {
76
+ reject(new Error('Falha ao criar conexão WebSocket.'));
77
+ return;
78
+ }
79
+
80
+ const handleOpen = () => {
81
+ cleanup();
82
+ resolve();
83
+ };
84
+
85
+ const handleError = () => {
86
+ cleanup();
87
+ this.socket?.close();
88
+ this.socket = null;
89
+ this.connectPromise = null;
90
+ reject(
91
+ new Error(
92
+ 'Não foi possível abrir conexão com o websocket do RIO Assist.',
93
+ ),
94
+ );
95
+ };
96
+
97
+ const cleanup = () => {
98
+ this.socket?.removeEventListener('open', handleOpen);
99
+ this.socket?.removeEventListener('error', handleError);
100
+ };
101
+
102
+ this.socket.addEventListener('open', handleOpen, { once: true });
103
+ this.socket.addEventListener('error', handleError, { once: true });
104
+ });
105
+
106
+ await this.connectPromise;
107
+
108
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
109
+ throw new Error('Conexão WebSocket do RIO Assist não está pronta.');
110
+ }
111
+
112
+ return this.socket;
113
+ }
114
+
115
+ private async handleMessage(event: MessageEvent) {
116
+ const raw = await this.readMessage(event.data);
117
+ let parsed: unknown = null;
118
+ let text = raw;
119
+
120
+ try {
121
+ parsed = JSON.parse(raw);
122
+ if (typeof parsed === 'object' && parsed !== null) {
123
+ const maybeText =
124
+ (parsed as any).message ??
125
+ (parsed as any).response ??
126
+ (parsed as any).text ??
127
+ (parsed as any).content;
128
+
129
+ if (typeof maybeText === 'string') {
130
+ text = maybeText;
131
+ }
132
+ }
133
+ } catch {
134
+ parsed = null;
135
+ }
136
+
137
+ this.listeners.forEach((listener) => listener({ text, raw, data: parsed }));
138
+ }
139
+
140
+ private async readMessage(
141
+ data: MessageEvent['data'],
142
+ ): Promise<string> {
143
+ if (typeof data === 'string') {
144
+ return data;
145
+ }
146
+
147
+ if (data instanceof Blob) {
148
+ return data.text();
149
+ }
150
+
151
+ if (data instanceof ArrayBuffer) {
152
+ return new TextDecoder().decode(new Uint8Array(data));
153
+ }
154
+
155
+ if (ArrayBuffer.isView(data)) {
156
+ return new TextDecoder().decode(
157
+ new Uint8Array(
158
+ data.buffer,
159
+ data.byteOffset,
160
+ data.byteLength,
161
+ ),
162
+ );
163
+ }
164
+
165
+ return String(data ?? '');
166
+ }
167
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": false,
5
+ "module": "ESNext",
6
+ "moduleResolution": "Node",
7
+ "lib": [
8
+ "ES2020",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "esModuleInterop": true,
13
+ "allowSyntheticDefaultImports": true,
14
+ "strict": true,
15
+ "skipLibCheck": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "noEmit": true,
18
+ "types": [
19
+ "vite/client"
20
+ ],
21
+ "baseUrl": "./src"
22
+ },
23
+ "include": [
24
+ "src/**/*.ts"
25
+ ]
26
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": [
9
+ "vite.config.ts"
10
+ ]
11
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ build: {
5
+ lib: {
6
+ name: 'RioAssist',
7
+ entry: 'src/main.ts',
8
+ fileName: () => 'rio-assist.js',
9
+ formats: ['es', 'iife'],
10
+ },
11
+ rollupOptions: {
12
+ output: {
13
+ globals: {
14
+ lit: 'lit',
15
+ },
16
+ },
17
+ },
18
+ },
19
+ });
package/widget.png ADDED
Binary file