Phase 2 complete.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { X, Key, Zap, Palette, User, Volume2, Moon, Sun, Monitor } from 'lucide-react'
|
||||
import { X, Key, Zap, Palette, User, Volume2, Moon, Sun, Monitor, Bell, Minimize2 } from 'lucide-react'
|
||||
import { useSettingsStore } from '../stores/settingsStore'
|
||||
import { getAllCharacters, getCharacter } from '../lib/characters'
|
||||
import { getElevenLabsClient, Voice } from '../lib/elevenlabs'
|
||||
@@ -26,6 +26,8 @@ export function SettingsPanel({ onClose }: SettingsPanelProps) {
|
||||
sttLanguage,
|
||||
sttMode,
|
||||
theme,
|
||||
notificationsEnabled,
|
||||
minimizeToTray,
|
||||
setOpenRouterApiKey,
|
||||
setElevenLabsApiKey,
|
||||
setTemperature,
|
||||
@@ -42,6 +44,8 @@ export function SettingsPanel({ onClose }: SettingsPanelProps) {
|
||||
setSttLanguage,
|
||||
setSttMode,
|
||||
setTheme,
|
||||
setNotificationsEnabled,
|
||||
setMinimizeToTray,
|
||||
} = useSettingsStore()
|
||||
|
||||
const [browserVoices, setBrowserVoices] = useState<SpeechSynthesisVoice[]>([])
|
||||
@@ -56,29 +60,52 @@ export function SettingsPanel({ onClose }: SettingsPanelProps) {
|
||||
useEffect(() => {
|
||||
console.log('⚙️ SettingsPanel mounted')
|
||||
console.log('📥 Current ttsVoice from store:', ttsVoice)
|
||||
console.log('🔑 OpenRouter API Key exists:', !!openrouterApiKey, 'Length:', openrouterApiKey?.length || 0)
|
||||
console.log('🔑 ElevenLabs API Key exists:', !!elevenLabsApiKey, 'Length:', elevenLabsApiKey?.length || 0)
|
||||
console.log('💾 LocalStorage contents:', localStorage.getItem('eve-settings'))
|
||||
}, [])
|
||||
}, [openrouterApiKey, elevenLabsApiKey, ttsVoice])
|
||||
|
||||
// Load browser voices
|
||||
useEffect(() => {
|
||||
// Check if speechSynthesis is available (may not be in Tauri WebView)
|
||||
if (typeof window === 'undefined' || !window.speechSynthesis) {
|
||||
console.warn('⚠️ Speech Synthesis API not available in this environment')
|
||||
return
|
||||
}
|
||||
|
||||
const loadVoices = () => {
|
||||
const voices = window.speechSynthesis.getVoices()
|
||||
setBrowserVoices(voices)
|
||||
console.log(`🔊 Loaded ${voices.length} browser voices`)
|
||||
|
||||
// Check for duplicate voiceURIs
|
||||
const voiceURIs = voices.map(v => v.voiceURI)
|
||||
const duplicates = voiceURIs.filter((uri, index) => voiceURIs.indexOf(uri) !== index)
|
||||
if (duplicates.length > 0) {
|
||||
console.warn('⚠️ Found duplicate voice URIs:', [...new Set(duplicates)])
|
||||
try {
|
||||
const voices = window.speechSynthesis.getVoices()
|
||||
setBrowserVoices(voices)
|
||||
console.log(`🔊 Loaded ${voices.length} browser voices`)
|
||||
|
||||
// Check for duplicate voiceURIs
|
||||
const voiceURIs = voices.map(v => v.voiceURI)
|
||||
const duplicates = voiceURIs.filter((uri, index) => voiceURIs.indexOf(uri) !== index)
|
||||
if (duplicates.length > 0) {
|
||||
console.warn('⚠️ Found duplicate voice URIs:', [...new Set(duplicates)])
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Failed to load browser voices:', error)
|
||||
}
|
||||
}
|
||||
|
||||
loadVoices()
|
||||
window.speechSynthesis.addEventListener('voiceschanged', loadVoices)
|
||||
|
||||
try {
|
||||
window.speechSynthesis.addEventListener('voiceschanged', loadVoices)
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Failed to add voiceschanged listener:', error)
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.speechSynthesis.removeEventListener('voiceschanged', loadVoices)
|
||||
try {
|
||||
if (window.speechSynthesis) {
|
||||
window.speechSynthesis.removeEventListener('voiceschanged', loadVoices)
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -695,6 +722,79 @@ export function SettingsPanel({ onClose }: SettingsPanelProps) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* System Integration Section */}
|
||||
<section className="mb-8">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Bell className="w-5 h-5 text-green-500" />
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
System Integration
|
||||
</h3>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell className="w-4 h-4 text-green-500" />
|
||||
<label className="font-medium text-gray-800 dark:text-white">
|
||||
Desktop Notifications
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
Get notified when EVE responds (even when window is hidden)
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notificationsEnabled}
|
||||
onChange={(e) => setNotificationsEnabled(e.target.checked)}
|
||||
className="w-5 h-5 text-green-500 rounded focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg opacity-50">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Minimize2 className="w-4 h-4 text-gray-500" />
|
||||
<label className="font-medium text-gray-800 dark:text-white">
|
||||
Minimize to Tray
|
||||
</label>
|
||||
<span className="text-xs bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-400 px-2 py-0.5 rounded">
|
||||
Linux: Coming Soon
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
System tray temporarily unavailable on Linux (icon format issue)
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={minimizeToTray}
|
||||
onChange={(e) => setMinimizeToTray(e.target.checked)}
|
||||
disabled
|
||||
className="w-5 h-5 text-gray-400 rounded cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300">
|
||||
<strong className="text-yellow-700 dark:text-yellow-400">⌨️ Global Shortcut:</strong> Press{' '}
|
||||
<kbd className="px-2 py-1 bg-gray-200 dark:bg-gray-700 rounded text-xs font-mono">
|
||||
Ctrl+Shift+E
|
||||
</kbd>{' '}
|
||||
(or{' '}
|
||||
<kbd className="px-2 py-1 bg-gray-200 dark:bg-gray-700 rounded text-xs font-mono">
|
||||
Cmd+Shift+E
|
||||
</kbd>{' '}
|
||||
on Mac) to quickly show/hide EVE.
|
||||
<br />
|
||||
<span className="text-xs text-yellow-600 dark:text-yellow-400 mt-1 inline-block">
|
||||
Note: May not work on some Linux desktop environments due to permission restrictions.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Info Section */}
|
||||
<section className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<div className="flex items-start gap-2">
|
||||
|
||||
Reference in New Issue
Block a user