RasoSpeak / settings.html
Sourabh-Kumar04
Fix all code review findings — remove dual-write, use public APIs
594ed02
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>RasoSpeak — Settings</title>
<link rel="icon" type="image/png" href="logo.png"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
<style>
* { font-family: 'Inter', system-ui, sans-serif; }
body { background: #FAFAFA; }
.nav-link { @apply px-4 py-2 rounded-lg text-sm font-medium transition-colors; }
.nav-link:hover { @apply bg-gray-100; }
.nav-link.active { @apply bg-emerald-50 text-emerald-700; }
.provider-btn { @apply p-4 border border-gray-200 rounded-lg text-center transition-colors cursor-pointer; }
.provider-btn:hover { @apply bg-gray-50; }
.provider-btn.active { @apply border-emerald-500 bg-emerald-50; }
</style>
</head>
<body class="min-h-screen">
<!-- Skip to main content -->
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-emerald-500 focus:text-white focus:rounded-lg focus:font-medium">Skip to main content</a>
<!-- Navigation -->
<nav class="bg-white border-b border-gray-200 sticky top-0 z-50" aria-label="Main navigation">
<div class="max-w-5xl mx-auto px-6">
<div class="flex items-center justify-between h-16">
<a href="index.html" class="flex items-center gap-3">
<img src="logo.png" alt="RasoSpeak" class="w-8 h-8">
<span class="font-bold text-lg">RasoSpeak</span>
</a>
<div class="flex items-center gap-1">
<a href="index.html" class="nav-link" aria-label="Home">Home</a>
<a href="chat.html" class="nav-link" aria-label="Chat">Chat</a>
<a href="memory.html" class="nav-link" aria-label="Memory">Memory</a>
<a href="coach.html" class="nav-link" aria-label="Coach">Coach</a>
<a href="docs.html" class="nav-link" aria-label="Docs">Docs</a>
<a href="settings.html" class="nav-link active" aria-label="Settings">Settings</a>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<main id="main-content" class="max-w-3xl mx-auto px-6 py-8">
<!-- AI Provider -->
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold mb-4">AI Provider</h2>
<p class="text-sm text-gray-500 mb-4">Choose which AI model to use</p>
<!-- Provider Buttons -->
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 mb-4">
<button onclick="setProvider('google')" id="btn-google" class="provider-btn active" aria-label="Select Google Gemini provider">
<div class="text-lg mb-1">🤖</div>
<div class="font-medium text-sm">Google Gemini</div>
<div class="text-xs text-gray-500">Free & Fast</div>
</button>
<button onclick="setProvider('nvidia')" id="btn-nvidia" class="provider-btn" aria-label="Select NVIDIA NIM provider">
<div class="text-lg mb-1">🎮</div>
<div class="font-medium text-sm">NVIDIA NIM</div>
<div class="text-xs text-gray-500">High Performance</div>
</button>
<button onclick="setProvider('openai')" id="btn-openai" class="provider-btn" aria-label="Select OpenAI GPT provider">
<div class="text-lg mb-1">🔵</div>
<div class="font-medium text-sm">OpenAI GPT</div>
<div class="text-xs text-gray-500">ChatGPT</div>
</button>
<button onclick="setProvider('anthropic')" id="btn-anthropic" class="provider-btn" aria-label="Select Anthropic Claude provider">
<div class="text-lg mb-1">🟣</div>
<div class="font-medium text-sm">Anthropic</div>
<div class="text-xs text-gray-500">Claude</div>
</button>
<button onclick="setProvider('openrouter')" id="btn-openrouter" class="provider-btn" aria-label="Select OpenRouter provider">
<div class="text-lg mb-1">🌐</div>
<div class="font-medium text-sm">OpenRouter</div>
<div class="text-xs text-gray-500">80+ Models</div>
</button>
<button onclick="setProvider('deepseek')" id="btn-deepseek" class="provider-btn" aria-label="Select DeepSeek provider">
<div class="text-lg mb-1">🔍</div>
<div class="font-medium text-sm">DeepSeek</div>
<div class="text-xs text-gray-500">Cost Effective</div>
</button>
</div>
<div id="provider-status" class="flex items-center gap-2 text-sm text-emerald-600">
<span class="w-2 h-2 bg-emerald-500 rounded-full"></span>
<span id="provider-name">Using: Google Gemini</span>
</div>
</div>
<!-- Wake Word -->
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold mb-4">Wake Word</h2>
<p class="text-sm text-gray-500 mb-4">Configure voice activation with "Hey Raso"</p>
<div class="flex items-center justify-between">
<div>
<p class="font-medium">"Hey Raso"</p>
<p class="text-sm text-gray-500">Say this to activate voice control</p>
</div>
<button id="wake-toggle" onclick="toggleWakeSetting()" class="px-6 py-3 bg-emerald-500 text-white rounded-lg font-medium hover:bg-emerald-600 transition-colors" aria-label="Toggle wake word">
Enable
</button>
</div>
<p id="wake-status" class="text-sm text-gray-500 mt-3">Status: Inactive</p>
</div>
<!-- Memory Settings -->
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold mb-4">Memory Settings</h2>
<div class="space-y-4">
<div class="flex items-center justify-between">
<div>
<p class="font-medium">Auto-save conversations</p>
<p class="text-sm text-gray-500">Automatically save chat history to memory</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="auto-save" class="sr-only peer" checked>
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-500"></div>
</label>
</div>
<div class="flex items-center justify-between">
<div>
<p class="font-medium">Remember personal facts</p>
<p class="text-sm text-gray-500">Store your preferences and facts</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="remember-facts" class="sr-only peer" checked>
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-emerald-500"></div>
</label>
</div>
<div class="pt-4 border-t border-gray-100">
<button onclick="cleanupMemory()" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-medium hover:bg-gray-200 mr-2" aria-label="Cleanup old sessions">
Cleanup Old Sessions
</button>
<button onclick="clearMemory()" class="text-red-600 text-sm hover:underline" aria-label="Clear all memory data">
Clear all memory data
</button>
</div>
</div>
</div>
<!-- Speech Coach Settings -->
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold mb-4">Speech Coach Settings</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">Chunk Size</label>
<select id="chunk-size" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500">
<option value="5">5 words (Easy)</option>
<option value="8" selected>8 words (Normal)</option>
<option value="12">12 words (Hard)</option>
<option value="15">15 words (Expert)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-2">Accuracy Threshold</label>
<select id="accuracy-threshold" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500">
<option value="0.6">60% (Beginner)</option>
<option value="0.75" selected>75% (Normal)</option>
<option value="0.85">85% (Strict)</option>
<option value="0.95">95% (Perfect)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium mb-2">Voice Speed</label>
<select id="voice-speed" class="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-emerald-500">
<option value="0.7">Slow (0.7x)</option>
<option value="0.85" selected>Normal (0.85x)</option>
<option value="1.0">Fast (1.0x)</option>
<option value="1.15">Very Fast (1.15x)</option>
</select>
</div>
</div>
</div>
<!-- System Status -->
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-6">
<h2 class="text-lg font-semibold mb-4">System Status</h2>
<div id="system-status" class="space-y-2 text-sm">
<div class="flex items-center gap-2">
<span class="w-2 h-2 bg-gray-400 rounded-full"></span>
<span>Loading system status...</span>
</div>
</div>
<button onclick="loadSystemStatus()" class="mt-4 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-medium hover:bg-gray-200" aria-label="Refresh system status">
Refresh Status
</button>
</div>
<!-- About -->
<div class="bg-white rounded-lg border border-gray-200 p-6">
<h2 class="text-lg font-semibold mb-4">About RasoSpeak</h2>
<div class="space-y-2 text-sm text-gray-600">
<p>Version 2.0.0</p>
<p>15 AI Agents sharing memory</p>
<p class="pt-4 text-xs text-gray-400">No GPU required - Uses external APIs</p>
</div>
</div>
</main>
<!-- Toast -->
<div id="toast" class="fixed bottom-6 left-1/2 -translate-x-1/2 bg-gray-900 text-white px-6 py-3 rounded-full text-sm font-medium shadow-xl transition-all duration-300 opacity-0 pointer-events-none translate-y-4 z-50" role="status" aria-live="polite" aria-atomic="true"></div>
<!-- Scripts -->
<script src="state.js"></script>
<script src="nlp.js"></script>
<script src="speech.js"></script>
<script src="ui.js"></script>
<script src="app.js"></script>
<script>
let currentProvider = 'google';
let wakeEnabled = false;
// Toast notification
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.remove('opacity-0', 'translate-y-4', 'pointer-events-none');
setTimeout(() => t.classList.add('opacity-0', 'translate-y-4', 'pointer-events-none'), 3000);
}
// Set AI Provider
async function setProvider(provider) {
try {
const resp = await fetch('/raso/provider', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider, temporary: false })
});
if (resp.ok) {
currentProvider = provider;
// Update UI
document.querySelectorAll('.provider-btn').forEach(btn => btn.classList.remove('active'));
document.getElementById(`btn-${provider}`).classList.add('active');
const names = {
google: 'Google Gemini',
nvidia: 'NVIDIA NIM',
openai: 'OpenAI GPT',
anthropic: 'Anthropic Claude',
openrouter: 'OpenRouter',
deepseek: 'DeepSeek'
};
document.getElementById('provider-name').textContent = `Using: ${names[provider] || provider}`;
showToast(`Switched to ${names[provider] || provider}`);
}
} catch (err) {
showToast('Failed to switch provider');
console.error(err);
}
}
// Toggle Wake Word
async function toggleWakeSetting() {
const btn = document.getElementById('wake-toggle');
const status = document.getElementById('wake-status');
try {
if (!wakeEnabled) {
// Start wake listening
const resp = await fetch('/voice/start', { method: 'POST' });
if (resp.ok) {
wakeEnabled = true;
btn.textContent = 'Disable';
btn.classList.remove('bg-emerald-500', 'hover:bg-emerald-600');
btn.classList.add('bg-red-500', 'hover:bg-red-600');
status.textContent = 'Status: Listening for "Hey Raso"';
showToast('Wake word enabled');
}
} else {
// Stop wake listening
const resp = await fetch('/voice/stop', { method: 'POST' });
if (resp.ok) {
wakeEnabled = false;
btn.textContent = 'Enable';
btn.classList.remove('bg-red-500', 'hover:bg-red-600');
btn.classList.add('bg-emerald-500', 'hover:bg-emerald-600');
status.textContent = 'Status: Inactive';
showToast('Wake word disabled');
}
}
} catch (err) {
showToast('Failed to toggle wake word');
console.error(err);
}
}
// Load System Status
async function loadSystemStatus() {
const container = document.getElementById('system-status');
try {
const resp = await fetch('/health');
const data = await resp.json();
const agents = data.agents || {};
const providers = data.providers || {};
const defaultProvider = data.default_provider || 'unknown';
let html = `
<div class="flex items-center gap-2">
<span class="w-2 h-2 ${data.status === 'ok' ? 'bg-emerald-500' : 'bg-red-500'} rounded-full"></span>
<span>Status: ${data.status}</span>
</div>
<div class="flex items-center gap-2">
<span class="w-2 h-2 bg-blue-500 rounded-full"></span>
<span>Default Provider: ${defaultProvider}</span>
</div>
<div class="flex items-center gap-2">
<span class="w-2 h-2 bg-blue-500 rounded-full"></span>
<span>Total Agents: ${data.total_agents || 0}</span>
</div>
<div class="mt-2 pt-2 border-t border-gray-100">
<p class="font-medium text-gray-700 mb-1">Available Providers:</p>
${Object.entries(providers).map(([key, val]) =>
`<div class="flex items-center gap-2 ml-4">
<span class="w-2 h-2 ${val ? 'bg-emerald-500' : 'bg-gray-300'} rounded-full"></span>
<span>${key.charAt(0).toUpperCase() + key.slice(1)}: ${val ? 'Configured' : 'Not configured'}</span>
</div>`
).join('')}
</div>
<div class="mt-2 pt-2 border-t border-gray-100">
<p class="font-medium text-gray-700 mb-1">Agents:</p>
${Object.entries(agents).map(([key, val]) =>
`<div class="flex items-center gap-2 ml-4">
<span class="w-2 h-2 ${val === 'ok' || val === 'ready' || val === 'active' || val === 'listening' ? 'bg-emerald-500' : val === 'failed' ? 'bg-red-500' : 'bg-yellow-500'} rounded-full"></span>
<span>${key}: ${val}</span>
</div>`
).join('')}
</div>
`;
container.innerHTML = html;
} catch (err) {
container.innerHTML = '<p class="text-red-500">Failed to load system status</p>';
console.error(err);
}
}
// Cleanup Memory (Second Brain handles this automatically)
async function cleanupMemory() {
try {
const resp = await fetch('/brain/clear', { method: 'POST' });
if (resp.ok) {
showToast('Old memories cleaned up');
} else {
throw new Error('Cleanup failed');
}
} catch (err) {
showToast('Cleanup unavailable');
console.error(err);
}
}
// Clear Memory
async function clearMemory() {
if (!confirm('Are you sure you want to clear all memory data? This cannot be undone.')) {
return;
}
try {
const resp = await fetch('/brain/clear', { method: 'POST' });
if (resp.ok) {
localStorage.clear();
showToast('Memory cleared');
} else {
throw new Error('Clear returned ' + resp.status);
}
} catch (err) {
localStorage.clear();
showToast('Memory cleared (local)');
console.error(err);
}
}
// Initialize
window.addEventListener('load', () => {
loadSystemStatus();
// Load saved settings
const savedProvider = localStorage.getItem('rs_provider');
if (savedProvider) {
setProvider(savedProvider);
}
});
</script>
</body>
</html>