|
|
|
|
@@ -52,6 +52,8 @@
|
|
|
|
|
.panel-title { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px; }
|
|
|
|
|
.toolbar { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin:10px 0; }
|
|
|
|
|
.toolbar button { margin-top:0; }
|
|
|
|
|
.detail-tools { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:8px; margin:10px 0; align-items:end; }
|
|
|
|
|
.detail-tools button { margin-top:0; }
|
|
|
|
|
details.collapsible > summary,
|
|
|
|
|
.manual-context > summary,
|
|
|
|
|
.group-panel > summary { cursor:pointer; font-weight:800; color:#eaf1f8; }
|
|
|
|
|
@@ -144,10 +146,9 @@
|
|
|
|
|
<div class="brand">
|
|
|
|
|
<div class="brand-row">
|
|
|
|
|
<span class="brand-mark">SH</span>
|
|
|
|
|
<h1>SillyHome Next</h1>
|
|
|
|
|
<h1>SillyHome</h1>
|
|
|
|
|
</div>
|
|
|
|
|
<p>Arbeitsdashboard für gelernte Home-Assistant-Bedienung: Geräte auswählen, Lernstand prüfen, Freigaben steuern.</p>
|
|
|
|
|
<p class="notice">Sicherer Start: Zuerst wird nur beobachtet und vorhergesagt. Ohne deine spätere Freigabe wird nichts geschaltet.</p>
|
|
|
|
|
<p>Geräte, Lernen, Freigaben und Systemzustand.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="header-actions">
|
|
|
|
|
<label for="section-jump">Menü</label>
|
|
|
|
|
@@ -156,7 +157,6 @@
|
|
|
|
|
<option value="observed">Lernen</option>
|
|
|
|
|
<option value="choose">Discovery & Einrichtung</option>
|
|
|
|
|
<option value="settings">Einstellungen</option>
|
|
|
|
|
<option value="guide">Ablauf</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div class="status-pill"><span class="dot"></span><span id="load-budget">Seite bereit, Status folgt ...</span></div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -165,11 +165,9 @@
|
|
|
|
|
<main>
|
|
|
|
|
<section class="control-panel app-view" id="choose">
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<h2>Steuerung</h2>
|
|
|
|
|
<span class="chip">v1</span>
|
|
|
|
|
<h2>Discovery & Einrichtung</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="muted">Wähle eine Lampe, einen Rollladen oder einen anderen unterstützten Aktor. Du wählst keine Sensoren und erstellst keine Regeln.</p>
|
|
|
|
|
<label for="actuator-input">Entitätsname oder Gerät aus Home Assistant</label>
|
|
|
|
|
<label for="actuator-input">Entity-ID</label>
|
|
|
|
|
<input id="actuator-input" list="actuator-options" placeholder="z. B. light.licht_abstellraum" autocomplete="off">
|
|
|
|
|
<datalist id="actuator-options"></datalist>
|
|
|
|
|
<div class="inline-controls">
|
|
|
|
|
@@ -199,16 +197,15 @@
|
|
|
|
|
<input id="actuator-search" placeholder="Raum, Gerät oder Entity" oninput="renderActuatorSelect()" onfocus="ensureActuatorDiscovery()" autocomplete="off">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<label for="actuator-select">Oder aus Liste wählen</label>
|
|
|
|
|
<label for="actuator-select">Geräteliste</label>
|
|
|
|
|
<select id="actuator-select" onchange="selectActuatorFromList()" onfocus="ensureActuatorDiscovery()">
|
|
|
|
|
<option value="">Geräteliste bei Bedarf laden</option>
|
|
|
|
|
</select>
|
|
|
|
|
<button onclick="configureActuator()">Gerät hinzufügen und Beobachtung starten</button>
|
|
|
|
|
<button class="secondary" onclick="ensureActuatorDiscovery()">Geräteliste laden</button>
|
|
|
|
|
<p id="actuator-config-result" class="muted">Noch kein Aktor ausgewählt.</p>
|
|
|
|
|
<p id="actuator-config-result" class="muted">Bereit.</p>
|
|
|
|
|
<details class="manual-context">
|
|
|
|
|
<summary>Vorschläge anzeigen</summary>
|
|
|
|
|
<p class="muted">Vorschläge können Home Assistant stark abfragen und werden deshalb nicht beim Start geladen.</p>
|
|
|
|
|
<button class="secondary" onclick="loadActuatorSuggestions()">Vorschläge laden</button>
|
|
|
|
|
</details>
|
|
|
|
|
<div id="actuator-suggestions" class="card-list"></div>
|
|
|
|
|
@@ -218,7 +215,6 @@
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<div>
|
|
|
|
|
<h2>Beobachtete Geräte</h2>
|
|
|
|
|
<p class="muted">Öffne „Details“, um Lernfortschritt, aktuelle Vorhersage und den automatisch gefundenen Kontext zu sehen.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="secondary compact" onclick="loadOverview()">Aktualisieren</button>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -226,8 +222,10 @@
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="detail-panel app-view" id="detail">
|
|
|
|
|
<h2>Lernfortschritt und Freigabe</h2>
|
|
|
|
|
<p class="muted">Die Freigabe erscheint erst, wenn genug eindeutig zugeordnete Handlungen gelernt wurden. Vorher bleibt das Gerät sicher im Beobachtungsmodus.</p>
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<h2>Details</h2>
|
|
|
|
|
<button class="secondary compact" onclick="showView('observed')">Zurück</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="actuator-detail" class="muted">Öffne bei einem beobachteten Gerät die Details.</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
@@ -235,7 +233,6 @@
|
|
|
|
|
<div class="panel-title">
|
|
|
|
|
<div>
|
|
|
|
|
<h2>System & Cache</h2>
|
|
|
|
|
<p class="muted">Die Startansicht nutzt lokale Summaries und Cache-Daten. Home-Assistant-Discovery lädt erst bei Bedarf.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="secondary compact" onclick="loadStatus()">Status prüfen</button>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -259,40 +256,9 @@
|
|
|
|
|
<option value="de">Deutsch</option>
|
|
|
|
|
<option value="en">English</option>
|
|
|
|
|
</select>
|
|
|
|
|
<p class="muted">Die API speichert stabile technische Werte. Die Oberfläche übersetzt sie in die gewählte Sprache.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h3>Performance-Standard</h3>
|
|
|
|
|
<p>Startansichten dürfen maximal 3 Sekunden brauchen. Schwere Daten werden nur nach Menüwechsel oder bei Bearbeitung geladen.</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="guide-panel app-view" id="guide">
|
|
|
|
|
<details class="collapsible">
|
|
|
|
|
<summary><span>So gehst du vor</span></summary>
|
|
|
|
|
<div class="steps">
|
|
|
|
|
<div class="step">
|
|
|
|
|
<span class="step-number">1</span>
|
|
|
|
|
<h3>Aktor auswählen</h3>
|
|
|
|
|
<p><strong>Wo?</strong> Links im Feld „Gerät auswählen“.</p>
|
|
|
|
|
<p><strong>Was passiert?</strong> SillyHome ordnet Raum, Sensoren, Zustände und vorhandene Historie automatisch zu.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step">
|
|
|
|
|
<span class="step-number">2</span>
|
|
|
|
|
<h3>Wie gewohnt bedienen</h3>
|
|
|
|
|
<p><strong>Wo?</strong> Weiterhin in Home Assistant, an Schaltern oder über deine bisherigen Bedienwege.</p>
|
|
|
|
|
<p><strong>Was passiert?</strong> SillyHome lernt deine Handlungen und zeigt Vorhersagen an, schaltet aber noch nicht selbst.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="step">
|
|
|
|
|
<span class="step-number">3</span>
|
|
|
|
|
<h3>Später freigeben</h3>
|
|
|
|
|
<p><strong>Wo?</strong> In den Details des ausgewählten Geräts, sobald genug Verhalten gelernt wurde.</p>
|
|
|
|
|
<p><strong>Was passiert?</strong> Erst dann darf SillyHome passende Vorhersagen automatisch ausführen. Die Freigabe kann jederzeit gestoppt werden.</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</details>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
<script>
|
|
|
|
|
const escapeHtml = value => String(value ?? "")
|
|
|
|
|
@@ -1071,6 +1037,20 @@ function selectActuatorFromList() {
|
|
|
|
|
if (value) document.getElementById("actuator-input").value = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectDetailActuator() {
|
|
|
|
|
const value = document.getElementById("detail-actuator-select")?.value;
|
|
|
|
|
if (value) void showActuator(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function detailActuatorOptions(selectedId) {
|
|
|
|
|
const rows = cachedActuators || [];
|
|
|
|
|
return rows.map(record => `
|
|
|
|
|
<option value="${escapeHtml(record.actuator_entity_id)}" ${record.actuator_entity_id === selectedId ? "selected" : ""}>
|
|
|
|
|
${escapeHtml(record.friendly_name || record.device_name || record.actuator_entity_id)}
|
|
|
|
|
</option>
|
|
|
|
|
`).join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderManualContextSelect() {
|
|
|
|
|
const select = document.getElementById("manual-context-select");
|
|
|
|
|
if (!select) return;
|
|
|
|
|
@@ -1426,7 +1406,6 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
const manualAssignment = `
|
|
|
|
|
<details class="manual-context">
|
|
|
|
|
<summary>Kontext selbst festlegen</summary>
|
|
|
|
|
<p class="muted">Die Vorschläge sind aktorbezogen vorsortiert. Wenn etwas fehlt, trage die Entity-ID unten manuell ein, z. B. PIR, Helligkeit außen, Luftfeuchtigkeit oder Lichtzustände.</p>
|
|
|
|
|
<label for="manual-numeric-select">Optionaler Haupt-Messsensor</label>
|
|
|
|
|
<select id="manual-numeric-select">
|
|
|
|
|
<option value="">Keinen numerischen Hauptsensor verwenden</option>
|
|
|
|
|
@@ -1477,9 +1456,17 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
<div class="detail-header">
|
|
|
|
|
<div>
|
|
|
|
|
<h3>${escapeHtml(record.actuator_entity_id)}</h3>
|
|
|
|
|
<p class="muted">Alle wichtigen Aktionen für dieses Gerät.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="secondary compact" onclick="showActuator('${escapeHtml(record.actuator_entity_id)}', 'Aktualisiert.')">Details aktualisieren</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="detail-tools">
|
|
|
|
|
<button class="secondary" onclick="showView('observed')">Zurück zur Übersicht</button>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="detail-actuator-select">Anderes Gerät</label>
|
|
|
|
|
<select id="detail-actuator-select" onchange="selectDetailActuator()">
|
|
|
|
|
${detailActuatorOptions(record.actuator_entity_id)}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="secondary" onclick="showActuator('${escapeHtml(record.actuator_entity_id)}', 'Aktualisiert.')">Aktualisieren</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="grid-two">
|
|
|
|
|
<div>
|
|
|
|
|
@@ -1487,7 +1474,6 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
<p><strong>Status:</strong> <span class="${statusClass(record)}">${escapeHtml(lifecycleLabel(record))}</span></p>
|
|
|
|
|
<p><strong>Kontextzuordnung:</strong> automatisch erledigt</p>
|
|
|
|
|
<p><strong>Zuordnungssicherheit:</strong> ${Math.round(record.assignment.confidence * 100)} %</p>
|
|
|
|
|
<p class="muted">Dieser Wert beschreibt, wie sicher Raum, Sensoren und Zustände zu diesem Gerät passen.</p>
|
|
|
|
|
<p><strong>Ergebnis:</strong> ${escapeHtml(record.assignment.reason)}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
@@ -1497,11 +1483,10 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
<p><strong>Davon eindeutig geregelt:</strong> ${record.behavior.high_confidence_sample_count}</p>
|
|
|
|
|
<p><strong>Erkannte HA-Automationen:</strong> ${escapeHtml(learnedAutomationActions)}</p>
|
|
|
|
|
<p><strong>Letztes Training:</strong> ${escapeHtml(formatDateTime(record.behavior.last_trained_at))}</p>
|
|
|
|
|
<p><strong>Was noch passiert:</strong> ${escapeHtml(record.behavior.reason)}</p>
|
|
|
|
|
<p><strong>Statusgrund:</strong> ${escapeHtml(record.behavior.reason)}</p>
|
|
|
|
|
<p><strong>Freigabestatus:</strong> <span class="${record.behavior.activation_ready ? "ok" : "warn"}">${escapeHtml(record.behavior.activation_reason)}</span></p>
|
|
|
|
|
<div class="actions">${activationButton}</div>
|
|
|
|
|
<button class="secondary" onclick="evaluateActuator('${escapeHtml(record.actuator_entity_id)}')">Aktuelle Situation auswerten</button>
|
|
|
|
|
<p class="muted">Die Prüfung simuliert keinen Sensorwechsel und schaltet keinen Aktor.</p>
|
|
|
|
|
${evaluationMessage ? `<p class="ok">${escapeHtml(evaluationMessage)}</p>` : ""}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -1518,13 +1503,11 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
${adaptivePanel}
|
|
|
|
|
${anomalyPanel}
|
|
|
|
|
<h3>Passende Home-Assistant-Automationen</h3>
|
|
|
|
|
<p class="muted">Bei einer Übernahme pausiert SillyHome diese Automationen. Beim Stoppen können sie gezielt fortgesetzt werden.</p>
|
|
|
|
|
<button class="secondary compact" onclick="refreshRelatedAutomations('${escapeHtml(record.actuator_entity_id)}')">Automationen neu suchen</button>
|
|
|
|
|
${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>Sensor-Gewichtung</h3>
|
|
|
|
|
<p class="muted">Automatische Relevanz kommt aus der Zuordnung. Die aktive Gewichtung kannst du korrigieren; Gruppen bündeln mehrere Sensoren/Zustände.</p>
|
|
|
|
|
${weightControls}
|
|
|
|
|
${weightGroupControls}
|
|
|
|
|
<h3>Verwendete Sensoren/Zustände ändern</h3>
|
|
|
|
|
@@ -1545,13 +1528,13 @@ function renderActuatorDetailShell(actuatorId) {
|
|
|
|
|
<div class="detail-header">
|
|
|
|
|
<div>
|
|
|
|
|
<h3>${escapeHtml(actuatorId)}</h3>
|
|
|
|
|
<p class="muted">Basisdaten werden geladen ...</p>
|
|
|
|
|
<p class="muted">Lädt ...</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="metric-grid">
|
|
|
|
|
<div class="metric"><strong>Phase 1</strong>Aktuelle Einstellung</div>
|
|
|
|
|
<div class="metric"><strong>Phase 2</strong>Lernstand</div>
|
|
|
|
|
<div class="metric"><strong>Phase 3</strong>Kontextvorschläge</div>
|
|
|
|
|
<div class="metric"><strong>Status</strong>...</div>
|
|
|
|
|
<div class="metric"><strong>Lernen</strong>...</div>
|
|
|
|
|
<div class="metric"><strong>Kontext</strong>...</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|