avichat-widget 1.0.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.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # avichat-widget
Binary file
package/avichat.js ADDED
@@ -0,0 +1,810 @@
1
+ (function () {
2
+
3
+ const AviChatWidget = {
4
+ // Carrega as dependências externas se não estiverem presentes
5
+ async loadDependencies() {
6
+ const scripts = [
7
+ { id: 'marked-js', url: 'https://cdn.jsdelivr.net/npm/marked/marked.min.js' },
8
+ { id: 'highlight-js', url: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js' }
9
+ ];
10
+
11
+ const promises = scripts.map(src => {
12
+ if (document.getElementById(src.id) || (src.id === 'marked-js' && typeof marked !== 'undefined')) return Promise.resolve();
13
+ return new Promise((resolve) => {
14
+ const s = document.createElement('script');
15
+ s.id = src.id;
16
+ s.src = src.url;
17
+ s.onload = resolve;
18
+ document.head.appendChild(s);
19
+ });
20
+ });
21
+
22
+ // Adiciona o CSS do Highlight.js para os blocos de código
23
+ if (!document.querySelector('link[href*="highlight.js"]')) {
24
+ const link = document.createElement('link');
25
+ link.rel = 'stylesheet';
26
+ link.href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
27
+ document.head.appendChild(link);
28
+ }
29
+
30
+ return Promise.all(promises);
31
+ },
32
+
33
+ async init(config) {
34
+ this.config = config || {};
35
+ // Aguarda o carregamento das libs externas antes de montar o widget
36
+ await this.loadDependencies();
37
+ this.mount();
38
+ },
39
+
40
+ mount() {
41
+ this.injectCSS();
42
+ this.injectHTML();
43
+ this.injectScripts();
44
+ },
45
+
46
+ injectCSS() {
47
+ const style = document.createElement('style');
48
+ style.innerHTML =
49
+ `
50
+
51
+
52
+ :root {
53
+ --primary-color: ${this.config.primaryColor || '#2b60ab'};
54
+ --primary-hover: ${this.config.primaryHover || '#1e4a8f'};
55
+ --primary-light: #e8f0ff;
56
+ --text-primary: #111827;
57
+ --text-secondary: #6b7280;
58
+ --border-color: #e5e7eb;
59
+ --bg-light: #f9fafb;
60
+ --bg-white: #ffffff;
61
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
62
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
63
+ --shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.15);
64
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
65
+ }
66
+
67
+ * {
68
+ box-sizing: border-box;
69
+ margin: 0;
70
+ padding: 0;
71
+ }
72
+
73
+ body {
74
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
75
+ -webkit-font-smoothing: antialiased;
76
+ -moz-osx-font-smoothing: grayscale;
77
+ }
78
+
79
+ button {
80
+ font-family: inherit;
81
+ outline: none;
82
+ cursor: pointer;
83
+ }
84
+
85
+ /* ========== BOTÃO FLUTUANTE ========== */
86
+ .chat-button {
87
+ position: fixed;
88
+ bottom: 24px;
89
+ right: 24px;
90
+ width: 64px;
91
+ height: 64px;
92
+ border-radius: 50%;
93
+ background: var(--primary-color);
94
+ color: white;
95
+ border: none;
96
+ font-size: 28px;
97
+ cursor: pointer;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ box-shadow: var(--shadow-lg);
102
+ transition: var(--transition);
103
+ z-index: 1000;
104
+ position: relative;
105
+ }
106
+
107
+ .chat-button:hover {
108
+ transform: scale(1.1);
109
+ box-shadow: 0 12px 48px rgba(43, 96, 171, 0.4);
110
+ }
111
+
112
+ .chat-button:active {
113
+ transform: scale(0.95);
114
+ }
115
+
116
+ .chat-icon {
117
+ width: 28px;
118
+ height: 28px;
119
+ }
120
+
121
+ .notification-badge {
122
+ position: absolute;
123
+ top: 4px;
124
+ right: 4px;
125
+ background: #ef4444;
126
+ color: white;
127
+ border-radius: 10px;
128
+ padding: 2px 6px;
129
+ font-size: 11px;
130
+ font-weight: 600;
131
+ display: none;
132
+ min-width: 18px;
133
+ text-align: center;
134
+ }
135
+
136
+ .notification-badge.show {
137
+ display: block;
138
+ animation: bounce 0.5s;
139
+ }
140
+
141
+ @keyframes bounce {
142
+ 0%, 100% { transform: translateY(0); }
143
+ 50% { transform: translateY(-5px); }
144
+ }
145
+
146
+ /* ========== CONTAINER DO CHAT ========== */
147
+ .chat-container {
148
+ position: fixed;
149
+ bottom: 0 !important;
150
+ right: 0 !important;
151
+ top: auto !important;
152
+ left: auto !important;
153
+ width: 400px;
154
+ height: 600px;
155
+ background: var(--bg-white);
156
+ display: none;
157
+ flex-direction: column;
158
+ border-radius: 16px 16px 0 0;
159
+ box-shadow: var(--shadow-lg);
160
+ z-index: 1001;
161
+ animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
162
+ }
163
+
164
+ @keyframes slideUp {
165
+ from {
166
+ transform: translateY(100%);
167
+ opacity: 0;
168
+ }
169
+ to {
170
+ transform: translateY(0);
171
+ opacity: 1;
172
+ }
173
+ }
174
+
175
+ .chat-container.active {
176
+ display: flex;
177
+ }
178
+
179
+ /* ========== HEADER ========== */
180
+ .chat-header {
181
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
182
+ color: white;
183
+ padding: 20px;
184
+ display: flex;
185
+ justify-content: space-between;
186
+ align-items: center;
187
+ border-radius: 16px 16px 0 0;
188
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
189
+ }
190
+
191
+ .chat-header-left {
192
+ display: flex;
193
+ align-items: center;
194
+ gap: 12px;
195
+ flex: 1;
196
+ }
197
+
198
+ .chat-avatar {
199
+ width: 48px;
200
+ height: 48px;
201
+ border-radius: 50%;
202
+ object-fit: cover;
203
+ border: 3px solid rgba(255, 255, 255, 0.3);
204
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
205
+ }
206
+
207
+ .chat-header-info {
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 2px;
211
+ }
212
+
213
+ .chat-title {
214
+ font-size: 17px;
215
+ font-weight: 600;
216
+ letter-spacing: -0.01em;
217
+ }
218
+
219
+ .chat-status {
220
+ font-size: 13px;
221
+ opacity: 0.9;
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 6px;
225
+ }
226
+
227
+ .chat-status::before {
228
+ content: '';
229
+ width: 8px;
230
+ height: 8px;
231
+ background: #10b981;
232
+ border-radius: 50%;
233
+ display: inline-block;
234
+ animation: pulse 2s infinite;
235
+ }
236
+
237
+ @keyframes pulse {
238
+ 0%, 100% { opacity: 1; }
239
+ 50% { opacity: 0.5; }
240
+ }
241
+
242
+ .chat-header-actions {
243
+ display: flex;
244
+ gap: 8px;
245
+ }
246
+
247
+ .header-btn {
248
+ background: rgba(255, 255, 255, 0.15);
249
+ border: none;
250
+ color: white;
251
+ width: 36px;
252
+ height: 36px;
253
+ border-radius: 8px;
254
+ cursor: pointer;
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ transition: var(--transition);
259
+ }
260
+
261
+ .header-btn:hover {
262
+ background: rgba(255, 255, 255, 0.25);
263
+ transform: translateY(-1px);
264
+ }
265
+
266
+ .header-btn:active {
267
+ transform: translateY(0);
268
+ }
269
+
270
+ .header-btn svg {
271
+ width: 18px;
272
+ height: 18px;
273
+ }
274
+
275
+ /* ========== MENSAGENS ========== */
276
+ .chat-messages {
277
+ flex: 1;
278
+ padding: 20px;
279
+ overflow-y: auto;
280
+ background: var(--bg-light);
281
+ display: flex;
282
+ flex-direction: column;
283
+ gap: 12px;
284
+ }
285
+
286
+ .chat-messages::-webkit-scrollbar {
287
+ width: 6px;
288
+ }
289
+
290
+ .chat-messages::-webkit-scrollbar-track {
291
+ background: transparent;
292
+ }
293
+
294
+ .chat-messages::-webkit-scrollbar-thumb {
295
+ background: #d1d5db;
296
+ border-radius: 3px;
297
+ }
298
+
299
+ .chat-messages::-webkit-scrollbar-thumb:hover {
300
+ background: #9ca3af;
301
+ }
302
+
303
+ .message {
304
+ max-width: 80%;
305
+ padding: 12px 16px;
306
+ border-radius: 16px;
307
+ font-size: 14px;
308
+ line-height: 1.5;
309
+ width: fit-content;
310
+ word-wrap: break-word;
311
+ animation: fadeIn 0.3s ease;
312
+ position: relative;
313
+ }
314
+
315
+ @keyframes fadeIn {
316
+ from {
317
+ opacity: 0;
318
+ transform: translateY(10px);
319
+ }
320
+ to {
321
+ opacity: 1;
322
+ transform: translateY(0);
323
+ }
324
+ }
325
+
326
+ .message.user {
327
+ background: var(--primary-color);
328
+ color: white;
329
+ margin-left: auto;
330
+ border-bottom-right-radius: 4px;
331
+ box-shadow: var(--shadow-sm);
332
+ }
333
+
334
+ .message.bot {
335
+ background: var(--bg-white);
336
+ color: var(--text-primary);
337
+ box-shadow: var(--shadow-sm);
338
+ border: 1px solid var(--border-color);
339
+ }
340
+
341
+ .message-time {
342
+ font-size: 11px;
343
+ opacity: 0.6;
344
+ margin-top: 6px;
345
+ display: block;
346
+ }
347
+
348
+ /* Markdown styling */
349
+ .message.bot p { margin: 0; margin-bottom: 8px; }
350
+ .message.bot p:last-child { margin-bottom: 0; }
351
+ .message.bot strong { font-weight: 600; color: var(--text-primary); }
352
+ .message.bot em { font-style: italic; }
353
+ .message.bot code { background: var(--bg-light); padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: 'Monaco', 'Courier New', monospace; color: #d73a49; }
354
+ .message.bot pre { background: #1f2937; padding: 12px; border-radius: 8px; overflow-x: auto; margin: 8px 0; }
355
+ .message.bot pre code { background: transparent; color: #e5e7eb; padding: 0; font-size: 13px; line-height: 1.6; }
356
+ .message.bot ul, .message.bot ol { margin: 8px 0; padding-left: 20px; }
357
+ .message.bot li { margin: 4px 0; }
358
+ .message.bot a { color: var(--primary-color); text-decoration: underline; transition: var(--transition); }
359
+ .message.bot a:hover { color: var(--primary-hover); }
360
+
361
+ /* ========== INPUT ========== */
362
+ .chat-input {
363
+ display: flex;
364
+ padding: 16px;
365
+ gap: 12px;
366
+ border-top: 1px solid var(--border-color);
367
+ background: var(--bg-white);
368
+ align-items: flex-end;
369
+ }
370
+
371
+ .chat-input textarea {
372
+ flex: 1;
373
+ padding: 12px 16px;
374
+ outline: none;
375
+ font-size: 14px;
376
+ font-family: inherit;
377
+ resize: none;
378
+ max-height: 120px;
379
+ transition: var(--transition);
380
+ line-height: 1.5;
381
+ }
382
+
383
+ .send-icon {
384
+ width: 20px;
385
+ height: 20px;
386
+ }
387
+ .chat-input button {
388
+ background: var(--primary-color);
389
+ border: none;
390
+ color: white;
391
+ width: 44px;
392
+ height: 44px;
393
+ border-radius: 12px;
394
+ cursor: pointer;
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ transition: var(--transition);
399
+ flex-shrink: 0;
400
+ }
401
+
402
+ /* ========== TYPING INDICATOR ========== */
403
+ .typing { display: inline-flex; gap: 4px; padding: 4px 0; }
404
+ .typing span { width: 8px; height: 8px; background: var(--text-secondary); border-radius: 50%; animation: blink 1.4s infinite both; }
405
+ .typing span:nth-child(2) { animation-delay: 0.2s; }
406
+ .typing span:nth-child(3) { animation-delay: 0.4s; }
407
+
408
+ @keyframes blink {
409
+ 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
410
+ 40% { opacity: 1; transform: scale(1); }
411
+ }
412
+
413
+ @media (max-width: 480px) {
414
+ .chat-container { width: 100%; height: 100vh; border-radius: 0; bottom: 0; right: 0; }
415
+ }
416
+ `;
417
+ document.head.appendChild(style);
418
+ },
419
+
420
+ injectHTML() {
421
+ const wrapper = document.createElement('div');
422
+ wrapper.innerHTML =
423
+ `
424
+ <style>
425
+ #chat { position: fixed !important; bottom: 24px !important; right: 24px !important; }
426
+ #chatButton { position: fixed !important; bottom: 24px !important; right: 24px !important; }
427
+ </style>
428
+
429
+ <button class="chat-button" id="chatButton" aria-label="Abrir chat">
430
+ <svg class="chat-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
431
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
432
+ </svg>
433
+ <span class="notification-badge" id="notificationBadge"></span>
434
+ </button>
435
+
436
+ <div class="chat-container" id="chat" role="dialog" aria-labelledby="chatTitle">
437
+ <div class="chat-header">
438
+ <div class="chat-header-left">
439
+ <img src="${this.config.avatarUrl || 'img/chatbot-maria.jpg'}"
440
+ alt="Avatar"
441
+ class="chat-avatar">
442
+
443
+ <div class="chat-header-info">
444
+ <span class="chat-title" id="chatTitle">
445
+ ${this.config.botName || 'Maria — Em Treinamento'}
446
+ </span>
447
+
448
+ <span class="chat-status" id="chatStatus">Online</span>
449
+ </div>
450
+ </div>
451
+ <div class="chat-header-actions">
452
+ <button id="clearChat" class="header-btn" aria-label="Limpar conversa" title="Limpar conversa">
453
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><polyline points="23 20 23 14 17 14"></polyline><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path></svg>
454
+ </button>
455
+ <button id="closeChat" class="header-btn" aria-label="Fechar chat">
456
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
457
+ </button>
458
+ </div>
459
+ </div>
460
+ <div class="chat-messages" id="messages" role="log" aria-live="polite"></div>
461
+ <div class="chat-input">
462
+ <textarea id="input" placeholder="Digite sua mensagem..." rows="1" aria-label="Digite sua mensagem"></textarea>
463
+ <button id="sendBtn" aria-label="Enviar mensagem">
464
+ <svg class="send-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
465
+ </button>
466
+ </div>
467
+ </div>
468
+ `;
469
+ document.body.appendChild(wrapper);
470
+ },
471
+
472
+ injectScripts() {
473
+ // ========= CONFIGURAÇÕES =========
474
+ const CONFIG = {
475
+ DIFY_API_URL: this.config.apiUrl,
476
+ BASE_URL: this.config.apiUrl.replace('/chat-messages', ''),
477
+ DIFY_API_KEY: this.config.apiKey,
478
+ USER_ID: 'web-' + Math.random().toString(36).slice(2),
479
+ WELCOME_MESSAGE: this.config.welcomeMessage || `Oi, seja bem-vindo(a)! 😊
480
+ Eu sou a Maria Rosa, mas pode me chamar de Rô.
481
+ Sou a assistente virtual do Governo de Rondônia e estou aqui pra te ajudar com os nossos serviços do Portal do Cidadão, de um jeito simples e sem complicação.
482
+ Antes de continuar, é importante você saber como seus dados são protegidos.
483
+ Ao seguir no atendimento, você concorda com a nossa Política de Privacidade.
484
+ Ah, só pra te avisar: estou em treinamento, aprendendo todos os dias pra te atender cada vez melhor 💚`,
485
+ TYPING_DELAY: 500,
486
+ AUTO_SCROLL_THRESHOLD: 100
487
+ };
488
+
489
+ const state = {
490
+ conversationId: '',
491
+ isTyping: false,
492
+ messageHistory: []
493
+ };
494
+
495
+ const elements = {
496
+ chat: document.getElementById('chat'),
497
+ chatButton: document.getElementById('chatButton'),
498
+ closeChat: document.getElementById('closeChat'),
499
+ clearChat: document.getElementById('clearChat'),
500
+ messages: document.getElementById('messages'),
501
+ input: document.getElementById('input'),
502
+ sendBtn: document.getElementById('sendBtn'),
503
+ chatStatus: document.getElementById('chatStatus'),
504
+ notificationBadge: document.getElementById('notificationBadge')
505
+ };
506
+
507
+ // Configuração do Marked (executa após garantir que a lib carregou)
508
+ marked.setOptions({
509
+ breaks: true,
510
+ gfm: true,
511
+ highlight: function(code, lang) {
512
+ if (lang && hljs.getLanguage(lang)) {
513
+ return hljs.highlight(code, { language: lang }).value;
514
+ }
515
+ return hljs.highlightAuto(code).value;
516
+ }
517
+ });
518
+
519
+ function initializeEventListeners() {
520
+ elements.chatButton.addEventListener('click', toggleChat);
521
+ elements.closeChat.addEventListener('click', toggleChat);
522
+ elements.clearChat.addEventListener('click', clearChat);
523
+ elements.sendBtn.addEventListener('click', sendMessage);
524
+ elements.input.addEventListener('input', autoResizeTextarea);
525
+ elements.input.addEventListener('keypress', (e) => {
526
+ if (e.key === 'Enter' && !e.shiftKey) {
527
+ e.preventDefault();
528
+ sendMessage();
529
+ }
530
+ });
531
+ }
532
+
533
+ function toggleChat() {
534
+ const isActive = elements.chat.classList.toggle('active');
535
+ elements.chatButton.style.display = isActive ? 'none' : 'flex';
536
+ if (isActive) {
537
+ elements.input.focus();
538
+ scrollToBottom(true);
539
+ clearNotificationBadge();
540
+ }
541
+ }
542
+
543
+ function clearChat() {
544
+ elements.messages.innerHTML = '';
545
+ state.conversationId = '';
546
+ state.messageHistory = [];
547
+ addWelcomeMessage();
548
+ saveToLocalStorage();
549
+ }
550
+
551
+ function addWelcomeMessage() {
552
+ setTimeout(() => { addMessage(CONFIG.WELCOME_MESSAGE, 'bot', false); }, 300);
553
+ }
554
+
555
+ function addMessage(text, type, showTime = true, messageId = null) {
556
+ const messageDiv = document.createElement('div');
557
+ messageDiv.className = `message ${type}`;
558
+
559
+ if (type === 'bot') {
560
+ messageDiv.innerHTML = marked.parse(text);
561
+ messageDiv.querySelectorAll('a').forEach(link => {
562
+ link.target = '_blank';
563
+ link.rel = 'noopener noreferrer';
564
+ });
565
+ messageDiv.querySelectorAll('pre code').forEach(block => {
566
+ hljs.highlightElement(block);
567
+ });
568
+ } else {
569
+ messageDiv.textContent = text;
570
+ }
571
+
572
+ if (showTime) {
573
+ const timeSpan = document.createElement('span');
574
+ timeSpan.className = 'message-time';
575
+ timeSpan.textContent = new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
576
+ messageDiv.appendChild(timeSpan);
577
+ }
578
+
579
+
580
+ if (type === 'bot' && messageId) {
581
+ const feedbackDiv = document.createElement('div');
582
+ feedbackDiv.style.display = 'flex';
583
+ feedbackDiv.style.gap = '10px';
584
+ feedbackDiv.style.marginTop = '10px';
585
+ feedbackDiv.style.alignItems = 'center';
586
+
587
+ const createBtn = (type) => {
588
+ const btn = document.createElement('button');
589
+ btn.innerHTML = type === 'like'
590
+ ? `<svg xmlns="http://www.w3.org/2000/svg" height="18px" viewBox="0 -960 960 960" width="18px" fill="#434343"><path d="M720-120H280v-520l280-280 50 50q7 7 11.5 19t4.5 23v14l-44 174h258q32 0 56 24t24 56v80q0 7-2 15t-4 15L794-168q-9 20-30 34t-44 14Zm-360-80h360l120-280v-80H480l54-220-174 174v406Zm0-406v406-406Zm-80-34v80H160v360h120v80H80v-520h200Z"/></svg>`
591
+ : `<svg xmlns="http://www.w3.org/2000/svg" height="18px" viewBox="0 -960 960 960" width="18px" fill="#434343"><path d="M240-840h440v520L400-40l-50-50q-7-7-11.5-19t-4.5-23v-14l44-174H120q-32 0-56-24t-24-56v-80q0-7 2-15t4-15l120-282q9-20 30-34t44-14Zm360 80H240L120-480v80h360l-54 220 174-174v-406Zm0 406v-406 406Zm80 34v-80h120v-360H680v-80h200v520H680Z"/></svg>`;
592
+
593
+ btn.style.border = '1px solid var(--border-color)';
594
+ btn.style.background = 'white';
595
+ btn.style.borderRadius = '8px';
596
+ btn.style.cursor = 'pointer';
597
+ btn.style.padding = '6px';
598
+ btn.style.transition = 'all 0.2s ease';
599
+
600
+ btn.onmouseenter = () => {
601
+ btn.style.background = '#f3f4f6';
602
+ };
603
+
604
+ btn.onmouseleave = () => {
605
+ btn.style.background = 'white';
606
+ };
607
+
608
+ btn.onclick = () => {
609
+ if (feedbackDiv.dataset.clicked) return;
610
+
611
+ feedbackDiv.dataset.clicked = "true";
612
+
613
+ if (type === 'like') {
614
+ btn.style.background = '#dcfce7';
615
+ btn.style.borderColor = '#16a34a';
616
+ btn.style.color = '#16a34a';
617
+ } else {
618
+ btn.style.background = '#fee2e2';
619
+ btn.style.borderColor = '#dc2626';
620
+ btn.style.color = '#dc2626';
621
+ }
622
+
623
+ sendFeedback(messageId, type, feedbackDiv);
624
+ };
625
+
626
+ return btn;
627
+ };
628
+
629
+ feedbackDiv.appendChild(createBtn('like'));
630
+ feedbackDiv.appendChild(createBtn('dislike'));
631
+
632
+ messageDiv.appendChild(feedbackDiv);
633
+ }
634
+
635
+
636
+
637
+
638
+ elements.messages.appendChild(messageDiv);
639
+ state.messageHistory.push({ text, type, time: new Date().toISOString() });
640
+ scrollToBottom();
641
+
642
+ if (!elements.chat.classList.contains('active') && type === 'bot') {
643
+ showNotification();
644
+ }
645
+ saveToLocalStorage();
646
+ }
647
+
648
+ async function sendMessage() {
649
+ const text = elements.input.value.trim();
650
+ if (!text || state.isTyping) return;
651
+ // Remove mensagem inicial se for a primeira interação
652
+ if (state.messageHistory.length === 1 &&
653
+ state.messageHistory[0].text === CONFIG.WELCOME_MESSAGE) {
654
+ elements.messages.innerHTML = '';
655
+ state.messageHistory = [];
656
+ }
657
+
658
+
659
+ addMessage(text, 'user');
660
+ elements.input.value = '';
661
+ autoResizeTextarea();
662
+
663
+ state.isTyping = true;
664
+ elements.sendBtn.disabled = true;
665
+ updateChatStatus('Digitando...');
666
+
667
+ showTyping();
668
+
669
+ try {
670
+ const response = await fetch(CONFIG.DIFY_API_URL, {
671
+ method: 'POST',
672
+ headers: {
673
+ 'Authorization': `Bearer ${CONFIG.DIFY_API_KEY}`,
674
+ 'Content-Type': 'application/json'
675
+ },
676
+ body: JSON.stringify({
677
+ inputs: {},
678
+ query: text,
679
+ response_mode: 'blocking',
680
+ conversation_id: state.conversationId,
681
+ user: CONFIG.USER_ID
682
+ })
683
+ });
684
+
685
+ const data = await response.json();
686
+ if (data.conversation_id) state.conversationId = data.conversation_id;
687
+ const reply = data.answer || 'Desculpe, não consegui processar sua mensagem.';
688
+ const messageId = data.message_id;
689
+
690
+ hideTyping();
691
+ addMessage(reply, 'bot', true, messageId);
692
+ } catch (error) {
693
+ hideTyping();
694
+ addMessage('😕 Erro ao conectar com o assistente.', 'bot');
695
+ } finally {
696
+ state.isTyping = false;
697
+ elements.sendBtn.disabled = false;
698
+ updateChatStatus('Online');
699
+ elements.input.focus();
700
+ }
701
+ }
702
+
703
+ function showTyping() {
704
+ if (document.getElementById('typing-indicator')) return;
705
+ const div = document.createElement('div');
706
+ div.className = 'message bot';
707
+ div.id = 'typing-indicator';
708
+ div.innerHTML = `<div class="typing"><span></span><span></span><span></span></div>`;
709
+ elements.messages.appendChild(div);
710
+ scrollToBottom();
711
+ }
712
+
713
+ function hideTyping() {
714
+ const el = document.getElementById('typing-indicator');
715
+ if (el) el.remove();
716
+ }
717
+
718
+ function autoResizeTextarea() {
719
+ elements.input.style.height = 'auto';
720
+ elements.input.style.height = Math.min(elements.input.scrollHeight, 120) + 'px';
721
+ }
722
+
723
+ function scrollToBottom(force = false) {
724
+ const container = elements.messages;
725
+ container.scrollTop = container.scrollHeight;
726
+ }
727
+
728
+
729
+
730
+ function updateChatStatus(status) { elements.chatStatus.textContent = status; }
731
+
732
+ function showNotification() {
733
+ const count = parseInt(elements.notificationBadge.textContent) || 0;
734
+ elements.notificationBadge.textContent = (count + 1).toString();
735
+ elements.notificationBadge.classList.add('show');
736
+ }
737
+
738
+ function clearNotificationBadge() {
739
+ elements.notificationBadge.textContent = '';
740
+ elements.notificationBadge.classList.remove('show');
741
+ }
742
+
743
+ function saveToLocalStorage() {
744
+ localStorage.setItem('chat_history', JSON.stringify(state.messageHistory));
745
+ localStorage.setItem('conversation_id', state.conversationId);
746
+ }
747
+
748
+ function loadFromLocalStorage() {
749
+ const history = localStorage.getItem('chat_history');
750
+ const convId = localStorage.getItem('conversation_id');
751
+ if (history) {
752
+ state.messageHistory = JSON.parse(history);
753
+ state.messageHistory.forEach(msg => {
754
+ const div = document.createElement('div');
755
+ div.className = `message ${msg.type}`;
756
+ div.innerHTML = msg.type === 'bot' ? marked.parse(msg.text) : msg.text;
757
+ elements.messages.appendChild(div);
758
+ });
759
+ scrollToBottom(true);
760
+ }
761
+ if (convId) state.conversationId = convId;
762
+ }
763
+
764
+ async function sendFeedback(messageId, rating, container) {
765
+ try {
766
+ const response = await fetch(`${CONFIG.BASE_URL}/messages/${messageId}/feedbacks`, {
767
+ method: 'POST',
768
+ headers: {
769
+ 'Authorization': `Bearer ${CONFIG.DIFY_API_KEY}`,
770
+ 'Content-Type': 'application/json'
771
+ },
772
+ body: JSON.stringify({
773
+ rating: rating,
774
+ user: CONFIG.USER_ID
775
+ })
776
+ });
777
+
778
+ if (!response.ok) {
779
+ const errorText = await response.text();
780
+ console.error("Erro Dify:", errorText);
781
+ return;
782
+ }
783
+
784
+ container.innerHTML = `
785
+ <span style="font-size:12px;color:#10b981;">
786
+ ✓ Obrigado pelo feedback
787
+ </span>
788
+ `;
789
+
790
+ } catch (err) {
791
+ console.error('Erro ao enviar feedback', err);
792
+ }
793
+ }
794
+
795
+
796
+
797
+
798
+
799
+ // Inicialização da lógica
800
+ initializeEventListeners();
801
+ loadFromLocalStorage();
802
+ if (state.messageHistory.length === 0) addWelcomeMessage();
803
+ elements.input.focus();
804
+ console.log('chat iniciado!');
805
+ }
806
+ };
807
+
808
+ window.AviChatWidget = AviChatWidget;
809
+
810
+ })();
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "avichat-widget",
3
+ "version": "1.0.0",
4
+ "description": "Embeddable AviChat widget",
5
+ "main": "avichat.js",
6
+ "author": "akmes",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/akmes/avichat-widget.git"
11
+ },
12
+ "homepage": "https://github.com/akmes/avichat-widget",
13
+ "bugs": {
14
+ "url": "https://github.com/akmes/avichat-widget/issues"
15
+ },
16
+ "keywords": [
17
+ "chat",
18
+ "widget",
19
+ "embed",
20
+ "javascript"
21
+ ]
22
+ }