updated voice, avatar and frontend to reflect changes
Some checks failed
Deploy FluentGerman.ai / deploy (push) Failing after 51s

This commit is contained in:
2026-02-16 20:23:46 +01:00
parent 84cd052ded
commit a708f84a07
5 changed files with 65 additions and 13 deletions

View File

@@ -81,6 +81,14 @@ jobs:
# Restart service
#sudo systemctl restart fluentgerman
echo "✓ FluentGerman.ai deployed"
# Run uvicorn
cd ${DEPLOY_PATH}/backend
source venv/bin/activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8999
echo "✓ FluentGerman.ai running"
REMOTE_SCRIPT
# Cleanup

View File

@@ -18,7 +18,17 @@ router = APIRouter(prefix="/api/voice", tags=["voice"])
@router.get("/config", response_model=VoiceConfigOut)
async def voice_config(user: User = Depends(get_current_user)):
"""Return current voice mode so frontend knows whether to use browser or API."""
return VoiceConfigOut(voice_mode=get_settings().voice_mode)
settings = get_settings()
# API STT (Whisper) works with OpenAI-compatible providers
api_available = bool(
settings.voice_mode == "api"
and settings.llm_api_key
and settings.llm_provider in ("openai",)
)
return VoiceConfigOut(
voice_mode=settings.voice_mode,
voice_api_available=api_available,
)
@router.post("/transcribe")

View File

@@ -90,3 +90,4 @@ class VoiceInstructionRequest(BaseModel):
class VoiceConfigOut(BaseModel):
voice_mode: str # "api" | "browser"
voice_api_available: bool = False # True if API STT (Whisper) is configured

View File

@@ -81,6 +81,14 @@ document.addEventListener('DOMContentLoaded', async () => {
const voice = new VoiceManager();
await voice.init();
// Disable mic button if no STT method is available
if (voice.isDisabled) {
voiceBtn.disabled = true;
voiceBtn.title = 'Voice input requires Chrome or Edge (with HTTPS)';
voiceBtn.style.opacity = '0.35';
voiceBtn.style.cursor = 'not-allowed';
}
voice.onResult = (text) => {
inputEl.value = text;
voice.lastInputWasVoice = true;

View File

@@ -6,12 +6,14 @@ class VoiceManager {
this.recognition = null;
this.synthesis = window.speechSynthesis;
this.isRecording = false;
this.lastInputWasVoice = false; // tracks if last message was spoken
this.isDisabled = false; // true when no STT method is available
this.lastInputWasVoice = false;
this.mediaRecorder = null;
this.audioChunks = [];
this.onResult = null;
this.onStateChange = null;
this.browserSTTSupported = false;
this.apiAvailable = false;
}
async init() {
@@ -24,21 +26,37 @@ class VoiceManager {
if (response?.ok) {
const config = await response.json();
this.mode = config.voice_mode;
console.log('[Voice] Server mode:', this.mode);
this.apiAvailable = config.voice_api_available || false;
console.log('[Voice] Server mode:', this.mode, '| API available:', this.apiAvailable);
}
} catch (e) {
console.warn('[Voice] Could not fetch config, using browser mode');
this.mode = 'browser';
}
// Auto-fallback: if server says "browser" but browser doesn't support STT, use API
// Determine the best available mode
if (this.mode === 'browser' && !this.browserSTTSupported) {
console.warn('[Voice] Browser STT not supported, falling back to API mode');
this.mode = 'api';
showToast('Using cloud voice recognition — your browser doesn\'t support built-in speech recognition.', 'info');
if (this.apiAvailable) {
console.log('[Voice] Browser STT not supported, falling back to API mode');
this.mode = 'api';
showToast('Using cloud voice recognition — your browser doesn\'t support built-in speech recognition.', 'info');
} else {
// Neither method works
console.warn('[Voice] No STT method available — disabling voice input');
this.isDisabled = true;
}
} else if (this.mode === 'api' && !this.apiAvailable) {
// Server says API but API isn't actually configured
if (this.browserSTTSupported) {
console.log('[Voice] API STT not configured, using browser STT');
this.mode = 'browser';
} else {
console.warn('[Voice] No STT method available — disabling voice input');
this.isDisabled = true;
}
}
console.log('[Voice] Active mode:', this.mode);
console.log('[Voice] Final mode:', this.isDisabled ? 'DISABLED' : this.mode);
}
_initBrowserSTT() {
@@ -84,6 +102,11 @@ class VoiceManager {
}
async startRecording() {
if (this.isDisabled) {
showToast('Voice input requires Chrome or Edge (with HTTPS). Firefox is not supported.', 'error');
return;
}
this.isRecording = true;
this.lastInputWasVoice = true;
if (this.onStateChange) this.onStateChange(true);
@@ -126,12 +149,10 @@ class VoiceManager {
showToast('Voice recognition failed to start. Try again.', 'error');
}
} else {
// Shouldn't happen after init() fallback, but safety net
console.warn('[Voice] No speech recognition available, switching to API');
this.mode = 'api';
console.warn('[Voice] No speech recognition available');
this.isRecording = false;
if (this.onStateChange) this.onStateChange(false);
showToast('Switched to cloud voice recognition. Please try again.', 'info');
showToast('Voice input requires Chrome or Edge (with HTTPS).', 'error');
}
}
}
@@ -187,7 +208,7 @@ class VoiceManager {
}
async speak(text) {
if (this.mode === 'api') {
if (this.mode === 'api' && this.apiAvailable) {
return this._speakAPI(text);
} else {
return this._speakBrowser(text);
@@ -233,6 +254,10 @@ class VoiceManager {
}
toggleRecording() {
if (this.isDisabled) {
showToast('Voice input requires Chrome or Edge (with HTTPS). Firefox is not supported.', 'error');
return;
}
if (this.isRecording) {
this.stopRecording();
} else {