Update version to 0.1.42 and add browser voice language integration with fixes for voice replies and configuration options
This commit is contained in:
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
||||||
|
|
||||||
|
## [0.1.42] - 2026-02-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added integration option `browser_voice_language` (shown when `voice_provider` is `browser`) to explicitly control browser STT/TTS language.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Browser voice provider now applies integration-configured browser voice language for both listening and spoken replies.
|
||||||
|
- Voice replies are no longer spoken for one-shot/manual voice sends unless continuous voice mode is actively running.
|
||||||
|
|
||||||
## [0.1.41] - 2026-02-20
|
## [0.1.41] - 2026-02-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ from .const import (
|
|||||||
CONF_WAKE_WORD,
|
CONF_WAKE_WORD,
|
||||||
CONF_WAKE_WORD_ENABLED,
|
CONF_WAKE_WORD_ENABLED,
|
||||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||||
|
CONF_BROWSER_VOICE_LANGUAGE,
|
||||||
CONF_VOICE_PROVIDER,
|
CONF_VOICE_PROVIDER,
|
||||||
CONTEXT_STRATEGY_TRUNCATE,
|
CONTEXT_STRATEGY_TRUNCATE,
|
||||||
DEFAULT_CONTEXT_MAX_CHARS,
|
DEFAULT_CONTEXT_MAX_CHARS,
|
||||||
@@ -67,6 +68,7 @@ from .const import (
|
|||||||
DEFAULT_WAKE_WORD,
|
DEFAULT_WAKE_WORD,
|
||||||
DEFAULT_WAKE_WORD_ENABLED,
|
DEFAULT_WAKE_WORD_ENABLED,
|
||||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
||||||
|
DEFAULT_BROWSER_VOICE_LANGUAGE,
|
||||||
DEFAULT_VOICE_PROVIDER,
|
DEFAULT_VOICE_PROVIDER,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_MESSAGE_RECEIVED,
|
EVENT_MESSAGE_RECEIVED,
|
||||||
@@ -90,7 +92,7 @@ _CARD_PATH = Path(__file__).parent / "www" / _CARD_FILENAME
|
|||||||
# URL at which the card JS is served (registered via register_static_path)
|
# URL at which the card JS is served (registered via register_static_path)
|
||||||
_CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}"
|
_CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}"
|
||||||
# Versioned URL used for Lovelace resource registration to avoid stale browser cache
|
# Versioned URL used for Lovelace resource registration to avoid stale browser cache
|
||||||
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.41"
|
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.42"
|
||||||
|
|
||||||
OpenClawConfigEntry = ConfigEntry
|
OpenClawConfigEntry = ConfigEntry
|
||||||
|
|
||||||
@@ -808,6 +810,10 @@ def _async_register_websocket_api(hass: HomeAssistant) -> None:
|
|||||||
CONF_VOICE_PROVIDER,
|
CONF_VOICE_PROVIDER,
|
||||||
DEFAULT_VOICE_PROVIDER,
|
DEFAULT_VOICE_PROVIDER,
|
||||||
),
|
),
|
||||||
|
CONF_BROWSER_VOICE_LANGUAGE: options.get(
|
||||||
|
CONF_BROWSER_VOICE_LANGUAGE,
|
||||||
|
DEFAULT_BROWSER_VOICE_LANGUAGE,
|
||||||
|
),
|
||||||
"language": hass.config.language,
|
"language": hass.config.language,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -45,7 +45,9 @@ from .const import (
|
|||||||
CONF_WAKE_WORD,
|
CONF_WAKE_WORD,
|
||||||
CONF_WAKE_WORD_ENABLED,
|
CONF_WAKE_WORD_ENABLED,
|
||||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||||
|
CONF_BROWSER_VOICE_LANGUAGE,
|
||||||
CONF_VOICE_PROVIDER,
|
CONF_VOICE_PROVIDER,
|
||||||
|
BROWSER_VOICE_LANGUAGES,
|
||||||
CONTEXT_STRATEGY_CLEAR,
|
CONTEXT_STRATEGY_CLEAR,
|
||||||
CONTEXT_STRATEGY_TRUNCATE,
|
CONTEXT_STRATEGY_TRUNCATE,
|
||||||
DEFAULT_GATEWAY_HOST,
|
DEFAULT_GATEWAY_HOST,
|
||||||
@@ -57,6 +59,7 @@ from .const import (
|
|||||||
DEFAULT_WAKE_WORD,
|
DEFAULT_WAKE_WORD,
|
||||||
DEFAULT_WAKE_WORD_ENABLED,
|
DEFAULT_WAKE_WORD_ENABLED,
|
||||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
||||||
|
DEFAULT_BROWSER_VOICE_LANGUAGE,
|
||||||
DEFAULT_VOICE_PROVIDER,
|
DEFAULT_VOICE_PROVIDER,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
OPENCLAW_CONFIG_REL_PATH,
|
OPENCLAW_CONFIG_REL_PATH,
|
||||||
@@ -414,66 +417,73 @@ class OpenClawOptionsFlow(OptionsFlow):
|
|||||||
return self.async_create_entry(title="", data=user_input)
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
options = self._config_entry.options
|
options = self._config_entry.options
|
||||||
return self.async_show_form(
|
selected_provider = options.get(CONF_VOICE_PROVIDER, DEFAULT_VOICE_PROVIDER)
|
||||||
step_id="init",
|
|
||||||
data_schema=vol.Schema(
|
schema: dict[Any, Any] = {
|
||||||
{
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_INCLUDE_EXPOSED_CONTEXT,
|
||||||
CONF_INCLUDE_EXPOSED_CONTEXT,
|
default=options.get(
|
||||||
default=options.get(
|
CONF_INCLUDE_EXPOSED_CONTEXT,
|
||||||
CONF_INCLUDE_EXPOSED_CONTEXT,
|
DEFAULT_INCLUDE_EXPOSED_CONTEXT,
|
||||||
DEFAULT_INCLUDE_EXPOSED_CONTEXT,
|
),
|
||||||
),
|
): bool,
|
||||||
): bool,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_CONTEXT_MAX_CHARS,
|
||||||
CONF_CONTEXT_MAX_CHARS,
|
default=options.get(
|
||||||
default=options.get(
|
CONF_CONTEXT_MAX_CHARS,
|
||||||
CONF_CONTEXT_MAX_CHARS,
|
DEFAULT_CONTEXT_MAX_CHARS,
|
||||||
DEFAULT_CONTEXT_MAX_CHARS,
|
),
|
||||||
),
|
): vol.All(int, vol.Range(min=1000, max=200000)),
|
||||||
): vol.All(int, vol.Range(min=1000, max=200000)),
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_CONTEXT_STRATEGY,
|
||||||
CONF_CONTEXT_STRATEGY,
|
default=options.get(
|
||||||
default=options.get(
|
CONF_CONTEXT_STRATEGY,
|
||||||
CONF_CONTEXT_STRATEGY,
|
DEFAULT_CONTEXT_STRATEGY,
|
||||||
DEFAULT_CONTEXT_STRATEGY,
|
),
|
||||||
),
|
): vol.In([CONTEXT_STRATEGY_TRUNCATE, CONTEXT_STRATEGY_CLEAR]),
|
||||||
): vol.In([CONTEXT_STRATEGY_TRUNCATE, CONTEXT_STRATEGY_CLEAR]),
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_ENABLE_TOOL_CALLS,
|
||||||
CONF_ENABLE_TOOL_CALLS,
|
default=options.get(
|
||||||
default=options.get(
|
CONF_ENABLE_TOOL_CALLS,
|
||||||
CONF_ENABLE_TOOL_CALLS,
|
DEFAULT_ENABLE_TOOL_CALLS,
|
||||||
DEFAULT_ENABLE_TOOL_CALLS,
|
),
|
||||||
),
|
): bool,
|
||||||
): bool,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_WAKE_WORD_ENABLED,
|
||||||
CONF_WAKE_WORD_ENABLED,
|
default=options.get(
|
||||||
default=options.get(
|
CONF_WAKE_WORD_ENABLED,
|
||||||
CONF_WAKE_WORD_ENABLED,
|
DEFAULT_WAKE_WORD_ENABLED,
|
||||||
DEFAULT_WAKE_WORD_ENABLED,
|
),
|
||||||
),
|
): bool,
|
||||||
): bool,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_WAKE_WORD,
|
||||||
CONF_WAKE_WORD,
|
default=options.get(
|
||||||
default=options.get(
|
CONF_WAKE_WORD,
|
||||||
CONF_WAKE_WORD,
|
DEFAULT_WAKE_WORD,
|
||||||
DEFAULT_WAKE_WORD,
|
),
|
||||||
),
|
): str,
|
||||||
): str,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
default=options.get(
|
||||||
default=options.get(
|
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
||||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
),
|
||||||
),
|
): bool,
|
||||||
): bool,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_VOICE_PROVIDER,
|
||||||
CONF_VOICE_PROVIDER,
|
default=selected_provider,
|
||||||
default=options.get(
|
): vol.In(["browser", "assist_stt"]),
|
||||||
CONF_VOICE_PROVIDER,
|
}
|
||||||
DEFAULT_VOICE_PROVIDER,
|
|
||||||
),
|
if selected_provider == "browser":
|
||||||
): vol.In(["browser", "assist_stt"]),
|
schema[
|
||||||
}
|
vol.Optional(
|
||||||
),
|
CONF_BROWSER_VOICE_LANGUAGE,
|
||||||
)
|
default=options.get(
|
||||||
|
CONF_BROWSER_VOICE_LANGUAGE,
|
||||||
|
DEFAULT_BROWSER_VOICE_LANGUAGE,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
] = vol.In(BROWSER_VOICE_LANGUAGES)
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="init", data_schema=vol.Schema(schema))
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ CONF_WAKE_WORD_ENABLED = "wake_word_enabled"
|
|||||||
CONF_WAKE_WORD = "wake_word"
|
CONF_WAKE_WORD = "wake_word"
|
||||||
CONF_ALLOW_BRAVE_WEBSPEECH = "allow_brave_webspeech"
|
CONF_ALLOW_BRAVE_WEBSPEECH = "allow_brave_webspeech"
|
||||||
CONF_VOICE_PROVIDER = "voice_provider"
|
CONF_VOICE_PROVIDER = "voice_provider"
|
||||||
|
CONF_BROWSER_VOICE_LANGUAGE = "browser_voice_language"
|
||||||
|
|
||||||
DEFAULT_INCLUDE_EXPOSED_CONTEXT = True
|
DEFAULT_INCLUDE_EXPOSED_CONTEXT = True
|
||||||
DEFAULT_CONTEXT_MAX_CHARS = 13000
|
DEFAULT_CONTEXT_MAX_CHARS = 13000
|
||||||
@@ -41,6 +42,44 @@ DEFAULT_WAKE_WORD_ENABLED = False
|
|||||||
DEFAULT_WAKE_WORD = "hey openclaw"
|
DEFAULT_WAKE_WORD = "hey openclaw"
|
||||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH = False
|
DEFAULT_ALLOW_BRAVE_WEBSPEECH = False
|
||||||
DEFAULT_VOICE_PROVIDER = "browser"
|
DEFAULT_VOICE_PROVIDER = "browser"
|
||||||
|
DEFAULT_BROWSER_VOICE_LANGUAGE = "auto"
|
||||||
|
|
||||||
|
BROWSER_VOICE_LANGUAGES: tuple[str, ...] = (
|
||||||
|
"auto",
|
||||||
|
"bg-BG",
|
||||||
|
"en-US",
|
||||||
|
"en-GB",
|
||||||
|
"de-DE",
|
||||||
|
"fr-FR",
|
||||||
|
"es-ES",
|
||||||
|
"it-IT",
|
||||||
|
"pt-PT",
|
||||||
|
"pt-BR",
|
||||||
|
"ru-RU",
|
||||||
|
"nl-NL",
|
||||||
|
"pl-PL",
|
||||||
|
"tr-TR",
|
||||||
|
"uk-UA",
|
||||||
|
"cs-CZ",
|
||||||
|
"ro-RO",
|
||||||
|
"el-GR",
|
||||||
|
"sv-SE",
|
||||||
|
"no-NO",
|
||||||
|
"da-DK",
|
||||||
|
"fi-FI",
|
||||||
|
"hu-HU",
|
||||||
|
"sk-SK",
|
||||||
|
"sl-SI",
|
||||||
|
"hr-HR",
|
||||||
|
"sr-RS",
|
||||||
|
"ja-JP",
|
||||||
|
"ko-KR",
|
||||||
|
"zh-CN",
|
||||||
|
"zh-TW",
|
||||||
|
"ar-SA",
|
||||||
|
"he-IL",
|
||||||
|
"hi-IN",
|
||||||
|
)
|
||||||
|
|
||||||
CONTEXT_STRATEGY_TRUNCATE = "truncate"
|
CONTEXT_STRATEGY_TRUNCATE = "truncate"
|
||||||
CONTEXT_STRATEGY_CLEAR = "clear"
|
CONTEXT_STRATEGY_CLEAR = "clear"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "0.1.41",
|
"version": "0.1.42",
|
||||||
"dependencies": ["conversation"],
|
"dependencies": ["conversation"],
|
||||||
"after_dependencies": ["hassio", "lovelace"]
|
"after_dependencies": ["hassio", "lovelace"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
"wake_word_enabled": "Require wake word",
|
"wake_word_enabled": "Require wake word",
|
||||||
"wake_word": "Wake word",
|
"wake_word": "Wake word",
|
||||||
"allow_brave_webspeech": "Allow Web Speech in Brave (experimental)",
|
"allow_brave_webspeech": "Allow Web Speech in Brave (experimental)",
|
||||||
"voice_provider": "Voice input provider (browser or HA STT)"
|
"voice_provider": "Voice input provider (browser or HA STT)",
|
||||||
|
"browser_voice_language": "Browser voice language"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
"wake_word_enabled": "Require wake word",
|
"wake_word_enabled": "Require wake word",
|
||||||
"wake_word": "Wake word",
|
"wake_word": "Wake word",
|
||||||
"allow_brave_webspeech": "Allow Web Speech in Brave (experimental)",
|
"allow_brave_webspeech": "Allow Web Speech in Brave (experimental)",
|
||||||
"voice_provider": "Voice input provider (browser or HA STT)"
|
"voice_provider": "Voice input provider (browser or HA STT)",
|
||||||
|
"browser_voice_language": "Browser voice language"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* + subscribes to openclaw_message_received events.
|
* + subscribes to openclaw_message_received events.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CARD_VERSION = "0.3.5";
|
const CARD_VERSION = "0.3.6";
|
||||||
|
|
||||||
// Max time (ms) to show the thinking indicator before falling back to an error
|
// Max time (ms) to show the thinking indicator before falling back to an error
|
||||||
const THINKING_TIMEOUT_MS = 120_000;
|
const THINKING_TIMEOUT_MS = 120_000;
|
||||||
@@ -68,6 +68,7 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
this._voiceNetworkErrorCount = 0;
|
this._voiceNetworkErrorCount = 0;
|
||||||
this._pendingResponses = 0;
|
this._pendingResponses = 0;
|
||||||
this._speechLangOverride = null;
|
this._speechLangOverride = null;
|
||||||
|
this._integrationBrowserVoiceLanguage = null;
|
||||||
this._integrationVoiceLanguage = null;
|
this._integrationVoiceLanguage = null;
|
||||||
this._integrationTtsLanguage = null;
|
this._integrationTtsLanguage = null;
|
||||||
this._allowBraveWebSpeechIntegration = false;
|
this._allowBraveWebSpeechIntegration = false;
|
||||||
@@ -229,7 +230,7 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
this._scrollToBottom();
|
this._scrollToBottom();
|
||||||
|
|
||||||
// In voice mode, speak the response
|
// In voice mode, speak the response
|
||||||
if (this._isVoiceMode && data.message) {
|
if (this._isVoiceMode && this._recognition && data.message) {
|
||||||
this._speak(data.message);
|
this._speak(data.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,6 +355,10 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
this._allowBraveWebSpeechIntegration = !!result?.allow_brave_webspeech;
|
this._allowBraveWebSpeechIntegration = !!result?.allow_brave_webspeech;
|
||||||
this._voiceProviderIntegration =
|
this._voiceProviderIntegration =
|
||||||
result?.voice_provider === "assist_stt" ? "assist_stt" : "browser";
|
result?.voice_provider === "assist_stt" ? "assist_stt" : "browser";
|
||||||
|
this._integrationBrowserVoiceLanguage =
|
||||||
|
result?.browser_voice_language && result?.browser_voice_language !== "auto"
|
||||||
|
? this._normalizeSpeechLanguage(result.browser_voice_language)
|
||||||
|
: null;
|
||||||
this._integrationVoiceLanguage = result?.language
|
this._integrationVoiceLanguage = result?.language
|
||||||
? this._normalizeSpeechLanguage(result.language)
|
? this._normalizeSpeechLanguage(result.language)
|
||||||
: null;
|
: null;
|
||||||
@@ -544,6 +549,13 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const configuredLang = this._config.voice_language;
|
const configuredLang = this._config.voice_language;
|
||||||
|
if (configuredLang) {
|
||||||
|
return this._normalizeSpeechLanguage(configuredLang);
|
||||||
|
}
|
||||||
|
const provider = this._getVoiceProvider();
|
||||||
|
if (provider === "browser" && this._integrationBrowserVoiceLanguage) {
|
||||||
|
return this._normalizeSpeechLanguage(this._integrationBrowserVoiceLanguage);
|
||||||
|
}
|
||||||
const integrationLang = this._integrationVoiceLanguage;
|
const integrationLang = this._integrationVoiceLanguage;
|
||||||
const hassLang =
|
const hassLang =
|
||||||
this._hass?.locale?.language || this._hass?.selectedLanguage || this._hass?.language;
|
this._hass?.locale?.language || this._hass?.selectedLanguage || this._hass?.language;
|
||||||
@@ -554,6 +566,13 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
|
|
||||||
_getSpeechSynthesisLanguage() {
|
_getSpeechSynthesisLanguage() {
|
||||||
const configuredLang = this._config.voice_language;
|
const configuredLang = this._config.voice_language;
|
||||||
|
if (configuredLang) {
|
||||||
|
return this._normalizeSpeechLanguage(configuredLang);
|
||||||
|
}
|
||||||
|
const provider = this._getVoiceProvider();
|
||||||
|
if (provider === "browser" && this._integrationBrowserVoiceLanguage) {
|
||||||
|
return this._normalizeSpeechLanguage(this._integrationBrowserVoiceLanguage);
|
||||||
|
}
|
||||||
const preferred =
|
const preferred =
|
||||||
configuredLang ||
|
configuredLang ||
|
||||||
this._integrationTtsLanguage ||
|
this._integrationTtsLanguage ||
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
if (!customElements.get("openclaw-chat-card")) {
|
if (!customElements.get("openclaw-chat-card")) {
|
||||||
const src = "/openclaw/openclaw-chat-card.js?v=0.1.41";
|
const src = "/openclaw/openclaw-chat-card.js?v=0.1.42";
|
||||||
console.info("OpenClaw loader importing", src);
|
console.info("OpenClaw loader importing", src);
|
||||||
await import(src);
|
await import(src);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user