diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c2a126..c676441 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.23] - 2026-02-20 + +### Fixed +- Improved handling for repeated `SpeechRecognition` `network` failures in Brave-like browsers. +- Added clear in-card status when browser speech backend appears blocked, and stopped endless retry loops in that case. +- Kept automatic locale fallback retry for transient speech-service issues. + ## [0.1.22] - 2026-02-20 ### Fixed diff --git a/custom_components/openclaw/__init__.py b/custom_components/openclaw/__init__.py index d9afead..5c065a0 100644 --- a/custom_components/openclaw/__init__.py +++ b/custom_components/openclaw/__init__.py @@ -74,7 +74,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.22" +_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.23" type OpenClawConfigEntry = ConfigEntry diff --git a/custom_components/openclaw/manifest.json b/custom_components/openclaw/manifest.json index 382421d..566b995 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.22", + "version": "0.1.23", "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 a654351..6cef6d7 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.2"; +const CARD_VERSION = "0.2.3"; // Max time (ms) to show the thinking indicator before falling back to an error const THINKING_TIMEOUT_MS = 120_000; @@ -66,7 +66,9 @@ class OpenClawChatCard extends HTMLElement { this._voiceStatus = ""; this._voiceRetryTimer = null; this._voiceRetryCount = 0; + this._voiceNetworkErrorCount = 0; this._speechLangOverride = null; + this._voiceBackendBlocked = false; } // ── HA card interface ─────────────────────────────────────────────── @@ -442,7 +444,20 @@ class OpenClawChatCard extends HTMLElement { return this._normalizeSpeechLanguage(preferred); } + _isLikelyBraveBrowser() { + const ua = (navigator.userAgent || "").toLowerCase(); + const uaDataBrands = navigator.userAgentData?.brands || []; + const brandMatch = uaDataBrands.some((brand) => + String(brand?.brand || "") + .toLowerCase() + .includes("brave") + ); + return brandMatch || ua.includes("brave") || !!navigator.brave; + } + _startVoiceRecognition() { + this._voiceBackendBlocked = false; + if (!("webkitSpeechRecognition" in window) && !("SpeechRecognition" in window)) { console.warn("OpenClaw: Speech recognition not supported in this browser"); this._voiceStatus = "Speech recognition not supported by this browser."; @@ -500,12 +515,22 @@ class OpenClawChatCard extends HTMLElement { console.error("OpenClaw: Speech recognition error:", event.error); const err = event?.error || "unknown"; if (err === "network") { - this._voiceStatus = - "Voice network error: browser speech service unavailable. Retrying with fallback locale…"; - + this._voiceNetworkErrorCount += 1; const browserLocale = this._normalizeSpeechLanguage(navigator.language || "en-US"); if (!this._speechLangOverride && browserLocale !== this._recognition.lang) { this._speechLangOverride = browserLocale; + this._voiceStatus = + "Voice network error: browser speech service unavailable. Retrying with fallback locale…"; + } else { + const braveLikely = this._isLikelyBraveBrowser(); + if (braveLikely && this._voiceNetworkErrorCount >= 2) { + this._voiceBackendBlocked = true; + this._voiceStatus = + "Brave speech backend blocked (network error). Voice input is unavailable in this browser session. Use Chrome/Edge for voice, or continue with text input."; + } else { + this._voiceStatus = + "Voice network error: browser speech service unavailable. Retrying…"; + } } } else if (err === "not-allowed") { this._voiceStatus = "Microphone access denied. Allow mic permission for this site."; @@ -515,7 +540,7 @@ class OpenClawChatCard extends HTMLElement { this._voiceStatus = `Voice error: ${err}`; } - if (["network", "audio-capture", "no-speech"].includes(err)) { + if (["network", "audio-capture", "no-speech"].includes(err) && !this._voiceBackendBlocked) { this._scheduleVoiceRetry(); } this._render(); @@ -549,11 +574,13 @@ class OpenClawChatCard extends HTMLElement { this._voiceRetryTimer = null; } this._voiceRetryCount = 0; + this._voiceNetworkErrorCount = 0; this._isVoiceMode = false; this._voiceStatus = ""; } _scheduleVoiceRetry() { + if (this._voiceBackendBlocked) return; if (!this._isVoiceMode && !this._speechLangOverride) return; if (this._voiceRetryCount >= 6) { this._voiceStatus = diff --git a/www/openclaw-chat-card.js b/www/openclaw-chat-card.js index 443be7c..879e87c 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.22"; + const src = "/openclaw/openclaw-chat-card.js?v=0.1.23"; console.info("OpenClaw loader importing", src); await import(src); }