diff --git a/app/api/chat.py b/app/api/chat.py index b0a3fa6..dbd12e1 100644 --- a/app/api/chat.py +++ b/app/api/chat.py @@ -50,7 +50,6 @@ async def chat( "input_endpoint": payload.input_endpoint, "output_endpoint": payload.output_endpoint, "language": payload.language, - "language_mode": payload.language_mode, "stt_provider": payload.stt_provider, "llm_provider": payload.llm_provider, "tts_provider": payload.tts_provider, @@ -106,7 +105,6 @@ async def chat( voice=voice, output=output, history=llm_context, - language_mode=route.language_mode, text_only=bool(payload.text_only), ) except Exception as exc: diff --git a/app/api/ws.py b/app/api/ws.py index 4199f5c..45d4060 100644 --- a/app/api/ws.py +++ b/app/api/ws.py @@ -42,7 +42,6 @@ _OVERRIDE_KEYS = ( "input_endpoint", "output_endpoint", "language", - "language_mode", "stt_provider", "llm_provider", "tts_provider", @@ -67,7 +66,7 @@ async def _resolve(user, session_id, options): async def _run_turn( websocket, store, user, session_id, route, orchestrator, output, text, options, - detected_language: str | None = None, effective_voice: str | None = None, + effective_voice: str | None = None, ): """Faehrt einen Antwort-Turn und streamt die Events an den Client.""" conversation = ( @@ -158,8 +157,6 @@ async def _run_turn( history=llm_context, on_token=on_token, on_audio=on_audio, - language_mode=route.language_mode, - detected_language=detected_language, text_only=text_only, ) else: @@ -169,8 +166,6 @@ async def _run_turn( voice=voice, output=output, history=llm_context, - language_mode=route.language_mode, - detected_language=detected_language, text_only=text_only, ) except Exception as exc: @@ -228,21 +223,9 @@ async def _voice_turn(websocket, store, user, session_id, audio, fmt, options): return try: - if route.language_mode == "flex": - # Flex: Sprache auto-erkennen, kein Übersetzen durch Whisper. - transcript, detected_language = await orchestrator.stt.transcribe_detect( - audio, fmt=fmt, language=None - ) - # Stimme folgt der erkannten Sprache (Fallback: Routensprache). - effective_voice = voice_for_route( - route.tts_provider, detected_language or route.language, route.voice_gender - ) - else: - # Fix: konfigurierte Sprache an Whisper → Whisper übersetzt automatisch. - transcript = await orchestrator.stt.transcribe(audio, fmt=fmt, language=route.language) - detected_language = None - # Stimme folgt der konfigurierten Sprache. - effective_voice = voice_for_route(route.tts_provider, route.language, route.voice_gender) + # Feste Sprache an Whisper → Whisper übersetzt automatisch. + transcript = await orchestrator.stt.transcribe(audio, fmt=fmt, language=route.language) + effective_voice = voice_for_route(route.tts_provider, route.language, route.voice_gender) except Exception as exc: await websocket.send_json({"type": "error", "status": 502, "detail": str(exc)}) return @@ -250,7 +233,6 @@ async def _voice_turn(websocket, store, user, session_id, audio, fmt, options): await websocket.send_json({ "type": "transcript", "text": transcript, - "detected_language": detected_language, }) if not transcript or not transcript.strip(): await websocket.send_json({ @@ -260,7 +242,7 @@ async def _voice_turn(websocket, store, user, session_id, audio, fmt, options): return await _run_turn( websocket, store, user, session_id, route, orchestrator, output, transcript, options, - detected_language=detected_language, effective_voice=effective_voice, + effective_voice=effective_voice, ) diff --git a/app/config.py b/app/config.py index bb2109a..ee07ed7 100644 --- a/app/config.py +++ b/app/config.py @@ -112,7 +112,6 @@ class Settings(BaseSettings): openrouter_tts_voice: str = "alloy" openrouter_llm_model: str = "openai/gpt-4.1-mini" default_language: str = "de" - default_language_mode: str = "fix" default_input_endpoint: str = "local-default" default_output_endpoint: str = "local-default" default_stt_provider: str = "openrouter" diff --git a/app/core/orchestrator.py b/app/core/orchestrator.py index 19cd11c..3458581 100644 --- a/app/core/orchestrator.py +++ b/app/core/orchestrator.py @@ -89,8 +89,6 @@ class Orchestrator: voice: str | None = None, output=None, history: list[dict] | None = None, - language_mode: str = "fix", - detected_language: str | None = None, text_only: bool = False, ): trace = PipelineTrace() @@ -98,10 +96,8 @@ class Orchestrator: trace.raw_transcript = text trace.cleaned_transcript = await self.input_cleaner.run(text or "") - # Flex: die erkannte Sprache bestimmt die Ausgabe. Gibt es keine (z. B. Text-Chat - # ohne Audio), bleibt sie None -> das LLM antwortet von selbst in der Eingabesprache. - # Fix: immer die konfigurierte Sprache (Whisper hat die Eingabe übersetzt). - effective_language = detected_language if language_mode == "flex" else language + # Immer die konfigurierte (feste) Sprache (Whisper hat die Eingabe übersetzt). + effective_language = language with _stage("llm"): trace.semantic_response = await self.llm.complete( @@ -144,8 +140,6 @@ class Orchestrator: history: list[dict] | None = None, on_token=None, on_audio=None, - language_mode: str = "fix", - detected_language: str | None = None, text_only: bool = False, ): """Wie chat_text, aber gestreamt. @@ -158,9 +152,8 @@ class Orchestrator: trace.raw_transcript = text trace.cleaned_transcript = await self.input_cleaner.run(text or "") - # Siehe chat_text: Flex folgt der erkannten Sprache (None -> LLM spiegelt Eingabe), - # Fix nutzt die konfigurierte Sprache. - effective_language = detected_language if language_mode == "flex" else language + # Immer die konfigurierte (feste) Sprache. + effective_language = language # text_only: kein Server-Audio (Geräte-TTS spricht selbst) -> kein Chunked-TTS. chunker = SentenceChunker() if (on_audio and not text_only) else None diff --git a/app/dependencies.py b/app/dependencies.py index b58407d..8f4097f 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -155,7 +155,6 @@ ROUTE_KEYS = ( "llm_provider", "tts_provider", "language", - "language_mode", "voice_gender", ) @@ -235,7 +234,6 @@ class ResolvedRoute: llm_provider: str tts_provider: str language: str - language_mode: str = "fix" voice_gender: str = "any" def as_dict(self) -> dict: @@ -246,7 +244,6 @@ class ResolvedRoute: "llm_provider": self.llm_provider, "tts_provider": self.tts_provider, "language": self.language, - "language_mode": self.language_mode, "voice_gender": self.voice_gender, } @@ -288,7 +285,6 @@ def resolve_route( "llm_provider": cfg.default_llm_provider, "tts_provider": cfg.default_tts_provider, "language": cfg.default_language, - "language_mode": cfg.default_language_mode, } user_prefs = user.prefs if user is not None else {} diff --git a/app/runtime_config.py b/app/runtime_config.py index 231e413..4f67f48 100644 --- a/app/runtime_config.py +++ b/app/runtime_config.py @@ -20,7 +20,6 @@ RUNTIME_SETTABLE: dict[str, tuple[str, str, str]] = { "default_llm_provider": ("LLM-Provider (Standard)", "str", "openrouter | local-openai-compatible"), "default_tts_provider": ("TTS-Provider (Standard)", "str", "openrouter | piper | chatterbox — Standard, pro Nutzer überschreibbar"), "default_language": ("Sprache (Standard)", "str", "de | en | … — Standard, pro Nutzer überschreibbar"), - "default_language_mode": ("Sprachmodus (Standard)", "str", "fix | flex — Standard, pro Nutzer überschreibbar"), "openrouter_llm_model": ("LLM-Modell (OpenRouter)", "str", "z.B. google/gemini-3.1-flash-lite"), "openrouter_tts_model": ("TTS-Modell (OpenRouter)", "str", "z.B. google/gemini-3.1-flash-tts-preview"), "openrouter_tts_voice": ("TTS-Stimme (OpenRouter)", "str", "z.B. Zephyr, Puck, Kore"), diff --git a/app/schemas.py b/app/schemas.py index 8fb6e6e..25f1814 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -46,7 +46,6 @@ class ChatRequest(BaseModel): input_endpoint: str | None = None output_endpoint: str | None = None language: str | None = None - language_mode: Literal["fix", "flex"] | None = None voice: str | None = None stt_provider: str | None = None llm_provider: str | None = None @@ -93,7 +92,6 @@ class UserPrefs(BaseModel): llm_provider: str | None = None tts_provider: str | None = None language: str | None = None - language_mode: Literal["fix", "flex"] | None = None class MemoryCreate(BaseModel): diff --git a/app/web/app.js b/app/web/app.js index c94ef0c..2af0310 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -13,13 +13,9 @@ const cartesiaHint = $("#cartesia-lang-hint"); let cartesiaUnsupportedLangs = []; // von /api/config: Sprachen, die Cartesia nicht spricht const CARTESIA_LANG_LABELS = { ar: "Arabisch", it: "Italienisch", ru: "Russisch", pl: "Polnisch", nl: "Niederländisch", sv: "Schwedisch", tr: "Türkisch" }; -// "Flex" ist im Sprachmenü nur ein weiterer Wert: keine feste Sprache, sondern -// automatische Erkennung. Eine konkrete Sprache = Fix-Modus (Eingabe wird übersetzt). -// Beide internen Felder (language, language_mode) werden aus dieser einen Auswahl abgeleitet. +// Die gewählte Sprache ist immer fest (Flex-Modus wurde entfernt). function langOverrides() { - const sel = langSel && langSel.value; - if (!sel || sel === "flex") return { language_mode: "flex" }; - return { language: sel, language_mode: "fix" }; + return { language: (langSel && langSel.value) || "de" }; } // Nutzer-Präferenzen als Request-Overrides für jeden Turn. @@ -348,10 +344,7 @@ function loadMe() { // Gespeicherte Nutzer-Präferenzen in die Dropdowns laden. const prefs = me.prefs || {}; if (langSel) { - // Flex-Modus -> "flex", sonst die feste Sprache (Fallback: Flex). - langSel.value = prefs.language_mode === "flex" - ? "flex" - : (prefs.language || "de"); + langSel.value = prefs.language || "de"; } applyPlaybackChoice(prefs.tts_provider); applyGenderChoice(prefs.voice_gender || 'f'); @@ -544,10 +537,9 @@ function runTurn(path, onopen) { const pcm = []; let answerEl = null; let sampleRate = 24000; - // Sprache dieses Turns (für den Replay je Bubble): Flex folgt erkannter Sprache. - let routeLang = null, routeMode = null, detectedLang = null; - const turnLang = () => - (routeMode === "flex" ? (detectedLang || routeLang) : routeLang) || null; + // Sprache dieses Turns (für den Replay je Bubble) = feste Routensprache. + let routeLang = null; + const turnLang = () => routeLang || null; ws.onopen = () => onopen(ws); ws.onerror = () => { statusEl.textContent = "Verbindungsfehler"; resolve(); }; @@ -560,10 +552,8 @@ function runTurn(path, onopen) { switch (msg.type) { case "ack": routeLang = (msg.route && msg.route.language) || null; - routeMode = (msg.route && msg.route.language_mode) || null; break; case "transcript": - detectedLang = msg.detected_language || null; if (msg.text) addMessage("user", msg.text, { lang: turnLang() }); break; case "token": @@ -620,9 +610,8 @@ async function sendText(text) { busy = true; setMicBusy(); await ensureSession(); - // Bei fester Sprache wird der eigene Text in dieser Sprache vorgelesen; bei Flex offen. const lo = langOverrides(); - addMessage("user", text, { lang: lo.language_mode === "fix" ? lo.language : null }); + addMessage("user", text, { lang: lo.language || null }); statusEl.textContent = "denkt …"; const audioStream = !isDeviceMode(); // Geräte-Modus: kein Server-Audio anfordern await runTurn("/ws/chat", (ws) => { diff --git a/app/web/index.html b/app/web/index.html index ecf9519..dd59388 100644 --- a/app/web/index.html +++ b/app/web/index.html @@ -219,9 +219,8 @@

Voice Assistant

-