diff --git a/custom_components/openclaw/__init__.py b/custom_components/openclaw/__init__.py index a83c72f..da0d4bc 100644 --- a/custom_components/openclaw/__init__.py +++ b/custom_components/openclaw/__init__.py @@ -61,6 +61,7 @@ from .const import ( CONF_ALLOW_BRAVE_WEBSPEECH, CONF_BROWSER_VOICE_LANGUAGE, CONF_VOICE_PROVIDER, + CONF_THINKING_TIMEOUT, CONTEXT_STRATEGY_TRUNCATE, DEFAULT_CONTEXT_MAX_CHARS, DEFAULT_CONTEXT_STRATEGY, @@ -71,6 +72,7 @@ from .const import ( DEFAULT_ALLOW_BRAVE_WEBSPEECH, DEFAULT_BROWSER_VOICE_LANGUAGE, DEFAULT_VOICE_PROVIDER, + DEFAULT_THINKING_TIMEOUT, DOMAIN, EVENT_MESSAGE_RECEIVED, EVENT_TOOL_INVOKED, @@ -837,6 +839,10 @@ def _async_register_websocket_api(hass: HomeAssistant) -> None: CONF_BROWSER_VOICE_LANGUAGE, DEFAULT_BROWSER_VOICE_LANGUAGE, ), + CONF_THINKING_TIMEOUT: options.get( + CONF_THINKING_TIMEOUT, + DEFAULT_THINKING_TIMEOUT, + ), "language": hass.config.language, }, ) diff --git a/custom_components/openclaw/config_flow.py b/custom_components/openclaw/config_flow.py index ecd6351..86c76df 100644 --- a/custom_components/openclaw/config_flow.py +++ b/custom_components/openclaw/config_flow.py @@ -48,6 +48,7 @@ from .const import ( CONF_ALLOW_BRAVE_WEBSPEECH, CONF_BROWSER_VOICE_LANGUAGE, CONF_VOICE_PROVIDER, + CONF_THINKING_TIMEOUT, BROWSER_VOICE_LANGUAGES, CONTEXT_STRATEGY_CLEAR, CONTEXT_STRATEGY_TRUNCATE, @@ -62,6 +63,7 @@ from .const import ( DEFAULT_ALLOW_BRAVE_WEBSPEECH, DEFAULT_BROWSER_VOICE_LANGUAGE, DEFAULT_VOICE_PROVIDER, + DEFAULT_THINKING_TIMEOUT, DOMAIN, OPENCLAW_CONFIG_REL_PATH, ) @@ -504,6 +506,13 @@ class OpenClawOptionsFlow(OptionsFlow): CONF_VOICE_PROVIDER, default=selected_provider, ): vol.In(["browser", "assist_stt"]), + vol.Optional( + CONF_THINKING_TIMEOUT, + default=options.get( + CONF_THINKING_TIMEOUT, + DEFAULT_THINKING_TIMEOUT, + ), + ): vol.All(int, vol.Range(min=10, max=3600)), } if selected_provider == "browser": diff --git a/custom_components/openclaw/const.py b/custom_components/openclaw/const.py index cb8da65..15cb33a 100644 --- a/custom_components/openclaw/const.py +++ b/custom_components/openclaw/const.py @@ -34,6 +34,7 @@ CONF_WAKE_WORD = "wake_word" CONF_ALLOW_BRAVE_WEBSPEECH = "allow_brave_webspeech" CONF_VOICE_PROVIDER = "voice_provider" CONF_BROWSER_VOICE_LANGUAGE = "browser_voice_language" +CONF_THINKING_TIMEOUT = "thinking_timeout" DEFAULT_INCLUDE_EXPOSED_CONTEXT = True DEFAULT_CONTEXT_MAX_CHARS = 13000 @@ -44,6 +45,7 @@ DEFAULT_WAKE_WORD = "hey openclaw" DEFAULT_ALLOW_BRAVE_WEBSPEECH = False DEFAULT_VOICE_PROVIDER = "browser" DEFAULT_BROWSER_VOICE_LANGUAGE = "auto" +DEFAULT_THINKING_TIMEOUT = 120 BROWSER_VOICE_LANGUAGES: tuple[str, ...] = ( "auto", diff --git a/custom_components/openclaw/strings.json b/custom_components/openclaw/strings.json index a09321d..582ccb7 100644 --- a/custom_components/openclaw/strings.json +++ b/custom_components/openclaw/strings.json @@ -45,7 +45,8 @@ "wake_word": "Wake word", "allow_brave_webspeech": "Allow Web Speech in Brave (experimental)", "voice_provider": "Voice input provider (browser or HA STT)", - "browser_voice_language": "Browser voice language" + "browser_voice_language": "Browser voice language", + "thinking_timeout": "Response timeout (seconds)" } } } diff --git a/custom_components/openclaw/translations/en.json b/custom_components/openclaw/translations/en.json index 4a51054..9ada55a 100644 --- a/custom_components/openclaw/translations/en.json +++ b/custom_components/openclaw/translations/en.json @@ -47,7 +47,8 @@ "wake_word": "Wake word", "allow_brave_webspeech": "Allow Web Speech in Brave (experimental)", "voice_provider": "Voice input provider (browser or HA STT)", - "browser_voice_language": "Browser voice language" + "browser_voice_language": "Browser voice language", + "thinking_timeout": "Response timeout (seconds)" } } } diff --git a/custom_components/openclaw/www/openclaw-chat-card.js b/custom_components/openclaw/www/openclaw-chat-card.js index 457096e..f6a5b4a 100644 --- a/custom_components/openclaw/www/openclaw-chat-card.js +++ b/custom_components/openclaw/www/openclaw-chat-card.js @@ -15,7 +15,7 @@ const CARD_VERSION = "0.3.12"; -// 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 (default; overridable via card config `thinking_timeout` in seconds) const THINKING_TIMEOUT_MS = 120_000; // Session storage key prefix for message persistence across dashboard navigations @@ -95,6 +95,7 @@ class OpenClawChatCard extends HTMLElement { this._lastAssistantEventSignature = null; this._autoScrollPinned = true; this._lastHassRenderSignature = null; + this._integrationThinkingTimeout = null; } // ── HA card interface ─────────────────────────────────────────────── @@ -117,6 +118,7 @@ class OpenClawChatCard extends HTMLElement { allow_brave_webspeech: config.allow_brave_webspeech === true, voice_provider: config.voice_provider || null, session_id: config.session_id || null, + thinking_timeout: config.thinking_timeout ?? 120, ...config, }; // Restore messages from sessionStorage if available @@ -420,6 +422,10 @@ class OpenClawChatCard extends HTMLElement { this._integrationVoiceLanguage = result?.language ? this._normalizeSpeechLanguage(result.language) : null; + this._integrationThinkingTimeout = + typeof result?.thinking_timeout === "number" && result.thinking_timeout >= 10 + ? result.thinking_timeout * 1000 + : null; if (includePipeline) { try { @@ -489,7 +495,7 @@ class OpenClawChatCard extends HTMLElement { } this._persistMessages(); this._render(); - }, THINKING_TIMEOUT_MS); + }, (Number(this._config?.thinking_timeout) * 1000) || this._integrationThinkingTimeout || THINKING_TIMEOUT_MS); } _clearThinkingTimer() {