Update changelog for version 0.1.11, enhance assistant message extraction, add fallback event emission on API errors, and improve custom card registration
This commit is contained in:
@@ -2,6 +2,20 @@
|
||||
|
||||
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
||||
|
||||
## [0.1.11] - 2026-02-20
|
||||
|
||||
### Fixed
|
||||
- Chat card no longer waits forever when the gateway returns a non-`choices[0].message.content` response shape.
|
||||
- `openclaw.send_message` now extracts assistant text from multiple OpenAI-compatible formats (`choices`, `output_text`, `response`, `message`, `content`, `answer`).
|
||||
- Added fallback event emission on API errors so the frontend always receives a response and exits the typing state.
|
||||
|
||||
## [0.1.10] - 2026-02-20
|
||||
|
||||
### Fixed
|
||||
- Made custom card registration idempotent (`customElements.get(...)` guards) to prevent duplicate-load exceptions that can block card discovery.
|
||||
- Prevented duplicate `window.customCards` entries for `openclaw-chat-card`.
|
||||
- Synced registration hardening in both packaged and root `www/` card scripts.
|
||||
|
||||
## [0.1.9] - 2026-02-20
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -318,26 +318,38 @@ def _async_register_services(hass: HomeAssistant) -> None:
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
choices = response.get("choices", [])
|
||||
if choices:
|
||||
assistant_message = (
|
||||
choices[0].get("message", {}).get("content", "")
|
||||
)
|
||||
model_used = response.get("model", "unknown")
|
||||
assistant_message = _extract_assistant_message(response)
|
||||
model_used = response.get("model", "unknown")
|
||||
|
||||
hass.bus.async_fire(
|
||||
EVENT_MESSAGE_RECEIVED,
|
||||
{
|
||||
ATTR_MESSAGE: assistant_message,
|
||||
ATTR_SESSION_ID: session_id,
|
||||
ATTR_MODEL: model_used,
|
||||
ATTR_TIMESTAMP: datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
if not assistant_message:
|
||||
assistant_message = "Response received, but no readable message content was found."
|
||||
_LOGGER.warning(
|
||||
"OpenClaw response had no parseable assistant message. Keys: %s",
|
||||
list(response.keys()),
|
||||
)
|
||||
coordinator.update_last_activity()
|
||||
|
||||
hass.bus.async_fire(
|
||||
EVENT_MESSAGE_RECEIVED,
|
||||
{
|
||||
ATTR_MESSAGE: assistant_message,
|
||||
ATTR_SESSION_ID: session_id,
|
||||
ATTR_MODEL: model_used,
|
||||
ATTR_TIMESTAMP: datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
)
|
||||
coordinator.update_last_activity()
|
||||
|
||||
except OpenClawApiError as err:
|
||||
_LOGGER.error("Failed to send message to OpenClaw: %s", err)
|
||||
hass.bus.async_fire(
|
||||
EVENT_MESSAGE_RECEIVED,
|
||||
{
|
||||
ATTR_MESSAGE: f"OpenClaw error: {err}",
|
||||
ATTR_SESSION_ID: session_id,
|
||||
ATTR_MODEL: "unknown",
|
||||
ATTR_TIMESTAMP: datetime.now(timezone.utc).isoformat(),
|
||||
},
|
||||
)
|
||||
|
||||
async def handle_clear_history(call: ServiceCall) -> None:
|
||||
"""Handle the openclaw.clear_history service call."""
|
||||
@@ -366,3 +378,49 @@ def _get_first_entry_data(hass: HomeAssistant) -> dict[str, Any] | None:
|
||||
if isinstance(entry_data, dict) and "client" in entry_data:
|
||||
return entry_data
|
||||
return None
|
||||
|
||||
|
||||
def _coerce_text(value: Any) -> str | None:
|
||||
"""Convert known message content shapes to plain text."""
|
||||
if isinstance(value, str):
|
||||
text = value.strip()
|
||||
return text or None
|
||||
|
||||
if isinstance(value, list):
|
||||
parts: list[str] = []
|
||||
for item in value:
|
||||
if isinstance(item, str):
|
||||
if item.strip():
|
||||
parts.append(item.strip())
|
||||
continue
|
||||
if isinstance(item, dict):
|
||||
text_part = item.get("text") or item.get("content")
|
||||
if isinstance(text_part, str) and text_part.strip():
|
||||
parts.append(text_part.strip())
|
||||
if parts:
|
||||
return "\n".join(parts)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _extract_assistant_message(response: dict[str, Any]) -> str | None:
|
||||
"""Extract assistant text from multiple OpenAI-compatible response shapes."""
|
||||
choices = response.get("choices")
|
||||
if isinstance(choices, list) and choices:
|
||||
first = choices[0]
|
||||
if isinstance(first, dict):
|
||||
message = first.get("message")
|
||||
if isinstance(message, dict):
|
||||
extracted = _coerce_text(message.get("content"))
|
||||
if extracted:
|
||||
return extracted
|
||||
extracted = _coerce_text(first.get("text"))
|
||||
if extracted:
|
||||
return extracted
|
||||
|
||||
for key in ("output_text", "response", "message", "content", "answer"):
|
||||
extracted = _coerce_text(response.get(key))
|
||||
if extracted:
|
||||
return extracted
|
||||
|
||||
return None
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
||||
"requirements": [],
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.11",
|
||||
"dependencies": ["conversation"],
|
||||
"after_dependencies": ["hassio", "lovelace"]
|
||||
}
|
||||
|
||||
@@ -836,16 +836,23 @@ class OpenClawChatCardEditor extends HTMLElement {
|
||||
|
||||
// ── Card registration ───────────────────────────────────────────────────────
|
||||
|
||||
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
||||
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
||||
if (!customElements.get("openclaw-chat-card-editor")) {
|
||||
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
||||
}
|
||||
|
||||
if (!customElements.get("openclaw-chat-card")) {
|
||||
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
||||
}
|
||||
|
||||
window.customCards = window.customCards || [];
|
||||
window.customCards.push({
|
||||
type: "openclaw-chat-card",
|
||||
name: "OpenClaw Chat",
|
||||
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
||||
preview: true,
|
||||
});
|
||||
if (!window.customCards.some((card) => card?.type === "openclaw-chat-card")) {
|
||||
window.customCards.push({
|
||||
type: "openclaw-chat-card",
|
||||
name: "OpenClaw Chat",
|
||||
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
||||
preview: true,
|
||||
});
|
||||
}
|
||||
|
||||
console.info(
|
||||
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,
|
||||
|
||||
@@ -836,16 +836,23 @@ class OpenClawChatCardEditor extends HTMLElement {
|
||||
|
||||
// ── Card registration ───────────────────────────────────────────────────────
|
||||
|
||||
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
||||
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
||||
if (!customElements.get("openclaw-chat-card-editor")) {
|
||||
customElements.define("openclaw-chat-card-editor", OpenClawChatCardEditor);
|
||||
}
|
||||
|
||||
if (!customElements.get("openclaw-chat-card")) {
|
||||
customElements.define("openclaw-chat-card", OpenClawChatCard);
|
||||
}
|
||||
|
||||
window.customCards = window.customCards || [];
|
||||
window.customCards.push({
|
||||
type: "openclaw-chat-card",
|
||||
name: "OpenClaw Chat",
|
||||
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
||||
preview: true,
|
||||
});
|
||||
if (!window.customCards.some((card) => card?.type === "openclaw-chat-card")) {
|
||||
window.customCards.push({
|
||||
type: "openclaw-chat-card",
|
||||
name: "OpenClaw Chat",
|
||||
description: "Chat interface for OpenClaw AI Assistant with streaming, voice, and file support.",
|
||||
preview: true,
|
||||
});
|
||||
}
|
||||
|
||||
console.info(
|
||||
`%c OPENCLAW-CHAT-CARD %c v${CARD_VERSION} `,
|
||||
|
||||
Reference in New Issue
Block a user