Web artifacts

Note

These are literal views of the files in a repo. Paths assume the docs folder is at ./, not at docs/ since files are at the repo root. If your layout differs, tweak the .. literalinclude:: paths accordingly.

chat-widget.js
  1// static/chat-widget.js
  2
  3// ---------------------------
  4// Circassian DNA Chat Widget
  5// ---------------------------
  6
  7/*
  8Copyright (C) 2025 Mukharbek Organokov
  9Website: www.circassiandna.com
 10
 11This program is free software: you can redistribute it and/or modify
 12it under the terms of the GNU General Public License as published by
 13the Free Software Foundation, either version 3 of the License, or
 14(at your option) any later version.
 15
 16This program is distributed in the hope that it will be useful,
 17but WITHOUT ANY WARRANTY; without even the implied warranty of
 18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 19GNU General Public License for more details.
 20
 21You should have received a copy of the GNU General Public License
 22along with this program.  If not, see <https://www.gnu.org/licenses/>.
 23*/
 24
 25window.ChatWidget = {
 26    init: function ({ apiUrl, containerId, onMessage, onTyping }) {
 27        const container = document.getElementById(containerId);
 28        if (!container) return;
 29
 30        // Create ONLY the input row - remove the old chatlog structure
 31        container.innerHTML = `
 32            <div class="cw-input-row">
 33                <input type="text" id="userInput" placeholder="Ask me something...">
 34                <button id="sendBtn">Send</button>
 35            </div>
 36        `;
 37
 38        const input = container.querySelector("#userInput");
 39        const button = container.querySelector("#sendBtn");
 40
 41        const sendMessage = async () => {
 42            const message = input.value.trim();
 43            if (!message) return;
 44
 45            // Add user message using callback
 46            if (onMessage) {
 47                onMessage(message, 'user');
 48            }
 49
 50            input.value = '';
 51
 52            // Show typing indicator
 53            if (onTyping) {
 54                onTyping();
 55            }
 56
 57            try {
 58                const response = await fetch(apiUrl, {
 59                    method: "POST",
 60                    headers: { "Content-Type": "application/json" },
 61                    body: JSON.stringify({ question: message }),
 62                });
 63
 64                if (!response.ok) throw new Error("Network error");
 65
 66                const data = await response.json();
 67
 68                // Add bot response using callback
 69                if (onMessage) {
 70                    onMessage(data.answer, 'bot');
 71                }
 72
 73            } catch (err) {
 74                console.error('Chat widget error:', err);
 75
 76                // Add error message using callback
 77                if (onMessage) {
 78                    onMessage('Sorry, an error occurred. Please try again.', 'bot');
 79                }
 80            }
 81        };
 82
 83        // Button click
 84        button.onclick = sendMessage;
 85
 86        // Press Enter key
 87        input.addEventListener("keydown", (e) => {
 88            if (e.key === "Enter") {
 89                e.preventDefault();
 90                sendMessage();
 91            }
 92        });
 93
 94        // Auto-focus on input when widget loads
 95        setTimeout(() => {
 96            input.focus();
 97        }, 100);
 98
 99        // Return object with utility methods
100        return {
101            sendMessage,
102            focusInput: () => input.focus(),
103            clearInput: () => input.value = '',
104            setPlaceholder: (text) => input.placeholder = text
105        };
106    }
107};
style.css
 1body {
 2    font-family: Arial, sans-serif;
 3    margin: 30px;
 4}
 5
 6#chat-box {
 7    background: #f4f4f4;
 8    padding: 15px;
 9    margin-bottom: 10px;
10}
chatbot-widget-global-web.php
  1add_action('wp_footer', function() {
  2?>
  3<style>
  4/* Chat container - Expanded to almost full window */
  5#chatbot {
  6    position: fixed;
  7    bottom: 20px;
  8    right: 20px;
  9    width: calc(100vw - 40px);
 10    height: calc(100vh - 40px);
 11    max-width: 1200px;
 12    border: none;
 13    border-radius: 20px;
 14    background: #fff;
 15    display: none;
 16    flex-direction: column;
 17    overflow: hidden;
 18    z-index: 9999;
 19    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 20    box-shadow: 0 20px 40px rgba(0,0,0,0.15);
 21}
 22
 23/* Show when active */
 24#chatbot.active {
 25    display: flex;
 26}
 27	
 28/* Header */
 29#chatbot-header {
 30    background: linear-gradient(135deg, #567675, #4a625f);
 31    color: #fff;
 32    padding: 25px;
 33    font-weight: 600;
 34    text-align: center;
 35    font-size: 20px;
 36    position: relative;
 37    flex-shrink: 0;
 38    border-radius: 20px 20px 0 0;
 39}
 40	
 41/* Close button */
 42#chatbot-close {
 43    position: absolute;
 44    right: 20px;
 45    top: 50%;
 46    transform: translateY(-50%);
 47    cursor: pointer;
 48    font-weight: bold;
 49    font-size: 20px;
 50    background: rgba(255,255,255,0.2);
 51    width: 35px;
 52    height: 35px;
 53    border-radius: 50%;
 54    display: flex;
 55    align-items: center;
 56    justify-content: center;
 57    transition: all 0.3s ease;
 58}
 59
 60#chatbot-close:hover {
 61    background: rgba(255,255,255,0.3);
 62    transform: translateY(-50%) scale(1.1);
 63}
 64
 65/* Messages container - Takes up most space, remove old chatlog styling */
 66#chatbot-container {
 67    flex: 1;
 68    display: flex;
 69    flex-direction: column;
 70    padding: 25px;
 71    overflow-y: auto;
 72    background: linear-gradient(135deg, #f8f9fa, #e9ecef);
 73    min-height: 0; /* Important for flex */
 74}
 75
 76/* Remove old chatlog styling completely */
 77#chatlog {
 78    border: none !important;
 79    height: auto !important;
 80    overflow: visible !important;
 81    padding: 0 !important;
 82    background: transparent !important;
 83}
 84
 85/* Messages area */
 86#chatbot-messages {
 87    display: flex;
 88    flex-direction: column;
 89    gap: 15px;
 90    min-height: 100%;
 91    justify-content: flex-end;
 92}
 93
 94/* Custom scrollbar */
 95#chatbot-container::-webkit-scrollbar {
 96    width: 8px;
 97}
 98
 99#chatbot-container::-webkit-scrollbar-track {
100    background: transparent;
101}
102
103#chatbot-container::-webkit-scrollbar-thumb {
104    background: rgba(86, 118, 117, 0.3);
105    border-radius: 10px;
106}
107
108#chatbot-container::-webkit-scrollbar-thumb:hover {
109    background: rgba(86, 118, 117, 0.5);
110}
111
112/* Chat bubbles - Proper styling */
113.chat-message {
114    max-width: 75%;
115    padding: 15px 20px;
116    border-radius: 20px;
117    font-size: 16px;
118    line-height: 1.5;
119    word-wrap: break-word;
120    position: relative;
121    opacity: 0;
122    transform: translateY(20px);
123    animation: slideInMessage 0.4s ease-out forwards;
124}
125
126.chat-message.user {
127    background: linear-gradient(135deg, #567675, #4a625f);
128    color: #fff;
129    align-self: flex-end;
130    border-bottom-right-radius: 6px;
131    box-shadow: 0 4px 15px rgba(86, 118, 117, 0.3);
132}
133
134.chat-message.bot {
135    background: #fff;
136    color: #333;
137    align-self: flex-start;
138    border-bottom-left-radius: 6px;
139    box-shadow: 0 4px 15px rgba(0,0,0,0.1);
140    border: 1px solid rgba(86, 118, 117, 0.1);
141}
142
143@keyframes slideInMessage {
144    to {
145        opacity: 1;
146        transform: translateY(0);
147    }
148}
149
150/* Typing indicator */
151.typing {
152    background: #fff;
153    color: #666;
154    align-self: flex-start;
155    border-radius: 20px 20px 20px 6px;
156    padding: 15px 20px;
157    font-style: italic;
158    box-shadow: 0 4px 15px rgba(0,0,0,0.1);
159    border: 1px solid rgba(86, 118, 117, 0.1);
160    opacity: 0;
161    animation: slideInMessage 0.4s ease-out forwards;
162}
163
164.typing::after {
165    content: '';
166    animation: typingDots 1.4s infinite;
167}
168
169@keyframes typingDots {
170    0%, 20% { content: '.'; }
171    40% { content: '..'; }
172    60%, 100% { content: '...'; }
173}
174
175/* Input container - Replace the basic input styling */
176.cw-input-row {
177    padding: 25px !important;
178    background: #fff !important;
179    border-top: 1px solid rgba(86, 118, 117, 0.1) !important;
180    flex-shrink: 0 !important;
181    display: flex !important;
182    align-items: center !important;
183    gap: 15px !important;
184    border-radius: 0 0 20px 20px !important;
185}
186
187/* Input field styling */
188.cw-input-row input,
189#userInput {
190    flex: 1 !important;
191    height: 55px !important;
192    padding: 0 25px !important;
193    border-radius: 30px !important;
194    border: 2px solid #e9ecef !important;
195    font-size: 16px !important;
196    outline: none !important;
197    transition: all 0.3s ease !important;
198    font-family: inherit !important;
199    background: #f8f9fa !important;
200    width: auto !important; /* Override the 80% width */
201}
202
203.cw-input-row input:focus,
204#userInput:focus {
205    border-color: #567675 !important;
206    background: #fff !important;
207    box-shadow: 0 0 0 3px rgba(86, 118, 117, 0.1) !important;
208}
209
210/* Button styling */
211.cw-input-row button,
212#sendBtn {
213    height: 55px !important;
214    padding: 0 30px !important;
215    border-radius: 30px !important;
216    background: linear-gradient(135deg, #567675, #4a625f) !important;
217    color: #fff !important;
218    border: none !important;
219    cursor: pointer !important;
220    font-weight: 600 !important;
221    font-size: 16px !important;
222    box-shadow: 0 4px 15px rgba(86, 118, 117, 0.3) !important;
223    transition: all 0.3s ease !important;
224    font-family: inherit !important;
225}
226
227.cw-input-row button:hover,
228#sendBtn:hover {
229    transform: translateY(-2px) !important;
230    box-shadow: 0 6px 20px rgba(86, 118, 117, 0.4) !important;
231}
232
233.cw-input-row button:active,
234#sendBtn:active {
235    transform: translateY(0) !important;
236}
237
238/* Chat toggle button */
239#chatbot-toggle {
240    position: fixed;
241    bottom: 25px;
242    right: 25px;
243    background: linear-gradient(135deg, #567675, #4a625f);
244    color: #fff;
245    padding: 18px 25px;
246    border-radius: 50px;
247    cursor: pointer;
248    z-index: 10000;
249    font-weight: 600;
250    font-size: 16px;
251    box-shadow: 0 8px 25px rgba(86, 118, 117, 0.4);
252    transition: all 0.3s ease;
253    border: none;
254}
255
256#chatbot-toggle:hover {
257    transform: translateY(-3px) scale(1.05);
258    box-shadow: 0 12px 30px rgba(86, 118, 117, 0.5);
259}
260
261/* Responsive design */
262@media (max-width: 768px) {
263    #chatbot {
264        width: calc(100vw - 20px);
265        height: calc(100vh - 20px);
266        bottom: 10px;
267        right: 10px;
268        border-radius: 15px;
269    }
270    
271    #chatbot-header {
272        padding: 20px;
273        font-size: 18px;
274        border-radius: 15px 15px 0 0;
275    }
276    
277    #chatbot-container {
278        padding: 20px;
279    }
280    
281    .chat-message {
282        max-width: 85%;
283        font-size: 15px;
284        padding: 12px 16px;
285    }
286    
287    .cw-input-row {
288        padding: 20px !important;
289        border-radius: 0 0 15px 15px !important;
290    }
291    
292    .cw-input-row input,
293    .cw-input-row button {
294        height: 50px !important;
295    }
296}
297
298@media (max-width: 480px) {
299    #chatbot {
300        width: 100vw;
301        height: 100vh;
302        bottom: 0;
303        right: 0;
304        border-radius: 0;
305    }
306    
307    #chatbot-header {
308        border-radius: 0;
309    }
310    
311    .cw-input-row {
312        border-radius: 0 !important;
313    }
314}
315</style>
316
317<div id="chatbot">
318    <div id="chatbot-header">
319        🧬 Chat with us!
320        <div id="chatbot-close">&times;</div>
321    </div>
322    <div id="chatbot-container">
323        <div id="chatbot-messages"></div>
324    </div>
325</div>
326
327<div id="chatbot-toggle">💬 Chat</div>
328
329<script src="https://circassiandna-chatbot.onrender.com/static/chat-widget.js"></script>
330<script>
331document.addEventListener('DOMContentLoaded', () => {
332    const chatbox = document.getElementById('chatbot');
333    const toggle = document.getElementById('chatbot-toggle');
334    const closeBtn = document.getElementById('chatbot-close');
335    const messages = document.getElementById('chatbot-messages');
336
337    // Toggle chat visibility
338    toggle.addEventListener('click', () => {
339        chatbox.classList.toggle('active');
340        if (chatbox.classList.contains('active')) {
341            chatbox.style.display = 'flex';
342            toggle.style.display = 'none';
343            // Focus on input after opening
344            setTimeout(() => {
345                const input = document.querySelector('#userInput');
346                if (input) input.focus();
347            }, 100);
348        } else {
349            chatbox.style.display = 'none';
350            toggle.style.display = 'block';
351        }
352    });
353
354    // Close chat functionality
355    closeBtn.addEventListener('click', () => {
356        chatbox.classList.remove('active');
357        chatbox.style.display = 'none';
358        toggle.style.display = 'block';
359    });
360
361    // Initialize ChatWidget
362    ChatWidget.init({
363        apiUrl: 'https://circassiandna-chatbot.onrender.com/api/chat',
364        containerId: 'chatbot-messages',
365        onMessage: (msg, sender) => {
366            removeTypingIndicator();
367            addMessage(msg, sender);
368        },
369        onTyping: () => {
370            showTypingIndicator();
371        }
372    });
373
374    // Helper functions
375    function addMessage(text, sender) {
376        const bubble = document.createElement('div');
377        bubble.className = `chat-message ${sender}`;
378        bubble.textContent = text;
379        messages.appendChild(bubble);
380        
381        // Smooth scroll to bottom
382        setTimeout(() => {
383            messages.scrollTop = messages.scrollHeight;
384        }, 100);
385    }
386
387    function showTypingIndicator() {
388        if (!document.querySelector('.typing')) {
389            const typing = document.createElement('div');
390            typing.className = 'typing';
391            typing.textContent = 'Bot is typing';
392            messages.appendChild(typing);
393            
394            setTimeout(() => {
395                messages.scrollTop = messages.scrollHeight;
396            }, 100);
397        }
398    }
399
400    function removeTypingIndicator() {
401        const typing = document.querySelector('.typing');
402        if (typing) typing.remove();
403    }
404});
405</script>
406<?php
407});