diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d151c..7bd3616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to the OpenClaw Home Assistant Integration will be documented in this file. +## [0.1.29] - 2026-02-20 + +### Fixed +- Voice language selection now prioritizes the preferred Assist pipeline language (`assist_pipeline/pipeline/list`) instead of only using Home Assistant UI language. +- Added separate TTS language resolution so spoken replies follow Assist pipeline TTS language when available. +- Retained safe fallbacks to integration/UI/browser language when Assist pipeline data is unavailable. + ## [0.1.28] - 2026-02-20 ### Fixed diff --git a/custom_components/openclaw/__init__.py b/custom_components/openclaw/__init__.py index a94ed2f..892342e 100644 --- a/custom_components/openclaw/__init__.py +++ b/custom_components/openclaw/__init__.py @@ -76,7 +76,7 @@ _CARD_PATH = Path(__file__).parent / "www" / _CARD_FILENAME # URL at which the card JS is served (registered via register_static_path) _CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}" # Versioned URL used for Lovelace resource registration to avoid stale browser cache -_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.28" +_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.29" OpenClawConfigEntry = ConfigEntry diff --git a/custom_components/openclaw/manifest.json b/custom_components/openclaw/manifest.json index 9cb2e08..b315bb6 100644 --- a/custom_components/openclaw/manifest.json +++ b/custom_components/openclaw/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues", "requirements": [], - "version": "0.1.28", + "version": "0.1.29", "dependencies": ["conversation"], "after_dependencies": ["hassio", "lovelace"] } diff --git a/custom_components/openclaw/www/openclaw-chat-card.js b/custom_components/openclaw/www/openclaw-chat-card.js index 6eda8cc..bd50ed7 100644 --- a/custom_components/openclaw/www/openclaw-chat-card.js +++ b/custom_components/openclaw/www/openclaw-chat-card.js @@ -13,7 +13,7 @@ * + subscribes to openclaw_message_received events. */ -const CARD_VERSION = "0.2.7"; +const CARD_VERSION = "0.2.8"; // Max time (ms) to show the thinking indicator before falling back to an error const THINKING_TIMEOUT_MS = 120_000; @@ -70,6 +70,7 @@ class OpenClawChatCard extends HTMLElement { this._pendingResponses = 0; this._speechLangOverride = null; this._integrationVoiceLanguage = null; + this._integrationTtsLanguage = null; this._allowBraveWebSpeechIntegration = false; this._voiceBackendBlocked = false; this._voiceStopRequested = false; @@ -320,6 +321,37 @@ class OpenClawChatCard extends HTMLElement { ? this._normalizeSpeechLanguage(result.language) : null; + try { + let pipelineResult; + if (typeof this._hass.callWS === "function") { + pipelineResult = await this._hass.callWS({ type: "assist_pipeline/pipeline/list" }); + } else { + pipelineResult = await this._hass.connection.sendMessagePromise({ + type: "assist_pipeline/pipeline/list", + }); + } + + const preferredPipelineId = pipelineResult?.preferred_pipeline; + const pipelines = Array.isArray(pipelineResult?.pipelines) + ? pipelineResult.pipelines + : []; + const preferredPipeline = + pipelines.find((pipeline) => pipeline?.id === preferredPipelineId) || pipelines[0]; + + const sttLanguage = preferredPipeline?.stt_language || preferredPipeline?.language; + const ttsLanguage = + preferredPipeline?.tts_language || preferredPipeline?.language || preferredPipeline?.stt_language; + + if (sttLanguage) { + this._integrationVoiceLanguage = this._normalizeSpeechLanguage(sttLanguage); + } + if (ttsLanguage) { + this._integrationTtsLanguage = this._normalizeSpeechLanguage(ttsLanguage); + } + } catch (pipelineErr) { + console.debug("OpenClaw: assist pipeline language detection skipped:", pipelineErr); + } + if (this._alwaysVoiceMode && !this._isVoiceMode) { this._isVoiceMode = true; this._startVoiceRecognition(); @@ -479,6 +511,20 @@ class OpenClawChatCard extends HTMLElement { return this._normalizeSpeechLanguage(preferred); } + _getSpeechSynthesisLanguage() { + const configuredLang = this._config.voice_language; + const preferred = + configuredLang || + this._integrationTtsLanguage || + this._integrationVoiceLanguage || + this._hass?.locale?.language || + this._hass?.selectedLanguage || + this._hass?.language || + navigator.language || + "en-US"; + return this._normalizeSpeechLanguage(preferred); + } + _isLikelyBraveBrowser() { const ua = (navigator.userAgent || "").toLowerCase(); const uaDataBrands = navigator.userAgentData?.brands || []; @@ -685,7 +731,7 @@ class OpenClawChatCard extends HTMLElement { if (!("speechSynthesis" in window)) return; // Strip markdown for TTS const plain = text.replace(/[*_`#\[\]()]/g, ""); - const language = this._getSpeechRecognitionLanguage(); + const language = this._getSpeechSynthesisLanguage(); const speakNow = () => { const utterance = new SpeechSynthesisUtterance(plain); diff --git a/www/openclaw-chat-card.js b/www/openclaw-chat-card.js index dd67800..53fbc81 100644 --- a/www/openclaw-chat-card.js +++ b/www/openclaw-chat-card.js @@ -1,7 +1,7 @@ (async () => { try { if (!customElements.get("openclaw-chat-card")) { - const src = "/openclaw/openclaw-chat-card.js?v=0.1.28"; + const src = "/openclaw/openclaw-chat-card.js?v=0.1.29"; console.info("OpenClaw loader importing", src); await import(src); }