feat: Home Assistant API tools (ha_get_state, ha_get_states, ha_call_service)

Register three HA orchestrator tools so Inara can read device states and
control devices via the HA REST API. ha_call_service requires admin role
and user confirmation. Also includes accumulated UI fixes (setProcessing
helper, wasNewSession flag cleanup).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-11 21:39:35 -04:00
parent ba91de37c5
commit fc6600c33e
5 changed files with 334 additions and 15 deletions

View File

@@ -384,6 +384,16 @@
updateSendBtnTitle();
}
function setProcessing(state) {
if (state) {
headerEmoji.classList.add('processing');
document.body.classList.add('processing');
} else {
headerEmoji.classList.remove('processing');
document.body.classList.remove('processing');
}
}
// ── Settings dropdown ─────────────────────────────────────────
settings_btn_el.addEventListener('click', (e) => {
e.stopPropagation();
@@ -1148,7 +1158,7 @@
// ── Chat fetch + SSE handler ─────────────────────────────────
// Extracted so the retry button can call it without re-adding the
// user message to the DOM or currentHistory.
async function _doSend(payload, thinkingDiv) {
async function _doSend(payload, thinkingDiv, wasNewSession = false) {
try {
const res = await fetch('/chat', {
method: 'POST',
@@ -1238,13 +1248,13 @@
activeController = new AbortController();
sendBtn.style.display = 'none';
stopBtn.style.display = 'flex';
headerEmoji.classList.add('processing');
setProcessing(true);
startRunTimer();
await _doSend(payload, thinkingDiv);
await _doSend(payload, thinkingDiv, false);
activeController = null;
headerEmoji.classList.remove('processing');
setProcessing(false);
sendBtn.style.display = 'block';
stopBtn.style.display = 'none';
stopRunTimer();
@@ -1265,7 +1275,7 @@
syncHeight();
sendBtn.style.display = 'none';
stopBtn.style.display = 'flex';
headerEmoji.classList.add('processing');
setProcessing(true);
startRunTimer();
activeController = new AbortController();
@@ -1294,10 +1304,10 @@
persona: CORTEX_PERSONA,
};
await _doSend(payload, thinkingDiv);
await _doSend(payload, thinkingDiv, wasNewSession);
activeController = null;
headerEmoji.classList.remove('processing');
setProcessing(false);
sendBtn.style.display = 'block';
stopBtn.style.display = 'none';
stopRunTimer();
@@ -1437,13 +1447,13 @@
activeController = new AbortController();
sendBtn.style.display = 'none';
stopBtn.style.display = 'flex';
headerEmoji.classList.add('processing');
setProcessing(true);
startRunTimer();
await _doOrchestrate(text, thinkingDiv, userMsgDiv);
activeController = null;
headerEmoji.classList.remove('processing');
setProcessing(false);
sendBtn.style.display = 'block';
stopBtn.style.display = 'none';
stopRunTimer();
@@ -1462,7 +1472,7 @@
syncHeight();
sendBtn.style.display = 'none';
stopBtn.style.display = 'flex';
headerEmoji.classList.add('processing');
setProcessing(true);
startRunTimer();
activeController = new AbortController();
@@ -1476,7 +1486,7 @@
await _doOrchestrate(text, thinkingDiv, userMsgDiv);
activeController = null;
headerEmoji.classList.remove('processing');
setProcessing(false);
sendBtn.style.display = 'block';
stopBtn.style.display = 'none';
stopRunTimer();
@@ -2215,4 +2225,4 @@
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(() => {});
}
}

View File

@@ -142,6 +142,15 @@
.header-emoji.processing { animation: shimmer 0.75s ease-in-out infinite; }
@keyframes border-pulse {
0%, 100% { box-shadow: inset 0 0 15px var(--amber-glow); }
50% { box-shadow: inset 0 0 30px var(--amber-glow); }
}
body.processing {
animation: border-pulse 1.5s ease-in-out infinite;
}
header .name { font-size: 1.1rem; font-weight: 600; color: var(--accent); }
header .subtitle { font-size: 0.78rem; color: var(--muted); }