| <!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"> |
|
|
| |
| <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> |
|
|
| |
| <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 id="main-content" class="max-w-3xl mx-auto px-6 py-8"> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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; |
| |
| |
| 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); |
| } |
| |
| |
| 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; |
| |
| |
| 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); |
| } |
| } |
| |
| |
| async function toggleWakeSetting() { |
| const btn = document.getElementById('wake-toggle'); |
| const status = document.getElementById('wake-status'); |
| |
| try { |
| if (!wakeEnabled) { |
| |
| 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 { |
| |
| 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); |
| } |
| } |
| |
| |
| 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); |
| } |
| } |
| |
| |
| 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); |
| } |
| } |
| |
| |
| 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); |
| } |
| } |
| |
| |
| window.addEventListener('load', () => { |
| loadSystemStatus(); |
| |
| |
| const savedProvider = localStorage.getItem('rs_provider'); |
| if (savedProvider) { |
| setProvider(savedProvider); |
| } |
| }); |
| </script> |
|
|
| </body> |
| </html> |