Tighten context onboarding and actuator suggestions

This commit is contained in:
2026-06-16 12:06:03 +02:00
parent 18999ff68a
commit 1b2b76455a
12 changed files with 310 additions and 8 deletions

View File

@@ -142,6 +142,8 @@
<option value="lock">Schlösser</option>
<option value="fan">Lüftung / Ventilatoren</option>
<option value="humidifier">Befeuchter / Entfeuchter</option>
<option value="media_player">TV / Medien</option>
<option value="remote">Fernbedienungen</option>
<option value="number">Numerische Helper</option>
<option value="valve">Ventile</option>
</select>
@@ -157,6 +159,7 @@
</select>
<button onclick="configureActuator()">Gerät hinzufügen und Beobachtung starten</button>
<p id="actuator-config-result" class="muted">Noch kein Aktor ausgewählt.</p>
<div id="actuator-suggestions" class="card-list"></div>
</section>
<section class="wide" id="observed">
@@ -302,7 +305,11 @@ async function loadOverview() {
status.innerHTML = `<p class="bad">${escapeHtml(error.message)}</p>`;
chips.innerHTML = "";
}
await Promise.all([loadActuatorDiscovery(), loadConfiguredActuators()]);
await Promise.all([
loadActuatorDiscovery(),
loadActuatorSuggestions(),
loadConfiguredActuators(),
]);
}
async function loadActuatorDiscovery() {
@@ -325,6 +332,32 @@ async function loadActuatorDiscovery() {
}
}
async function loadActuatorSuggestions() {
const box = document.getElementById("actuator-suggestions");
if (!box) return;
try {
const suggestions = await api("v1/actuators/suggestions");
box.innerHTML = suggestions.length ? `
<h3>Vorschläge aus bestehenden Zusammenhängen</h3>
${suggestions.slice(0, 8).map(item => `
<article class="actuator-card">
<div class="card-title">
<div>
<div class="entity-id">${escapeHtml(item.entity_id)}</div>
<div class="muted">${escapeHtml(item.area_name || item.device_name || item.domain)}</div>
</div>
<span class="chip">${Math.round(item.confidence * 100)} %</span>
</div>
<p class="muted">${escapeHtml(item.reason)}</p>
<button class="secondary" onclick="configureSuggestedActuator('${escapeHtml(item.entity_id)}')">Vorschlag übernehmen</button>
</article>
`).join("")}
` : "";
} catch (_) {
box.innerHTML = "";
}
}
function actuatorGroupLabel(domain) {
const labels = {
button: "Buttons",
@@ -333,7 +366,9 @@ function actuatorGroupLabel(domain) {
input_boolean: "Helper-Schalter",
input_button: "Helper-Buttons",
lock: "Schlösser",
media_player: "TV / Medien",
number: "Numerische Helper",
remote: "Fernbedienungen",
switch: "Schalter / Steckdosen",
cover: "Rollläden / Cover",
fan: "Lüftung / Ventilatoren",
@@ -486,6 +521,14 @@ async function showActuator(actuatorId, evaluationMessage = "") {
.filter(candidate => contexts.includes(candidate.entity_id))
.map(candidate => `<li><strong>${escapeHtml(candidate.friendly_name || candidate.entity_id)}</strong>: ${candidate.evidence.map(escapeHtml).join(", ") || "statistisch relevanter Kandidat"}</li>`)
.join("");
const currentContextControls = contexts.length
? `<ul>${contexts.map(entityId => `
<li>
<code>${escapeHtml(entityId)}</code>
<button class="secondary compact" onclick="removeContextEntity('${escapeHtml(record.actuator_entity_id)}', '${escapeHtml(entityId)}')">Entfernen</button>
</li>
`).join("")}</ul>`
: "<p class='muted'>Noch keine Kontext-Entity ausgewählt.</p>";
const prediction = record.behavior.prediction;
const learnedAutomationActions = record.behavior.patterns.filter(
pattern => pattern.source === "automation",
@@ -601,6 +644,8 @@ async function showActuator(actuatorId, evaluationMessage = "") {
${automationControls}
<h3>Welche Zusammenhänge automatisch verwendet werden</h3>
${evidence ? `<ul>${evidence}</ul>` : "<p class='warn'>Noch kein geeigneter Kontext erkannt. SillyHome prüft bei neuen HA-Daten erneut.</p>"}
<h3>Verwendete Sensoren/Zustände ändern</h3>
${currentContextControls}
${manualAssignment}
`;
document.getElementById("detail").scrollIntoView({behavior: "smooth", block: "start"});
@@ -635,6 +680,34 @@ async function saveManualAssignment(actuatorId) {
}
}
async function removeContextEntity(actuatorId, entityId) {
try {
const record = await api(`v1/actuators/${encodeURIComponent(actuatorId)}`);
const numericEntityId = record.assignment.selected_numeric_entity_id === entityId
? null
: record.assignment.selected_numeric_entity_id;
const contextEntityIds = (record.assignment.selected_context_entity_ids || [])
.filter(id => id !== entityId);
await api(`v1/actuators/${encodeURIComponent(actuatorId)}/assignment`, {
method: "POST",
body: JSON.stringify({
numeric_entity_id: numericEntityId,
context_entity_ids: contextEntityIds,
note: `Entity ${entityId} entfernt`,
}),
});
await loadConfiguredActuators();
await showActuator(actuatorId, "Kontext-Entity entfernt.");
} catch (error) {
alert(error.message);
}
}
async function configureSuggestedActuator(actuatorId) {
document.getElementById("actuator-input").value = actuatorId;
await configureActuator();
}
async function evaluateActuator(actuatorId) {
try {
const record = await api(