rio-assist-widget 0.1.10 → 0.1.14
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/dist/rio-assist.js +170 -67
- package/package.json +1 -1
- package/src/assets/icons/arrowButton.png +0 -0
- package/src/assets/icons/hamburgerBlack.png +0 -0
- package/src/assets/icons/resizeScreen.png +0 -0
- package/src/components/conversations-panel/conversations-panel.styles.ts +43 -1
- package/src/components/conversations-panel/conversations-panel.template.ts +26 -1
- package/src/components/fullscreen/fullscreen.styles.ts +37 -16
- package/src/components/fullscreen/fullscreen.template.ts +41 -12
- package/src/components/mini-panel/mini-panel.styles.ts +68 -66
- package/src/components/mini-panel/mini-panel.template.ts +9 -0
- package/src/components/rio-assist/rio-assist.ts +517 -78
- package/src/services/rioWebsocket.ts +5 -6
|
@@ -19,13 +19,20 @@ export type ChatMessage = {
|
|
|
19
19
|
timestamp: number;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
type ConversationItem = {
|
|
23
|
-
id: string;
|
|
24
|
-
title: string;
|
|
25
|
-
updatedAt: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export
|
|
22
|
+
type ConversationItem = {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
updatedAt: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type HeaderActionConfig = {
|
|
29
|
+
id?: string;
|
|
30
|
+
iconUrl: string;
|
|
31
|
+
ariaLabel?: string;
|
|
32
|
+
onClick?: () => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export class RioAssistWidget extends LitElement {
|
|
29
36
|
static styles = widgetStyles;
|
|
30
37
|
|
|
31
38
|
static properties = {
|
|
@@ -44,11 +51,15 @@ export class RioAssistWidget extends LitElement {
|
|
|
44
51
|
showConversations: { type: Boolean, state: true },
|
|
45
52
|
conversationSearch: { type: String, state: true },
|
|
46
53
|
conversationMenuId: { state: true },
|
|
47
|
-
conversationMenuPlacement: { state: true },
|
|
48
|
-
isFullscreen: { type: Boolean, state: true },
|
|
49
|
-
conversationScrollbar: { state: true },
|
|
54
|
+
conversationMenuPlacement: { state: true },
|
|
55
|
+
isFullscreen: { type: Boolean, state: true },
|
|
56
|
+
conversationScrollbar: { state: true },
|
|
50
57
|
showNewConversationShortcut: { type: Boolean, state: true },
|
|
51
58
|
conversations: { state: true },
|
|
59
|
+
conversationHistoryLoading: { type: Boolean, state: true },
|
|
60
|
+
activeConversationTitle: { state: true },
|
|
61
|
+
headerActions: { attribute: false },
|
|
62
|
+
homeUrl: { type: String, attribute: 'data-home-url' },
|
|
52
63
|
};
|
|
53
64
|
|
|
54
65
|
open = false;
|
|
@@ -83,25 +94,88 @@ export class RioAssistWidget extends LitElement {
|
|
|
83
94
|
|
|
84
95
|
conversationMenuPlacement: 'above' | 'below' = 'below';
|
|
85
96
|
|
|
86
|
-
isFullscreen = false;
|
|
87
|
-
|
|
88
|
-
showNewConversationShortcut = false;
|
|
89
|
-
|
|
90
|
-
conversationScrollbar = {
|
|
91
|
-
height: 0,
|
|
92
|
-
top: 0,
|
|
93
|
-
visible: false,
|
|
94
|
-
};
|
|
97
|
+
isFullscreen = false;
|
|
98
|
+
|
|
99
|
+
showNewConversationShortcut = false;
|
|
100
|
+
|
|
101
|
+
conversationScrollbar = {
|
|
102
|
+
height: 0,
|
|
103
|
+
top: 0,
|
|
104
|
+
visible: false,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
conversationHistoryLoading = false;
|
|
108
|
+
|
|
109
|
+
private refreshConversationsAfterResponse = false;
|
|
110
|
+
|
|
111
|
+
activeConversationTitle: string | null = null;
|
|
112
|
+
|
|
113
|
+
headerActions: HeaderActionConfig[] = [];
|
|
114
|
+
|
|
115
|
+
homeUrl = '';
|
|
116
|
+
|
|
117
|
+
private generateConversationId() {
|
|
118
|
+
if (!this.conversationUserId) {
|
|
119
|
+
this.conversationUserId = this.inferUserIdFromToken();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const userSegment = this.conversationUserId ?? 'user';
|
|
123
|
+
const id = `default-${userSegment}-${this.randomId(8)}`;
|
|
124
|
+
console.info('[RioAssist][conversation] gerando conversationId', id);
|
|
125
|
+
return id;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private inferUserIdFromToken(): string | null {
|
|
129
|
+
const token = this.rioToken.trim();
|
|
130
|
+
if (!token || !token.includes('.')) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const [, payload] = token.split('.');
|
|
135
|
+
try {
|
|
136
|
+
const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
|
|
137
|
+
const candidate =
|
|
138
|
+
decoded?.userId ??
|
|
139
|
+
decoded?.user_id ??
|
|
140
|
+
decoded?.sub ??
|
|
141
|
+
decoded?.id ??
|
|
142
|
+
decoded?.email ??
|
|
143
|
+
decoded?.username;
|
|
144
|
+
|
|
145
|
+
if (candidate && typeof candidate === 'string') {
|
|
146
|
+
return candidate.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private randomId(length: number) {
|
|
156
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
157
|
+
let result = '';
|
|
158
|
+
for (let i = 0; i < length; i += 1) {
|
|
159
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
95
163
|
|
|
96
164
|
private conversationScrollbarRaf: number | null = null;
|
|
97
165
|
|
|
98
|
-
private rioClient: RioWebsocketClient | null = null;
|
|
99
|
-
|
|
100
|
-
private rioUnsubscribe: (() => void) | null = null;
|
|
101
|
-
|
|
102
|
-
private loadingTimer: number | null = null;
|
|
103
|
-
|
|
104
|
-
private
|
|
166
|
+
private rioClient: RioWebsocketClient | null = null;
|
|
167
|
+
|
|
168
|
+
private rioUnsubscribe: (() => void) | null = null;
|
|
169
|
+
|
|
170
|
+
private loadingTimer: number | null = null;
|
|
171
|
+
|
|
172
|
+
private currentConversationId: string | null = null;
|
|
173
|
+
|
|
174
|
+
private conversationCounter = 0;
|
|
175
|
+
|
|
176
|
+
private conversationUserId: string | null = null;
|
|
177
|
+
|
|
178
|
+
private conversationScrollbarDraggingId: number | null = null;
|
|
105
179
|
|
|
106
180
|
private conversationScrollbarDragState: {
|
|
107
181
|
startY: number;
|
|
@@ -224,13 +298,28 @@ export class RioAssistWidget extends LitElement {
|
|
|
224
298
|
this.requestConversationHistory();
|
|
225
299
|
}
|
|
226
300
|
|
|
227
|
-
toggleNewConversationShortcut() {
|
|
228
|
-
this.showNewConversationShortcut = !this.showNewConversationShortcut;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
301
|
+
toggleNewConversationShortcut() {
|
|
302
|
+
this.showNewConversationShortcut = !this.showNewConversationShortcut;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
handleConversationSelect(conversationId: string) {
|
|
306
|
+
if (!conversationId) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.showConversations = false;
|
|
311
|
+
this.conversationMenuId = null;
|
|
312
|
+
this.errorMessage = '';
|
|
313
|
+
this.currentConversationId = conversationId;
|
|
314
|
+
this.activeConversationTitle = this.lookupConversationTitle(conversationId);
|
|
315
|
+
|
|
316
|
+
console.info('[RioAssist][history] carregando conversa', conversationId);
|
|
317
|
+
this.requestConversationHistory(conversationId);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
handleConversationSearch(event: InputEvent) {
|
|
321
|
+
this.conversationSearch = (event.target as HTMLInputElement).value;
|
|
322
|
+
}
|
|
234
323
|
|
|
235
324
|
handleConversationMenuToggle(event: Event, id: string) {
|
|
236
325
|
event.stopPropagation();
|
|
@@ -267,24 +356,70 @@ export class RioAssistWidget extends LitElement {
|
|
|
267
356
|
}
|
|
268
357
|
}
|
|
269
358
|
|
|
270
|
-
handleConversationAction(action: 'rename' | 'delete', id: string) {
|
|
271
|
-
this.conversationMenuId = null;
|
|
272
|
-
const conversation = this.conversations.find((item) => item.id === id);
|
|
273
|
-
if (!conversation) {
|
|
274
|
-
return;
|
|
359
|
+
handleConversationAction(action: 'rename' | 'delete', id: string) {
|
|
360
|
+
this.conversationMenuId = null;
|
|
361
|
+
const conversation = this.conversations.find((item) => item.id === id);
|
|
362
|
+
if (!conversation) {
|
|
363
|
+
return;
|
|
275
364
|
}
|
|
276
365
|
|
|
277
|
-
const message = `${
|
|
278
|
-
action === 'rename' ? 'Renomear' : 'Excluir'
|
|
279
|
-
} "${conversation.title}"`;
|
|
280
|
-
console.info(`[Mock] ${message}`);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
366
|
+
const message = `${
|
|
367
|
+
action === 'rename' ? 'Renomear' : 'Excluir'
|
|
368
|
+
} "${conversation.title}"`;
|
|
369
|
+
console.info(`[Mock] ${message}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
handleHomeNavigation() {
|
|
373
|
+
const detail = { url: this.homeUrl || null };
|
|
374
|
+
const allowed = this.dispatchEvent(
|
|
375
|
+
new CustomEvent('rioassist:home', {
|
|
376
|
+
detail,
|
|
377
|
+
bubbles: true,
|
|
378
|
+
composed: true,
|
|
379
|
+
cancelable: true,
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (!allowed) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (this.homeUrl) {
|
|
388
|
+
window.location.assign(this.homeUrl);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
handleHeaderActionClick(action: HeaderActionConfig, index: number) {
|
|
393
|
+
const detail = {
|
|
394
|
+
index,
|
|
395
|
+
id: action.id ?? null,
|
|
396
|
+
ariaLabel: action.ariaLabel ?? null,
|
|
397
|
+
iconUrl: action.iconUrl,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const allowed = this.dispatchEvent(
|
|
401
|
+
new CustomEvent('rioassist:header-action', {
|
|
402
|
+
detail,
|
|
403
|
+
bubbles: true,
|
|
404
|
+
composed: true,
|
|
405
|
+
cancelable: true,
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
if (!allowed) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (typeof action.onClick === 'function') {
|
|
414
|
+
action.onClick();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
handleCloseAction() {
|
|
419
|
+
if (this.isFullscreen) {
|
|
420
|
+
this.exitFullscreen(true);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
288
423
|
|
|
289
424
|
if (this.showConversations) {
|
|
290
425
|
this.closeConversationsPanel();
|
|
@@ -316,23 +451,25 @@ export class RioAssistWidget extends LitElement {
|
|
|
316
451
|
}
|
|
317
452
|
}
|
|
318
453
|
|
|
319
|
-
handleCreateConversation() {
|
|
320
|
-
if (!this.hasActiveConversation) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
this.clearLoadingGuard();
|
|
325
|
-
this.isLoading = false;
|
|
326
|
-
this.messages = [];
|
|
327
|
-
this.message = '';
|
|
328
|
-
this.errorMessage = '';
|
|
329
|
-
this.showConversations = false;
|
|
330
|
-
this.teardownRioClient();
|
|
331
|
-
this.
|
|
332
|
-
this.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
454
|
+
handleCreateConversation() {
|
|
455
|
+
if (!this.hasActiveConversation) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
this.clearLoadingGuard();
|
|
460
|
+
this.isLoading = false;
|
|
461
|
+
this.messages = [];
|
|
462
|
+
this.message = '';
|
|
463
|
+
this.errorMessage = '';
|
|
464
|
+
this.showConversations = false;
|
|
465
|
+
this.teardownRioClient();
|
|
466
|
+
this.currentConversationId = this.generateConversationId();
|
|
467
|
+
this.activeConversationTitle = null;
|
|
468
|
+
this.showNewConversationShortcut = false;
|
|
469
|
+
this.dispatchEvent(
|
|
470
|
+
new CustomEvent('rioassist:new-conversation', {
|
|
471
|
+
bubbles: true,
|
|
472
|
+
composed: true,
|
|
336
473
|
}),
|
|
337
474
|
);
|
|
338
475
|
}
|
|
@@ -494,13 +631,18 @@ export class RioAssistWidget extends LitElement {
|
|
|
494
631
|
timestamp: Date.now(),
|
|
495
632
|
};
|
|
496
633
|
}
|
|
497
|
-
|
|
634
|
+
|
|
498
635
|
private async processMessage(rawValue: string) {
|
|
499
636
|
const content = rawValue.trim();
|
|
500
637
|
if (!content || this.isLoading) {
|
|
501
638
|
return;
|
|
502
639
|
}
|
|
503
640
|
|
|
641
|
+
if (!this.currentConversationId) {
|
|
642
|
+
this.currentConversationId = this.generateConversationId();
|
|
643
|
+
this.activeConversationTitle = null;
|
|
644
|
+
}
|
|
645
|
+
|
|
504
646
|
const wasEmptyConversation = this.messages.length === 0;
|
|
505
647
|
|
|
506
648
|
this.dispatchEvent(
|
|
@@ -519,20 +661,21 @@ export class RioAssistWidget extends LitElement {
|
|
|
519
661
|
this.messages = [...this.messages, userMessage];
|
|
520
662
|
if (wasEmptyConversation) {
|
|
521
663
|
this.showNewConversationShortcut = true;
|
|
664
|
+
this.refreshConversationsAfterResponse = true;
|
|
522
665
|
}
|
|
523
666
|
this.message = '';
|
|
524
667
|
this.errorMessage = '';
|
|
525
668
|
this.isLoading = true;
|
|
526
669
|
this.startLoadingGuard();
|
|
527
670
|
|
|
528
|
-
try {
|
|
529
|
-
const client = this.ensureRioClient();
|
|
530
|
-
await client.sendMessage(content);
|
|
531
|
-
} catch (error) {
|
|
532
|
-
this.clearLoadingGuard();
|
|
533
|
-
this.isLoading = false;
|
|
534
|
-
this.errorMessage = error instanceof Error
|
|
535
|
-
? error.message
|
|
671
|
+
try {
|
|
672
|
+
const client = this.ensureRioClient();
|
|
673
|
+
await client.sendMessage(content, this.currentConversationId);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
this.clearLoadingGuard();
|
|
676
|
+
this.isLoading = false;
|
|
677
|
+
this.errorMessage = error instanceof Error
|
|
678
|
+
? error.message
|
|
536
679
|
: 'Nao foi possivel enviar a mensagem para o agente.';
|
|
537
680
|
}
|
|
538
681
|
}
|
|
@@ -559,14 +702,26 @@ export class RioAssistWidget extends LitElement {
|
|
|
559
702
|
private handleIncomingMessage(message: RioIncomingMessage) {
|
|
560
703
|
if (this.isHistoryPayload(message)) {
|
|
561
704
|
this.logHistoryPayload(message);
|
|
562
|
-
this.
|
|
705
|
+
this.handleHistoryPayload(message.data);
|
|
563
706
|
return;
|
|
564
707
|
}
|
|
565
708
|
|
|
709
|
+
console.info('[RioAssist][ws] resposta de mensagem recebida', {
|
|
710
|
+
action: message.action ?? 'message',
|
|
711
|
+
text: message.text,
|
|
712
|
+
raw: message.raw,
|
|
713
|
+
data: message.data,
|
|
714
|
+
});
|
|
715
|
+
|
|
566
716
|
const assistantMessage = this.createMessage('assistant', message.text);
|
|
567
717
|
this.messages = [...this.messages, assistantMessage];
|
|
568
718
|
this.clearLoadingGuard();
|
|
569
719
|
this.isLoading = false;
|
|
720
|
+
|
|
721
|
+
if (this.refreshConversationsAfterResponse) {
|
|
722
|
+
this.refreshConversationsAfterResponse = false;
|
|
723
|
+
this.requestConversationHistory();
|
|
724
|
+
}
|
|
570
725
|
}
|
|
571
726
|
|
|
572
727
|
private teardownRioClient() {
|
|
@@ -591,9 +746,32 @@ export class RioAssistWidget extends LitElement {
|
|
|
591
746
|
limit,
|
|
592
747
|
});
|
|
593
748
|
|
|
749
|
+
this.conversationHistoryLoading = true;
|
|
594
750
|
await client.requestHistory({ conversationId, limit });
|
|
595
751
|
} catch (error) {
|
|
596
752
|
console.error('[RioAssist][history] erro ao solicitar historico', error);
|
|
753
|
+
this.conversationHistoryLoading = false;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
private handleHistoryPayload(payload: unknown) {
|
|
758
|
+
const entries = this.extractHistoryEntries(payload);
|
|
759
|
+
const conversationId = this.extractConversationId(payload);
|
|
760
|
+
|
|
761
|
+
if (conversationId !== null && conversationId !== undefined) {
|
|
762
|
+
this.applyMessageHistory(entries, conversationId);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (this.isMessageHistoryEntries(entries)) {
|
|
767
|
+
this.applyMessageHistory(entries);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
this.applyConversationHistoryFromEntries(entries);
|
|
772
|
+
|
|
773
|
+
if (this.refreshConversationsAfterResponse) {
|
|
774
|
+
this.refreshConversationsAfterResponse = false;
|
|
597
775
|
}
|
|
598
776
|
}
|
|
599
777
|
|
|
@@ -630,11 +808,11 @@ export class RioAssistWidget extends LitElement {
|
|
|
630
808
|
console.info(label, message.raw);
|
|
631
809
|
}
|
|
632
810
|
|
|
633
|
-
private
|
|
634
|
-
const entries = this.extractHistoryEntries(payload);
|
|
811
|
+
private applyConversationHistoryFromEntries(entries: unknown[]) {
|
|
635
812
|
if (entries.length === 0) {
|
|
636
813
|
console.info('[RioAssist][history] payload sem itens para montar lista de conversas');
|
|
637
814
|
this.conversations = [];
|
|
815
|
+
this.conversationHistoryLoading = false;
|
|
638
816
|
return;
|
|
639
817
|
}
|
|
640
818
|
|
|
@@ -674,9 +852,44 @@ export class RioAssistWidget extends LitElement {
|
|
|
674
852
|
});
|
|
675
853
|
|
|
676
854
|
this.conversations = conversations;
|
|
855
|
+
this.conversationHistoryLoading = false;
|
|
856
|
+
this.syncActiveConversationTitle();
|
|
677
857
|
console.info('[RioAssist][history] conversas normalizadas', conversations);
|
|
678
858
|
}
|
|
679
859
|
|
|
860
|
+
private applyMessageHistory(entries: unknown[], conversationId?: string | null) {
|
|
861
|
+
if (entries.length === 0) {
|
|
862
|
+
console.info('[RioAssist][history] lista de mensagens vazia', { conversationId });
|
|
863
|
+
this.messages = [];
|
|
864
|
+
this.showConversations = false;
|
|
865
|
+
this.clearLoadingGuard();
|
|
866
|
+
this.isLoading = false;
|
|
867
|
+
this.conversationHistoryLoading = false;
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const normalized = entries.flatMap((entry, index) =>
|
|
872
|
+
this.normalizeHistoryMessages(entry as Record<string, unknown>, index),
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
if (conversationId) {
|
|
876
|
+
this.currentConversationId = conversationId;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
this.messages = normalized;
|
|
880
|
+
this.showConversations = false;
|
|
881
|
+
this.clearLoadingGuard();
|
|
882
|
+
this.isLoading = false;
|
|
883
|
+
this.showNewConversationShortcut = normalized.length > 0;
|
|
884
|
+
this.conversationHistoryLoading = false;
|
|
885
|
+
this.refreshConversationsAfterResponse = false;
|
|
886
|
+
|
|
887
|
+
console.info('[RioAssist][history] mensagens carregadas', {
|
|
888
|
+
conversationId: conversationId ?? null,
|
|
889
|
+
total: normalized.length,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
680
893
|
private extractHistoryEntries(payload: unknown): unknown[] {
|
|
681
894
|
if (Array.isArray(payload)) {
|
|
682
895
|
return payload;
|
|
@@ -709,6 +922,62 @@ export class RioAssistWidget extends LitElement {
|
|
|
709
922
|
return [];
|
|
710
923
|
}
|
|
711
924
|
|
|
925
|
+
private extractConversationId(payload: unknown): string | null | undefined {
|
|
926
|
+
if (payload && typeof payload === 'object') {
|
|
927
|
+
const record = payload as Record<string, unknown>;
|
|
928
|
+
const candidates = [
|
|
929
|
+
record.conversationId,
|
|
930
|
+
record.conversationUUID,
|
|
931
|
+
record.conversationUuid,
|
|
932
|
+
record.uuid,
|
|
933
|
+
record.id,
|
|
934
|
+
];
|
|
935
|
+
|
|
936
|
+
for (const candidate of candidates) {
|
|
937
|
+
if (candidate === null) {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (candidate !== undefined) {
|
|
942
|
+
return String(candidate);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return undefined;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
private isMessageHistoryEntries(entries: unknown[]) {
|
|
951
|
+
return entries.some((entry) => this.looksLikeMessageHistoryEntry(entry));
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
private looksLikeMessageHistoryEntry(entry: unknown) {
|
|
955
|
+
if (!entry || typeof entry !== 'object') {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const item = entry as Record<string, unknown>;
|
|
960
|
+
const role = item.role ?? item.sender ?? item.from ?? item.author ?? item.type;
|
|
961
|
+
if (typeof role === 'string' && role.trim().length > 0) {
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (
|
|
966
|
+
typeof item.content === 'string' ||
|
|
967
|
+
typeof item.message === 'string' ||
|
|
968
|
+
typeof item.text === 'string' ||
|
|
969
|
+
typeof item.response === 'string'
|
|
970
|
+
) {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (Array.isArray(item.parts) && item.parts.length > 0) {
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
|
|
712
981
|
private normalizeConversationItem(
|
|
713
982
|
value: Record<string, unknown>,
|
|
714
983
|
index: number,
|
|
@@ -751,6 +1020,176 @@ export class RioAssistWidget extends LitElement {
|
|
|
751
1020
|
return { id, title, updatedAt };
|
|
752
1021
|
}
|
|
753
1022
|
|
|
1023
|
+
private normalizeHistoryMessages(
|
|
1024
|
+
value: Record<string, unknown>,
|
|
1025
|
+
index: number,
|
|
1026
|
+
): ChatMessage[] {
|
|
1027
|
+
const messages: ChatMessage[] = [];
|
|
1028
|
+
|
|
1029
|
+
const rawUserText = value.message ?? value.question ?? value.query ?? value.text ?? value.content;
|
|
1030
|
+
const userText = typeof rawUserText === 'string' ? rawUserText.trim() : '';
|
|
1031
|
+
|
|
1032
|
+
const rawResponseText =
|
|
1033
|
+
value.response ?? value.answer ?? value.reply ?? value.completion ?? value.body ?? value.preview;
|
|
1034
|
+
const responseText = typeof rawResponseText === 'string' ? rawResponseText.trim() : '';
|
|
1035
|
+
|
|
1036
|
+
const rawId = value.id ?? value.messageId ?? value.uuid ?? value.conversationMessageId;
|
|
1037
|
+
const baseId = rawId !== undefined && rawId !== null
|
|
1038
|
+
? String(rawId)
|
|
1039
|
+
: `history-${index + 1}`;
|
|
1040
|
+
|
|
1041
|
+
const userTimestampValue =
|
|
1042
|
+
value.timestamp ??
|
|
1043
|
+
value.createdAt ??
|
|
1044
|
+
value.created_at ??
|
|
1045
|
+
value.date ??
|
|
1046
|
+
value.time;
|
|
1047
|
+
const assistantTimestampValue =
|
|
1048
|
+
value.responseTimestamp ??
|
|
1049
|
+
value.responseTime ??
|
|
1050
|
+
value.responseDate ??
|
|
1051
|
+
value.response_at ??
|
|
1052
|
+
value.updatedAt ??
|
|
1053
|
+
value.updated_at;
|
|
1054
|
+
|
|
1055
|
+
const userTimestamp = this.parseTimestamp(userTimestampValue);
|
|
1056
|
+
const assistantTimestamp = this.parseTimestamp(
|
|
1057
|
+
assistantTimestampValue,
|
|
1058
|
+
userTimestamp + 1,
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1061
|
+
if (responseText) {
|
|
1062
|
+
if (userText) {
|
|
1063
|
+
messages.push({
|
|
1064
|
+
id: `${baseId}-user`,
|
|
1065
|
+
role: 'user',
|
|
1066
|
+
text: userText,
|
|
1067
|
+
html: this.renderMarkdown(userText),
|
|
1068
|
+
timestamp: userTimestamp,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
messages.push({
|
|
1073
|
+
id: `${baseId}-assistant`,
|
|
1074
|
+
role: 'assistant',
|
|
1075
|
+
text: responseText,
|
|
1076
|
+
html: this.renderMarkdown(responseText),
|
|
1077
|
+
timestamp: assistantTimestamp,
|
|
1078
|
+
});
|
|
1079
|
+
} else if (userText) {
|
|
1080
|
+
// Se n�o tiver resposta, n�o exibimos a mensagem do usuario isolada.
|
|
1081
|
+
return [];
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (messages.length > 0) {
|
|
1085
|
+
return messages;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const fallback = this.normalizeSingleHistoryMessage(value, index);
|
|
1089
|
+
return fallback ? [fallback] : [];
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private normalizeSingleHistoryMessage(
|
|
1093
|
+
value: Record<string, unknown>,
|
|
1094
|
+
index: number,
|
|
1095
|
+
): ChatMessage | null {
|
|
1096
|
+
const rawText =
|
|
1097
|
+
value.text ??
|
|
1098
|
+
value.message ??
|
|
1099
|
+
value.content ??
|
|
1100
|
+
value.response ??
|
|
1101
|
+
value.body ??
|
|
1102
|
+
value.preview;
|
|
1103
|
+
|
|
1104
|
+
const text = typeof rawText === 'string' && rawText.trim().length > 0
|
|
1105
|
+
? rawText
|
|
1106
|
+
: '';
|
|
1107
|
+
|
|
1108
|
+
if (!text) {
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const role = this.normalizeRole(
|
|
1113
|
+
value.role ??
|
|
1114
|
+
value.sender ??
|
|
1115
|
+
value.from ??
|
|
1116
|
+
value.author ??
|
|
1117
|
+
value.type ??
|
|
1118
|
+
value.direction,
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
const rawId = value.id ?? value.messageId ?? value.uuid ?? value.conversationMessageId;
|
|
1122
|
+
const id = rawId !== undefined && rawId !== null
|
|
1123
|
+
? String(rawId)
|
|
1124
|
+
: `history-message-${index + 1}`;
|
|
1125
|
+
|
|
1126
|
+
const timestampValue =
|
|
1127
|
+
value.timestamp ??
|
|
1128
|
+
value.createdAt ??
|
|
1129
|
+
value.created_at ??
|
|
1130
|
+
value.updatedAt ??
|
|
1131
|
+
value.updated_at ??
|
|
1132
|
+
value.date ??
|
|
1133
|
+
value.time;
|
|
1134
|
+
|
|
1135
|
+
const timestamp = this.parseTimestamp(timestampValue);
|
|
1136
|
+
|
|
1137
|
+
return {
|
|
1138
|
+
id,
|
|
1139
|
+
role,
|
|
1140
|
+
text,
|
|
1141
|
+
html: this.renderMarkdown(text),
|
|
1142
|
+
timestamp,
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
private normalizeRole(value: unknown): ChatRole {
|
|
1147
|
+
if (typeof value === 'string') {
|
|
1148
|
+
const normalized = value.toLowerCase();
|
|
1149
|
+
if (normalized.includes('user') || normalized.includes('client')) {
|
|
1150
|
+
return 'user';
|
|
1151
|
+
}
|
|
1152
|
+
if (normalized.includes('assistant') || normalized.includes('agent') || normalized.includes('bot')) {
|
|
1153
|
+
return 'assistant';
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return 'assistant';
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
private parseTimestamp(value: unknown, fallback?: number) {
|
|
1161
|
+
const parsed = Date.parse(this.toIsoString(value));
|
|
1162
|
+
if (Number.isFinite(parsed)) {
|
|
1163
|
+
return parsed;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (Number.isFinite(fallback ?? NaN)) {
|
|
1167
|
+
return fallback as number;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return Date.now();
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
private lookupConversationTitle(conversationId: string | null) {
|
|
1174
|
+
if (!conversationId) {
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const found = this.conversations.find((item) => item.id === conversationId);
|
|
1179
|
+
return found ? found.title : null;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
private syncActiveConversationTitle() {
|
|
1183
|
+
if (!this.currentConversationId) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const title = this.lookupConversationTitle(this.currentConversationId);
|
|
1188
|
+
if (title) {
|
|
1189
|
+
this.activeConversationTitle = title;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
754
1193
|
private toIsoString(value: unknown) {
|
|
755
1194
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
756
1195
|
const date = new Date(value);
|