node-red-contrib-knx-ultimate 4.3.0 → 4.3.2
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/CHANGELOG.md +12 -0
- package/nodes/knxUltimate.js +1 -1
- package/nodes/knxUltimateAI.html +215 -18
- package/nodes/knxUltimateAI.js +289 -70
- package/nodes/knxUltimateHueLight.js +807 -810
- package/nodes/knxUltimateLogger.js +3 -4
- package/nodes/knxUltimateMultiRouting.js +0 -1
- package/nodes/locales/de/knxUltimateAI.html +11 -0
- package/nodes/locales/de/knxUltimateAI.json +13 -2
- package/nodes/locales/en/knxUltimateAI.html +11 -0
- package/nodes/locales/en/knxUltimateAI.json +12 -3
- package/nodes/locales/es/knxUltimateAI.html +11 -0
- package/nodes/locales/es/knxUltimateAI.json +13 -2
- package/nodes/locales/fr/knxUltimateAI.html +11 -0
- package/nodes/locales/fr/knxUltimateAI.json +13 -2
- package/nodes/locales/it/knxUltimateAI.html +11 -0
- package/nodes/locales/it/knxUltimateAI.json +12 -3
- package/nodes/locales/zh-CN/knxUltimateAI.html +11 -0
- package/nodes/locales/zh-CN/knxUltimateAI.json +13 -2
- package/nodes/plugins/knxUltimateAI-vue/assets/app.css +1 -1
- package/nodes/plugins/knxUltimateAI-vue/assets/app.js +3 -3
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 4.3.2** - April 2026<br/>
|
|
10
|
+
|
|
11
|
+
- Docs/help/wiki: updated **KNX AI** help HTML and wiki pages in all supported languages (EN/IT/DE/FR/ES/zh-CN) to reflect the latest LLM/Ollama UX changes.<br/>
|
|
12
|
+
- Docs/help/wiki: documented the new **Ollama quick setup** flow (**Download model** -> **Install it**).<br/>
|
|
13
|
+
- Docs/help/wiki: clarified that the **LLM Assistant** tab is shown first in the KNX AI editor for faster setup.<br/>
|
|
14
|
+
|
|
15
|
+
**Version 4.3.1** - April 2026<br/>
|
|
16
|
+
|
|
17
|
+
- Bumped KNXEngine to 5.5.2<br/>
|
|
18
|
+
- UI: **KNX AI Web** now always opens on the first menu item (**Overview**).<br/>
|
|
19
|
+
- FIX: **KNX AI Web Assistant** preset prompts now send the exact current button text to the AI request, so localized UI labels are used directly (no forced English fallback).<br/>
|
|
20
|
+
|
|
9
21
|
**Version 4.3.0** - April 2026<br/>
|
|
10
22
|
|
|
11
23
|
- Bumped KNXEngine to 5.5.1<br/>
|
package/nodes/knxUltimate.js
CHANGED
package/nodes/knxUltimateAI.html
CHANGED
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
<script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
|
|
2
|
+
<style type="text/css">
|
|
3
|
+
#knx-ai-models-status {
|
|
4
|
+
margin-top: 6px;
|
|
5
|
+
margin-bottom: 6px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
#knx-ai-ollama-install-row {
|
|
9
|
+
align-items: flex-start;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#knx-ai-ollama-install-row > label {
|
|
13
|
+
padding-top: 6px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#knx-ai-ollama-install-row .knx-ai-ollama-actions {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-wrap: wrap;
|
|
19
|
+
gap: 8px;
|
|
20
|
+
align-items: center;
|
|
21
|
+
max-width: calc(100% - 300px);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#knx-ai-ollama-install-row .knx-ai-ollama-actions .red-ui-button {
|
|
25
|
+
margin: 0 !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#knx-ai-ollama-steps,
|
|
29
|
+
#knx-ai-ollama-empty-help {
|
|
30
|
+
margin-left: 290px;
|
|
31
|
+
margin-top: 6px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@media (max-width: 900px) {
|
|
35
|
+
#knx-ai-ollama-install-row > label {
|
|
36
|
+
width: 100% !important;
|
|
37
|
+
padding-top: 0;
|
|
38
|
+
margin-bottom: 6px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#knx-ai-ollama-install-row .knx-ai-ollama-actions {
|
|
42
|
+
max-width: 100%;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#knx-ai-ollama-steps,
|
|
46
|
+
#knx-ai-ollama-empty-help {
|
|
47
|
+
margin-left: 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
2
51
|
|
|
3
52
|
<script type="text/javascript">
|
|
4
53
|
RED.nodes.registerType('knxUltimateAI', {
|
|
@@ -41,11 +90,11 @@
|
|
|
41
90
|
llmMaxEventsInPrompt: { value: 120, required: false, validate: RED.validators.number() },
|
|
42
91
|
llmIncludeRaw: { value: false },
|
|
43
92
|
llmIncludeFlowContext: { value: true },
|
|
44
|
-
llmMaxFlowNodesInPrompt: { value:
|
|
93
|
+
llmMaxFlowNodesInPrompt: { value: 400, required: false, validate: RED.validators.number() },
|
|
45
94
|
llmIncludeDocsSnippets: { value: true },
|
|
46
95
|
llmDocsLanguage: { value: "en" },
|
|
47
96
|
llmDocsMaxSnippets: { value: 5, required: false, validate: RED.validators.number() },
|
|
48
|
-
llmDocsMaxChars: { value:
|
|
97
|
+
llmDocsMaxChars: { value: 60000, required: false, validate: RED.validators.number() }
|
|
49
98
|
},
|
|
50
99
|
credentials: {
|
|
51
100
|
llmApiKey: { type: "password" }
|
|
@@ -66,6 +115,19 @@
|
|
|
66
115
|
paletteLabel: "KNX AI",
|
|
67
116
|
oneditprepare: function () {
|
|
68
117
|
try { RED.sidebar.show("help"); } catch (error) { }
|
|
118
|
+
const OPENAI_COMPAT_DEFAULT_CHAT_URL = "https://api.openai.com/v1/chat/completions";
|
|
119
|
+
const OLLAMA_DEFAULT_CHAT_URL = "http://localhost:11434/api/chat";
|
|
120
|
+
const OLLAMA_LIBRARY_URL = "https://ollama.com/library";
|
|
121
|
+
const OPENAI_COMPAT_DEFAULT_MODELS = ["gpt-5.4", "gpt-4o-mini"];
|
|
122
|
+
const OLLAMA_DEFAULT_MODEL = "llama3.1";
|
|
123
|
+
|
|
124
|
+
const $accordion = $("#knx-ai-accordion");
|
|
125
|
+
const $llmContent = $accordion.find("#node-input-llmEnabled").closest("#knx-ai-accordion > div").first();
|
|
126
|
+
const $llmHeader = $llmContent.prev("h3");
|
|
127
|
+
if ($llmHeader.length && $llmContent.length) {
|
|
128
|
+
$llmContent.prependTo($accordion);
|
|
129
|
+
$llmHeader.prependTo($accordion);
|
|
130
|
+
}
|
|
69
131
|
|
|
70
132
|
$("#knx-ai-accordion").accordion({
|
|
71
133
|
header: "h3",
|
|
@@ -85,19 +147,6 @@
|
|
|
85
147
|
$("#node-input-llmEnabled").on("change", toggleLLM);
|
|
86
148
|
toggleLLM();
|
|
87
149
|
|
|
88
|
-
const toggleProvider = () => {
|
|
89
|
-
const provider = $("#node-input-llmProvider").val();
|
|
90
|
-
if (provider === "ollama") {
|
|
91
|
-
$("#knx-ai-apikey-row").hide();
|
|
92
|
-
$("#knx-ai-ollama-warning").show();
|
|
93
|
-
} else {
|
|
94
|
-
$("#knx-ai-apikey-row").show();
|
|
95
|
-
$("#knx-ai-ollama-warning").hide();
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
$("#node-input-llmProvider").on("change", toggleProvider);
|
|
99
|
-
toggleProvider();
|
|
100
|
-
|
|
101
150
|
const nodeId = this.id;
|
|
102
151
|
const resolveAdminRoot = () => {
|
|
103
152
|
const raw = (RED.settings && typeof RED.settings.httpAdminRoot === "string") ? RED.settings.httpAdminRoot : "/";
|
|
@@ -114,6 +163,11 @@
|
|
|
114
163
|
return "";
|
|
115
164
|
}
|
|
116
165
|
};
|
|
166
|
+
const t = (key, fallback) => {
|
|
167
|
+
const value = RED._(key);
|
|
168
|
+
if (!value || value === key) return fallback || "";
|
|
169
|
+
return value;
|
|
170
|
+
};
|
|
117
171
|
|
|
118
172
|
$("#knx-ai-open-web-page-vue").on("click", function (evt) {
|
|
119
173
|
evt.preventDefault();
|
|
@@ -138,6 +192,16 @@
|
|
|
138
192
|
if (level === "err") $status.addClass("knx-ai-models-err");
|
|
139
193
|
};
|
|
140
194
|
|
|
195
|
+
const setOllamaNoModelsVisible = (visible) => {
|
|
196
|
+
if (visible) $("#knx-ai-ollama-empty-help").show();
|
|
197
|
+
else $("#knx-ai-ollama-empty-help").hide();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const setOllamaInstallUiState = (busy) => {
|
|
201
|
+
$("#knx-ai-installOllamaModel").prop("disabled", !!busy);
|
|
202
|
+
$("#knx-ai-downloadOllamaModel").prop("disabled", !!busy);
|
|
203
|
+
};
|
|
204
|
+
|
|
141
205
|
const populateModels = (models) => {
|
|
142
206
|
const $dl = $("#knx-ai-llmModels");
|
|
143
207
|
if (!$dl || $dl.length === 0) return;
|
|
@@ -147,15 +211,37 @@
|
|
|
147
211
|
});
|
|
148
212
|
};
|
|
149
213
|
|
|
214
|
+
const normalizeUrl = (value) => String(value || "").trim().replace(/\/+$/, "").toLowerCase();
|
|
215
|
+
const isOpenAiDefaultUrl = (value) => normalizeUrl(value) === normalizeUrl(OPENAI_COMPAT_DEFAULT_CHAT_URL);
|
|
216
|
+
const isOllamaDefaultUrl = (value) => normalizeUrl(value) === normalizeUrl(OLLAMA_DEFAULT_CHAT_URL);
|
|
217
|
+
const isOpenAiDefaultModel = (value) => OPENAI_COMPAT_DEFAULT_MODELS.indexOf(String(value || "").trim().toLowerCase()) >= 0;
|
|
218
|
+
|
|
219
|
+
const applyProviderDefaults = (provider) => {
|
|
220
|
+
const $baseUrl = $("#node-input-llmBaseUrl");
|
|
221
|
+
const $model = $("#node-input-llmModel");
|
|
222
|
+
const currentBaseUrl = String($baseUrl.val() || "").trim();
|
|
223
|
+
const currentModel = String($model.val() || "").trim();
|
|
224
|
+
|
|
225
|
+
if (provider === "ollama") {
|
|
226
|
+
if (!currentBaseUrl || isOpenAiDefaultUrl(currentBaseUrl)) $baseUrl.val(OLLAMA_DEFAULT_CHAT_URL);
|
|
227
|
+
if (!currentModel || isOpenAiDefaultModel(currentModel)) $model.val(OLLAMA_DEFAULT_MODEL);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!currentBaseUrl || isOllamaDefaultUrl(currentBaseUrl)) $baseUrl.val(OPENAI_COMPAT_DEFAULT_CHAT_URL);
|
|
232
|
+
if (!currentModel || String(currentModel).trim().toLowerCase() === OLLAMA_DEFAULT_MODEL) $model.val(OPENAI_COMPAT_DEFAULT_MODELS[0]);
|
|
233
|
+
};
|
|
234
|
+
|
|
150
235
|
const refreshModels = () => {
|
|
151
236
|
const provider = $("#node-input-llmProvider").val();
|
|
152
237
|
const baseUrl = $("#node-input-llmBaseUrl").val() || "";
|
|
153
238
|
const apiKey = $("#node-input-llmApiKey").val() || "";
|
|
154
239
|
const payload = { nodeId: nodeId, provider: provider, baseUrl: baseUrl };
|
|
240
|
+
if (provider === "ollama") payload.autoStart = true;
|
|
155
241
|
// Node-RED uses "__PWRD__" as placeholder for stored credentials: don't send it.
|
|
156
242
|
if (apiKey && apiKey !== "__PWRD__") payload.apiKey = apiKey;
|
|
157
243
|
|
|
158
|
-
setModelsStatus(
|
|
244
|
+
setModelsStatus(t('knxUltimateAI.messages.loadingModels', "Loading models..."), "warn");
|
|
159
245
|
$("#knx-ai-refreshModels").prop("disabled", true);
|
|
160
246
|
|
|
161
247
|
$.ajax({
|
|
@@ -166,9 +252,23 @@
|
|
|
166
252
|
})
|
|
167
253
|
.done(function (data) {
|
|
168
254
|
const models = (data && data.models) ? data.models : [];
|
|
255
|
+
let msg = "";
|
|
169
256
|
populateModels(models);
|
|
170
|
-
|
|
171
|
-
|
|
257
|
+
if (provider === "ollama" && data && data.ollamaStarted) {
|
|
258
|
+
try {
|
|
259
|
+
const startedMsg = t('knxUltimateAI.messages.ollamaStartedAuto', "Ollama server started automatically.");
|
|
260
|
+
RED.notify(startedMsg, "success");
|
|
261
|
+
} catch (e) { }
|
|
262
|
+
}
|
|
263
|
+
if (provider === "ollama" && models.length === 0) {
|
|
264
|
+
msg = t('knxUltimateAI.messages.ollamaNoModels', "No local Ollama model found. Install one from https://ollama.com/library.");
|
|
265
|
+
setModelsStatus(msg, "warn");
|
|
266
|
+
setOllamaNoModelsVisible(true);
|
|
267
|
+
} else {
|
|
268
|
+
msg = t('knxUltimateAI.messages.loadedModels', "Models loaded") + ": " + models.length;
|
|
269
|
+
setModelsStatus(msg, "ok");
|
|
270
|
+
setOllamaNoModelsVisible(false);
|
|
271
|
+
}
|
|
172
272
|
try { RED.notify(msg, "success"); } catch (e) { }
|
|
173
273
|
})
|
|
174
274
|
.fail(function (xhr) {
|
|
@@ -185,10 +285,88 @@
|
|
|
185
285
|
});
|
|
186
286
|
};
|
|
187
287
|
|
|
288
|
+
const installOllamaModel = () => {
|
|
289
|
+
const provider = $("#node-input-llmProvider").val();
|
|
290
|
+
if (provider !== "ollama") return;
|
|
291
|
+
const baseUrl = $("#node-input-llmBaseUrl").val() || "";
|
|
292
|
+
const model = String($("#node-input-llmModel").val() || "").trim() || OLLAMA_DEFAULT_MODEL;
|
|
293
|
+
const payload = { nodeId: nodeId, baseUrl: baseUrl, model: model };
|
|
294
|
+
|
|
295
|
+
setModelsStatus(t('knxUltimateAI.messages.installingOllamaModel', "Installing Ollama model..."), "warn");
|
|
296
|
+
$("#knx-ai-refreshModels").prop("disabled", true);
|
|
297
|
+
setOllamaInstallUiState(true);
|
|
298
|
+
|
|
299
|
+
$.ajax({
|
|
300
|
+
url: "knxUltimateAI/ollama/pull",
|
|
301
|
+
type: "POST",
|
|
302
|
+
contentType: "application/json",
|
|
303
|
+
data: JSON.stringify(payload)
|
|
304
|
+
})
|
|
305
|
+
.done(function (data) {
|
|
306
|
+
try {
|
|
307
|
+
const okMsg = t('knxUltimateAI.messages.installedOllamaModel', "Ollama model installed") + ": " + model;
|
|
308
|
+
RED.notify(okMsg, "success");
|
|
309
|
+
if (data && data.ollamaStarted) {
|
|
310
|
+
const startedMsg = t('knxUltimateAI.messages.ollamaStartedAuto', "Ollama server started automatically.");
|
|
311
|
+
RED.notify(startedMsg, "success");
|
|
312
|
+
}
|
|
313
|
+
} catch (e) { }
|
|
314
|
+
refreshModels();
|
|
315
|
+
})
|
|
316
|
+
.fail(function (xhr) {
|
|
317
|
+
let err = t('knxUltimateAI.messages.installOllamaModelFailed', "Failed to install Ollama model");
|
|
318
|
+
try {
|
|
319
|
+
const resp = xhr && xhr.responseJSON;
|
|
320
|
+
if (resp && resp.error) err = resp.error;
|
|
321
|
+
} catch (e) { }
|
|
322
|
+
setModelsStatus(err, "err");
|
|
323
|
+
try { RED.notify(err, "error"); } catch (e) { }
|
|
324
|
+
})
|
|
325
|
+
.always(function () {
|
|
326
|
+
$("#knx-ai-refreshModels").prop("disabled", false);
|
|
327
|
+
setOllamaInstallUiState(false);
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const openOllamaLibrary = () => {
|
|
332
|
+
const wnd = window.open(OLLAMA_LIBRARY_URL, "_blank", "noopener,noreferrer");
|
|
333
|
+
try { if (wnd && typeof wnd.focus === "function") wnd.focus(); } catch (e) { }
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const toggleProvider = ({ applyDefaults = false, autoLoadModels = false } = {}) => {
|
|
337
|
+
const provider = $("#node-input-llmProvider").val();
|
|
338
|
+
if (applyDefaults) applyProviderDefaults(provider);
|
|
339
|
+
if (provider === "ollama") {
|
|
340
|
+
$("#knx-ai-apikey-row").hide();
|
|
341
|
+
$("#knx-ai-ollama-warning").show();
|
|
342
|
+
$("#knx-ai-ollama-install-row").show();
|
|
343
|
+
$("#knx-ai-ollama-steps").show();
|
|
344
|
+
if (autoLoadModels) refreshModels();
|
|
345
|
+
} else {
|
|
346
|
+
$("#knx-ai-apikey-row").show();
|
|
347
|
+
$("#knx-ai-ollama-warning").hide();
|
|
348
|
+
$("#knx-ai-ollama-install-row").hide();
|
|
349
|
+
$("#knx-ai-ollama-steps").hide();
|
|
350
|
+
setOllamaNoModelsVisible(false);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
$("#node-input-llmProvider").on("change", function () {
|
|
354
|
+
toggleProvider({ applyDefaults: true, autoLoadModels: true });
|
|
355
|
+
});
|
|
356
|
+
toggleProvider({ applyDefaults: true });
|
|
357
|
+
|
|
188
358
|
$("#knx-ai-refreshModels").on("click", function (evt) {
|
|
189
359
|
evt.preventDefault();
|
|
190
360
|
refreshModels();
|
|
191
361
|
});
|
|
362
|
+
$("#knx-ai-downloadOllamaModel").on("click", function (evt) {
|
|
363
|
+
evt.preventDefault();
|
|
364
|
+
openOllamaLibrary();
|
|
365
|
+
});
|
|
366
|
+
$("#knx-ai-installOllamaModel").on("click", function (evt) {
|
|
367
|
+
evt.preventDefault();
|
|
368
|
+
installOllamaModel();
|
|
369
|
+
});
|
|
192
370
|
},
|
|
193
371
|
oneditsave: function () {
|
|
194
372
|
try { RED.sidebar.show("info"); } catch (error) { }
|
|
@@ -342,6 +520,25 @@
|
|
|
342
520
|
</div>
|
|
343
521
|
<datalist id="knx-ai-llmModels"></datalist>
|
|
344
522
|
<div class="form-tips" id="knx-ai-models-status"></div>
|
|
523
|
+
<div class="form-row" id="knx-ai-ollama-install-row" style="display:none;">
|
|
524
|
+
<label style="width:290px"><i class="fa fa-download"></i> Ollama</label>
|
|
525
|
+
<div class="knx-ai-ollama-actions">
|
|
526
|
+
<button type="button" class="red-ui-button" id="knx-ai-downloadOllamaModel">
|
|
527
|
+
<i class="fa fa-cloud-download"></i> <span data-i18n="knxUltimateAI.buttons.downloadOllamaModel"></span>
|
|
528
|
+
</button>
|
|
529
|
+
<button type="button" class="red-ui-button" id="knx-ai-installOllamaModel">
|
|
530
|
+
<i class="fa fa-download"></i> <span data-i18n="knxUltimateAI.buttons.installOllamaModel"></span>
|
|
531
|
+
</button>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
<div class="form-tips" id="knx-ai-ollama-steps" style="display:none;">
|
|
535
|
+
<span data-i18n="knxUltimateAI.messages.ollamaInstallSteps"></span>
|
|
536
|
+
</div>
|
|
537
|
+
<div class="form-tips" id="knx-ai-ollama-empty-help" style="display:none;">
|
|
538
|
+
<span data-i18n="knxUltimateAI.messages.ollamaNoModels"></span>
|
|
539
|
+
<br>
|
|
540
|
+
<code>ollama pull llama3.1</code>
|
|
541
|
+
</div>
|
|
345
542
|
|
|
346
543
|
<div class="form-row">
|
|
347
544
|
<label style="width:290px" for="node-input-llmSystemPrompt"><i class="fa fa-commenting-o"></i> <span data-i18n="knxUltimateAI.properties.llmSystemPrompt"></span></label>
|