updated voice, avatar and frontend to reflect changes
Some checks failed
Deploy FluentGerman.ai / deploy (push) Failing after 51s
Some checks failed
Deploy FluentGerman.ai / deploy (push) Failing after 51s
This commit is contained in:
@@ -81,6 +81,14 @@ jobs:
|
|||||||
# Restart service
|
# Restart service
|
||||||
#sudo systemctl restart fluentgerman
|
#sudo systemctl restart fluentgerman
|
||||||
echo "✓ FluentGerman.ai deployed"
|
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
|
REMOTE_SCRIPT
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
|||||||
@@ -18,7 +18,17 @@ router = APIRouter(prefix="/api/voice", tags=["voice"])
|
|||||||
@router.get("/config", response_model=VoiceConfigOut)
|
@router.get("/config", response_model=VoiceConfigOut)
|
||||||
async def voice_config(user: User = Depends(get_current_user)):
|
async def voice_config(user: User = Depends(get_current_user)):
|
||||||
"""Return current voice mode so frontend knows whether to use browser or API."""
|
"""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")
|
@router.post("/transcribe")
|
||||||
|
|||||||
@@ -90,3 +90,4 @@ class VoiceInstructionRequest(BaseModel):
|
|||||||
|
|
||||||
class VoiceConfigOut(BaseModel):
|
class VoiceConfigOut(BaseModel):
|
||||||
voice_mode: str # "api" | "browser"
|
voice_mode: str # "api" | "browser"
|
||||||
|
voice_api_available: bool = False # True if API STT (Whisper) is configured
|
||||||
|
|||||||
@@ -81,6 +81,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const voice = new VoiceManager();
|
const voice = new VoiceManager();
|
||||||
await voice.init();
|
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) => {
|
voice.onResult = (text) => {
|
||||||
inputEl.value = text;
|
inputEl.value = text;
|
||||||
voice.lastInputWasVoice = true;
|
voice.lastInputWasVoice = true;
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ class VoiceManager {
|
|||||||
this.recognition = null;
|
this.recognition = null;
|
||||||
this.synthesis = window.speechSynthesis;
|
this.synthesis = window.speechSynthesis;
|
||||||
this.isRecording = false;
|
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.mediaRecorder = null;
|
||||||
this.audioChunks = [];
|
this.audioChunks = [];
|
||||||
this.onResult = null;
|
this.onResult = null;
|
||||||
this.onStateChange = null;
|
this.onStateChange = null;
|
||||||
this.browserSTTSupported = false;
|
this.browserSTTSupported = false;
|
||||||
|
this.apiAvailable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@@ -24,21 +26,37 @@ class VoiceManager {
|
|||||||
if (response?.ok) {
|
if (response?.ok) {
|
||||||
const config = await response.json();
|
const config = await response.json();
|
||||||
this.mode = config.voice_mode;
|
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) {
|
} catch (e) {
|
||||||
console.warn('[Voice] Could not fetch config, using browser mode');
|
console.warn('[Voice] Could not fetch config, using browser mode');
|
||||||
this.mode = 'browser';
|
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) {
|
if (this.mode === 'browser' && !this.browserSTTSupported) {
|
||||||
console.warn('[Voice] Browser STT not supported, falling back to API mode');
|
if (this.apiAvailable) {
|
||||||
|
console.log('[Voice] Browser STT not supported, falling back to API mode');
|
||||||
this.mode = 'api';
|
this.mode = 'api';
|
||||||
showToast('Using cloud voice recognition — your browser doesn\'t support built-in speech recognition.', 'info');
|
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() {
|
_initBrowserSTT() {
|
||||||
@@ -84,6 +102,11 @@ class VoiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async startRecording() {
|
async startRecording() {
|
||||||
|
if (this.isDisabled) {
|
||||||
|
showToast('Voice input requires Chrome or Edge (with HTTPS). Firefox is not supported.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.isRecording = true;
|
this.isRecording = true;
|
||||||
this.lastInputWasVoice = true;
|
this.lastInputWasVoice = true;
|
||||||
if (this.onStateChange) this.onStateChange(true);
|
if (this.onStateChange) this.onStateChange(true);
|
||||||
@@ -126,12 +149,10 @@ class VoiceManager {
|
|||||||
showToast('Voice recognition failed to start. Try again.', 'error');
|
showToast('Voice recognition failed to start. Try again.', 'error');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Shouldn't happen after init() fallback, but safety net
|
console.warn('[Voice] No speech recognition available');
|
||||||
console.warn('[Voice] No speech recognition available, switching to API');
|
|
||||||
this.mode = 'api';
|
|
||||||
this.isRecording = false;
|
this.isRecording = false;
|
||||||
if (this.onStateChange) this.onStateChange(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) {
|
async speak(text) {
|
||||||
if (this.mode === 'api') {
|
if (this.mode === 'api' && this.apiAvailable) {
|
||||||
return this._speakAPI(text);
|
return this._speakAPI(text);
|
||||||
} else {
|
} else {
|
||||||
return this._speakBrowser(text);
|
return this._speakBrowser(text);
|
||||||
@@ -233,6 +254,10 @@ class VoiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleRecording() {
|
toggleRecording() {
|
||||||
|
if (this.isDisabled) {
|
||||||
|
showToast('Voice input requires Chrome or Edge (with HTTPS). Firefox is not supported.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.isRecording) {
|
if (this.isRecording) {
|
||||||
this.stopRecording();
|
this.stopRecording();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user