aryachakraborty's picture
Upload 5 files
cdff304 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>A.R.I.A | Advanced Voice Assistant</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #3b82f6;
--secondary: #1d4ed8;
--accent: #60a5fa;
--bg-dark: #0f172a;
--bg-darker: #020617;
--text-light: #f1f5f9;
}
body {
background-color: var(--bg-darker);
color: var(--text-light);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
overflow-x: hidden;
}
.glass-effect {
background: rgba(15, 23, 42, 0.75);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(148, 163, 184, 0.1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
/* Header styles without shadow */
.header-title {
font-size: 6rem;
font-weight: 800;
background: linear-gradient(to right, #60a5fa, #3b82f6);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
position: relative;
letter-spacing: 0.2em;
text-transform: uppercase;
}
.header-subtitle {
font-size: 1.25rem;
color: #94a3b8;
letter-spacing: 0.1em;
margin-top: 1rem;
font-weight: 500;
background: linear-gradient(to right, #94a3b8, #64748b);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Background effects */
.background-grid {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
linear-gradient(rgba(30, 41, 59, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(30, 41, 59, 0.1) 1px, transparent 1px);
background-size: 30px 30px;
z-index: -1;
opacity: 0.5;
}
.glow-effect {
position: absolute;
width: 150px;
height: 150px;
border-radius: 50%;
background: var(--primary);
filter: blur(100px);
opacity: 0.15;
animation: float 8s infinite ease-in-out;
}
@keyframes float {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(30px, -30px); }
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
}
70% {
box-shadow: 0 0 0 15px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
.wave {
position: relative;
height: 60px;
width: 60px;
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
}
.wave .dot {
display: inline-block;
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--primary);
animation: wave 1.3s linear infinite;
}
.wave .dot:nth-child(2) {
animation-delay: -1.1s;
}
.wave .dot:nth-child(3) {
animation-delay: -0.9s;
}
@keyframes wave {
0%, 60%, 100% {
transform: initial;
}
30% {
transform: translateY(-10px);
}
}
.voice-btn {
transition: all 0.3s ease;
background: linear-gradient(135deg, var(--primary), var(--secondary));
}
.voice-btn:hover {
transform: scale(1.05);
}
.voice-btn.active {
background: var(--accent);
transform: scale(0.95);
}
.response-text {
border-left: 3px solid var(--primary);
animation: textAppear 0.5s ease-out;
}
@keyframes textAppear {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.gradient-border {
position: relative;
border-radius: 0.75rem;
}
.gradient-border::before {
content: '';
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
background: linear-gradient(135deg, var(--primary), var(--accent));
border-radius: 0.75rem;
z-index: -1;
opacity: 0.5;
}
#voiceSelect {
background-color: rgba(30, 41, 59, 0.9);
color: var(--text-light);
border: 1px solid var(--primary);
}
.status-badge {
background: rgba(30, 41, 59, 0.9);
border: 1px solid rgba(148, 163, 184, 0.2);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.mode-btn {
position: relative;
overflow: hidden;
transform: scale(1);
}
.mode-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: currentColor;
opacity: 0;
transition: opacity 0.3s;
z-index: -1;
}
.mode-btn:hover {
transform: scale(1.05);
}
.mode-btn.selected {
background: rgba(255, 255, 255, 0.1);
transform: scale(0.98);
}
.mode-btn.selected.text-blue-400 {
box-shadow: 0 0 15px rgba(59, 130, 246, 0.3);
}
.mode-btn.selected.text-purple-400 {
box-shadow: 0 0 15px rgba(168, 85, 247, 0.3);
}
.mode-btn.selected.text-green-400 {
box-shadow: 0 0 15px rgba(74, 222, 128, 0.3);
}
.spectrum-button {
width: 80px;
height: 80px;
position: relative;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary), var(--secondary));
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
.spectrum-bars {
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
height: 40px;
}
.spectrum-bar {
width: 4px;
height: 20px;
background: white;
border-radius: 2px;
transition: height 0.2s ease;
}
.spectrum-button.active .spectrum-bars {
opacity: 1;
}
.spectrum-button.active .spectrum-bar {
animation: spectrum-dance 0.5s ease infinite;
}
.spectrum-button:hover {
transform: scale(1.05);
box-shadow: 0 0 20px rgba(var(--primary), 0.5);
}
.spectrum-button.active {
transform: scale(0.95);
background: var(--accent);
}
@keyframes spectrum-dance {
0%, 100% {
height: 20px;
}
50% {
height: 35px;
}
}
.spectrum-bar:nth-child(1) { animation-delay: 0.1s; }
.spectrum-bar:nth-child(2) { animation-delay: 0.2s; }
.spectrum-bar:nth-child(3) { animation-delay: 0.3s; }
.spectrum-bar:nth-child(4) { animation-delay: 0.4s; }
.spectrum-bar:nth-child(5) { animation-delay: 0.5s; }
</style>
</head>
<body>
<!-- Background effects -->
<div class="background-grid"></div>
<div class="glow-effect" style="top: 20%; left: 10%;"></div>
<div class="glow-effect" style="top: 60%; right: 10%; animation-delay: -4s; background: var(--accent);"></div>
<div class="min-h-screen flex flex-col items-center justify-center p-4">
<div class="glass-effect rounded-2xl p-8 w-full max-w-3xl mx-auto relative">
<!-- Improved header -->
<div class="text-center mb-12">
<h1 class="header-title" data-text="A.R.I.A">A.R.I.A</h1>
<p class="header-subtitle">Your Futuristic Voice-Controlled Companion</p>
<p class="text-slate-500 text-sm mt-3">Powered by smolLM2</p>
</div>
<!-- Status indicator -->
<div class="flex justify-center mb-6">
<div class="status-badge rounded-full px-4 py-2 flex items-center space-x-2">
<div id="statusIndicator" class="w-2.5 h-2.5 rounded-full bg-slate-500"></div>
<span id="statusText" class="text-sm font-medium text-slate-300">Ready</span>
</div>
</div>
<!-- Voice visualization -->
<div class="flex justify-center mb-6">
<div id="voiceVisualization" class="wave hidden">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</div>
<!-- Response area -->
<div id="responseArea" class="glass-effect rounded-xl p-6 mb-6 min-h-[120px] gradient-border">
<div class="flex items-start space-x-4">
<div class="flex-shrink-0 h-10 w-10 rounded-full bg-blue-900/50 flex items-center justify-center">
<i class="fas fa-robot text-blue-400"></i>
</div>
<div class="flex-1">
<p class="font-medium mb-2 text-blue-400">Assistant</p>
<div id="responseText" class="response-text pl-4 text-slate-300">
<!-- Response will appear here -->
</div>
</div>
</div>
</div>
<!-- Controls -->
<div class="space-y-4">
<!-- Mode selection -->
<div class="flex justify-center space-x-3 mb-4">
<button id="modeWeb" class="mode-btn px-4 py-2 rounded-lg glass-effect text-sm font-medium border transition-all duration-300 text-blue-400 border-blue-400/30 hover:border-blue-400/60 selected">
<i class="fas fa-search mr-2"></i>Web Search
</button>
<button id="modeReasoning" class="mode-btn px-4 py-2 rounded-lg glass-effect text-sm font-medium border transition-all duration-300 text-purple-400 border-purple-400/30 hover:border-purple-400/60">
<i class="fas fa-brain mr-2"></i>Reasoning
</button>
<button id="modeCreative" class="mode-btn px-4 py-2 rounded-lg glass-effect text-sm font-medium border transition-all duration-300 text-green-400 border-green-400/30 hover:border-green-400/60">
<i class="fas fa-magic mr-2"></i>Creative
</button>
</div>
<!-- Voice button with spectrum -->
<div class="flex justify-center">
<button id="voiceButton" class="spectrum-button shadow-lg">
<div class="spectrum-bars">
<div class="spectrum-bar"></div>
<div class="spectrum-bar"></div>
<div class="spectrum-bar"></div>
<div class="spectrum-bar"></div>
<div class="spectrum-bar"></div>
</div>
</button>
</div>
</div>
<!-- Instructions -->
<div class="mt-6 text-center">
<p class="text-slate-400 text-xs">Press and hold to speak, release to send</p>
</div>
</div>
</div>
<!-- Hidden form -->
<form id="queryForm" action="/" method="POST" class="hidden">
<input type="text" id="queryInput" name="query">
</form>
<script>
// DOM Elements
const voiceButton = document.getElementById('voiceButton');
const responseArea = document.getElementById('responseArea');
const responseText = document.getElementById('responseText');
const statusIndicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
const voiceVisualization = document.getElementById('voiceVisualization');
const queryForm = document.getElementById('queryForm');
const queryInput = document.getElementById('queryInput');
// Check if response area should be hidden initially
if (!responseText.textContent.trim()) {
responseArea.classList.add('hidden');
}
// Speech recognition setup
let recognition;
let isListening = false;
let finalTranscript = '';
let speechSynthesis = window.speechSynthesis;
let recognitionTimeout;
let recognitionRetries = 0;
const MAX_RETRIES = 3;
const checkSpeechSupport = () => {
return 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;
};
// Function to initialize speech recognition
function initSpeechRecognition() {
if (checkSpeechSupport()) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = true;
recognition.lang = 'en-US'; // Set language explicitly
recognition.onstart = () => {
clearTimeout(recognitionTimeout); // Clear timeout when properly started
isListening = true;
voiceButton.classList.add('active');
statusIndicator.classList.remove('bg-gray-500', 'bg-red-500');
statusIndicator.classList.add('bg-green-500');
statusText.textContent = 'Listening...';
voiceVisualization.classList.remove('hidden');
finalTranscript = '';
recognitionRetries = 0; // Reset retries counter
};
recognition.onresult = (event) => {
let interimTranscript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript;
} else {
interimTranscript += transcript;
}
}
// Show interim results
if (interimTranscript || finalTranscript) {
responseArea.classList.remove('hidden');
responseText.innerHTML = `<span class="text-gray-400">You said: "${interimTranscript || finalTranscript}"</span>${interimTranscript ? '<span class="typing-cursor"></span>' : ''}`;
}
};
recognition.onerror = (event) => {
console.error('Speech recognition error', event.error);
isListening = false;
voiceButton.classList.remove('active');
voiceVisualization.classList.add('hidden');
statusIndicator.classList.remove('bg-green-500');
statusIndicator.classList.add('bg-red-500');
// Special handling for permission errors
if (event.error === 'not-allowed') {
statusText.textContent = 'Microphone permission denied';
} else if (event.error === 'network') {
statusText.textContent = 'Network error. Check your connection.';
} else {
statusText.textContent = 'Error: ' + event.error;
}
setTimeout(resetStatus, 3000);
};
recognition.onend = () => {
clearTimeout(recognitionTimeout);
isListening = false;
voiceButton.classList.remove('active');
voiceVisualization.classList.add('hidden');
if (finalTranscript) {
processVoiceCommand(finalTranscript);
} else if (recognitionRetries < MAX_RETRIES) {
// Recognition ended without results, try again
recognitionRetries++;
statusText.textContent = `No speech detected, retrying (${recognitionRetries}/${MAX_RETRIES})...`;
setTimeout(() => {
try {
recognition.start();
} catch (err) {
console.error('Failed to restart recognition:', err);
resetStatus();
}
}, 1000);
} else {
statusText.textContent = 'No speech detected. Please try again.';
setTimeout(resetStatus, 2000);
}
};
// Button event handlers
voiceButton.addEventListener('mousedown', startListening);
voiceButton.addEventListener('touchstart', startListening);
voiceButton.addEventListener('mouseup', stopListening);
voiceButton.addEventListener('touchend', stopListening);
voiceButton.addEventListener('mouseleave', stopListening);
return true;
} else {
console.error('Speech recognition not supported in this browser');
voiceButton.disabled = true;
voiceButton.innerHTML = '<i class="fas fa-microphone-slash"></i>';
statusIndicator.classList.remove('bg-gray-500');
statusIndicator.classList.add('bg-red-500');
statusText.textContent = 'Voice not supported';
return false;
}
}
// Initialize speech recognition and voices when page loads
let speechInitialized = false;
window.addEventListener('DOMContentLoaded', () => {
speechInitialized = initSpeechRecognition();
// Handle any existing response from Flask
const flaskResponse = {{ response|tojson|safe }} || "";
if (flaskResponse && flaskResponse.trim().length > 0) {
responseArea.classList.remove('hidden');
responseText.textContent = flaskResponse;
speakResponse(flaskResponse);
}
// Try to test speech recognition without actually listening
if (speechInitialized) {
try {
// Just ping the recognition system to trigger permission requests
const testRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
testRecognition.continuous = false;
testRecognition.interimResults = false;
testRecognition.maxAlternatives = 1;
let testTimeout = setTimeout(() => {
try { testRecognition.stop(); } catch(e) {}
}, 1000);
testRecognition.onstart = () => {
clearTimeout(testTimeout);
setTimeout(() => {
try { testRecognition.stop(); } catch(e) {}
}, 100);
};
testRecognition.start();
} catch(e) {
console.warn('Speech recognition test failed:', e);
}
}
});
function startListening(e) {
e.preventDefault();
if (!isListening && recognition) {
try {
recognition.start();
// Set timeout in case recognition doesn't trigger onstart
recognitionTimeout = setTimeout(() => {
if (!isListening) {
console.warn("Recognition didn't start properly, retrying...");
try {
recognition.stop();
setTimeout(() => {
try {
recognition.start();
} catch(err) {
console.error('Failed to restart recognition:', err);
resetStatus();
}
}, 300);
} catch (err) {
console.error('Failed to stop non-started recognition:', err);
resetStatus();
}
}
}, 2000);
} catch (err) {
console.error('Recognition error:', err);
statusIndicator.classList.remove('bg-gray-500');
statusIndicator.classList.add('bg-red-500');
statusText.textContent = 'Error starting recognition';
setTimeout(resetStatus, 3000);
}
}
}
function stopListening(e) {
e.preventDefault();
if (recognition) {
try {
recognition.stop();
} catch (err) {
console.error('Error stopping recognition:', err);
}
}
}
function resetStatus() {
statusIndicator.classList.remove('bg-green-500', 'bg-red-500', 'bg-yellow-500', 'bg-blue-500');
statusIndicator.classList.add('bg-gray-500');
statusText.textContent = 'Ready';
}
// Mode selection handling
const modeButtons = document.querySelectorAll('.mode-btn');
let currentMode = 'web'; // Default mode
function setActiveMode(selectedButton) {
// Remove selected class from all buttons
modeButtons.forEach(btn => btn.classList.remove('selected'));
// Add selected class to clicked button
selectedButton.classList.add('selected');
// Set current mode based on button id
currentMode = selectedButton.id.replace('mode', '').toLowerCase();
}
// Initialize the web search button as selected
document.getElementById('modeWeb').classList.add('selected');
modeButtons.forEach(button => {
button.addEventListener('click', () => {
setActiveMode(button);
});
});
function processVoiceCommand(command) {
// Show recognized text
responseArea.classList.remove('hidden');
responseText.innerHTML = `<span class="text-gray-400">You said: "${command}"</span>`;
// Set query and mode in form
queryInput.value = command;
// Create or update mode input
let modeInput = queryForm.querySelector('input[name="mode"]');
if (!modeInput) {
modeInput = document.createElement('input');
modeInput.type = 'hidden';
modeInput.name = 'mode';
queryForm.appendChild(modeInput);
}
modeInput.value = currentMode;
// Simulate AI thinking
statusIndicator.classList.remove('bg-green-500');
statusIndicator.classList.add('bg-yellow-500');
statusText.textContent = 'Processing...';
// Submit form to Flask backend
queryForm.submit();
}
function speakResponse(text) {
if (speechSynthesis) {
// Cancel any ongoing speech
speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.rate = 1.1;
utterance.pitch = 1.1;
// Start speaking
speechSynthesis.speak(utterance);
// Visual feedback
statusIndicator.classList.remove('bg-yellow-500');
statusIndicator.classList.add('bg-blue-500');
statusText.textContent = 'Speaking';
utterance.onend = () => {
resetStatus();
};
}
}
</script>
</body>
</html>