iobroker.eos-admin 7.9.89 → 7.9.93
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.
- package/INSTALL_TEST_V7.9.93.md +12 -0
- package/NEXOWATT_EOS_BUILD_INFO.json +19 -26
- package/PUBLISH_STABLE_V7.9.93.md +19 -0
- package/README_STABILITY_V7.9.93.md +15 -0
- package/RELEASE_ACCEPTANCE_V7.9.93.md +15 -0
- package/RELEASE_NOTES_V7.9.93.md +10 -0
- package/adminWww/assets/Adapters-B5_jQ7DE-v84.js +1 -1
- package/adminWww/assets/Instances-YdaGnS5a-v84.js +1 -1
- package/adminWww/css/nexowatt-native-shell.css +304 -13
- package/adminWww/index.html +11 -11
- package/adminWww/js/eos-account-management.js +1 -1
- package/adminWww/js/eos-assistant.js +487 -55
- package/adminWww/js/eos-basic-settings.js +1 -1
- package/adminWww/js/eos-branding-sanitizer.js +3 -1
- package/adminWww/js/eos-native-security.js +1 -1
- package/adminWww/js/eos-policy-client.js +1 -1
- package/adminWww/js/eos-role-bootstrap.js +180 -46
- package/adminWww/js/eos-role-ui.js +156 -23
- package/adminWww/js/nexowatt-native-shell.js +1 -1
- package/build/lib/chat/assistantKnowledge.js +21 -6
- package/build/lib/chat/chatOrchestrator.js +33 -38
- package/build/lib/chat/mcpClientManager.js +1 -1
- package/build/lib/web.js +39 -0
- package/build/main.js +307 -1
- package/io-package.json +23 -8
- package/package.json +21 -13
- package/tools/nexowatt-account-management-selftest.cjs +1 -1
- package/tools/nexowatt-ensure-release-defaults.cjs +72 -0
- package/tools/nexowatt-internal-reserve-selftest.cjs +4 -1
- package/tools/nexowatt-login-layout-selftest.cjs +62 -0
- package/tools/nexowatt-modern-ui-selftest.cjs +7 -5
- package/tools/nexowatt-release-defaults-selftest.cjs +29 -0
- package/tools/nexowatt-validate-package.cjs +18 -8
- package/INSTALL_TEST_V7.9.89.md +0 -73
- package/PUBLISH_STABLE_V7.9.89.md +0 -33
- package/README_STABILITY_V7.9.89.md +0 -26
- package/RELEASE_ACCEPTANCE_V7.9.89.md +0 -40
- package/admin/img/eos/nexowatt-192-maskable.png +0 -0
- package/admin/img/eos/nexowatt-24.png +0 -0
- package/admin/img/eos/nexowatt-512-maskable.png +0 -0
- package/admin/img/eos/nexowatt-eos-logo.png +0 -0
- package/adminWww/admin.png +0 -0
- package/adminWww/img/eos/nexowatt-24.png +0 -0
- package/adminWww/img/eos/nexowatt-eos-logo.png +0 -0
- package/adminWww/img/eos/nexowatt-header-128.png +0 -0
- package/adminWww/img/eos/nexowatt-header-192.png +0 -0
- package/adminWww/img/eos/nexowatt-header-256.png +0 -0
- package/adminWww/img/eos/nexowatt-header-512.png +0 -0
- package/adminWww/img/eos/nexowatt-header-64.png +0 -0
- package/adminWww/img/logo192.png +0 -0
- package/adminWww/js/eos-adapter-surface-cleanup.js +0 -141
- package/adminWww/js/eos-toolbar-ghost-cleanup.js +0 -102
- package/docs/NEXOWATT_EOS_BRANDING_DE.md +0 -29
- package/docs/NEXOWATT_EOS_UI_LOGO_LOGIN_V6_DE.md +0 -9
- package/docs/NEXOWATT_EOS_UI_PERFORMANCE_V5_DE.md +0 -25
- package/docs/NEXOWATT_EOS_UI_V12_HEADER_LOGO_NAV_CLEANUP_DE.md +0 -16
- package/docs/NEXOWATT_EOS_UI_V12_HEADER_NAV_LOGO_FIX_DE.md +0 -16
- package/docs/NEXOWATT_EOS_UI_V15_DE.md +0 -8
- package/docs/NEXOWATT_EOS_UI_V16_DE.md +0 -9
- package/docs/NEXOWATT_EOS_UI_V17_DE.md +0 -24
- package/docs/NEXOWATT_EOS_UI_V6_FIXES_DE.md +0 -13
- package/docs/NEXOWATT_EOS_UI_V6_LOGIN_LOGO_404_FIX_DE.md +0 -9
- package/docs/NEXOWATT_EOS_UI_V6_LOGO_LOGIN_FIX_DE.md +0 -10
- package/docs/NEXOWATT_EOS_UI_V7_RESTORE_LOGO_SCOPE_FIX_DE.md +0 -18
|
@@ -1,42 +1,410 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v93-header-eos-assist-live-system';
|
|
5
5
|
const previous = window.NEXOWATT_EOS_ASSIST;
|
|
6
6
|
if (previous?.version === VERSION) return;
|
|
7
7
|
previous?.destroy?.();
|
|
8
8
|
|
|
9
|
-
const scriptUrl =
|
|
9
|
+
const scriptUrl =
|
|
10
|
+
document.currentScript?.src || document.querySelector('script[src*="eos-assistant.js"]')?.src || location.href;
|
|
10
11
|
const logoUrl = new URL('../img/eos/nexowatt-192.png', scriptUrl).href;
|
|
11
12
|
const abort = new AbortController();
|
|
12
13
|
let unsubscribeDom = null;
|
|
13
|
-
let
|
|
14
|
+
let reconnectTimer = 0;
|
|
14
15
|
let destroyed = false;
|
|
16
|
+
let socket = null;
|
|
17
|
+
let status = null;
|
|
18
|
+
let settings = {
|
|
19
|
+
provider: 'anthropic',
|
|
20
|
+
model: '',
|
|
21
|
+
credentialId: '',
|
|
22
|
+
baseUrl: '',
|
|
23
|
+
allowSelfSignedCerts: false,
|
|
24
|
+
};
|
|
25
|
+
let credentials = [];
|
|
26
|
+
let apiMessages = [];
|
|
27
|
+
let historyKey = 'NexoWatt.EOS.Assist.anonymous';
|
|
28
|
+
let loading = false;
|
|
15
29
|
|
|
16
30
|
const currentContext = () => {
|
|
17
31
|
const hash = String(location.hash || '').toLowerCase();
|
|
18
|
-
if (hash.includes('tab-adapters'))
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (hash.includes('tab-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
if (hash.includes('tab-adapters')) {
|
|
33
|
+
return ['Module', 'Fragen zu installierten Modulen, Versionen, Kompatibilität und Einrichtung.'];
|
|
34
|
+
}
|
|
35
|
+
if (hash.includes('tab-instances')) {
|
|
36
|
+
return ['Dienste', 'Fragen zu laufenden Diensten, Status, Einstellungen, Speicherverbrauch und Fehlern.'];
|
|
37
|
+
}
|
|
38
|
+
if (hash.includes('tab-objects')) {
|
|
39
|
+
return ['Datenpunkte', 'Fragen zu Messwerten, Metadaten, Schreibfreigaben und aktuellen Zuständen.'];
|
|
40
|
+
}
|
|
41
|
+
if (hash.includes('tab-enums')) {
|
|
42
|
+
return ['Struktur', 'Fragen zu Räumen, Funktionen, Smart-Home-Zuordnungen und Geräten.'];
|
|
43
|
+
}
|
|
44
|
+
if (hash.includes('tab-logs')) {
|
|
45
|
+
return ['Systemlogs', 'Fragen zu aktuellen Meldungen, Verbindungen und Fehlerursachen.'];
|
|
46
|
+
}
|
|
47
|
+
if (hash.includes('tab-users')) {
|
|
48
|
+
return ['Zugänge & Rechte', 'Fragen zu EOS-Rollen, Zugängen und erlaubten Funktionen.'];
|
|
49
|
+
}
|
|
50
|
+
if (hash.includes('nexowatt-backup') || hash.includes('tab-backup')) {
|
|
51
|
+
return ['NexoWatt Backup', 'Fragen zu Sicherungsstatus, Sicherungszielen und Wiederherstellung.'];
|
|
52
|
+
}
|
|
53
|
+
if (hash.includes('tab-intro')) {
|
|
54
|
+
return ['Übersicht', 'Fragen zum Anlagenstatus und zu allen freigegebenen EOS-Bereichen.'];
|
|
55
|
+
}
|
|
56
|
+
return ['EOS Assist', 'Fragen zum laufenden System, Einstellungen, Modulen, Geräten und Datenpunkten.'];
|
|
24
57
|
};
|
|
25
58
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
const findToolbar = () =>
|
|
60
|
+
document.querySelector(
|
|
61
|
+
'.eos-top-toolbar, #root > .MuiPaper-root > .MuiAppBar-root .MuiToolbar-root, .MuiAppBar-root .MuiToolbar-root',
|
|
62
|
+
);
|
|
63
|
+
const findUserAnchor = toolbar =>
|
|
64
|
+
Array.from(toolbar?.children || []).find(
|
|
65
|
+
child => child.querySelector?.('.MuiAvatar-root') || /\b(admin|installer|guest|endkunde|service)\b/i.test(child.textContent || ''),
|
|
66
|
+
) || null;
|
|
67
|
+
|
|
68
|
+
const socketPath = () => {
|
|
69
|
+
let parts = location.pathname.split('/');
|
|
70
|
+
parts.pop();
|
|
71
|
+
if (location.pathname.match(/^\/admin\//)) parts = [];
|
|
72
|
+
const value = `${parts.join('/')}/socket.io`.replace(/\/{2,}/g, '/');
|
|
73
|
+
return value.startsWith('/') ? value : `/${value}`;
|
|
34
74
|
};
|
|
35
75
|
|
|
36
|
-
const
|
|
37
|
-
|
|
76
|
+
const emit = (event, payload = {}, timeoutMs = 30_000) =>
|
|
77
|
+
new Promise((resolve, reject) => {
|
|
78
|
+
if (!socket?.connected) {
|
|
79
|
+
reject(new Error('Keine Verbindung zum EOS-System.'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
let finished = false;
|
|
83
|
+
const timer = window.setTimeout(() => {
|
|
84
|
+
if (finished) return;
|
|
85
|
+
finished = true;
|
|
86
|
+
reject(new Error('Zeitüberschreitung bei der EOS-Anfrage.'));
|
|
87
|
+
}, timeoutMs);
|
|
88
|
+
socket.emit(event, payload, response => {
|
|
89
|
+
if (finished) return;
|
|
90
|
+
finished = true;
|
|
91
|
+
window.clearTimeout(timer);
|
|
92
|
+
resolve(response || {});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
38
95
|
|
|
39
96
|
const remove = () => document.getElementById('eos-assist-root')?.remove();
|
|
97
|
+
const escapeRole = role => (role === 'admin' ? 'Admin / Service' : role === 'installer' ? 'Installateur' : 'Endkunde');
|
|
98
|
+
|
|
99
|
+
const persistHistory = () => {
|
|
100
|
+
try {
|
|
101
|
+
sessionStorage.setItem(historyKey, JSON.stringify({ apiMessages: apiMessages.slice(-24) }));
|
|
102
|
+
} catch {
|
|
103
|
+
// A conversation must still work if session storage is unavailable.
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const loadHistory = () => {
|
|
108
|
+
apiMessages = [];
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(sessionStorage.getItem(historyKey) || '{}');
|
|
111
|
+
if (Array.isArray(parsed.apiMessages)) {
|
|
112
|
+
apiMessages = parsed.apiMessages
|
|
113
|
+
.filter(
|
|
114
|
+
message =>
|
|
115
|
+
(message?.role === 'user' || message?.role === 'assistant') &&
|
|
116
|
+
typeof message.content === 'string',
|
|
117
|
+
)
|
|
118
|
+
.slice(-24);
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
apiMessages = [];
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const appendLinkAwareText = (container, text) => {
|
|
126
|
+
const source = String(text || '');
|
|
127
|
+
const regex = /\[([^\]]+)\]\((#[^)]+)\)/g;
|
|
128
|
+
let cursor = 0;
|
|
129
|
+
let match;
|
|
130
|
+
while ((match = regex.exec(source))) {
|
|
131
|
+
if (match.index > cursor) container.append(document.createTextNode(source.substring(cursor, match.index)));
|
|
132
|
+
const link = document.createElement('a');
|
|
133
|
+
link.href = match[2];
|
|
134
|
+
link.textContent = match[1];
|
|
135
|
+
link.addEventListener(
|
|
136
|
+
'click',
|
|
137
|
+
event => {
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
location.hash = match[2];
|
|
140
|
+
},
|
|
141
|
+
{ signal: abort.signal },
|
|
142
|
+
);
|
|
143
|
+
container.append(link);
|
|
144
|
+
cursor = regex.lastIndex;
|
|
145
|
+
}
|
|
146
|
+
if (cursor < source.length) container.append(document.createTextNode(source.substring(cursor)));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const appendMessage = (role, text, extraClass = '') => {
|
|
150
|
+
const root = document.getElementById('eos-assist-root');
|
|
151
|
+
const transcript = root?.querySelector('.eos-assist-transcript');
|
|
152
|
+
if (!transcript) return;
|
|
153
|
+
transcript.querySelector('.eos-assist-welcome')?.remove();
|
|
154
|
+
const article = document.createElement('article');
|
|
155
|
+
article.className = `eos-assist-message eos-assist-message-${role}${extraClass ? ` ${extraClass}` : ''}`;
|
|
156
|
+
const label = document.createElement('strong');
|
|
157
|
+
label.textContent = role === 'user' ? 'Du' : role === 'error' ? 'Hinweis' : 'EOS Assist';
|
|
158
|
+
const body = document.createElement('div');
|
|
159
|
+
appendLinkAwareText(body, text);
|
|
160
|
+
article.append(label, body);
|
|
161
|
+
transcript.append(article);
|
|
162
|
+
transcript.scrollTop = transcript.scrollHeight;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const renderHistory = () => {
|
|
166
|
+
const root = document.getElementById('eos-assist-root');
|
|
167
|
+
const transcript = root?.querySelector('.eos-assist-transcript');
|
|
168
|
+
if (!transcript) return;
|
|
169
|
+
transcript.innerHTML = '';
|
|
170
|
+
if (!apiMessages.length) {
|
|
171
|
+
const welcome = document.createElement('div');
|
|
172
|
+
welcome.className = 'eos-assist-welcome';
|
|
173
|
+
welcome.textContent =
|
|
174
|
+
'Frage nach dem aktuellen Anlagenstatus, einer Einstellung, einem Modul, Dienst, Gerät, Datenpunkt oder Fehler.';
|
|
175
|
+
transcript.append(welcome);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const message of apiMessages) appendMessage(message.role, message.content);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const setConnectionText = (text, kind = '') => {
|
|
182
|
+
const element = document.querySelector('#eos-assist-root .eos-assist-status');
|
|
183
|
+
if (!element) return;
|
|
184
|
+
element.textContent = text;
|
|
185
|
+
element.dataset.kind = kind;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const updateStatusUi = () => {
|
|
189
|
+
const root = document.getElementById('eos-assist-root');
|
|
190
|
+
if (!root) return;
|
|
191
|
+
const configure = root.querySelector('.eos-assist-configure');
|
|
192
|
+
configure?.classList.toggle('eos-assist-config-hidden', !status?.canConfigure);
|
|
193
|
+
root.classList.toggle('eos-assist-not-configured', status?.configured === false);
|
|
194
|
+
const role = status?.role ? escapeRole(status.role) : '';
|
|
195
|
+
if (!socket?.connected) setConnectionText('Verbindung wird hergestellt …', 'waiting');
|
|
196
|
+
else if (!status) setConnectionText('Systemstatus wird geladen …', 'waiting');
|
|
197
|
+
else if (!status.configured) {
|
|
198
|
+
setConnectionText(
|
|
199
|
+
status.canConfigure
|
|
200
|
+
? 'EOS Assist muss einmalig konfiguriert werden.'
|
|
201
|
+
: 'EOS Assist wurde noch nicht durch Admin / Service konfiguriert.',
|
|
202
|
+
'warning',
|
|
203
|
+
);
|
|
204
|
+
} else setConnectionText(`Verbunden · ${role} · schreibgeschützt`, 'ready');
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const fillSettingsForm = () => {
|
|
208
|
+
const root = document.getElementById('eos-assist-root');
|
|
209
|
+
if (!root) return;
|
|
210
|
+
const provider = root.querySelector('.eos-assist-provider');
|
|
211
|
+
const credential = root.querySelector('.eos-assist-credential');
|
|
212
|
+
const model = root.querySelector('.eos-assist-model');
|
|
213
|
+
const baseUrl = root.querySelector('.eos-assist-base-url');
|
|
214
|
+
const selfSigned = root.querySelector('.eos-assist-self-signed');
|
|
215
|
+
if (provider) provider.value = settings.provider || 'anthropic';
|
|
216
|
+
if (credential) {
|
|
217
|
+
credential.innerHTML = '<option value="">Kein Zugang ausgewählt</option>';
|
|
218
|
+
for (const entry of credentials) {
|
|
219
|
+
const option = document.createElement('option');
|
|
220
|
+
option.value = entry.id || '';
|
|
221
|
+
option.textContent = entry.name || entry.id || 'KI-Zugang';
|
|
222
|
+
credential.append(option);
|
|
223
|
+
}
|
|
224
|
+
credential.value = settings.credentialId || '';
|
|
225
|
+
}
|
|
226
|
+
if (model) model.value = settings.model || '';
|
|
227
|
+
if (baseUrl) baseUrl.value = settings.baseUrl || '';
|
|
228
|
+
if (selfSigned) selfSigned.checked = settings.allowSelfSignedCerts === true;
|
|
229
|
+
root.classList.toggle('eos-assist-custom-provider', provider?.value === 'custom');
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const readSettingsForm = () => {
|
|
233
|
+
const root = document.getElementById('eos-assist-root');
|
|
234
|
+
return {
|
|
235
|
+
provider: root?.querySelector('.eos-assist-provider')?.value || 'anthropic',
|
|
236
|
+
credentialId: root?.querySelector('.eos-assist-credential')?.value || '',
|
|
237
|
+
model: root?.querySelector('.eos-assist-model')?.value?.trim() || '',
|
|
238
|
+
baseUrl: root?.querySelector('.eos-assist-base-url')?.value?.trim() || '',
|
|
239
|
+
allowSelfSignedCerts: root?.querySelector('.eos-assist-self-signed')?.checked === true,
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const loadStatus = async () => {
|
|
244
|
+
try {
|
|
245
|
+
const response = await emit('eosAssistStatus');
|
|
246
|
+
if (response.error) throw new Error(response.error);
|
|
247
|
+
status = response;
|
|
248
|
+
settings = { ...settings, ...(response.settings || {}) };
|
|
249
|
+
credentials = Array.isArray(response.credentials) ? response.credentials : [];
|
|
250
|
+
const nextKey = `NexoWatt.EOS.Assist.${response.user || 'anonymous'}`;
|
|
251
|
+
if (historyKey !== nextKey) {
|
|
252
|
+
historyKey = nextKey;
|
|
253
|
+
loadHistory();
|
|
254
|
+
renderHistory();
|
|
255
|
+
}
|
|
256
|
+
fillSettingsForm();
|
|
257
|
+
updateStatusUi();
|
|
258
|
+
} catch (error) {
|
|
259
|
+
status = null;
|
|
260
|
+
setConnectionText(error instanceof Error ? error.message : String(error), 'error');
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const connectSocket = () => {
|
|
265
|
+
if (destroyed || socket?.connected) return;
|
|
266
|
+
if (!window.io?.connect) {
|
|
267
|
+
reconnectTimer = window.setTimeout(connectSocket, 250);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
socket = window.io.connect('/', { path: socketPath() });
|
|
271
|
+
socket.on('connect', () => {
|
|
272
|
+
setConnectionText('Verbunden. Systemstatus wird geladen …', 'waiting');
|
|
273
|
+
void loadStatus();
|
|
274
|
+
});
|
|
275
|
+
socket.on('disconnect', () => {
|
|
276
|
+
status = null;
|
|
277
|
+
updateStatusUi();
|
|
278
|
+
});
|
|
279
|
+
socket.on('reauthenticate', () => {
|
|
280
|
+
setConnectionText('Die Sitzung ist abgelaufen. Bitte erneut anmelden.', 'error');
|
|
281
|
+
});
|
|
282
|
+
socket.on('connect_error', error => {
|
|
283
|
+
setConnectionText(`Verbindungsfehler: ${error?.message || error}`, 'error');
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const setBusy = busy => {
|
|
288
|
+
loading = busy;
|
|
289
|
+
const root = document.getElementById('eos-assist-root');
|
|
290
|
+
const input = root?.querySelector('.eos-assist-input');
|
|
291
|
+
const send = root?.querySelector('.eos-assist-send');
|
|
292
|
+
if (input) input.disabled = busy;
|
|
293
|
+
if (send) {
|
|
294
|
+
send.disabled = busy;
|
|
295
|
+
send.textContent = busy ? 'Prüft …' : 'Fragen';
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const sendQuestion = async () => {
|
|
300
|
+
const root = document.getElementById('eos-assist-root');
|
|
301
|
+
const input = root?.querySelector('.eos-assist-input');
|
|
302
|
+
const text = String(input?.value || '').trim();
|
|
303
|
+
if (!text || loading) return;
|
|
304
|
+
if (!status?.configured) {
|
|
305
|
+
appendMessage(
|
|
306
|
+
'error',
|
|
307
|
+
status?.canConfigure
|
|
308
|
+
? 'Öffne die Einstellungen von EOS Assist und hinterlege Anbieter, Zugang und Modell.'
|
|
309
|
+
: 'EOS Assist muss zuerst durch Admin / NexoWatt Service konfiguriert werden.',
|
|
310
|
+
);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
apiMessages.push({ role: 'user', content: text });
|
|
314
|
+
apiMessages = apiMessages.slice(-24);
|
|
315
|
+
persistHistory();
|
|
316
|
+
appendMessage('user', text);
|
|
317
|
+
input.value = '';
|
|
318
|
+
setBusy(true);
|
|
319
|
+
setConnectionText('EOS Assist prüft das aktuelle System und die freigegebenen Daten …', 'waiting');
|
|
320
|
+
try {
|
|
321
|
+
const response = await emit(
|
|
322
|
+
'eosAssist',
|
|
323
|
+
{ messages: apiMessages, uiContext: { hash: location.hash } },
|
|
324
|
+
620_000,
|
|
325
|
+
);
|
|
326
|
+
if (response.error) {
|
|
327
|
+
appendMessage('error', response.error);
|
|
328
|
+
if (response.code === 'notConfigured') {
|
|
329
|
+
status = { ...(status || {}), configured: false, canConfigure: response.canConfigure === true };
|
|
330
|
+
}
|
|
331
|
+
updateStatusUi();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const answer = String(response.content || 'Es wurde keine Antwort erzeugt.');
|
|
335
|
+
apiMessages.push({ role: 'assistant', content: answer });
|
|
336
|
+
apiMessages = apiMessages.slice(-24);
|
|
337
|
+
persistHistory();
|
|
338
|
+
appendMessage('assistant', answer);
|
|
339
|
+
for (const action of response.clientActions || []) {
|
|
340
|
+
if (action?.type === 'navigate' && typeof action.hash === 'string') {
|
|
341
|
+
location.hash = action.hash.startsWith('#') ? action.hash : `#${action.hash}`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
setConnectionText(`Antwort auf Basis des aktuellen EOS-Systems · ${escapeRole(status.role)}`, 'ready');
|
|
345
|
+
} catch (error) {
|
|
346
|
+
appendMessage('error', error instanceof Error ? error.message : String(error));
|
|
347
|
+
setConnectionText('Die Anfrage konnte nicht abgeschlossen werden.', 'error');
|
|
348
|
+
} finally {
|
|
349
|
+
setBusy(false);
|
|
350
|
+
input?.focus();
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const openSettings = open => {
|
|
355
|
+
const root = document.getElementById('eos-assist-root');
|
|
356
|
+
if (!root || !status?.canConfigure) return;
|
|
357
|
+
root.classList.toggle('eos-assist-settings-open', open);
|
|
358
|
+
fillSettingsForm();
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const testSettings = async () => {
|
|
362
|
+
const payload = readSettingsForm();
|
|
363
|
+
setConnectionText('Verbindung und verfügbare Modelle werden geprüft …', 'waiting');
|
|
364
|
+
try {
|
|
365
|
+
const response = await emit('eosAssistTestSettings', payload, 30_000);
|
|
366
|
+
if (response.error) throw new Error(response.error);
|
|
367
|
+
const models = Array.isArray(response.models) ? response.models : [];
|
|
368
|
+
const list = document.querySelector('#eos-assist-root .eos-assist-model-list');
|
|
369
|
+
if (list) {
|
|
370
|
+
list.innerHTML = '';
|
|
371
|
+
for (const id of models) {
|
|
372
|
+
const option = document.createElement('option');
|
|
373
|
+
option.value = id;
|
|
374
|
+
list.append(option);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const model = document.querySelector('#eos-assist-root .eos-assist-model');
|
|
378
|
+
if (model && !model.value && models[0]) model.value = models[0];
|
|
379
|
+
setConnectionText(`${models.length} Modell(e) gefunden.`, 'ready');
|
|
380
|
+
} catch (error) {
|
|
381
|
+
setConnectionText(error instanceof Error ? error.message : String(error), 'error');
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const saveSettings = async () => {
|
|
386
|
+
const payload = readSettingsForm();
|
|
387
|
+
setConnectionText('EOS-Assist-Einstellungen werden gespeichert …', 'waiting');
|
|
388
|
+
try {
|
|
389
|
+
const response = await emit('eosAssistSaveSettings', payload, 30_000);
|
|
390
|
+
if (response.error) throw new Error(response.error);
|
|
391
|
+
settings = { ...settings, ...(response.settings || payload) };
|
|
392
|
+
status = { ...(status || {}), configured: response.configured === true };
|
|
393
|
+
openSettings(false);
|
|
394
|
+
updateStatusUi();
|
|
395
|
+
setConnectionText('EOS Assist ist konfiguriert und schreibgeschützt bereit.', 'ready');
|
|
396
|
+
} catch (error) {
|
|
397
|
+
setConnectionText(error instanceof Error ? error.message : String(error), 'error');
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const newConversation = () => {
|
|
402
|
+
apiMessages = [];
|
|
403
|
+
persistHistory();
|
|
404
|
+
renderHistory();
|
|
405
|
+
document.querySelector('#eos-assist-root .eos-assist-input')?.focus();
|
|
406
|
+
};
|
|
407
|
+
|
|
40
408
|
const ensure = () => {
|
|
41
409
|
if (destroyed) return;
|
|
42
410
|
const hasApp = !!document.getElementById('app-paper');
|
|
@@ -54,40 +422,101 @@
|
|
|
54
422
|
root.className = 'eos-assist-root eos-assist-header-root';
|
|
55
423
|
root.innerHTML = `
|
|
56
424
|
<button class="eos-assist-button" type="button" aria-expanded="false" title="EOS Assist">
|
|
57
|
-
<span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>
|
|
425
|
+
<span class="eos-assist-dot"></span><strong>EOS Assist</strong><small>Systemhilfe</small>
|
|
58
426
|
</button>
|
|
59
427
|
<div class="eos-assist-panel" role="dialog" aria-label="EOS Assist">
|
|
60
|
-
<div class="eos-assist-head"
|
|
61
|
-
|
|
62
|
-
|
|
428
|
+
<div class="eos-assist-head">
|
|
429
|
+
<img src="${logoUrl}" alt="NexoWatt EOS">
|
|
430
|
+
<div><strong class="eos-assist-title">EOS Assist</strong><span class="eos-assist-text"></span></div>
|
|
431
|
+
<button class="eos-assist-close" type="button" aria-label="Schließen">×</button>
|
|
432
|
+
</div>
|
|
433
|
+
<div class="eos-assist-toolbar">
|
|
434
|
+
<button class="eos-assist-new" type="button">Neue Unterhaltung</button>
|
|
435
|
+
<button class="eos-assist-configure eos-assist-config-hidden" type="button">Einstellungen</button>
|
|
436
|
+
</div>
|
|
437
|
+
<div class="eos-assist-conversation">
|
|
438
|
+
<div class="eos-assist-transcript" aria-live="polite"></div>
|
|
439
|
+
<div class="eos-assist-input-row">
|
|
440
|
+
<textarea class="eos-assist-input" rows="2" maxlength="4000" placeholder="Frage zum System, einer Einstellung, einem Modul oder Datenpunkt"></textarea>
|
|
441
|
+
<button class="eos-assist-send" type="button">Fragen</button>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="eos-assist-settings" aria-label="EOS Assist Einstellungen">
|
|
445
|
+
<label>Anbieter<select class="eos-assist-provider">
|
|
446
|
+
<option value="anthropic">Anthropic</option>
|
|
447
|
+
<option value="openai">OpenAI</option>
|
|
448
|
+
<option value="gemini">Gemini</option>
|
|
449
|
+
<option value="deepseek">DeepSeek</option>
|
|
450
|
+
<option value="custom">Lokal / individueller Anbieter</option>
|
|
451
|
+
</select></label>
|
|
452
|
+
<label>Gespeicherter KI-Zugang<select class="eos-assist-credential"></select></label>
|
|
453
|
+
<label class="eos-assist-base-url-label">Basis-URL<input class="eos-assist-base-url" placeholder="http://127.0.0.1:11434/v1"></label>
|
|
454
|
+
<label>Modell<input class="eos-assist-model" list="eos-assist-model-list" placeholder="Modellbezeichnung"><datalist id="eos-assist-model-list" class="eos-assist-model-list"></datalist></label>
|
|
455
|
+
<label class="eos-assist-check"><input class="eos-assist-self-signed" type="checkbox"> Selbstsigniertes Zertifikat zulassen</label>
|
|
456
|
+
<div class="eos-assist-settings-actions">
|
|
457
|
+
<button class="eos-assist-settings-back" type="button">Zurück</button>
|
|
458
|
+
<button class="eos-assist-test" type="button">Verbindung testen</button>
|
|
459
|
+
<button class="eos-assist-save" type="button">Speichern</button>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="eos-assist-status" data-kind="waiting">Verbindung wird hergestellt …</div>
|
|
63
463
|
</div>`;
|
|
64
464
|
const userAnchor = findUserAnchor(toolbar);
|
|
65
465
|
toolbar.insertBefore(root, userAnchor);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
button.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
466
|
+
renderHistory();
|
|
467
|
+
root.addEventListener(
|
|
468
|
+
'click',
|
|
469
|
+
event => {
|
|
470
|
+
const button = event.target?.closest?.('button');
|
|
471
|
+
if (!button) return;
|
|
472
|
+
if (button.classList.contains('eos-assist-button')) {
|
|
473
|
+
const open = !root.classList.contains('eos-assist-open');
|
|
474
|
+
root.classList.toggle('eos-assist-open', open);
|
|
475
|
+
button.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
476
|
+
if (open) {
|
|
477
|
+
void loadStatus();
|
|
478
|
+
window.setTimeout(() => root.querySelector('.eos-assist-input')?.focus(), 30);
|
|
479
|
+
}
|
|
480
|
+
} else if (button.classList.contains('eos-assist-close')) {
|
|
481
|
+
root.classList.remove('eos-assist-open', 'eos-assist-settings-open');
|
|
482
|
+
root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
|
|
483
|
+
} else if (button.classList.contains('eos-assist-send')) void sendQuestion();
|
|
484
|
+
else if (button.classList.contains('eos-assist-new')) newConversation();
|
|
485
|
+
else if (button.classList.contains('eos-assist-configure')) openSettings(true);
|
|
486
|
+
else if (button.classList.contains('eos-assist-settings-back')) openSettings(false);
|
|
487
|
+
else if (button.classList.contains('eos-assist-test')) void testSettings();
|
|
488
|
+
else if (button.classList.contains('eos-assist-save')) void saveSettings();
|
|
489
|
+
},
|
|
490
|
+
{ signal: abort.signal },
|
|
491
|
+
);
|
|
492
|
+
root.addEventListener(
|
|
493
|
+
'keydown',
|
|
494
|
+
event => {
|
|
495
|
+
if (
|
|
496
|
+
event.key === 'Enter' &&
|
|
497
|
+
!event.shiftKey &&
|
|
498
|
+
event.target?.classList?.contains('eos-assist-input')
|
|
499
|
+
) {
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
void sendQuestion();
|
|
502
|
+
}
|
|
503
|
+
if (event.key === 'Escape') {
|
|
504
|
+
if (root.classList.contains('eos-assist-settings-open')) openSettings(false);
|
|
505
|
+
else {
|
|
506
|
+
root.classList.remove('eos-assist-open');
|
|
507
|
+
root.querySelector('.eos-assist-button')?.setAttribute('aria-expanded', 'false');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
{ signal: abort.signal },
|
|
512
|
+
);
|
|
513
|
+
root.querySelector('.eos-assist-provider')?.addEventListener(
|
|
514
|
+
'change',
|
|
515
|
+
event => root.classList.toggle('eos-assist-custom-provider', event.target.value === 'custom'),
|
|
516
|
+
{ signal: abort.signal },
|
|
517
|
+
);
|
|
518
|
+
updateStatusUi();
|
|
519
|
+
connectSocket();
|
|
91
520
|
} else if (root.parentElement !== toolbar) {
|
|
92
521
|
const userAnchor = findUserAnchor(toolbar);
|
|
93
522
|
toolbar.insertBefore(root, userAnchor);
|
|
@@ -97,10 +526,10 @@
|
|
|
97
526
|
root.querySelector('.eos-assist-text').textContent = text;
|
|
98
527
|
};
|
|
99
528
|
|
|
100
|
-
const
|
|
529
|
+
const connectDom = () => {
|
|
101
530
|
const coordinator = window.NEXOWATT_EOS_DOM_COORDINATOR;
|
|
102
531
|
if (!coordinator?.subscribe) {
|
|
103
|
-
|
|
532
|
+
reconnectTimer = window.setTimeout(connectDom, 250);
|
|
104
533
|
return;
|
|
105
534
|
}
|
|
106
535
|
unsubscribeDom = coordinator.subscribe(ensure);
|
|
@@ -108,10 +537,11 @@
|
|
|
108
537
|
|
|
109
538
|
window.addEventListener('hashchange', ensure, { signal: abort.signal });
|
|
110
539
|
window.addEventListener('resize', ensure, { signal: abort.signal });
|
|
111
|
-
if (document.readyState === 'loading')
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
540
|
+
if (document.readyState === 'loading') {
|
|
541
|
+
document.addEventListener('DOMContentLoaded', ensure, { once: true, signal: abort.signal });
|
|
542
|
+
} else ensure();
|
|
543
|
+
connectDom();
|
|
544
|
+
[250, 800, 1600].forEach(delay => window.setTimeout(ensure, delay));
|
|
115
545
|
|
|
116
546
|
window.NEXOWATT_EOS_ASSIST = {
|
|
117
547
|
version: VERSION,
|
|
@@ -120,7 +550,9 @@
|
|
|
120
550
|
destroyed = true;
|
|
121
551
|
abort.abort();
|
|
122
552
|
unsubscribeDom?.();
|
|
123
|
-
if (
|
|
553
|
+
if (reconnectTimer) window.clearTimeout(reconnectTimer);
|
|
554
|
+
socket?.disconnect?.();
|
|
555
|
+
socket = null;
|
|
124
556
|
remove();
|
|
125
557
|
},
|
|
126
558
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
|
-
const VERSION = '
|
|
3
|
+
const VERSION = 'v93-visible-branding-publisher';
|
|
4
4
|
const existing = window.NEXOWATT_EOS_BRANDING_SANITIZER;
|
|
5
5
|
if (existing?.version === VERSION) return;
|
|
6
6
|
existing?.destroy?.();
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
let unsubscribe = null;
|
|
9
9
|
const skip = new Set(['SCRIPT','STYLE','CODE','PRE','TEXTAREA','NOSCRIPT']);
|
|
10
10
|
const rewrite = value => String(value || '')
|
|
11
|
+
.replace(/\[\s*iobroker\.eos-admin@[^\]\r\n]+\]/gi, '[NexoWatt]')
|
|
12
|
+
.replace(/\[\s*eos-admin@[^\]\r\n]+\]/gi, '[NexoWatt]')
|
|
11
13
|
.replace(/ioBroker\.admin/gi, 'NexoWatt EOS Admin')
|
|
12
14
|
.replace(/ioBroker\s*Admin/gi, 'NexoWatt EOS Admin')
|
|
13
15
|
.replace(/ioBroker/gi, 'NexoWatt EOS');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const VERSION = '
|
|
4
|
+
const VERSION = 'v93-product-role-access';
|
|
5
5
|
const protectedAdapters = new Set([
|
|
6
6
|
'admin', 'eos-admin', 'backitup', 'nexowatt-devices', 'nexowatt-device', 'nexowatt-dev', 'nexowatt-ui',
|
|
7
7
|
]);
|