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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rio-assist-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Web Component do painel lateral Rio Insight, pronto para ser embutido em qualquer projeto.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -26,9 +26,13 @@
|
|
|
26
26
|
"README.md"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"
|
|
29
|
+
"dompurify": "^3.3.0",
|
|
30
|
+
"lit": "^3.1.2",
|
|
31
|
+
"markdown-it": "^14.1.0",
|
|
32
|
+
"markdown-it-task-lists": "^2.1.1"
|
|
30
33
|
},
|
|
31
34
|
"devDependencies": {
|
|
35
|
+
"@types/dompurify": "^3.0.5",
|
|
32
36
|
"playwright-chromium": "^1.56.1",
|
|
33
37
|
"typescript": "^5.4.5",
|
|
34
38
|
"vite": "^5.2.0"
|
|
@@ -169,25 +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
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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;
|
|
191
224
|
color: #1f2f36;
|
|
192
225
|
padding: 8px 10px;
|
|
193
226
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { html } from 'lit';
|
|
2
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
2
3
|
import { classMap } from 'lit/directives/class-map.js';
|
|
3
4
|
import type { RioAssistWidget } from '../rio-assist/rio-assist';
|
|
4
5
|
import { renderConversationsPanel } from '../conversations-panel/conversations-panel.template';
|
|
@@ -30,7 +31,9 @@ export const renderChatSurface = (component: RioAssistWidget) => {
|
|
|
30
31
|
'message--assistant': message.role === 'assistant',
|
|
31
32
|
})}
|
|
32
33
|
>
|
|
33
|
-
<
|
|
34
|
+
<div class="message__content">
|
|
35
|
+
${unsafeHTML(message.html ?? message.text)}
|
|
36
|
+
</div>
|
|
34
37
|
<time>
|
|
35
38
|
${new Date(message.timestamp).toLocaleTimeString('pt-BR', {
|
|
36
39
|
hour: '2-digit',
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
import { widgetStyles } from './rio-assist.styles';
|
|
3
|
-
import { renderRioAssist } from './rio-assist.template';
|
|
4
|
-
import {
|
|
5
|
-
RioWebsocketClient,
|
|
6
|
-
type RioIncomingMessage,
|
|
7
|
-
} from '../../services/rioWebsocket';
|
|
1
|
+
import { LitElement, type PropertyValues } from 'lit';
|
|
2
|
+
import { widgetStyles } from './rio-assist.styles';
|
|
3
|
+
import { renderRioAssist } from './rio-assist.template';
|
|
4
|
+
import {
|
|
5
|
+
RioWebsocketClient,
|
|
6
|
+
type RioIncomingMessage,
|
|
7
|
+
} from '../../services/rioWebsocket';
|
|
8
|
+
import MarkdownIt from 'markdown-it';
|
|
9
|
+
import markdownItTaskLists from 'markdown-it-task-lists';
|
|
10
|
+
import DOMPurify from 'dompurify';
|
|
8
11
|
|
|
9
12
|
type ChatRole = 'user' | 'assistant';
|
|
10
13
|
|
|
11
|
-
export type ChatMessage = {
|
|
12
|
-
id: string;
|
|
13
|
-
role: ChatRole;
|
|
14
|
-
text: string;
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export type ChatMessage = {
|
|
15
|
+
id: string;
|
|
16
|
+
role: ChatRole;
|
|
17
|
+
text: string;
|
|
18
|
+
html?: string;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
};
|
|
17
21
|
|
|
18
22
|
type ConversationItem = {
|
|
19
23
|
id: string;
|
|
@@ -43,8 +47,9 @@ export class RioAssistWidget extends LitElement {
|
|
|
43
47
|
conversationMenuPlacement: { state: true },
|
|
44
48
|
isFullscreen: { type: Boolean, state: true },
|
|
45
49
|
conversationScrollbar: { state: true },
|
|
46
|
-
showNewConversationShortcut: { type: Boolean, state: true },
|
|
47
|
-
|
|
50
|
+
showNewConversationShortcut: { type: Boolean, state: true },
|
|
51
|
+
conversations: { state: true },
|
|
52
|
+
};
|
|
48
53
|
|
|
49
54
|
open = false;
|
|
50
55
|
|
|
@@ -98,33 +103,21 @@ export class RioAssistWidget extends LitElement {
|
|
|
98
103
|
|
|
99
104
|
private conversationScrollbarDraggingId: number | null = null;
|
|
100
105
|
|
|
101
|
-
private conversationScrollbarDragState: {
|
|
102
|
-
startY: number;
|
|
103
|
-
startThumbTop: number;
|
|
104
|
-
trackHeight: number;
|
|
105
|
-
thumbHeight: number;
|
|
106
|
-
list: HTMLElement;
|
|
107
|
-
} | null = null;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'Como automatizar preenchimento de odômetro.',
|
|
117
|
-
'Valor das peças da próxima revisão.',
|
|
118
|
-
'O que é revisão de assentamento?',
|
|
119
|
-
'Alertas críticos ativos.',
|
|
120
|
-
'Veículo superaquecendo, causas e recomendações.',
|
|
121
|
-
'Calibragem recomendada nos pneus do e-Delivery.',
|
|
122
|
-
'Quantos mil km trocar o óleo do motor.',
|
|
123
|
-
'Qual a vida útil da bateria Moura M100HE.',
|
|
124
|
-
][index % 11],
|
|
125
|
-
updatedAt: new Date(Date.now() - index * 3600_000).toISOString(),
|
|
126
|
-
}),
|
|
127
|
-
);
|
|
106
|
+
private conversationScrollbarDragState: {
|
|
107
|
+
startY: number;
|
|
108
|
+
startThumbTop: number;
|
|
109
|
+
trackHeight: number;
|
|
110
|
+
thumbHeight: number;
|
|
111
|
+
list: HTMLElement;
|
|
112
|
+
} | null = null;
|
|
113
|
+
|
|
114
|
+
private markdownRenderer = new MarkdownIt({
|
|
115
|
+
html: false,
|
|
116
|
+
linkify: true,
|
|
117
|
+
breaks: true,
|
|
118
|
+
}).use(markdownItTaskLists);
|
|
119
|
+
|
|
120
|
+
conversations: ConversationItem[] = [];
|
|
128
121
|
|
|
129
122
|
get suggestions(): string[] {
|
|
130
123
|
if (!this.suggestionsSource) {
|
|
@@ -211,21 +204,25 @@ export class RioAssistWidget extends LitElement {
|
|
|
211
204
|
}
|
|
212
205
|
}
|
|
213
206
|
|
|
214
|
-
openConversationsPanel() {
|
|
215
|
-
this.showConversations = true;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
this.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
207
|
+
openConversationsPanel() {
|
|
208
|
+
this.showConversations = true;
|
|
209
|
+
this.requestConversationHistory();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
closeConversationsPanel() {
|
|
213
|
+
this.showConversations = false;
|
|
214
|
+
this.conversationMenuId = null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
toggleConversationsPanel() {
|
|
218
|
+
this.showConversations = !this.showConversations;
|
|
219
|
+
if (!this.showConversations) {
|
|
220
|
+
this.conversationMenuId = null;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.requestConversationHistory();
|
|
225
|
+
}
|
|
229
226
|
|
|
230
227
|
toggleNewConversationShortcut() {
|
|
231
228
|
this.showNewConversationShortcut = !this.showNewConversationShortcut;
|
|
@@ -479,23 +476,24 @@ export class RioAssistWidget extends LitElement {
|
|
|
479
476
|
await this.processMessage(suggestion);
|
|
480
477
|
}
|
|
481
478
|
|
|
482
|
-
async handleSubmit(event: SubmitEvent) {
|
|
483
|
-
event.preventDefault();
|
|
484
|
-
await this.processMessage(this.message);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
private createMessage(role: ChatRole, text: string): ChatMessage {
|
|
488
|
-
const id = typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
|
489
|
-
? crypto.randomUUID()
|
|
490
|
-
: `${Date.now()}-${Math.random()}`;
|
|
491
|
-
|
|
492
|
-
return {
|
|
493
|
-
id,
|
|
494
|
-
role,
|
|
495
|
-
text,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
479
|
+
async handleSubmit(event: SubmitEvent) {
|
|
480
|
+
event.preventDefault();
|
|
481
|
+
await this.processMessage(this.message);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private createMessage(role: ChatRole, text: string): ChatMessage {
|
|
485
|
+
const id = typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
|
486
|
+
? crypto.randomUUID()
|
|
487
|
+
: `${Date.now()}-${Math.random()}`;
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
id,
|
|
491
|
+
role,
|
|
492
|
+
text,
|
|
493
|
+
html: this.renderMarkdown(text),
|
|
494
|
+
timestamp: Date.now(),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
499
497
|
|
|
500
498
|
private async processMessage(rawValue: string) {
|
|
501
499
|
const content = rawValue.trim();
|
|
@@ -539,11 +537,11 @@ export class RioAssistWidget extends LitElement {
|
|
|
539
537
|
}
|
|
540
538
|
}
|
|
541
539
|
|
|
542
|
-
private ensureRioClient() {
|
|
543
|
-
const token = this.rioToken.trim();
|
|
544
|
-
if (!token) {
|
|
545
|
-
throw new Error(
|
|
546
|
-
'Informe o token RIO em data-rio-token para conectar no websocket do assistente.',
|
|
540
|
+
private ensureRioClient() {
|
|
541
|
+
const token = this.rioToken.trim();
|
|
542
|
+
if (!token) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
'Informe o token RIO em data-rio-token para conectar no websocket do assistente.',
|
|
547
545
|
);
|
|
548
546
|
}
|
|
549
547
|
|
|
@@ -554,16 +552,22 @@ export class RioAssistWidget extends LitElement {
|
|
|
554
552
|
this.handleIncomingMessage(incoming);
|
|
555
553
|
});
|
|
556
554
|
}
|
|
557
|
-
|
|
558
|
-
return this.rioClient;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
private handleIncomingMessage(message: RioIncomingMessage) {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
555
|
+
|
|
556
|
+
return this.rioClient;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private handleIncomingMessage(message: RioIncomingMessage) {
|
|
560
|
+
if (this.isHistoryPayload(message)) {
|
|
561
|
+
this.logHistoryPayload(message);
|
|
562
|
+
this.applyConversationHistory(message.data);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const assistantMessage = this.createMessage('assistant', message.text);
|
|
567
|
+
this.messages = [...this.messages, assistantMessage];
|
|
568
|
+
this.clearLoadingGuard();
|
|
569
|
+
this.isLoading = false;
|
|
570
|
+
}
|
|
567
571
|
|
|
568
572
|
private teardownRioClient() {
|
|
569
573
|
if (this.rioUnsubscribe) {
|
|
@@ -571,15 +575,196 @@ export class RioAssistWidget extends LitElement {
|
|
|
571
575
|
this.rioUnsubscribe = null;
|
|
572
576
|
}
|
|
573
577
|
|
|
574
|
-
if (this.rioClient) {
|
|
575
|
-
this.rioClient.close();
|
|
576
|
-
this.rioClient = null;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
578
|
+
if (this.rioClient) {
|
|
579
|
+
this.rioClient.close();
|
|
580
|
+
this.rioClient = null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async requestConversationHistory(conversationId?: string) {
|
|
585
|
+
try {
|
|
586
|
+
const client = this.ensureRioClient();
|
|
587
|
+
const limit = 50;
|
|
588
|
+
|
|
589
|
+
console.info('[RioAssist][history] solicitando historico de conversas', {
|
|
590
|
+
conversationId: conversationId ?? null,
|
|
591
|
+
limit,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
await client.requestHistory({ conversationId, limit });
|
|
595
|
+
} catch (error) {
|
|
596
|
+
console.error('[RioAssist][history] erro ao solicitar historico', error);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private isHistoryPayload(message: RioIncomingMessage) {
|
|
601
|
+
if (
|
|
602
|
+
typeof message.action === 'string' &&
|
|
603
|
+
message.action.toLowerCase().includes('history')
|
|
604
|
+
) {
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const data = message.data;
|
|
609
|
+
if (data && typeof data === 'object') {
|
|
610
|
+
const action = (data as any).action;
|
|
611
|
+
if (typeof action === 'string' && action.toLowerCase().includes('history')) {
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (Array.isArray((data as any).history) || Array.isArray((data as any).conversations)) {
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private logHistoryPayload(message: RioIncomingMessage) {
|
|
624
|
+
const label = '[RioAssist][history] payload recebido do websocket';
|
|
625
|
+
if (message.data !== null && message.data !== undefined) {
|
|
626
|
+
console.info(label, message.data);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
console.info(label, message.raw);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private applyConversationHistory(payload: unknown) {
|
|
634
|
+
const entries = this.extractHistoryEntries(payload);
|
|
635
|
+
if (entries.length === 0) {
|
|
636
|
+
console.info('[RioAssist][history] payload sem itens para montar lista de conversas');
|
|
637
|
+
this.conversations = [];
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const map = new Map<string, ConversationItem>();
|
|
642
|
+
|
|
643
|
+
entries.forEach((entry, index) => {
|
|
644
|
+
if (!entry || typeof entry !== 'object') {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const normalized = this.normalizeConversationItem(
|
|
649
|
+
entry as Record<string, unknown>,
|
|
650
|
+
index,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
if (!normalized) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const current = map.get(normalized.id);
|
|
658
|
+
if (!current) {
|
|
659
|
+
map.set(normalized.id, normalized);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const currentTime = Date.parse(current.updatedAt);
|
|
664
|
+
const nextTime = Date.parse(normalized.updatedAt);
|
|
665
|
+
|
|
666
|
+
if (Number.isFinite(nextTime) && nextTime > currentTime) {
|
|
667
|
+
map.set(normalized.id, normalized);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const conversations = Array.from(map.values()).sort((a, b) => {
|
|
672
|
+
const order = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
|
|
673
|
+
return Number.isFinite(order) ? order : 0;
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
this.conversations = conversations;
|
|
677
|
+
console.info('[RioAssist][history] conversas normalizadas', conversations);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private extractHistoryEntries(payload: unknown): unknown[] {
|
|
681
|
+
if (Array.isArray(payload)) {
|
|
682
|
+
return payload;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (payload && typeof payload === 'object') {
|
|
686
|
+
const record = payload as Record<string, unknown>;
|
|
687
|
+
const candidates = [
|
|
688
|
+
record.history,
|
|
689
|
+
record.conversations,
|
|
690
|
+
record.data,
|
|
691
|
+
record.items,
|
|
692
|
+
record.messages,
|
|
693
|
+
];
|
|
694
|
+
|
|
695
|
+
for (const candidate of candidates) {
|
|
696
|
+
if (Array.isArray(candidate)) {
|
|
697
|
+
return candidate;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (record.data && typeof record.data === 'object' && !Array.isArray(record.data)) {
|
|
702
|
+
const nested = this.extractHistoryEntries(record.data);
|
|
703
|
+
if (nested.length > 0) {
|
|
704
|
+
return nested;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
private normalizeConversationItem(
|
|
713
|
+
value: Record<string, unknown>,
|
|
714
|
+
index: number,
|
|
715
|
+
): ConversationItem | null {
|
|
716
|
+
const rawId =
|
|
717
|
+
value.id ??
|
|
718
|
+
value.conversationId ??
|
|
719
|
+
value.conversationUUID ??
|
|
720
|
+
value.conversationUuid ??
|
|
721
|
+
value.uuid;
|
|
722
|
+
|
|
723
|
+
const id = rawId !== undefined && rawId !== null ? String(rawId) : `history-${index + 1}`;
|
|
724
|
+
|
|
725
|
+
const rawTitle =
|
|
726
|
+
value.title ??
|
|
727
|
+
value.name ??
|
|
728
|
+
value.topic ??
|
|
729
|
+
value.subject ??
|
|
730
|
+
value.question ??
|
|
731
|
+
value.query ??
|
|
732
|
+
value.message;
|
|
733
|
+
|
|
734
|
+
const title =
|
|
735
|
+
typeof rawTitle === 'string' && rawTitle.trim().length > 0
|
|
736
|
+
? rawTitle.trim()
|
|
737
|
+
: `Conversa ${index + 1}`;
|
|
738
|
+
|
|
739
|
+
const rawUpdated =
|
|
740
|
+
value.updatedAt ??
|
|
741
|
+
value.updated_at ??
|
|
742
|
+
value.lastMessageAt ??
|
|
743
|
+
value.last_message_at ??
|
|
744
|
+
value.createdAt ??
|
|
745
|
+
value.created_at ??
|
|
746
|
+
value.timestamp ??
|
|
747
|
+
value.date;
|
|
748
|
+
|
|
749
|
+
const updatedAt = this.toIsoString(rawUpdated);
|
|
750
|
+
|
|
751
|
+
return { id, title, updatedAt };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private toIsoString(value: unknown) {
|
|
755
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
756
|
+
const date = new Date(value);
|
|
757
|
+
if (!Number.isNaN(date.getTime())) {
|
|
758
|
+
return date.toISOString();
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return new Date().toISOString();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private startLoadingGuard() {
|
|
766
|
+
this.clearLoadingGuard();
|
|
767
|
+
this.loadingTimer = window.setTimeout(() => {
|
|
583
768
|
this.loadingTimer = null;
|
|
584
769
|
this.isLoading = false;
|
|
585
770
|
}, 15000);
|
|
@@ -604,6 +789,67 @@ export class RioAssistWidget extends LitElement {
|
|
|
604
789
|
});
|
|
605
790
|
}
|
|
606
791
|
|
|
792
|
+
private renderMarkdown(content: string) {
|
|
793
|
+
const rendered = this.markdownRenderer.render(content);
|
|
794
|
+
const clean = DOMPurify.sanitize(rendered, {
|
|
795
|
+
ALLOWED_TAGS: [
|
|
796
|
+
'a',
|
|
797
|
+
'p',
|
|
798
|
+
'ul',
|
|
799
|
+
'ol',
|
|
800
|
+
'li',
|
|
801
|
+
'code',
|
|
802
|
+
'pre',
|
|
803
|
+
'strong',
|
|
804
|
+
'em',
|
|
805
|
+
'blockquote',
|
|
806
|
+
'table',
|
|
807
|
+
'thead',
|
|
808
|
+
'tbody',
|
|
809
|
+
'tr',
|
|
810
|
+
'th',
|
|
811
|
+
'td',
|
|
812
|
+
'del',
|
|
813
|
+
'hr',
|
|
814
|
+
'br',
|
|
815
|
+
'img',
|
|
816
|
+
'span',
|
|
817
|
+
'input',
|
|
818
|
+
],
|
|
819
|
+
ALLOWED_ATTR: [
|
|
820
|
+
'href',
|
|
821
|
+
'title',
|
|
822
|
+
'target',
|
|
823
|
+
'rel',
|
|
824
|
+
'src',
|
|
825
|
+
'alt',
|
|
826
|
+
'class',
|
|
827
|
+
'type',
|
|
828
|
+
'checked',
|
|
829
|
+
'disabled',
|
|
830
|
+
'aria-label',
|
|
831
|
+
],
|
|
832
|
+
ALLOW_DATA_ATTR: false,
|
|
833
|
+
FORBID_TAGS: ['style', 'script'],
|
|
834
|
+
USE_PROFILES: { html: true },
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
const container = document.createElement('div');
|
|
838
|
+
container.innerHTML = clean;
|
|
839
|
+
|
|
840
|
+
container.querySelectorAll('a').forEach((anchor) => {
|
|
841
|
+
anchor.setAttribute('target', '_blank');
|
|
842
|
+
anchor.setAttribute('rel', 'noopener noreferrer');
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
container.querySelectorAll('input[type="checkbox"]').forEach((checkbox) => {
|
|
846
|
+
checkbox.setAttribute('disabled', '');
|
|
847
|
+
checkbox.setAttribute('tabindex', '-1');
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
return container.innerHTML;
|
|
851
|
+
}
|
|
852
|
+
|
|
607
853
|
render() {
|
|
608
854
|
return renderRioAssist(this);
|
|
609
855
|
}
|
|
@@ -614,12 +860,7 @@ declare global {
|
|
|
614
860
|
'rio-assist-widget': RioAssistWidget;
|
|
615
861
|
}
|
|
616
862
|
}
|
|
617
|
-
|
|
618
|
-
if (!customElements.get('rio-assist-widget')) {
|
|
619
|
-
customElements.define('rio-assist-widget', RioAssistWidget);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
863
|
+
|
|
864
|
+
if (!customElements.get('rio-assist-widget')) {
|
|
865
|
+
customElements.define('rio-assist-widget', RioAssistWidget);
|
|
866
|
+
}
|
package/src/playground.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
+
'Veículos com problemas',
|
|
15
|
+
'Valor das peças',
|
|
16
|
+
'Planos de manutenção',
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (window.RioAssist) {
|
|
22
|
+
boot();
|
|
23
|
+
} else {
|
|
24
|
+
window.addEventListener('rio-assist-ready', boot, { once: true });
|
|
25
|
+
}
|