Update changelog for version 0.1.16, add configurable wake-word support, optional always-on voice mode, and websocket settings endpoint; enhance chat card voice recognition and context handling.

This commit is contained in:
techartdev
2026-02-20 17:21:14 +02:00
parent c0f9e5c42e
commit 0445c595ef
12 changed files with 691 additions and 24 deletions
@@ -59,6 +59,9 @@ class OpenClawChatCard extends HTMLElement {
this._recognition = null;
this._eventUnsubscribe = null;
this._thinkingTimer = null;
this._wakeWordEnabled = false;
this._wakeWord = "hey openclaw";
this._alwaysVoiceMode = false;
}
// ── HA card interface ───────────────────────────────────────────────
@@ -91,6 +94,8 @@ class OpenClawChatCard extends HTMLElement {
this._hass = hass;
if (firstSet) {
this._subscribeToEvents();
this._syncHistoryFromBackend();
this._loadIntegrationSettings();
this._render();
}
}
@@ -100,6 +105,8 @@ class OpenClawChatCard extends HTMLElement {
}
connectedCallback() {
this._syncHistoryFromBackend();
this._loadIntegrationSettings();
this._render();
}
@@ -136,7 +143,7 @@ class OpenClawChatCard extends HTMLElement {
if (!data || !data.message) return;
// Check if this event is for our session
const sessionId = this._config.session_id || "default";
const sessionId = this._getSessionId();
if (data.session_id && data.session_id !== sessionId) return;
// If we're waiting for a response, update the last "thinking" message
@@ -179,10 +186,14 @@ class OpenClawChatCard extends HTMLElement {
// ── Message persistence ──────────────────────────────────────────────
_getStorageKey() {
const session = this._config.session_id || "default";
const session = this._getSessionId();
return `${STORAGE_PREFIX}${session}`;
}
_getSessionId() {
return this._config.session_id || "default";
}
_persistMessages() {
try {
const toSave = this._messages.filter((m) => !m._thinking);
@@ -206,6 +217,73 @@ class OpenClawChatCard extends HTMLElement {
}
}
async _syncHistoryFromBackend() {
if (!this._hass) return;
const sessionId = this._getSessionId();
try {
let result;
if (typeof this._hass.callWS === "function") {
result = await this._hass.callWS({
type: "openclaw/get_history",
session_id: sessionId,
});
} else {
result = await this._hass.connection.sendMessagePromise({
type: "openclaw/get_history",
session_id: sessionId,
});
}
const serverMessages = Array.isArray(result?.messages) ? result.messages : [];
if (!serverMessages.length) return;
const validMessages = serverMessages.filter(
(m) => m && (m.role === "user" || m.role === "assistant") && typeof m.content === "string"
);
if (!validMessages.length) return;
if (validMessages.length > this._messages.length) {
this._messages = validMessages;
this._isProcessing = false;
this._clearThinkingTimer();
this._persistMessages();
this._render();
this._scrollToBottom();
}
} catch (err) {
console.debug("OpenClaw: history sync skipped:", err);
}
}
async _loadIntegrationSettings() {
if (!this._hass) return;
try {
let result;
if (typeof this._hass.callWS === "function") {
result = await this._hass.callWS({ type: "openclaw/get_settings" });
} else {
result = await this._hass.connection.sendMessagePromise({
type: "openclaw/get_settings",
});
}
this._wakeWordEnabled = !!result?.wake_word_enabled;
this._wakeWord = (result?.wake_word || "hey openclaw").toString().trim().toLowerCase();
this._alwaysVoiceMode = !!result?.always_voice_mode;
if (this._alwaysVoiceMode && !this._isVoiceMode) {
this._isVoiceMode = true;
this._startVoiceRecognition();
}
this._render();
} catch (err) {
console.debug("OpenClaw: settings sync skipped:", err);
}
}
// ── Thinking timer ──────────────────────────────────────────────────
_startThinkingTimer() {
@@ -302,7 +380,26 @@ class OpenClawChatCard extends HTMLElement {
this._recognition.onresult = (event) => {
const result = event.results[event.results.length - 1];
if (result.isFinal) {
const text = result[0].transcript;
const text = result[0].transcript?.trim();
if (!text) return;
if (this._wakeWordEnabled) {
const wake = this._wakeWord || "hey openclaw";
const lower = text.toLowerCase();
const wakePos = lower.indexOf(wake);
if (wakePos < 0) {
return;
}
let command = text.slice(wakePos + wake.length).trim();
command = command.replace(/^[,:;.!?\-]+\s*/, "");
if (!command) {
return;
}
this._sendMessage(command);
return;
}
this._sendMessage(text);
}
};