remove emotion-library moves + praise; tune detection thresholds
Browse files- main.py: drop _PERSONALITY_*_EMOTIONS dicts and play_move(emo, ...)
calls — they hammered the motor controller (Serial I/O errors) and
fought TTS audio. Shame path keeps TTS + lightweight count-based
animation. Putdown handler is silent (just streak timer).
- audio.py auto-path now prefers Groq Orpheus when groq_key is set
(ElevenLabs > Groq > Edge). enable_orpheus_tags fires when Groq is
in play (provider=groq or auto+key), so the LLM emits [angry] etc
whenever Orpheus will consume them.
- config.py: PICKUP_THRESHOLD 3 -> 2 frames (Pi 5 hits ~3 fps so 3
frames was a full second), DETECTION_CONFIDENCE 0.3 -> 0.2 (more
eager pickup detection).
- UI: drop emotion-animations + praise checkboxes from settings modal
(along with the JS handlers). praise field still accepted for
back-compat but ignored server-side.
|
@@ -413,7 +413,8 @@ class TextToSpeech:
|
|
| 413 |
# For ElevenLabs and Edge (auto path), strip Orpheus tags so they aren't spoken verbatim
|
| 414 |
text_no_tags = strip_orpheus_tags(text)
|
| 415 |
|
| 416 |
-
#
|
|
|
|
| 417 |
# Try ElevenLabs first if available and under limit
|
| 418 |
if self.eleven_client and (self.chars_used + len(text_no_tags)) < self.MONTHLY_LIMIT:
|
| 419 |
# Check cache first
|
|
@@ -441,9 +442,20 @@ class TextToSpeech:
|
|
| 441 |
continue
|
| 442 |
|
| 443 |
# All voices failed
|
| 444 |
-
logger.warning(f"All ElevenLabs voices failed for {self.personality}, falling back to Edge
|
| 445 |
|
| 446 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
logger.info(f"Using Edge TTS with voice: {edge_voice}")
|
| 448 |
return await self._synthesize_edge(text_no_tags, output_path, edge_voice)
|
| 449 |
|
|
|
|
| 413 |
# For ElevenLabs and Edge (auto path), strip Orpheus tags so they aren't spoken verbatim
|
| 414 |
text_no_tags = strip_orpheus_tags(text)
|
| 415 |
|
| 416 |
+
# Auto path: ElevenLabs (if works) > Groq Orpheus (if key) > Edge (fallback).
|
| 417 |
+
# Explicit "elevenlabs": same as auto-with-eleven, falls through to Edge on failure.
|
| 418 |
# Try ElevenLabs first if available and under limit
|
| 419 |
if self.eleven_client and (self.chars_used + len(text_no_tags)) < self.MONTHLY_LIMIT:
|
| 420 |
# Check cache first
|
|
|
|
| 442 |
continue
|
| 443 |
|
| 444 |
# All voices failed
|
| 445 |
+
logger.warning(f"All ElevenLabs voices failed for {self.personality}, falling back to Groq/Edge")
|
| 446 |
|
| 447 |
+
# In the auto path, prefer Groq Orpheus over Edge when groq_key is available.
|
| 448 |
+
# (Explicit provider="edge"/"groq"/"elevenlabs" already short-circuited above.)
|
| 449 |
+
if self.groq_key:
|
| 450 |
+
try:
|
| 451 |
+
wav_path = output_path.rsplit(".", 1)[0] + ".wav"
|
| 452 |
+
logger.info(f"Auto-path -> Groq Orpheus voice={self.groq_tts_voice}")
|
| 453 |
+
# Tags pass through to Orpheus
|
| 454 |
+
return await self._synthesize_groq(text, wav_path)
|
| 455 |
+
except Exception as e:
|
| 456 |
+
logger.warning(f"Groq TTS failed in auto-path: {e}, falling back to Edge TTS")
|
| 457 |
+
|
| 458 |
+
# Final fallback: Edge TTS (always works, unlimited)
|
| 459 |
logger.info(f"Using Edge TTS with voice: {edge_voice}")
|
| 460 |
return await self._synthesize_edge(text_no_tags, output_path, edge_voice)
|
| 461 |
|
|
@@ -8,9 +8,11 @@ from dataclasses import dataclass
|
|
| 8 |
class Config:
|
| 9 |
"""App configuration."""
|
| 10 |
# Detection settings
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
PUTDOWN_THRESHOLD: int = 15 # Frames to confirm phone put down (~3 sec)
|
| 13 |
-
DETECTION_CONFIDENCE: float = 0.
|
| 14 |
COOLDOWN_SECONDS: float = 10.0 # Min time between shames
|
| 15 |
|
| 16 |
# API Keys (optional - leave empty for free defaults)
|
|
|
|
| 8 |
class Config:
|
| 9 |
"""App configuration."""
|
| 10 |
# Detection settings
|
| 11 |
+
# Pi 5 camera path yields ~3 fps; threshold=2 -> ~0.7 s held phone before
|
| 12 |
+
# first shame (vs 1 s with the upstream default of 3 frames).
|
| 13 |
+
PICKUP_THRESHOLD: int = 2 # Frames to confirm phone pickup
|
| 14 |
PUTDOWN_THRESHOLD: int = 15 # Frames to confirm phone put down (~3 sec)
|
| 15 |
+
DETECTION_CONFIDENCE: float = 0.2 # Higher = fewer false positives
|
| 16 |
COOLDOWN_SECONDS: float = 10.0 # Min time between shames
|
| 17 |
|
| 18 |
# API Keys (optional - leave empty for free defaults)
|
|
@@ -408,85 +408,35 @@ class JudgyReachyNoPhone(ReachyMiniApp):
|
|
| 408 |
text = self.llm.get_response(count)
|
| 409 |
logger.info(f"Response: {text}")
|
| 410 |
|
| 411 |
-
# Generate and play audio
|
|
|
|
|
|
|
| 412 |
try:
|
| 413 |
loop = asyncio.new_event_loop()
|
| 414 |
asyncio.set_event_loop(loop)
|
| 415 |
audio_path = loop.run_until_complete(self.tts.synthesize(text))
|
| 416 |
loop.close()
|
| 417 |
|
| 418 |
-
# Play audio
|
| 419 |
reachy.media.play_sound(audio_path)
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
# falls back to count-based generic animation if emotions disabled or unavailable.
|
| 423 |
-
emo_name = _pick_emotion(self.llm.personality, "shame") if self.emotions_enabled else None
|
| 424 |
-
if emo_name and self.emotions:
|
| 425 |
-
try:
|
| 426 |
-
emo = self.emotions.get(emo_name)
|
| 427 |
-
logger.info(f"Playing shame emotion: {emo_name} (personality={self.llm.personality})")
|
| 428 |
-
reachy.play_move(emo, sound=False)
|
| 429 |
-
except Exception as e:
|
| 430 |
-
logger.warning(f"Emotion '{emo_name}' failed: {e}; falling back to generic animation")
|
| 431 |
-
get_animation_for_count(count)(reachy)
|
| 432 |
-
else:
|
| 433 |
-
animation = get_animation_for_count(count)
|
| 434 |
-
animation(reachy)
|
| 435 |
|
| 436 |
except Exception as e:
|
| 437 |
logger.error(f"Shame response error: {e}")
|
| 438 |
-
# Fallback: just animate
|
| 439 |
play_sound_safe(reachy, "confused1.wav")
|
| 440 |
disappointed_shake(reachy)
|
| 441 |
|
| 442 |
def _handle_phone_putdown(self, reachy: ReachyMini):
|
| 443 |
-
"""Handle phone put down event.
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
self.current_streak_start = time.time()
|
| 448 |
|
| 449 |
-
# Check if using Pure Reachy mode (no TTS, just emotions)
|
| 450 |
-
if self.llm.personality == "pure_reachy" and self.emotions:
|
| 451 |
-
# Randomly pick a praise emotion from the config list
|
| 452 |
-
import random
|
| 453 |
-
personality_data = PERSONALITIES["pure_reachy"]
|
| 454 |
-
praise_emotions = personality_data.get("praise_emotions", ["yes1"])
|
| 455 |
-
emotion_name = random.choice(praise_emotions)
|
| 456 |
-
emotion = self.emotions.get(emotion_name)
|
| 457 |
-
logger.info(f"Pure Reachy praise: {emotion_name}")
|
| 458 |
-
|
| 459 |
-
# Play emotion (includes sound + animation automatically)
|
| 460 |
-
reachy.play_move(emotion)
|
| 461 |
-
else:
|
| 462 |
-
# Normal mode: Get praise via TTS
|
| 463 |
-
text = self.llm.get_praise()
|
| 464 |
-
logger.info(f"Praise: {text}")
|
| 465 |
-
|
| 466 |
-
try:
|
| 467 |
-
loop = asyncio.new_event_loop()
|
| 468 |
-
asyncio.set_event_loop(loop)
|
| 469 |
-
audio_path = loop.run_until_complete(self.tts.synthesize(text))
|
| 470 |
-
loop.close()
|
| 471 |
-
|
| 472 |
-
reachy.media.play_sound(audio_path)
|
| 473 |
-
|
| 474 |
-
emo_name = _pick_emotion(self.llm.personality, "praise") if self.emotions_enabled else None
|
| 475 |
-
if emo_name and self.emotions:
|
| 476 |
-
try:
|
| 477 |
-
emo = self.emotions.get(emo_name)
|
| 478 |
-
logger.info(f"Playing praise emotion: {emo_name} (personality={self.llm.personality})")
|
| 479 |
-
reachy.play_move(emo, sound=False)
|
| 480 |
-
except Exception as e:
|
| 481 |
-
logger.warning(f"Emotion '{emo_name}' failed: {e}; falling back to approving_nod")
|
| 482 |
-
approving_nod(reachy)
|
| 483 |
-
else:
|
| 484 |
-
approving_nod(reachy)
|
| 485 |
-
|
| 486 |
-
except Exception as e:
|
| 487 |
-
logger.debug(f"Praise error: {e}")
|
| 488 |
-
approving_nod(reachy)
|
| 489 |
-
|
| 490 |
def _run_ui(self, reachy_mini: ReachyMini, stop_event: threading.Event):
|
| 491 |
"""Setup FastAPI routes for the UI."""
|
| 492 |
|
|
@@ -606,7 +556,7 @@ class JudgyReachyNoPhone(ReachyMiniApp):
|
|
| 606 |
user_name=req.user_name,
|
| 607 |
custom_prompt=req.custom_prompt,
|
| 608 |
model=req.model,
|
| 609 |
-
enable_orpheus_tags=(req.tts_provider
|
| 610 |
)
|
| 611 |
self.emotions_enabled = req.emotions_enabled
|
| 612 |
|
|
@@ -776,7 +726,7 @@ class JudgyReachyNoPhone(ReachyMiniApp):
|
|
| 776 |
user_name=req.user_name,
|
| 777 |
custom_prompt=req.custom_prompt,
|
| 778 |
model=req.model,
|
| 779 |
-
enable_orpheus_tags=(req.tts_provider
|
| 780 |
)
|
| 781 |
self.emotions_enabled = req.emotions_enabled
|
| 782 |
|
|
@@ -812,7 +762,8 @@ class JudgyReachyNoPhone(ReachyMiniApp):
|
|
| 812 |
text = self.llm.get_response(self.detector.phone_count)
|
| 813 |
logger.info(f"Test response: {text}")
|
| 814 |
|
| 815 |
-
# Play audio
|
|
|
|
| 816 |
try:
|
| 817 |
loop = asyncio.new_event_loop()
|
| 818 |
asyncio.set_event_loop(loop)
|
|
@@ -820,20 +771,8 @@ class JudgyReachyNoPhone(ReachyMiniApp):
|
|
| 820 |
loop.close()
|
| 821 |
|
| 822 |
reachy_mini.media.play_sound(audio_path)
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
emo_name = _pick_emotion(req.personality, "shame") if req.emotions_enabled else None
|
| 826 |
-
if emo_name and self.emotions:
|
| 827 |
-
try:
|
| 828 |
-
emo = self.emotions.get(emo_name)
|
| 829 |
-
logger.info(f"Test playing emotion: {emo_name} (personality={req.personality})")
|
| 830 |
-
reachy_mini.play_move(emo, sound=False)
|
| 831 |
-
except Exception as e:
|
| 832 |
-
logger.warning(f"Test emotion '{emo_name}' failed: {e}; generic animation")
|
| 833 |
-
get_animation_for_count(self.detector.phone_count)(reachy_mini)
|
| 834 |
-
else:
|
| 835 |
-
animation = get_animation_for_count(self.detector.phone_count)
|
| 836 |
-
animation(reachy_mini)
|
| 837 |
except Exception as e:
|
| 838 |
logger.error(f"Test error: {e}")
|
| 839 |
play_sound_safe(reachy_mini, "confused1.wav")
|
|
@@ -852,7 +791,7 @@ class JudgyReachyNoPhone(ReachyMiniApp):
|
|
| 852 |
user_name=req.user_name,
|
| 853 |
custom_prompt=req.custom_prompt,
|
| 854 |
model=req.model,
|
| 855 |
-
enable_orpheus_tags=(req.tts_provider
|
| 856 |
)
|
| 857 |
self.emotions_enabled = req.emotions_enabled
|
| 858 |
logger.info(
|
|
|
|
| 408 |
text = self.llm.get_response(count)
|
| 409 |
logger.info(f"Response: {text}")
|
| 410 |
|
| 411 |
+
# Generate and play audio + generic animation only.
|
| 412 |
+
# (Emotion-library play_move() was triggering motor I/O errors —
|
| 413 |
+
# removed in favor of the lightweight personality-agnostic animation.)
|
| 414 |
try:
|
| 415 |
loop = asyncio.new_event_loop()
|
| 416 |
asyncio.set_event_loop(loop)
|
| 417 |
audio_path = loop.run_until_complete(self.tts.synthesize(text))
|
| 418 |
loop.close()
|
| 419 |
|
|
|
|
| 420 |
reachy.media.play_sound(audio_path)
|
| 421 |
+
animation = get_animation_for_count(count)
|
| 422 |
+
animation(reachy)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
| 424 |
except Exception as e:
|
| 425 |
logger.error(f"Shame response error: {e}")
|
|
|
|
| 426 |
play_sound_safe(reachy, "confused1.wav")
|
| 427 |
disappointed_shake(reachy)
|
| 428 |
|
| 429 |
def _handle_phone_putdown(self, reachy: ReachyMini):
|
| 430 |
+
"""Handle phone put down event.
|
| 431 |
+
|
| 432 |
+
Praise was removed per user preference — the robot should react when
|
| 433 |
+
the phone is picked up, but stay silent when it's put down (the user
|
| 434 |
+
doesn't need a reward for the absence of a problem).
|
| 435 |
+
Only the streak timer restarts; no audio, no motion.
|
| 436 |
+
"""
|
| 437 |
+
logger.info("Phone put down (silent — praise disabled)")
|
| 438 |
self.current_streak_start = time.time()
|
| 439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
def _run_ui(self, reachy_mini: ReachyMini, stop_event: threading.Event):
|
| 441 |
"""Setup FastAPI routes for the UI."""
|
| 442 |
|
|
|
|
| 556 |
user_name=req.user_name,
|
| 557 |
custom_prompt=req.custom_prompt,
|
| 558 |
model=req.model,
|
| 559 |
+
enable_orpheus_tags=bool(req.groq_key) and req.tts_provider in ("groq", "auto"),
|
| 560 |
)
|
| 561 |
self.emotions_enabled = req.emotions_enabled
|
| 562 |
|
|
|
|
| 726 |
user_name=req.user_name,
|
| 727 |
custom_prompt=req.custom_prompt,
|
| 728 |
model=req.model,
|
| 729 |
+
enable_orpheus_tags=bool(req.groq_key) and req.tts_provider in ("groq", "auto"),
|
| 730 |
)
|
| 731 |
self.emotions_enabled = req.emotions_enabled
|
| 732 |
|
|
|
|
| 762 |
text = self.llm.get_response(self.detector.phone_count)
|
| 763 |
logger.info(f"Test response: {text}")
|
| 764 |
|
| 765 |
+
# Play audio + lightweight count-based animation. Emotion-library
|
| 766 |
+
# play_move() removed — was the source of motor I/O errors.
|
| 767 |
try:
|
| 768 |
loop = asyncio.new_event_loop()
|
| 769 |
asyncio.set_event_loop(loop)
|
|
|
|
| 771 |
loop.close()
|
| 772 |
|
| 773 |
reachy_mini.media.play_sound(audio_path)
|
| 774 |
+
animation = get_animation_for_count(self.detector.phone_count)
|
| 775 |
+
animation(reachy_mini)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 776 |
except Exception as e:
|
| 777 |
logger.error(f"Test error: {e}")
|
| 778 |
play_sound_safe(reachy_mini, "confused1.wav")
|
|
|
|
| 791 |
user_name=req.user_name,
|
| 792 |
custom_prompt=req.custom_prompt,
|
| 793 |
model=req.model,
|
| 794 |
+
enable_orpheus_tags=bool(req.groq_key) and req.tts_provider in ("groq", "auto"),
|
| 795 |
)
|
| 796 |
self.emotions_enabled = req.emotions_enabled
|
| 797 |
logger.info(
|
|
@@ -180,18 +180,6 @@
|
|
| 180 |
<label for="cooldown">Cooldown between shames (seconds): <span id="cooldown-value">10</span></label>
|
| 181 |
<input type="range" id="cooldown" min="10" max="120" value="10" step="5">
|
| 182 |
</div>
|
| 183 |
-
<div class="form-group">
|
| 184 |
-
<label class="checkbox-label">
|
| 185 |
-
<input type="checkbox" id="praise-toggle" checked>
|
| 186 |
-
Praise when phone is put down
|
| 187 |
-
</label>
|
| 188 |
-
</div>
|
| 189 |
-
<div class="form-group">
|
| 190 |
-
<label class="checkbox-label">
|
| 191 |
-
<input type="checkbox" id="emotions-toggle" checked>
|
| 192 |
-
Emotion animations (head/antenna moves from Reachy emotion library)
|
| 193 |
-
</label>
|
| 194 |
-
</div>
|
| 195 |
</div>
|
| 196 |
<div class="settings-footer">
|
| 197 |
<button class="btn btn-done" id="done-settings">✓ Done</button>
|
|
|
|
| 180 |
<label for="cooldown">Cooldown between shames (seconds): <span id="cooldown-value">10</span></label>
|
| 181 |
<input type="range" id="cooldown" min="10" max="120" value="10" step="5">
|
| 182 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
</div>
|
| 184 |
<div class="settings-footer">
|
| 185 |
<button class="btn btn-done" id="done-settings">✓ Done</button>
|
|
@@ -16,22 +16,17 @@ function getCustomSettings() {
|
|
| 16 |
const model = (document.getElementById('groq-model')?.value || '').trim();
|
| 17 |
const ttsProvider = (document.getElementById('tts-provider')?.value || 'auto').trim();
|
| 18 |
const groqTtsVoice = (document.getElementById('groq-tts-voice')?.value || '').trim();
|
| 19 |
-
// Default to true if checkbox missing in older DOM
|
| 20 |
-
const emotionsEl = document.getElementById('emotions-toggle');
|
| 21 |
-
const emotionsEnabled = emotionsEl ? !!emotionsEl.checked : true;
|
| 22 |
localStorage.setItem('userName', userName);
|
| 23 |
localStorage.setItem('customPrompt', customPrompt);
|
| 24 |
localStorage.setItem('groqModel', model);
|
| 25 |
localStorage.setItem('ttsProvider', ttsProvider);
|
| 26 |
localStorage.setItem('groqTtsVoice', groqTtsVoice);
|
| 27 |
-
localStorage.setItem('emotionsEnabled', emotionsEnabled ? '1' : '0');
|
| 28 |
return {
|
| 29 |
user_name: userName,
|
| 30 |
custom_prompt: customPrompt,
|
| 31 |
model: model,
|
| 32 |
tts_provider: ttsProvider,
|
| 33 |
groq_tts_voice: groqTtsVoice,
|
| 34 |
-
emotions_enabled: emotionsEnabled,
|
| 35 |
};
|
| 36 |
}
|
| 37 |
|
|
@@ -50,12 +45,6 @@ function hydrateCustomSettings() {
|
|
| 50 |
if (v !== null) el.value = v;
|
| 51 |
}
|
| 52 |
}
|
| 53 |
-
// Hydrate emotions toggle (default ON if never set)
|
| 54 |
-
const emo = document.getElementById('emotions-toggle');
|
| 55 |
-
if (emo) {
|
| 56 |
-
const stored = localStorage.getItem('emotionsEnabled');
|
| 57 |
-
emo.checked = stored === null ? true : stored === '1';
|
| 58 |
-
}
|
| 59 |
// Also hydrate groq-key & eleven-key from localStorage if present (existing UX gap).
|
| 60 |
const gk = document.getElementById('groq-key');
|
| 61 |
if (gk && localStorage.getItem('groqKey')) gk.value = localStorage.getItem('groqKey');
|
|
@@ -130,7 +119,7 @@ async function updateUIForAPIKeys() {
|
|
| 130 |
const groqKey = document.getElementById('groq-key').value;
|
| 131 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 132 |
const cooldown = document.getElementById('cooldown').value;
|
| 133 |
-
const praise = document.getElementById('praise-toggle').checked;
|
| 134 |
|
| 135 |
// If no API keys at all, show default message
|
| 136 |
if (!groqKey && !elevenKey) {
|
|
@@ -379,7 +368,7 @@ async function toggleMonitoring() {
|
|
| 379 |
const groqKey = document.getElementById('groq-key').value;
|
| 380 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 381 |
const cooldown = document.getElementById('cooldown').value;
|
| 382 |
-
const praise = document.getElementById('praise-toggle').checked;
|
| 383 |
|
| 384 |
// Get voice override for selected personality
|
| 385 |
const voiceOverride = voiceOverrides[selectedPersonality] || {};
|
|
@@ -428,7 +417,7 @@ async function testShame() {
|
|
| 428 |
const groqKey = document.getElementById('groq-key').value;
|
| 429 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 430 |
const cooldown = document.getElementById('cooldown').value;
|
| 431 |
-
const praise = document.getElementById('praise-toggle').checked;
|
| 432 |
|
| 433 |
// Get voice override for selected personality
|
| 434 |
const voiceOverride = voiceOverrides[selectedPersonality] || {};
|
|
@@ -460,7 +449,7 @@ async function updatePersonalityWhileRunning() {
|
|
| 460 |
const groqKey = document.getElementById('groq-key').value;
|
| 461 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 462 |
const cooldown = document.getElementById('cooldown').value;
|
| 463 |
-
const praise = document.getElementById('praise-toggle').checked;
|
| 464 |
const voiceOverride = voiceOverrides[selectedPersonality] || {};
|
| 465 |
|
| 466 |
try {
|
|
@@ -580,7 +569,6 @@ async function initialize() {
|
|
| 580 |
document.getElementById('groq-model')?.addEventListener('change', () => getCustomSettings());
|
| 581 |
document.getElementById('tts-provider')?.addEventListener('change', () => getCustomSettings());
|
| 582 |
document.getElementById('groq-tts-voice')?.addEventListener('change', () => getCustomSettings());
|
| 583 |
-
document.getElementById('emotions-toggle')?.addEventListener('change', () => getCustomSettings());
|
| 584 |
|
| 585 |
// Initial UI update
|
| 586 |
updateDisplay();
|
|
|
|
| 16 |
const model = (document.getElementById('groq-model')?.value || '').trim();
|
| 17 |
const ttsProvider = (document.getElementById('tts-provider')?.value || 'auto').trim();
|
| 18 |
const groqTtsVoice = (document.getElementById('groq-tts-voice')?.value || '').trim();
|
|
|
|
|
|
|
|
|
|
| 19 |
localStorage.setItem('userName', userName);
|
| 20 |
localStorage.setItem('customPrompt', customPrompt);
|
| 21 |
localStorage.setItem('groqModel', model);
|
| 22 |
localStorage.setItem('ttsProvider', ttsProvider);
|
| 23 |
localStorage.setItem('groqTtsVoice', groqTtsVoice);
|
|
|
|
| 24 |
return {
|
| 25 |
user_name: userName,
|
| 26 |
custom_prompt: customPrompt,
|
| 27 |
model: model,
|
| 28 |
tts_provider: ttsProvider,
|
| 29 |
groq_tts_voice: groqTtsVoice,
|
|
|
|
| 30 |
};
|
| 31 |
}
|
| 32 |
|
|
|
|
| 45 |
if (v !== null) el.value = v;
|
| 46 |
}
|
| 47 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
// Also hydrate groq-key & eleven-key from localStorage if present (existing UX gap).
|
| 49 |
const gk = document.getElementById('groq-key');
|
| 50 |
if (gk && localStorage.getItem('groqKey')) gk.value = localStorage.getItem('groqKey');
|
|
|
|
| 119 |
const groqKey = document.getElementById('groq-key').value;
|
| 120 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 121 |
const cooldown = document.getElementById('cooldown').value;
|
| 122 |
+
const praise = document.getElementById('praise-toggle')?.checked ?? true;
|
| 123 |
|
| 124 |
// If no API keys at all, show default message
|
| 125 |
if (!groqKey && !elevenKey) {
|
|
|
|
| 368 |
const groqKey = document.getElementById('groq-key').value;
|
| 369 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 370 |
const cooldown = document.getElementById('cooldown').value;
|
| 371 |
+
const praise = document.getElementById('praise-toggle')?.checked ?? true;
|
| 372 |
|
| 373 |
// Get voice override for selected personality
|
| 374 |
const voiceOverride = voiceOverrides[selectedPersonality] || {};
|
|
|
|
| 417 |
const groqKey = document.getElementById('groq-key').value;
|
| 418 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 419 |
const cooldown = document.getElementById('cooldown').value;
|
| 420 |
+
const praise = document.getElementById('praise-toggle')?.checked ?? true;
|
| 421 |
|
| 422 |
// Get voice override for selected personality
|
| 423 |
const voiceOverride = voiceOverrides[selectedPersonality] || {};
|
|
|
|
| 449 |
const groqKey = document.getElementById('groq-key').value;
|
| 450 |
const elevenKey = document.getElementById('eleven-key').value;
|
| 451 |
const cooldown = document.getElementById('cooldown').value;
|
| 452 |
+
const praise = document.getElementById('praise-toggle')?.checked ?? true;
|
| 453 |
const voiceOverride = voiceOverrides[selectedPersonality] || {};
|
| 454 |
|
| 455 |
try {
|
|
|
|
| 569 |
document.getElementById('groq-model')?.addEventListener('change', () => getCustomSettings());
|
| 570 |
document.getElementById('tts-provider')?.addEventListener('change', () => getCustomSettings());
|
| 571 |
document.getElementById('groq-tts-voice')?.addEventListener('change', () => getCustomSettings());
|
|
|
|
| 572 |
|
| 573 |
// Initial UI update
|
| 574 |
updateDisplay();
|