iobroker.eos-admin 7.9.30 → 7.9.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3022,3 +3022,119 @@ html:not(.eos-security-admin):not(.eos-security-admin-user) [data-eos-security-a
3022
3022
  html.eos-app .eos-native-drawer-header { left: 4px !important; }
3023
3023
  .eos-assist-root { right: 14px !important; bottom: 16px !important; }
3024
3024
  }
3025
+
3026
+
3027
+ /* === NexoWatt EOS v31: clean arrow tile, security text repair, AI-ready Assist === */
3028
+ :root { --eos-nav-toggle-slot: 82px; }
3029
+ html.eos-app .MuiDrawer-paper,
3030
+ html.eos-app .eos-drawer {
3031
+ padding-left: var(--eos-nav-toggle-slot) !important;
3032
+ }
3033
+ html.eos-app.eos-nav-compact .MuiDrawer-paper,
3034
+ html.eos-app.eos-nav-compact .eos-drawer {
3035
+ padding-left: var(--eos-nav-toggle-slot) !important;
3036
+ transform: none !important;
3037
+ visibility: visible !important;
3038
+ opacity: 1 !important;
3039
+ }
3040
+ html.eos-app .eos-native-drawer-header,
3041
+ html.eos-app .eos-nav-toggle-shell {
3042
+ position: absolute !important;
3043
+ left: 14px !important;
3044
+ top: 50% !important;
3045
+ transform: translateY(-50%) !important;
3046
+ width: 54px !important;
3047
+ min-width: 54px !important;
3048
+ max-width: 54px !important;
3049
+ height: 54px !important;
3050
+ min-height: 54px !important;
3051
+ max-height: 54px !important;
3052
+ padding: 0 !important;
3053
+ margin: 0 !important;
3054
+ display: grid !important;
3055
+ place-items: center !important;
3056
+ border-radius: 18px !important;
3057
+ background: linear-gradient(135deg, rgba(0, 0, 0, 0.84), rgba(0, 255, 136, 0.13)) !important;
3058
+ border: 1px solid rgba(0, 255, 136, 0.50) !important;
3059
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 0 18px rgba(0,255,136,.20) !important;
3060
+ overflow: visible !important;
3061
+ }
3062
+ html.eos-app .eos-native-drawer-header > div:first-child,
3063
+ html.eos-app .eos-nav-toggle-shell > div:first-child {
3064
+ width: 54px !important;
3065
+ min-width: 54px !important;
3066
+ max-width: 54px !important;
3067
+ height: 54px !important;
3068
+ min-height: 54px !important;
3069
+ max-height: 54px !important;
3070
+ padding: 0 !important;
3071
+ margin: 0 !important;
3072
+ display: grid !important;
3073
+ place-items: center !important;
3074
+ grid-template-columns: 1fr !important;
3075
+ gap: 0 !important;
3076
+ }
3077
+ html.eos-app .eos-native-drawer-header a,
3078
+ html.eos-app .eos-native-drawer-header img,
3079
+ html.eos-app .eos-native-drawer-header .MuiAvatar-root,
3080
+ html.eos-app .eos-native-drawer-header .MuiAvatar-img,
3081
+ html.eos-app .eos-native-drawer-header .eos-native-title,
3082
+ html.eos-app .eos-nav-toggle-decor-hidden {
3083
+ display: none !important;
3084
+ visibility: hidden !important;
3085
+ opacity: 0 !important;
3086
+ pointer-events: none !important;
3087
+ }
3088
+ html.eos-app .eos-native-drawer-header button.eos-nav-compact-toggle,
3089
+ html.eos-app .eos-native-drawer-header .eos-nav-compact-toggle,
3090
+ html.eos-app .eos-native-drawer-header button,
3091
+ html.eos-app .eos-native-drawer-header .MuiIconButton-root {
3092
+ position: static !important;
3093
+ transform: none !important;
3094
+ margin: 0 !important;
3095
+ width: 42px !important;
3096
+ height: 42px !important;
3097
+ min-width: 42px !important;
3098
+ min-height: 42px !important;
3099
+ display: grid !important;
3100
+ place-items: center !important;
3101
+ border-radius: 15px !important;
3102
+ background: radial-gradient(circle at 50% 50%, rgba(0,255,136,.24), rgba(0,0,0,.88) 72%) !important;
3103
+ border: 1px solid rgba(110,255,211,.58) !important;
3104
+ color: #eefcff !important;
3105
+ box-shadow: 0 0 16px rgba(0,255,136,.28), inset 0 0 12px rgba(0,255,136,.12) !important;
3106
+ }
3107
+ html.eos-app .eos-native-drawer-header button.eos-nav-compact-toggle svg,
3108
+ html.eos-app .eos-native-drawer-header .eos-nav-compact-toggle svg,
3109
+ html.eos-app .eos-native-drawer-header .MuiSvgIcon-root {
3110
+ width: 22px !important;
3111
+ height: 22px !important;
3112
+ }
3113
+ html.eos-app .eos-hidden-logout,
3114
+ html.eos-app .eos-hidden-logout *,
3115
+ html.eos-app .eos-native-logout-hidden,
3116
+ html.eos-app .eos-native-logout-hidden * {
3117
+ display: none !important;
3118
+ visibility: hidden !important;
3119
+ opacity: 0 !important;
3120
+ pointer-events: none !important;
3121
+ width: 0 !important;
3122
+ min-width: 0 !important;
3123
+ max-width: 0 !important;
3124
+ padding: 0 !important;
3125
+ margin: 0 !important;
3126
+ border: 0 !important;
3127
+ }
3128
+ .eos-assist-panel { max-height: min(690px, calc(100vh - 150px)) !important; }
3129
+ .eos-assist-answer { white-space: pre-line !important; }
3130
+ .eos-assist-foot strong { color: #00ff88; }
3131
+ @media (max-width: 720px) {
3132
+ :root { --eos-nav-toggle-slot: 72px; }
3133
+ html.eos-app .eos-native-drawer-header,
3134
+ html.eos-app .eos-nav-toggle-shell { left: 10px !important; width: 48px !important; min-width: 48px !important; max-width: 48px !important; height: 48px !important; min-height: 48px !important; }
3135
+ html.eos-app .eos-native-drawer-header > div:first-child { width: 48px !important; min-width: 48px !important; max-width: 48px !important; height: 48px !important; min-height: 48px !important; }
3136
+ html.eos-app .eos-native-drawer-header button.eos-nav-compact-toggle,
3137
+ html.eos-app .eos-native-drawer-header .eos-nav-compact-toggle,
3138
+ html.eos-app .eos-native-drawer-header button,
3139
+ html.eos-app .eos-native-drawer-header .MuiIconButton-root { width: 38px !important; height: 38px !important; min-width: 38px !important; min-height: 38px !important; }
3140
+ }
@@ -31,7 +31,7 @@
31
31
  rel="stylesheet"
32
32
  href="css/leaflet.css"
33
33
  />
34
- <link rel="stylesheet" href="./css/eos-branding.css?v=30" />
34
+ <link rel="stylesheet" href="./css/eos-branding.css?v=31" />
35
35
  <link
36
36
  rel="manifest"
37
37
  href="manifest.json"
@@ -154,9 +154,9 @@
154
154
  <script type="module" crossorigin src="./assets/index-CQZugZ1z.js"></script>
155
155
  <link rel="modulepreload" crossorigin href="./assets/preload-helper-BDBacUwf.js">
156
156
  <link rel="modulepreload" crossorigin href="./assets/iobroker_admin__mf_v__runtimeInit__mf_v__-g2X2zhAf.js">
157
- <script defer src="./js/eos-branding.js?v=30"></script>
158
- <script defer src="./js/eos-security-ui.js?v=30"></script>
159
- <script defer src="./js/eos-assistant.js?v=30"></script>
157
+ <script defer src="./js/eos-branding.js?v=31"></script>
158
+ <script defer src="./js/eos-security-ui.js?v=31"></script>
159
+ <script defer src="./js/eos-assistant.js?v=31"></script>
160
160
  </head>
161
161
  <body>
162
162
  <noscript>You need to enable JavaScript to run this app.</noscript>
@@ -1,6 +1,6 @@
1
1
  (() => {
2
2
  'use strict';
3
- // v30: EOS Assist is rendered by eos-branding.js so it can share route/security state.
3
+ // v31: EOS Assist is rendered by eos-branding.js so it can share route/security state.
4
4
  // This file stays as a lightweight compatibility hook for cache-safe deployments.
5
- window.NEXOWATT_EOS_ASSIST_VERSION = 'v30-integrated-with-eos-branding';
5
+ window.NEXOWATT_EOS_ASSIST_VERSION = 'v31-integrated-ai-ready';
6
6
  })();
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- window.NEXOWATT_EOS_UI_VERSION = 'v30-nav-assist-security-polish';
4
+ window.NEXOWATT_EOS_UI_VERSION = 'v31-ui-cleanup-ai-ready';
5
5
 
6
6
  const BRAND = 'NexoWatt EOS';
7
7
  const EOS_MEANING = 'Energy Operation System';
@@ -25,6 +25,29 @@
25
25
  [/\bioBroker\b/gi, BRAND],
26
26
  ];
27
27
 
28
+
29
+ const MOJIBAKE_REPLACEMENTS = new Map(Object.entries({
30
+ 'dürfen': 'dürfen', 'Dürfen': 'Dürfen',
31
+ 'für': 'für', 'Für': 'Für',
32
+ 'müssen': 'müssen', 'Müssen': 'Müssen',
33
+ 'können': 'können', 'Können': 'Können',
34
+ 'möglich': 'möglich', 'Möglich': 'Möglich',
35
+ 'Löschen': 'Löschen', 'löschen': 'löschen',
36
+ 'schützen': 'schützen', 'Schützen': 'Schützen',
37
+ 'Schützt': 'Schützt', 'schützt': 'schützt',
38
+ 'Geschützte': 'Geschützte', 'geschützte': 'geschützte',
39
+ 'ausgewählte': 'ausgewählte', 'Ausgewählte': 'Ausgewählte',
40
+ 'ändern': 'ändern', 'Ändern': 'Ändern',
41
+ 'über': 'über', 'Über': 'Über',
42
+ 'Wähle': 'Wähle', 'wähle': 'wähle',
43
+ 'öffnen': 'öffnen', 'Öffnen': 'Öffnen',
44
+ 'schließen': 'schließen', 'Schließen': 'Schließen',
45
+ 'Gerät': 'Gerät', 'gerät': 'gerät',
46
+ 'Geräte': 'Geräte', 'geräte': 'geräte',
47
+ 'Zugänge': 'Zugänge', 'zugänge': 'zugänge',
48
+ 'ß': 'ß', 'Ä': 'Ä', 'Ö': 'Ö', 'Ü': 'Ü', 'ä': 'ä', 'ö': 'ö', 'ü': 'ü'
49
+ }));
50
+
28
51
  const EXACT_LABELS = new Map(Object.entries({
29
52
  'Admin': BRAND,
30
53
  'NEXOWATT': 'NEXOWATT EOS',
@@ -82,6 +105,9 @@
82
105
  const replaceBrand = value => {
83
106
  if (!value || typeof value !== 'string') return value;
84
107
  let next = value;
108
+ for (const [from, to] of MOJIBAKE_REPLACEMENTS) {
109
+ if (next.includes(from)) next = next.split(from).join(to);
110
+ }
85
111
  for (const [pattern, replacement] of TEXT_REPLACEMENTS) next = next.replace(pattern, replacement);
86
112
  const compact = next.trim();
87
113
  if (EXACT_LABELS.has(compact)) next = next.replace(compact, EXACT_LABELS.get(compact));
@@ -511,17 +537,30 @@
511
537
  };
512
538
 
513
539
  const hideNativeLogoutNav = () => safe(() => {
514
- const candidates = Array.from(document.querySelectorAll('.MuiDrawer-paper a, .MuiDrawer-paper button, .MuiDrawer-paper .MuiListItem-root, .MuiDrawer-paper .MuiListItemButton-root, .MuiDrawer-paper [role="button"], nav a, nav button, nav [role="button"]'));
540
+ const candidates = Array.from(document.querySelectorAll('.MuiDrawer-paper a, .MuiDrawer-paper button, .MuiDrawer-paper li, .MuiDrawer-paper .MuiListItem-root, .MuiDrawer-paper .MuiListItemButton-root, .MuiDrawer-paper [role="button"], nav a, nav button, nav li, nav [role="button"]'));
515
541
  candidates.forEach(el => {
516
542
  const text = normalize(`${el.textContent || ''} ${el.getAttribute?.('aria-label') || ''} ${el.getAttribute?.('title') || ''}`);
517
543
  const href = String(el.getAttribute?.('href') || '');
518
544
  const isLogout = /(?:^|\b)(abmelden|logout|ra_logout)(?:\b|$)/.test(text) || /(?:^|[/?#])logout(?:[/?#]|$)/i.test(href);
519
545
  if (!isLogout) return;
520
- const item = el.closest('.MuiListItem-root, li, .MuiButtonBase-root, .MuiListItemButton-root') || el.closest('a, button, [role="button"]') || el;
521
- item.classList.add('eos-hidden-logout', 'eos-native-logout-hidden');
522
- item.setAttribute('aria-hidden', 'true');
523
- item.setAttribute('tabindex', '-1');
524
- if (item.style) item.style.display = 'none';
546
+ const targets = new Set([
547
+ el,
548
+ el.closest('.MuiListItem-root'),
549
+ el.closest('li'),
550
+ el.closest('.MuiListItemButton-root'),
551
+ el.closest('.MuiButtonBase-root'),
552
+ el.closest('a, button, [role="button"]'),
553
+ ].filter(Boolean));
554
+ targets.forEach(item => {
555
+ item.classList.add('eos-hidden-logout', 'eos-native-logout-hidden');
556
+ item.setAttribute('aria-hidden', 'true');
557
+ item.setAttribute('tabindex', '-1');
558
+ if (item.style) {
559
+ item.style.display = 'none';
560
+ item.style.visibility = 'hidden';
561
+ item.style.pointerEvents = 'none';
562
+ }
563
+ });
525
564
  });
526
565
  });
527
566
 
@@ -538,7 +577,7 @@
538
577
  || directChildren.find(el => !isListLike(el) && el.querySelector && (el.querySelector('button') || el.querySelector('img') || el.querySelector('.MuiAvatar-root')));
539
578
  }
540
579
  if (header) {
541
- header.classList.add('eos-native-drawer-header');
580
+ header.classList.add('eos-native-drawer-header', 'eos-nav-toggle-shell');
542
581
  const toggleButton = header.querySelector('button, .MuiIconButton-root, [role="button"]');
543
582
  if (toggleButton && !toggleButton.dataset.eosNavCompactToggle) {
544
583
  toggleButton.dataset.eosNavCompactToggle = 'true';
@@ -559,6 +598,9 @@
559
598
  if (event.key === 'Enter' || event.key === ' ') toggleCompact(event);
560
599
  }, true);
561
600
  }
601
+ header.querySelectorAll('a,img,.MuiAvatar-root,.MuiAvatar-img,.eos-native-title').forEach(el => {
602
+ if (!el.closest?.('button')) el.classList.add('eos-nav-toggle-decor-hidden');
603
+ });
562
604
  const img = header.querySelector('img');
563
605
  if (img) patchImage(img);
564
606
  const avatarImg = header.querySelector('.MuiAvatar-img');
@@ -808,18 +850,65 @@
808
850
  };
809
851
  };
810
852
 
811
- const assistAnswer = query => {
853
+ const configuredAssistEndpoint = () => safe(() => {
854
+ const globalEndpoint = typeof window.NEXOWATT_EOS_ASSIST_ENDPOINT === 'string' ? window.NEXOWATT_EOS_ASSIST_ENDPOINT.trim() : '';
855
+ const storedEndpoint = localStorage.getItem('nexowatt:eosAssistEndpoint') || '';
856
+ return globalEndpoint || storedEndpoint.trim();
857
+ }) || '';
858
+
859
+ const localAssistAnswer = query => {
812
860
  const q = normalize(query);
813
- if (!q) return 'Beschreibe kurz, was eingerichtet werden soll, zum Beispiel: PV, Speicher, Wallbox, Modbus, Backup oder Benutzerrechte.';
814
- if (/wallbox|evcs|lade|auto|ocpp/.test(q)) return 'Für Ladepunkte: zuerst OCPP/EVCS-Modul installieren, Verbindung zur Wallbox prüfen, Ladepunkt-ID setzen, danach Datenpunkte für Status, Leistung und Freigabe testen.';
815
- if (/pv|solar|wechselrichter|sun2000|fronius|kostal|sma/.test(q)) return 'Für PV/Wechselrichter: IP-Adresse, Modbus/TCP oder Hersteller-API aktivieren, Abfrageintervall moderat setzen und danach Erzeugung, Bezug und Einspeisung in den Datenpunkten prüfen.';
816
- if (/speicher|batterie|akku/.test(q)) return 'Für Speicher: Kommunikationsweg prüfen, Lade-/Entladeleistung und SoC-Datenpunkte kontrollieren und Grenzwerte erst setzen, wenn Livewerte plausibel sind.';
817
- if (/modbus/.test(q)) return 'Für Modbus: Host/IP, Port 502, Unit-ID und Registertabelle prüfen. Danach erst lesen testen, dann Schreibrechte gezielt freischalten.';
818
- if (/mqtt/.test(q)) return 'Für MQTT: Broker-Adresse, Zugangsdaten, Topic-Struktur und TLS prüfen. Danach mit einem Test-Topic starten und erst dann produktive Topics freigeben.';
819
- if (/backup|sicherung|restore/.test(q)) return 'Für Sicherung: BackItUp aktiv halten, Zielpfad oder Cloud-Ziel prüfen, Test-Backup starten und Restore-Hinweise dokumentieren.';
820
- if (/rechte|benutzer|installateur|kunde|admin/.test(q)) return 'Für Rechte: Benutzer in Rollen trennen. Installateure dürfen konfigurieren, aber geschützte EOS-Systemmodule nicht löschen. Endkunden bekommen nur Bedien- und Leserechte.';
821
- if (/fehler|log|offline|404|timeout/.test(q)) return 'Bei Fehlern: zuerst Systemlogs öffnen, betroffene Instanz filtern, letzte Änderung prüfen, Dienst neu starten und danach Port/WebSocket/Repository prüfen.';
822
- return 'Vorschlag: Starte mit dem passenden Modul, prüfe die Verbindung, kontrolliere die erzeugten Datenpunkte und sichere danach die Rechte. Für konkrete Hilfe nenne bitte Modulname und Zielgerät.';
861
+ if (!q) return 'Beschreibe kurz, was eingerichtet werden soll, zum Beispiel: Wallbox, PV, Speicher, Modbus, MQTT, Sicherung oder Benutzerrechte. EOS Assist zeigt mehrere Integrationswege und nicht nur einen einzelnen Adapter.';
862
+ if (/wallbox|evcs|lade|auto|ocpp|ladestation|charge/.test(q)) return [
863
+ 'Wallbox / Ladepunkt: Es gibt mehrere mögliche Integrationswege. Empfehlung nach Priorität prüfen:',
864
+ '1. Herstelleradapter: nutzen, wenn ein stabiler Adapter für Hersteller/Modell vorhanden ist.',
865
+ '2. Modbus TCP/RTU: bevorzugt für lokale EMS-Werte wie Leistung, Strom, Freigabe, Zählerstand und Phasen.',
866
+ '3. OCPP: passend, wenn die Wallbox als Ladepunkt-Backend angebunden werden soll oder mehrere Ladepunkte zentral verwaltet werden.',
867
+ '4. HTTP/REST oder MQTT: sinnvoll bei offenen APIs, eigener Firmware oder Gateway-Lösungen.',
868
+ '5. Datenpunkt-Mapping: Fallback, wenn Werte bereits aus einem anderen System kommen.',
869
+ 'Nächster Schritt: Hersteller, Modell, IP-Adresse und verfügbare Schnittstellen nennen. Dann kann EOS Assist den besten Weg vorschlagen.'
870
+ ].join('\n');
871
+ if (/keba|kecontact|mennekes|abl|alfen|easee|heidelberg|go-?e|openwb|zaptec|wallbe|duosida/.test(q)) return [
872
+ 'Wallbox-Hersteller erkannt. Bitte nicht automatisch nur OCPP verwenden.',
873
+ 'Prüfe zuerst: gibt es einen nativen Adapter oder eine lokale Modbus-/HTTP-Schnittstelle?',
874
+ 'OCPP ist gut für Backend-/Ladepunktverwaltung; Modbus/HTTP ist oft besser für lokale EMS-Regelung und schnelle Leistungswerte.',
875
+ 'Für EOS empfehle ich: lokale Schnittstelle für Regelung + OCPP nur, wenn Backend-Funktionen gebraucht werden.'
876
+ ].join('\n');
877
+ if (/pv|solar|wechselrichter|sun2000|fronius|kostal|sma|huawei|growatt|sungrow/.test(q)) return 'PV/Wechselrichter: Herstelleradapter oder Modbus TCP prüfen. Für EOS sind Erzeugung, Bezug, Einspeisung, Batterieladeleistung und Statusdaten wichtig. Erst Livewerte validieren, dann Optimierung aktivieren.';
878
+ if (/speicher|batterie|akku|soc/.test(q)) return 'Speicher: SoC, Lade-/Entladeleistung, Betriebsmodus und Grenzwerte prüfen. Schreibbefehle erst freigeben, wenn Lese-Datenpunkte stabil und plausibel sind.';
879
+ if (/modbus/.test(q)) return 'Modbus: IP/Port 502, Unit-ID, Registerliste, Datentyp und Byte-Reihenfolge prüfen. Erst nur lesen testen, danach Schreibrechte gezielt und mit Schutzliste freischalten.';
880
+ if (/mqtt/.test(q)) return 'MQTT: Broker, Authentifizierung, Topic-Struktur und TLS prüfen. Mit Test-Topic starten, dann produktive Topics in EOS-Datenpunkte mappen.';
881
+ if (/backup|sicherung|restore|backitup/.test(q)) return 'Sicherung: BackItUp aktiv halten, Zielpfad/Cloud-Ziel testen, Test-Backup erstellen und Restore-Ablauf dokumentieren. BackItUp bleibt Systemadapter und sollte geschützt sein.';
882
+ if (/rechte|benutzer|installateur|kunde|admin|sicherheit|security/.test(q)) return 'Rechte: Administratoren verwalten Systemschutz. Installateure dürfen konfigurieren, aber geschützte Systemadapter nicht löschen, stoppen oder kritisch ändern. Endkunden bekommen Bedien- und Leserechte.';
883
+ if (/fehler|log|offline|404|timeout|startet nicht|server/.test(q)) return 'Fehlersuche: Systemlogs filtern, betroffene Instanz prüfen, letzte Änderung identifizieren, Dienst neu starten und danach Port, WebSocket, Repository und Paketversion kontrollieren.';
884
+ return 'EOS Assist Empfehlung: Gerätetyp, Hersteller/Modell und verfügbare Schnittstellen nennen. Danach wird der beste Integrationsweg gewählt: nativer Adapter, Modbus, OCPP, HTTP/REST, MQTT oder Datenpunkt-Mapping.';
885
+ };
886
+
887
+ const requestRemoteAssist = async query => {
888
+ const endpoint = configuredAssistEndpoint();
889
+ if (!endpoint) return '';
890
+ try {
891
+ const response = await fetch(endpoint, {
892
+ method: 'POST',
893
+ credentials: 'same-origin',
894
+ headers: { 'content-type': 'application/json' },
895
+ body: JSON.stringify({ query, route: window.location.hash || '', ui: 'nexowatt-eos-admin' }),
896
+ });
897
+ if (!response.ok) return '';
898
+ const data = await response.json().catch(() => ({}));
899
+ return String(data.answer || data.text || data.message || '').trim();
900
+ } catch { return ''; }
901
+ };
902
+
903
+ const assistAnswer = query => localAssistAnswer(query);
904
+
905
+ const sendAssistQuestion = async (root, query) => {
906
+ const out = root?.querySelector?.('.eos-assist-answer');
907
+ if (!out) return;
908
+ const endpoint = configuredAssistEndpoint();
909
+ if (endpoint) out.textContent = 'EOS Assist fragt die KI an ...';
910
+ const remote = endpoint ? await requestRemoteAssist(query) : '';
911
+ out.textContent = remote || localAssistAnswer(query);
823
912
  };
824
913
 
825
914
  const ensureEosAssist = () => safe(() => {
@@ -837,7 +926,7 @@
837
926
  root.className = 'eos-assist-root';
838
927
  root.innerHTML = `
839
928
  <button class="eos-assist-button" type="button" aria-expanded="false">
840
- <span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>Einrichtungshilfe</small>
929
+ <span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>KI-Hilfe</small>
841
930
  </button>
842
931
  <div class="eos-assist-panel" role="dialog" aria-label="EOS Assist Einrichtungshilfe">
843
932
  <div class="eos-assist-head">
@@ -847,15 +936,16 @@
847
936
  </div>
848
937
  <div class="eos-assist-steps"></div>
849
938
  <div class="eos-assist-actions">
939
+ <button type="button" data-question="Wallbox einrichten: Welche Wege gibt es?">Wallbox</button>
850
940
  <button type="button" data-question="Wie richte ich dieses Modul ein?">Modul einrichten</button>
851
941
  <button type="button" data-question="Wie prüfe ich Fehler in den Logs?">Fehler prüfen</button>
852
942
  <button type="button" data-question="Welche Rechte braucht der Installateur?">Rechte erklären</button>
853
943
  <button type="button" data-question="Was muss ich vor einem Update beachten?">Update-Check</button>
854
944
  </div>
855
945
  <label class="eos-assist-input-label">Was möchtest du einrichten?</label>
856
- <div class="eos-assist-input-row"><input class="eos-assist-input" placeholder="z. B. Wallbox, PV, Modbus, Rechte..." /><button type="button" class="eos-assist-send">Fragen</button></div>
946
+ <div class="eos-assist-input-row"><input class="eos-assist-input" placeholder="z. B. Wallbox Keba Modbus, OCPP, PV, Rechte..." /><button type="button" class="eos-assist-send">Fragen</button></div>
857
947
  <div class="eos-assist-answer"></div>
858
- <div class="eos-assist-foot">Lokale Bedienhilfe ohne Cloud. Eine echte KI-Anbindung kann später über einen NexoWatt-Dienst ergänzt werden.</div>
948
+ <div class="eos-assist-foot">EOS Assist nutzt eine lokale Entscheidungslogik und ist für eine echte NexoWatt-KI-Anbindung vorbereitet.</div>
859
949
  </div>
860
950
  `;
861
951
  document.body.appendChild(root);
@@ -897,7 +987,7 @@
897
987
  event.preventDefault();
898
988
  const value = root.querySelector('.eos-assist-input')?.value || '';
899
989
  const out = root.querySelector('.eos-assist-answer');
900
- if (out) out.textContent = assistAnswer(value);
990
+ sendAssistQuestion(root, value);
901
991
  return;
902
992
  }
903
993
  if (target.hasAttribute('data-question')) {
@@ -906,13 +996,13 @@
906
996
  const field = root.querySelector('.eos-assist-input');
907
997
  if (field) field.value = question;
908
998
  const out = root.querySelector('.eos-assist-answer');
909
- if (out) out.textContent = assistAnswer(question);
999
+ sendAssistQuestion(root, question);
910
1000
  }
911
1001
  }, true);
912
1002
  root.addEventListener('keydown', event => {
913
1003
  if (event.key !== 'Enter' || !event.target?.classList?.contains('eos-assist-input')) return;
914
1004
  const out = root.querySelector('.eos-assist-answer');
915
- if (out) out.textContent = assistAnswer(event.target.value || '');
1005
+ sendAssistQuestion(root, event.target.value || '');
916
1006
  }, true);
917
1007
  }
918
1008
  });
@@ -948,7 +1038,7 @@
948
1038
  event.preventDefault();
949
1039
  event.stopPropagation();
950
1040
  event.stopImmediatePropagation?.();
951
- if (answer) answer.textContent = assistAnswer(input?.value || '');
1041
+ sendAssistQuestion(root, input?.value || '');
952
1042
  state.assistOpen = true;
953
1043
  root.classList.add('eos-assist-open');
954
1044
  }
@@ -1,7 +1,7 @@
1
1
  (() => {
2
2
  'use strict';
3
3
 
4
- const VERSION = 'v30-security-admin-only-polish';
4
+ const VERSION = 'v31-security-text-polish';
5
5
  const LEGACY_ADMIN = 'admin';
6
6
  const LEGACY_ADMIN_INSTANCE = 'admin.0';
7
7
  const ASSET_BASE = (() => {
@@ -29,6 +29,21 @@
29
29
  };
30
30
 
31
31
  const normalize = value => String(value || '').replace(/\s+/g, ' ').trim();
32
+
33
+ const fixMojibake = value => {
34
+ let text = String(value || '');
35
+ const map = new Map(Object.entries({
36
+ 'dürfen': 'dürfen', 'Dürfen': 'Dürfen', 'für': 'für', 'Für': 'Für',
37
+ 'können': 'können', 'Können': 'Können', 'möglich': 'möglich', 'Möglich': 'Möglich',
38
+ 'Löschen': 'Löschen', 'löschen': 'löschen', 'schützen': 'schützen', 'Schützen': 'Schützen',
39
+ 'Schützt': 'Schützt', 'schützt': 'schützt', 'Geschützte': 'Geschützte', 'geschützte': 'geschützte',
40
+ 'ausgewählte': 'ausgewählte', 'Ausgewählte': 'Ausgewählte', 'ändern': 'ändern', 'Ändern': 'Ändern',
41
+ 'über': 'über', 'Über': 'Über', 'Wähle': 'Wähle', 'wähle': 'wähle',
42
+ 'ß': 'ß', 'Ä': 'Ä', 'Ö': 'Ö', 'Ü': 'Ü', 'ä': 'ä', 'ö': 'ö', 'ü': 'ü'
43
+ }));
44
+ for (const [from, to] of map) if (text.includes(from)) text = text.split(from).join(to);
45
+ return text;
46
+ };
32
47
  const normalizeFlat = value => normalize(value).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
33
48
  const normalizeAdapter = value => {
34
49
  let adapter = String(value || '').trim().toLowerCase();
@@ -163,6 +178,8 @@
163
178
  changed = true;
164
179
  }
165
180
  }
181
+ const fixed = fixMojibake(value);
182
+ if (fixed !== value) { value = fixed; changed = true; }
166
183
  if (changed) node.nodeValue = value;
167
184
  }
168
185
  };
@@ -0,0 +1,12 @@
1
+ # NexoWatt EOS Admin v31
2
+
3
+ ## Inhalt
4
+
5
+ - Kompakt-Pfeil im Navigationsrail sitzt zentriert in einer einzelnen Kachel.
6
+ - Die Abmelden-Kachel neben dem Pfeil wird robuster ausgeblendet.
7
+ - EOS-Security-Texte werden UTF-8-sicher zur Laufzeit repariert.
8
+ - EOS Assist wurde als KI-vorbereiteter Einrichtungsassistent erweitert. Bei Wallboxen werden mehrere Wege vorgeschlagen: Herstelleradapter, Modbus TCP/RTU, OCPP, MQTT/HTTP/REST und Datenpunkt-Mapping.
9
+
10
+ ## Version
11
+
12
+ `7.9.31`