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">×</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});