Andrewbluebird commited on
Commit
0bf715e
·
verified ·
1 Parent(s): 17d2409

Replace this block with a image placeholder

Browse files
Files changed (4) hide show
  1. IntroPage.html +2 -2
  2. SignIn.html +2 -2
  3. SignUp.html +2 -2
  4. index.html +394 -336
IntroPage.html CHANGED
@@ -156,10 +156,10 @@
156
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
157
  <div class="container mx-auto flex justify-between items-center">
158
  <div class="flex items-center space-x-2">
159
- <i data-feather="message-circle" class="text-blue-600"></i>
160
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
161
  </div>
162
- <div class="hidden md:flex space-x-8">
163
  <a href="IntroPage.html" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
164
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
165
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
 
156
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
157
  <div class="container mx-auto flex justify-between items-center">
158
  <div class="flex items-center space-x-2">
159
+ <img src="http://static.photos/animals/200x200/1" alt="FurryChat Logo" class="w-8 h-8 rounded-full">
160
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
161
  </div>
162
+ <div class="hidden md:flex space-x-8">
163
  <a href="IntroPage.html" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
164
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
165
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
SignIn.html CHANGED
@@ -18,10 +18,10 @@
18
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
19
  <div class="container mx-auto flex justify-between items-center">
20
  <div class="flex items-center space-x-2">
21
- <i data-feather="message-circle" class="text-blue-600"></i>
22
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
23
  </div>
24
- <div class="hidden md:flex space-x-8">
25
  <a href="IntroPage.html" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
26
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
27
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
 
18
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
19
  <div class="container mx-auto flex justify-between items-center">
20
  <div class="flex items-center space-x-2">
21
+ <img src="http://static.photos/animals/200x200/1" alt="FurryChat Logo" class="w-8 h-8 rounded-full">
22
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
23
  </div>
24
+ <div class="hidden md:flex space-x-8">
25
  <a href="IntroPage.html" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
26
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
27
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
SignUp.html CHANGED
@@ -18,10 +18,10 @@
18
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
19
  <div class="container mx-auto flex justify-between items-center">
20
  <div class="flex items-center space-x-2">
21
- <i data-feather="message-circle" class="text-blue-600"></i>
22
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
23
  </div>
24
- <div class="hidden md:flex space-x-8">
25
  <a href="IntroPage.html" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
26
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
27
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
 
18
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
19
  <div class="container mx-auto flex justify-between items-center">
20
  <div class="flex items-center space-x-2">
21
+ <img src="http://static.photos/animals/200x200/1" alt="FurryChat Logo" class="w-8 h-8 rounded-full">
22
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
23
  </div>
24
+ <div class="hidden md:flex space-x-8">
25
  <a href="IntroPage.html" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
26
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
27
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
index.html CHANGED
@@ -12,10 +12,6 @@
12
  <script src="https://unpkg.com/feather-icons"></script>
13
  <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
14
  <script src="https://unpkg.com/three@0.132.2/build/three.min.js"></script>
15
- <script src="https://unpkg.com/@react-three/fiber@7.0.24/dist/react-three-fiber.js"></script>
16
- <script src="https://unpkg.com/@react-three/drei@8.0.2/dist/drei.umd.js"></script>
17
- <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
18
- <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
19
  <script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
20
  <style>
21
  .chat-container {
@@ -31,6 +27,71 @@
31
  from { opacity: 0; transform: translateY(10px); }
32
  to { opacity: 1; transform: translateY(0); }
33
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  .typing-indicator {
35
  display: inline-flex;
36
  align-items: center;
@@ -76,75 +137,68 @@
76
  border-radius: 10px;
77
  }
78
 
79
- .animation-controls {
80
- background: rgba(255, 255, 255, 0.9);
81
- padding: 15px;
82
- border-radius: 10px;
83
- margin-top: 10px;
84
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
 
 
85
  }
86
 
87
- .animation-buttons {
88
- display: flex;
89
- flex-wrap: wrap;
90
- gap: 8px;
91
- margin-top: 10px;
92
  }
93
 
94
- .animation-btn {
95
- flex: 1;
96
- min-width: 80px;
97
- padding: 8px 12px;
98
- background: #3b82f6;
 
 
 
 
 
99
  color: white;
100
- border: none;
101
- border-radius: 6px;
102
- cursor: pointer;
103
- transition: all 0.3s;
104
- font-size: 12px;
105
- font-weight: 500;
106
- }
107
-
108
- .animation-btn:hover {
109
- background: #2563eb;
110
  }
111
 
112
- .animation-btn.active {
113
- background: #1d4ed8;
114
- }
 
115
 
116
- .upload-section {
117
- background: rgba(255, 255, 255, 0.9);
118
- padding: 15px;
119
- border-radius: 10px;
120
- margin-top: 10px;
121
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
122
  }
123
 
124
- .upload-btn {
125
- display: block;
126
- width: 100%;
127
- padding: 10px;
128
- background: #10b981;
129
- color: white;
130
- border: none;
131
- border-radius: 8px;
132
- font-weight: bold;
133
- cursor: pointer;
134
- transition: all 0.3s;
135
- margin-top: 10px;
136
  }
137
 
138
- .upload-btn:hover {
139
- background: #059669;
 
140
  }
141
 
142
- .model-info {
143
- margin-top: 10px;
144
- font-size: 12px;
145
- text-align: center;
146
- color: #6b7280;
 
 
147
  }
 
 
148
  </style>
149
  </head>
150
  <body class="bg-gray-50">
@@ -156,10 +210,10 @@
156
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
157
  <div class="container mx-auto flex justify-between items-center">
158
  <div class="flex items-center space-x-2">
159
- <i data-feather="message-circle" class="text-blue-600"></i>
160
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
161
  </div>
162
- <div class="hidden md:flex space-x-8">
163
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
164
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
165
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
@@ -176,7 +230,7 @@
176
  <!-- Combined Avatar and Chat Window -->
177
  <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-lg overflow-hidden" data-aos="fade-up">
178
  <!-- Combined Header with Avatar and Chat Info -->
179
- <div class="bg-gradient-to-r from-blue-600 to-purple-600 p-6 text-white">
180
  <div class="flex items-center space-x-4">
181
  <!-- 3D Avatar Container -->
182
  <div class="w-16 h-16 bg-white bg-opacity-20 rounded-lg overflow-hidden">
@@ -185,62 +239,48 @@
185
  <div>
186
  <h2 class="text-xl font-semibold">Sancia</h2>
187
  <p class="text-blue-100 text-sm">The Free-to-try furry AI</p>
 
 
 
 
188
  </div>
189
  </div>
190
  </div>
191
 
192
  <!-- Combined Chat Container -->
193
  <div class="flex" style="height: 500px;">
194
- <!-- 3D Avatar Side Panel -->
195
- <div class="w-1/3 bg-gray-100 border-r border-gray-200 flex flex-col">
196
- <!-- 3D Model Viewer -->
197
- <div class="flex-1 p-4">
198
  <model-viewer
199
  id="model-viewer"
200
  src=""
201
- alt="A 3D model"
202
  auto-rotate
203
  camera-controls
204
  shadow-intensity="1"
205
  environment-image="neutral"
206
  exposure="1">
 
207
  </model-viewer>
208
  </div>
209
-
210
- <!-- Upload Section -->
211
- <div class="upload-section">
212
- <h3 class="font-semibold text-gray-800 mb-2">Upload 3D Model</h3>
213
- <input type="file" id="model-upload" accept=".glb,.gltf" style="display: none;">
214
- <button class="upload-btn" id="upload-btn">Choose 3D Model File</button>
215
- <p class="model-info" id="model-info">No model loaded. Supported formats: .glb, .gltf</p>
216
- </div>
217
-
218
- <!-- Animation Controls -->
219
- <div class="animation-controls" id="animation-controls" style="display: none;">
220
- <h3 class="font-semibold text-gray-800 mb-2">Test Animation Controls</h3>
221
- <div class="animation-buttons" id="animation-buttons">
222
- <!-- Animation buttons will be added dynamically -->
223
- </div>
224
- </div>
225
  </div>
226
 
227
  <!-- Chat Container -->
228
  <div class="w-2/3 flex flex-col">
229
  <div class="chat-container p-4 bg-gray-50 flex-1" id="chat-messages">
230
- <!-- Welcome message -->
231
- <div class="message bg-white rounded-lg p-4 shadow-sm mb-4 ml-auto" data-aos="fade-left">
232
- <p class="text-gray-800">Hello! I'm Sancia, Free to try Furry AI. What topics would you like to talk with?</p>
233
- <p class="text-gray-800">(Pro tip: Sign up or Log in FurryChat.ai to get access to huge collection of community made AI Furries and make and customize your own AI Furry)</p>
234
- <span class="text-xs text-gray-500 block mt-1">Just now</span>
235
- </div>
236
 
237
  <!-- Sample conversation starters -->
238
- <div class="grid grid-cols-1 gap-2 mb-4">
239
  <button class="suggestion-btn bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg p-3 text-sm transition-colors" data-message="What can you do?">
240
  <i data-feather="help-circle" class="w-4 h-4 inline mr-1"></i> What can you do?
241
  </button>
242
  <button class="suggestion-btn bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg p-3 text-sm transition-colors" data-message="Tell me about you">
243
- <i data-feather="smile" class="w-4 h-4 inline mr-1"></i> Tell me a joke
244
  </button>
245
  <button class="suggestion-btn bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg p-3 text-sm transition-colors" data-message="How does AI Furry work?">
246
  <i data-feather="code" class="w-4 h-4 inline mr-1"></i> How does AI Furry work?
@@ -261,7 +301,6 @@
261
  </div>
262
  </div>
263
  </div>
264
-
265
  </div>
266
 
267
  <!-- Footer -->
@@ -324,7 +363,7 @@
324
  });
325
  feather.replace();
326
 
327
- // Initialize Vanta.js background
328
  VANTA.GLOBE({
329
  el: "#vanta-bg",
330
  mouseControls: true,
@@ -339,313 +378,332 @@
339
  size: 0.8
340
  });
341
 
342
- // Initialize 3D Avatar
343
- function initAvatar() {
344
- const canvas = document.getElementById('avatar-canvas');
345
- const mainCanvas = document.getElementById('avatar-canvas-main');
346
-
347
- // Create React component for small avatar in header
348
- const SmallAvatar = () => {
349
- return React.createElement(
350
- ReactThreeFiber.Canvas,
351
- {
352
- camera: { position: [0, 0, 2], fov: 60 },
353
- style: { height: '100%', width: '100%' }
354
- },
355
- React.createElement(
356
- ReactThreeFiber.Suspense,
357
- { fallback: null },
358
- React.createElement(
359
- ReactThreeFiber.mesh,
360
- { rotation: [0.5, 0.5, 0] },
361
- React.createElement(
362
- ReactThreeDrei.BoxGeometry,
363
- { args: [0.8, 0.8, 0.8] }
364
- ),
365
- React.createElement(
366
- ReactThreeFiber.meshStandardMaterial,
367
- { color: '#ffffff' }
368
- )
369
- ),
370
- React.createElement(
371
- ReactThreeFiber.ambientLight,
372
- { intensity: 0.7 }
373
- ),
374
- React.createElement(
375
- ReactThreeFiber.pointLight,
376
- { position: [2, 2, 2], intensity: 0.8 }
377
- )
378
- )
379
- );
380
- };
381
-
382
- // Create React component for main avatar
383
- const MainAvatar = () => {
384
- return React.createElement(
385
- ReactThreeFiber.Canvas,
386
- {
387
- camera: { position: [0, 0, 3], fov: 75 },
388
- style: { height: '100%', width: '100%' }
389
- },
390
- React.createElement(
391
- ReactThreeFiber.Suspense,
392
- { fallback: null },
393
- React.createElement(
394
- ReactThreeFiber.mesh,
395
- { rotation: [0, 0, 0] },
396
- React.createElement(
397
- ReactThreeDrei.BoxGeometry,
398
- { args: [1, 1, 1] }
399
- ),
400
- React.createElement(
401
- ReactThreeFiber.meshStandardMaterial,
402
- { color: '#3b82f6' }
403
- )
404
- ),
405
- React.createElement(
406
- ReactThreeFiber.ambientLight,
407
- { intensity: 0.5 }
408
- ),
409
- React.createElement(
410
- ReactThreeFiber.pointLight,
411
- { position: [5, 5, 5], intensity: 1 }
412
- ),
413
- React.createElement(
414
- ReactThreeFiber.pointLight,
415
- { position: [-5, -5, -5], intensity: 0.5 }
416
- )
417
- )
418
- );
419
- };
420
 
421
- // Render both avatars
422
- ReactDOM.render(React.createElement(SmallAvatar), canvas);
423
- ReactDOM.render(React.createElement(MainAvatar), mainCanvas);
424
- }
425
-
426
- // Initialize avatar after page load
427
- setTimeout(initAvatar, 100);
428
-
429
- // 3D Model Viewer Functionality
430
- document.addEventListener('DOMContentLoaded', function() {
431
- const modelViewer = document.getElementById('model-viewer');
432
- const uploadBtn = document.getElementById('upload-btn');
433
- const modelUpload = document.getElementById('model-upload');
434
- const modelInfo = document.getElementById('model-info');
435
- const animationControls = document.getElementById('animation-controls');
436
- const animationButtons = document.getElementById('animation-buttons');
437
 
438
- let currentAnimations = [];
 
439
 
440
- // Handle file upload
441
- uploadBtn.addEventListener('click', function() {
442
- modelUpload.click();
443
- });
444
 
445
- modelUpload.addEventListener('change', function(e) {
446
- const file = e.target.files[0];
447
- if (file) {
448
- const url = URL.createObjectURL(file);
449
- modelViewer.src = url;
450
- modelInfo.textContent = `Loaded: ${file.name}`;
451
-
452
- // Reset animation state
453
- currentAnimations = [];
454
- animationControls.style.display = 'none';
455
- animationButtons.innerHTML = '';
456
-
457
- // Check for animations after model loads
458
- modelViewer.addEventListener('load', function() {
459
- checkForAnimations();
460
- });
461
- }
462
- });
463
 
464
- // Check if the model has animations
465
- function checkForAnimations() {
466
- // This is a simplified approach - in a real implementation,
467
- // you would need to access the Three.js scene and check for animations
468
- // For now, we'll simulate this with a timeout
469
-
470
- setTimeout(() => {
471
- // Simulate finding animations
472
- const availableAnimations = ['Idle', 'Wave', 'Jump', 'Dance'];
473
-
474
- if (availableAnimations.length > 0) {
475
- currentAnimations = availableAnimations;
476
- setupAnimationControls(availableAnimations);
477
- } else {
478
- // No animations found
479
- }
480
- }, 1000);
481
- }
482
 
483
- // Set up animation control buttons
484
- function setupAnimationControls(animations) {
485
- animationControls.style.display = 'block';
486
- animationButtons.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
- animations.forEach(animation => {
489
- const button = document.createElement('button');
490
- button.classList.add('animation-btn');
491
- button.textContent = animation;
492
- button.addEventListener('click', () => {
493
- playAnimation(animation);
494
-
495
- // Update active state
496
- document.querySelectorAll('.animation-btn').forEach(btn => {
497
- btn.classList.remove('active');
498
- });
499
- button.classList.add('active');
500
- });
501
-
502
- animationButtons.appendChild(button);
503
- });
504
 
505
- // Add stop button
506
- const stopButton = document.createElement('button');
507
- stopButton.classList.add('animation-btn');
508
- stopButton.textContent = 'Stop';
509
- stopButton.addEventListener('click', () => {
510
- stopAnimation();
511
-
512
- // Remove active state from all buttons
513
- document.querySelectorAll('.animation-btn').forEach(btn => {
514
- btn.classList.remove('active');
515
- });
516
- });
517
 
518
- animationButtons.appendChild(stopButton);
 
 
519
  }
520
 
521
- // Play animation
522
- function playAnimation(animationName) {
523
- // In a real implementation, this would use Three.js AnimationMixer
524
- // For this demo, we'll simulate the behavior
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
 
526
- console.log(`Playing animation: ${animationName}`);
 
527
 
528
- // In a real implementation, you would:
529
- // 1. Get the animation clip by name
530
- // 2. Create an AnimationMixer if it doesn't exist
531
- // 3. Play the animation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  }
 
 
 
 
 
 
533
 
534
- // Stop animation
535
- function stopAnimation() {
536
- // In a real implementation, this would stop the AnimationMixer
537
-
538
- console.log("Animation stopped");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  }
 
 
540
  });
541
 
542
- // Chat functionality with better error handling
543
  document.addEventListener('DOMContentLoaded', function() {
544
  const chatMessages = document.getElementById('chat-messages');
545
  const userInput = document.getElementById('user-input');
546
  const sendBtn = document.getElementById('send-btn');
547
 
548
- // Test API connection on load
549
- testAPIConnection();
550
-
551
- async function testAPIConnection() {
552
- try {
553
- const response = await fetch('/api/test');
554
- const data = await response.json();
555
- console.log('API test:', data);
556
- } catch (error) {
557
- console.error('API test failed:', error);
558
- }
559
- }
560
 
561
- function handleKeyPress(event) {
562
- if (event.key === 'Enter') {
 
 
563
  sendMessage();
564
  }
565
- }
 
 
 
 
 
 
 
 
566
 
567
  async function sendMessage() {
568
  const message = userInput.value.trim();
569
  if (!message) return;
570
 
571
- // Add user message to chat
572
- addMessage(message, 'user');
573
  userInput.value = '';
574
  sendBtn.disabled = true;
575
 
576
  try {
577
- console.log('Sending message to API...');
578
 
579
  const response = await fetch('/api/chat', {
580
  method: 'POST',
581
- headers: {
582
- 'Content-Type': 'application/json',
583
- },
584
  body: JSON.stringify({
585
  message: message,
586
- conversation_history: []
587
  })
588
  });
589
 
590
- console.log('Response status:', response.status);
591
-
592
- // Check if response is JSON
593
- const contentType = response.headers.get('content-type');
594
- if (!contentType || !contentType.includes('application/json')) {
595
- const text = await response.text();
596
- console.error('Non-JSON response:', text.substring(0, 200));
597
- throw new Error('Server returned HTML instead of JSON. Check the API endpoint.');
598
- }
599
-
600
  const data = await response.json();
601
- console.log('Response data:', data);
602
-
603
  if (response.ok) {
604
- addMessage(data.response, 'bot');
 
605
  } else {
606
- addMessage(`Error: ${data.error || 'Unknown error'}`, 'bot');
607
  }
608
  } catch (error) {
609
- console.error('Network error:', error);
610
- addMessage(`Network error: ${error.message}`, 'bot');
611
  } finally {
612
  sendBtn.disabled = false;
613
  userInput.focus();
614
  }
615
  }
616
 
617
- function addMessage(text, sender) {
618
  const messageDiv = document.createElement('div');
 
619
 
620
- if (sender === 'user') {
621
- messageDiv.className = 'message bg-blue-100 rounded-lg p-4 shadow-sm mb-4 ml-auto';
622
- messageDiv.innerHTML = `
623
- <p class="text-blue-800">${text}</p>
624
- <span class="text-xs text-blue-600 block mt-1">${new Date().toLocaleTimeString()}</span>
625
- `;
626
- } else {
627
- messageDiv.className = 'message bg-white rounded-lg p-4 shadow-sm mb-4 mr-auto';
628
- messageDiv.innerHTML = `
629
- <p class="text-gray-800">${text}</p>
630
- <span class="text-xs text-gray-500 block mt-1">${new Date().toLocaleTimeString()}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  `;
632
  }
633
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  chatMessages.appendChild(messageDiv);
635
  chatMessages.scrollTop = chatMessages.scrollHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  }
637
 
638
- // Event listeners
639
- sendBtn.addEventListener('click', sendMessage);
640
- userInput.addEventListener('keypress', handleKeyPress);
641
-
642
- // Suggestion buttons functionality
643
- document.querySelectorAll('.suggestion-btn').forEach(button => {
644
- button.addEventListener('click', function() {
645
- userInput.value = this.getAttribute('data-message');
646
- sendMessage();
647
- });
648
- });
649
  });
650
  </script>
651
  </body>
 
12
  <script src="https://unpkg.com/feather-icons"></script>
13
  <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
14
  <script src="https://unpkg.com/three@0.132.2/build/three.min.js"></script>
 
 
 
 
15
  <script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
16
  <style>
17
  .chat-container {
 
27
  from { opacity: 0; transform: translateY(10px); }
28
  to { opacity: 1; transform: translateY(0); }
29
  }
30
+
31
+ /* Enhanced Audio Player Styles */
32
+ .message-audio {
33
+ margin-top: 10px;
34
+ padding: 8px;
35
+ background: #f8fafc;
36
+ border-radius: 8px;
37
+ border: 1px solid #e5e7eb;
38
+ }
39
+
40
+ .message-audio audio {
41
+ width: 100%;
42
+ height: 32px;
43
+ border-radius: 4px;
44
+ }
45
+
46
+ .audio-indicator {
47
+ display: inline-block;
48
+ background: #3b82f6;
49
+ color: white;
50
+ padding: 2px 6px;
51
+ border-radius: 4px;
52
+ font-size: 10px;
53
+ margin-left: 8px;
54
+ font-weight: 600;
55
+ }
56
+
57
+ .message-header {
58
+ display: flex;
59
+ justify-content: space-between;
60
+ align-items: center;
61
+ margin-bottom: 4px;
62
+ }
63
+
64
+ .message-sender {
65
+ font-weight: 600;
66
+ color: #374151;
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 6px;
70
+ }
71
+
72
+ .message-timestamp {
73
+ font-size: 11px;
74
+ color: #6b7280;
75
+ }
76
+
77
+ .bot-message {
78
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
79
+ border-left: 4px solid #3b82f6;
80
+ }
81
+
82
+ .user-message {
83
+ background: linear-gradient(135deg, #e0f2fe 0%, #dbeafe 100%);
84
+ border-right: 4px solid #1d4ed8;
85
+ }
86
+
87
+ .chat-welcome {
88
+ background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
89
+ border: 1px solid #a7f3d0;
90
+ border-radius: 12px;
91
+ padding: 20px;
92
+ margin-bottom: 20px;
93
+ }
94
+
95
  .typing-indicator {
96
  display: inline-flex;
97
  align-items: center;
 
137
  border-radius: 10px;
138
  }
139
 
140
+ .loading-spinner {
141
+ border: 3px solid #f3f3f3;
142
+ border-top: 3px solid #3b82f6;
143
+ border-radius: 50%;
144
+ width: 30px;
145
+ height: 30px;
146
+ animation: spin 1s linear infinite;
147
+ margin: 20px auto;
148
  }
149
 
150
+ @keyframes spin {
151
+ 0% { transform: rotate(0deg); }
152
+ 100% { transform: rotate(360deg); }
 
 
153
  }
154
 
155
+ /* Notification styles */
156
+ .notification {
157
+ position: fixed;
158
+ top: 20px;
159
+ right: 20px;
160
+ padding: 15px 25px;
161
+ border-radius: 10px;
162
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
163
+ z-index: 1000;
164
+ animation: slideIn 0.3s ease-out;
165
  color: white;
166
+ font-weight: 600;
167
+ max-width: 400px;
168
+ word-wrap: break-word;
 
 
 
 
 
 
 
169
  }
170
 
171
+ .notification-success { background: #2ecc71; }
172
+ .notification-error { background: #e74c3c; }
173
+ .notification-warning { background: #f39c12; }
174
+ .notification-info { background: #3498db; }
175
 
176
+ .notification-content {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 10px;
 
 
180
  }
181
 
182
+ @keyframes slideIn {
183
+ from { transform: translateX(100%); opacity: 0; }
184
+ to { transform: translateX(0); opacity: 1; }
 
 
 
 
 
 
 
 
 
185
  }
186
 
187
+ @keyframes slideOut {
188
+ from { transform: translateX(0); opacity: 1; }
189
+ to { transform: translateX(100%); opacity: 0; }
190
  }
191
 
192
+ /* Status indicators */
193
+ .status-dot {
194
+ display: inline-block;
195
+ width: 8px;
196
+ height: 8px;
197
+ border-radius: 50%;
198
+ margin-right: 6px;
199
  }
200
+ .status-online { background-color: #10b981; }
201
+ .status-offline { background-color: #ef4444; }
202
  </style>
203
  </head>
204
  <body class="bg-gray-50">
 
210
  <nav class="bg-white bg-opacity-90 backdrop-blur-sm shadow-sm py-4 px-6">
211
  <div class="container mx-auto flex justify-between items-center">
212
  <div class="flex items-center space-x-2">
213
+ <img src="http://static.photos/animals/200x200/1" alt="FurryChat Logo" class="w-8 h-8 rounded-full">
214
  <span class="text-xl font-bold text-gray-800">FurryChat.ai</span>
215
  </div>
216
+ <div class="hidden md:flex space-x-8">
217
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Home</a>
218
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Features</a>
219
  <a href="#" class="text-gray-600 hover:text-blue-600 transition-colors">Pricing</a>
 
230
  <!-- Combined Avatar and Chat Window -->
231
  <div class="max-w-4xl mx-auto bg-white rounded-xl shadow-lg overflow-hidden" data-aos="fade-up">
232
  <!-- Combined Header with Avatar and Chat Info -->
233
+ <div class="bg-gradient-to-r from-blue-600 to-blue-700 p-6 text-white">
234
  <div class="flex items-center space-x-4">
235
  <!-- 3D Avatar Container -->
236
  <div class="w-16 h-16 bg-white bg-opacity-20 rounded-lg overflow-hidden">
 
239
  <div>
240
  <h2 class="text-xl font-semibold">Sancia</h2>
241
  <p class="text-blue-100 text-sm">The Free-to-try furry AI</p>
242
+ <div id="tts-status" class="text-xs opacity-80 mt-1">
243
+ <span class="status-dot status-online"></span>
244
+ <span>TTS: Initializing...</span>
245
+ </div>
246
  </div>
247
  </div>
248
  </div>
249
 
250
  <!-- Combined Chat Container -->
251
  <div class="flex" style="height: 500px;">
252
+ <!-- 3D Avatar Side Panel (Now full width, clean) -->
253
+ <div class="w-1/3 bg-gray-100 border-r border-gray-200">
254
+ <!-- 3D Model Viewer - Full panel, no extra elements -->
255
+ <div class="h-full p-4">
256
  <model-viewer
257
  id="model-viewer"
258
  src=""
259
+ alt="Sancia Furry Avatar"
260
  auto-rotate
261
  camera-controls
262
  shadow-intensity="1"
263
  environment-image="neutral"
264
  exposure="1">
265
+ <div class="loading-spinner" id="model-loading"></div>
266
  </model-viewer>
267
  </div>
268
+ <!-- No TTS status bar here anymore -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  </div>
270
 
271
  <!-- Chat Container -->
272
  <div class="w-2/3 flex flex-col">
273
  <div class="chat-container p-4 bg-gray-50 flex-1" id="chat-messages">
274
+ <!-- Welcome message area -->
275
+ <div id="welcome-container"></div>
 
 
 
 
276
 
277
  <!-- Sample conversation starters -->
278
+ <div class="grid grid-cols-1 gap-2 mb-4 mt-4">
279
  <button class="suggestion-btn bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg p-3 text-sm transition-colors" data-message="What can you do?">
280
  <i data-feather="help-circle" class="w-4 h-4 inline mr-1"></i> What can you do?
281
  </button>
282
  <button class="suggestion-btn bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg p-3 text-sm transition-colors" data-message="Tell me about you">
283
+ <i data-feather="smile" class="w-4 h-4 inline mr-1"></i> Tell me about yourself
284
  </button>
285
  <button class="suggestion-btn bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg p-3 text-sm transition-colors" data-message="How does AI Furry work?">
286
  <i data-feather="code" class="w-4 h-4 inline mr-1"></i> How does AI Furry work?
 
301
  </div>
302
  </div>
303
  </div>
 
304
  </div>
305
 
306
  <!-- Footer -->
 
363
  });
364
  feather.replace();
365
 
366
+ // Initialize Vanta.js background with blue color
367
  VANTA.GLOBE({
368
  el: "#vanta-bg",
369
  mouseControls: true,
 
378
  size: 0.8
379
  });
380
 
381
+ // TTS State - TTS is always enabled by default
382
+ let ttsEnabled = true;
383
+ let ttsSupported = false;
384
+ let selectedVoice = 'default'; // Always use default voice
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
+ // Utility functions
387
+ function showNotification(message, type = 'info') {
388
+ const existingNotification = document.querySelector('.notification');
389
+ if (existingNotification) {
390
+ existingNotification.remove();
391
+ }
 
 
 
 
 
 
 
 
 
 
392
 
393
+ const notification = document.createElement('div');
394
+ notification.className = `notification notification-${type}`;
395
 
396
+ const featherIcon = type === 'success' ? 'check-circle' :
397
+ type === 'error' ? 'alert-circle' :
398
+ type === 'warning' ? 'alert-triangle' : 'info';
 
399
 
400
+ notification.innerHTML = `
401
+ <div class="notification-content">
402
+ <i data-feather="${featherIcon}"></i>
403
+ <span>${message}</span>
404
+ </div>
405
+ `;
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
+ document.body.appendChild(notification);
408
+ feather.replace();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
+ const duration = type === 'error' ? 5000 : 4000;
411
+ setTimeout(() => {
412
+ notification.style.animation = 'slideOut 0.3s ease-out forwards';
413
+ setTimeout(() => notification.remove(), 300);
414
+ }, duration);
415
+ }
416
+
417
+ function escapeHtml(text) {
418
+ const div = document.createElement('div');
419
+ div.textContent = text;
420
+ return div.innerHTML;
421
+ }
422
+
423
+ // SIMPLIFIED TTS INITIALIZATION - No user controls needed
424
+ async function initializeTTS() {
425
+ try {
426
+ console.log('🚀 Initializing TTS System...');
427
 
428
+ // Check TTS health
429
+ const ttsResponse = await fetch('/api/tts/info');
430
+ const ttsInfo = await ttsResponse.json();
 
 
 
 
 
 
 
 
 
 
 
 
 
431
 
432
+ console.log(`📊 TTS Info:`, ttsInfo);
433
+
434
+ ttsSupported = ttsInfo.tts_available === true;
435
+
436
+ if (ttsSupported) {
437
+ console.log(`✅ TTS: ${ttsInfo.tts_engine} - Ready`);
438
+ updateTTSStatus('ready');
439
+ } else {
440
+ console.error(' TTS not available on server');
441
+ updateTTSStatus('unavailable');
442
+ }
 
443
 
444
+ } catch (error) {
445
+ console.error('❌ TTS initialization failed:', error);
446
+ updateTTSStatus('error');
447
  }
448
 
449
+ // ALWAYS load welcome message (even if TTS fails)
450
+ await loadWelcomeMessage();
451
+ }
452
+
453
+ // Update TTS status display (only in header now)
454
+ function updateTTSStatus(status) {
455
+ const statusElement = document.getElementById('tts-status');
456
+
457
+ if (status === 'ready') {
458
+ statusElement.innerHTML = `<span class="status-dot status-online"></span>TTS: Ready`;
459
+ } else if (status === 'unavailable') {
460
+ statusElement.innerHTML = `<span class="status-dot status-offline"></span>TTS: Unavailable`;
461
+ } else if (status === 'error') {
462
+ statusElement.innerHTML = `<span class="status-dot status-offline"></span>TTS: Error`;
463
+ } else {
464
+ statusElement.innerHTML = `<span class="status-dot status-offline"></span>TTS: ${status}`;
465
+ }
466
+ }
467
+
468
+ // RELIABLE welcome message loading
469
+ async function loadWelcomeMessage() {
470
+ try {
471
+ console.log('📥 Loading welcome message...');
472
 
473
+ // Give backend a moment to be ready
474
+ await new Promise(resolve => setTimeout(resolve, 1000));
475
 
476
+ const response = await fetch('/api/welcome');
477
+ const data = await response.json();
478
+
479
+ if (data.status === 'success' && data.message) {
480
+ const welcomeContainer = document.getElementById('welcome-container');
481
+ const hasAudio = data.has_audio && data.audio_url;
482
+
483
+ console.log(`🎯 Welcome loaded. Has audio: ${hasAudio}`);
484
+
485
+ // Create welcome message
486
+ const welcomeHTML = `
487
+ <div class="chat-welcome" data-aos="fade-right">
488
+ <div class="message-header">
489
+ <div class="message-sender">
490
+ <i data-feather="smile" class="w-4 h-4"></i>
491
+ Sancia
492
+ ${hasAudio ? '<span class="audio-indicator"><i data-feather="volume-2" class="w-3 h-3"></i> TTS</span>' : ''}
493
+ </div>
494
+ <div class="message-timestamp">Just now</div>
495
+ </div>
496
+ <div class="message-content">${data.message.replace(/\n/g, '<br>')}</div>
497
+ ${hasAudio ? `
498
+ <div class="message-audio">
499
+ <audio controls preload="auto">
500
+ <source src="${data.audio_url}" type="audio/wav">
501
+ Your browser does not support audio playback.
502
+ </audio>
503
+ <div class="text-xs text-gray-500 mt-1 flex items-center">
504
+ <i data-feather="headphones" class="w-3 h-3 mr-1"></i>
505
+ Click play to hear the welcome message
506
+ </div>
507
+ </div>
508
+ ` : ''}
509
+ </div>
510
+ `;
511
+
512
+ welcomeContainer.innerHTML = welcomeHTML;
513
+ feather.replace();
514
+
515
+ }
516
+ } catch (error) {
517
+ console.error('❌ Failed to load welcome:', error);
518
+ // Fallback welcome message
519
+ document.getElementById('welcome-container').innerHTML = `
520
+ <div class="chat-welcome">
521
+ <div class="message-header">
522
+ <div class="message-sender">
523
+ <i data-feather="smile" class="w-4 h-4"></i>
524
+ Sancia
525
+ </div>
526
+ <div class="message-timestamp">Just now</div>
527
+ </div>
528
+ <div class="message-content">
529
+ (Seems something wrong just happended, please visit the page later)
530
+ </div>
531
+ </div>
532
+ `;
533
+ feather.replace();
534
  }
535
+ }
536
+
537
+ // 3D Model Viewer
538
+ document.addEventListener('DOMContentLoaded', function() {
539
+ const modelViewer = document.getElementById('model-viewer');
540
+ const modelLoading = document.getElementById('model-loading');
541
 
542
+ async function loadSpecificModel() {
543
+ try {
544
+ modelLoading.style.display = 'block';
545
+
546
+ const response = await fetch('/api/model');
547
+ const data = await response.json();
548
+
549
+ if (data.status === 'success') {
550
+ const modelUrl = '/api/model/file';
551
+ modelViewer.src = modelUrl;
552
+
553
+ modelViewer.addEventListener('load', function() {
554
+ modelLoading.style.display = 'none';
555
+ });
556
+ } else {
557
+ console.error('Failed to load model:', data.error);
558
+ modelLoading.style.display = 'none';
559
+ }
560
+ } catch (error) {
561
+ console.error('Error loading model:', error);
562
+ modelLoading.style.display = 'none';
563
+ }
564
  }
565
+
566
+ loadSpecificModel();
567
  });
568
 
569
+ // Chat functionality
570
  document.addEventListener('DOMContentLoaded', function() {
571
  const chatMessages = document.getElementById('chat-messages');
572
  const userInput = document.getElementById('user-input');
573
  const sendBtn = document.getElementById('send-btn');
574
 
575
+ // Initialize TTS (auto-enabled)
576
+ initializeTTS();
 
 
 
 
 
 
 
 
 
 
577
 
578
+ // Event Listeners
579
+ sendBtn.addEventListener('click', sendMessage);
580
+ userInput.addEventListener('keypress', function(e) {
581
+ if (e.key === 'Enter') {
582
  sendMessage();
583
  }
584
+ });
585
+
586
+ // Suggestion buttons
587
+ document.querySelectorAll('.suggestion-btn').forEach(button => {
588
+ button.addEventListener('click', function() {
589
+ userInput.value = this.getAttribute('data-message');
590
+ sendMessage();
591
+ });
592
+ });
593
 
594
  async function sendMessage() {
595
  const message = userInput.value.trim();
596
  if (!message) return;
597
 
598
+ // Add user message
599
+ addUserMessage(message);
600
  userInput.value = '';
601
  sendBtn.disabled = true;
602
 
603
  try {
604
+ console.log('📤 Sending to AI...');
605
 
606
  const response = await fetch('/api/chat', {
607
  method: 'POST',
608
+ headers: {'Content-Type': 'application/json'},
 
 
609
  body: JSON.stringify({
610
  message: message,
611
+ voice: selectedVoice // Always use default
612
  })
613
  });
614
 
 
 
 
 
 
 
 
 
 
 
615
  const data = await response.json();
616
+
 
617
  if (response.ok) {
618
+ // Add bot response with audio player
619
+ addBotMessage(data.response, data.audio_url);
620
  } else {
621
+ addBotMessage(`Error: ${data.error || 'Unknown error'}`, null);
622
  }
623
  } catch (error) {
624
+ console.error('Network error:', error);
625
+ addBotMessage(`Network error: ${error.message}`, null);
626
  } finally {
627
  sendBtn.disabled = false;
628
  userInput.focus();
629
  }
630
  }
631
 
632
+ function addUserMessage(text) {
633
  const messageDiv = document.createElement('div');
634
+ messageDiv.className = 'message user-message rounded-lg p-4 shadow-sm mb-4 ml-auto';
635
 
636
+ const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
637
+
638
+ messageDiv.innerHTML = `
639
+ <div class="message-header">
640
+ <div class="message-sender">
641
+ <i data-feather="user" class="w-4 h-4"></i>
642
+ You
643
+ </div>
644
+ <div class="message-timestamp">${timestamp}</div>
645
+ </div>
646
+ <div class="message-content">${escapeHtml(text)}</div>
647
+ `;
648
+
649
+ chatMessages.appendChild(messageDiv);
650
+ chatMessages.scrollTop = chatMessages.scrollHeight;
651
+ feather.replace();
652
+ }
653
+
654
+ function addBotMessage(text, audioUrl) {
655
+ const messageDiv = document.createElement('div');
656
+ messageDiv.className = 'message bot-message rounded-lg p-4 shadow-sm mb-4 mr-auto';
657
+
658
+ const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
659
+
660
+ let audioHTML = '';
661
+ if (audioUrl) {
662
+ audioHTML = `
663
+ <div class="message-audio">
664
+ <audio controls>
665
+ <source src="${audioUrl}" type="audio/wav">
666
+ Your browser does not support audio playback.
667
+ </audio>
668
+ <div class="text-xs text-gray-500 mt-1 flex items-center">
669
+ <i data-feather="headphones" class="w-3 h-3 mr-1"></i>
670
+ Click play to hear the response
671
+ </div>
672
+ </div>
673
  `;
674
  }
675
 
676
+ messageDiv.innerHTML = `
677
+ <div class="message-header">
678
+ <div class="message-sender">
679
+ <i data-feather="smile" class="w-4 h-4"></i>
680
+ Sancia
681
+ ${audioUrl ? '<span class="audio-indicator"><i data-feather="volume-2" class="w-3 h-3"></i> TTS</span>' : ''}
682
+ </div>
683
+ <div class="message-timestamp">${timestamp}</div>
684
+ </div>
685
+ <div class="message-content">${escapeHtml(text)}</div>
686
+ ${audioHTML}
687
+ `;
688
+
689
  chatMessages.appendChild(messageDiv);
690
  chatMessages.scrollTop = chatMessages.scrollHeight;
691
+ feather.replace();
692
+
693
+ // Auto-play if TTS is enabled and we have audio
694
+ if (ttsEnabled && audioUrl) {
695
+ setTimeout(() => {
696
+ const audioElement = messageDiv.querySelector('audio');
697
+ if (audioElement) {
698
+ audioElement.play().catch(e => {
699
+ console.log('Auto-play prevented:', e);
700
+ });
701
+ }
702
+ }, 500);
703
+ }
704
  }
705
 
706
+ console.log('🚀 Chat system ready with auto-TTS');
 
 
 
 
 
 
 
 
 
 
707
  });
708
  </script>
709
  </body>