node-red-contrib-knx-ultimate 6.2.1 → 6.2.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.
@@ -1,11 +1,6 @@
1
1
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
2
2
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/KNXAIChatAdapterMappings.js"></script>
3
3
  <style type="text/css">
4
- #knx-ai-models-status {
5
- margin-top: 6px;
6
- margin-bottom: 6px;
7
- }
8
-
9
4
  #knx-ai-ollama-install-row {
10
5
  align-items: flex-start;
11
6
  }
@@ -26,18 +21,29 @@
26
21
  margin: 0 !important;
27
22
  }
28
23
 
29
- .knx-ai-chat-adapter-code {
30
- display: block;
24
+ .knx-ai-chat-adapter-editor {
31
25
  width: 100% !important;
32
- min-width: 100%;
33
- max-width: none !important;
34
- min-height: 190px;
26
+ height: 220px;
27
+ min-height: 180px;
35
28
  box-sizing: border-box;
36
- clear: both;
37
- resize: vertical;
38
- font-family: var(--red-ui-monospace-font, monospace);
39
- font-size: 12px;
40
- line-height: 1.4;
29
+ }
30
+
31
+ #node-input-chatInputCode-editor .monaco-editor,
32
+ #node-input-chatInputCode-editor .monaco-editor-background,
33
+ #node-input-chatInputCode-editor .monaco-editor .margin,
34
+ #node-input-chatInputCode-editor .monaco-editor .overflow-guard,
35
+ #node-input-chatInputCode-editor .monaco-editor .lines-content,
36
+ #node-input-chatInputCode-editor .monaco-editor .editor-scrollable {
37
+ background-color: #e8f5e9 !important;
38
+ }
39
+
40
+ #node-input-chatOutputCode-editor .monaco-editor,
41
+ #node-input-chatOutputCode-editor .monaco-editor-background,
42
+ #node-input-chatOutputCode-editor .monaco-editor .margin,
43
+ #node-input-chatOutputCode-editor .monaco-editor .overflow-guard,
44
+ #node-input-chatOutputCode-editor .monaco-editor .lines-content,
45
+ #node-input-chatOutputCode-editor .monaco-editor .editor-scrollable {
46
+ background-color: #fffde7 !important;
41
47
  }
42
48
 
43
49
  #knx-ai-accordion {
@@ -93,12 +99,6 @@
93
99
  display: block;
94
100
  }
95
101
 
96
- #knx-ai-ollama-steps,
97
- #knx-ai-ollama-empty-help {
98
- margin-left: 290px;
99
- margin-top: 6px;
100
- }
101
-
102
102
  @media (max-width: 900px) {
103
103
  #knx-ai-ollama-install-row>label {
104
104
  width: 100% !important;
@@ -110,10 +110,6 @@
110
110
  max-width: 100%;
111
111
  }
112
112
 
113
- #knx-ai-ollama-steps,
114
- #knx-ai-ollama-empty-help {
115
- margin-left: 0;
116
- }
117
113
  }
118
114
  </style>
119
115
 
@@ -126,37 +122,19 @@
126
122
  name: { value: "KNX AI", required: false },
127
123
  topic: { value: "", required: false },
128
124
 
129
- notifyreadrequest: { value: true },
130
- notifyresponse: { value: true },
131
- notifywrite: { value: true },
132
-
133
- analysisWindowSec: { value: 120, required: true, validate: RED.validators.number() },
134
- historyWindowSec: { value: 600, required: true, validate: RED.validators.number() },
125
+ analysisWindowSec: { value: 120 },
126
+ historyWindowSec: { value: 600 },
135
127
  historyStoreToDisk: { value: true },
136
- historyStoreRetentionDays: { value: 10, required: true, validate: RED.validators.number() },
137
- emitIntervalSec: { value: 0, required: true, validate: RED.validators.number() },
138
- topN: { value: 10, required: true, validate: RED.validators.number() },
139
- maxEvents: { value: 50000, required: true, validate: RED.validators.number() },
140
-
141
- rateWindowSec: { value: 10, required: true, validate: RED.validators.number() },
142
- maxTelegramPerSecOverall: { value: 0, required: true, validate: RED.validators.number() },
143
- maxTelegramPerSecPerGA: { value: 0, required: true, validate: RED.validators.number() },
144
-
145
- flapWindowSec: { value: 30, required: true, validate: RED.validators.number() },
146
- flapMaxChanges: { value: 0, required: true, validate: RED.validators.number() },
147
-
148
- enablePattern: { value: true },
149
- patternMaxLagMs: { value: 1500, required: true, validate: RED.validators.number() },
150
- patternMinCount: { value: 8, required: true, validate: RED.validators.number() },
128
+ historyStoreRetentionDays: { value: 10 },
129
+ emitIntervalSec: { value: 0 },
130
+ topN: { value: 10 },
131
+ maxEvents: { value: 50000 },
151
132
 
152
133
  llmEnabled: { value: false },
153
134
  llmProvider: { value: "openai_compat" },
154
135
  llmBaseUrl: { value: "https://api.openai.com/v1/chat/completions" },
155
136
  llmModel: { value: "gpt-5.4" },
156
- llmSystemPrompt: { value: "" },
157
- llmIncludeRaw: { value: false },
158
137
  llmIncludeFlowContext: { value: true },
159
- llmIncludeDocsSnippets: { value: true },
160
138
  llmDocsLanguage: { value: "en" },
161
139
  llmAllowKnxCommands: { value: false },
162
140
  llmRequireCommandConfirmation: { value: true },
@@ -165,11 +143,11 @@
165
143
  chatOutputCode: { value: "" },
166
144
  proactiveEnabled: { value: false },
167
145
  proactiveRecipient: { value: "" },
168
- proactiveOpenMinutes: { value: 120, required: true, validate: RED.validators.number() },
169
- proactiveCooldownMinutes: { value: 360, required: true, validate: RED.validators.number() },
146
+ proactiveOpenMinutes: { value: 120 },
147
+ proactiveCooldownMinutes: { value: 360 },
170
148
  proactiveQuietStart: { value: "23:00" },
171
149
  proactiveQuietEnd: { value: "07:00" },
172
- homeMemoryMaxKb: { value: 256, required: true, validate: RED.validators.number() },
150
+ homeMemoryMaxKb: { value: 256 },
173
151
  aiEducation: { value: "" }
174
152
  },
175
153
  credentials: {
@@ -192,6 +170,7 @@
192
170
  paletteLabel: "KNX AI",
193
171
  oneditprepare: function () {
194
172
  try { RED.sidebar.show("help"); } catch (error) { }
173
+ const node = this;
195
174
  const OPENAI_COMPAT_DEFAULT_CHAT_URL = "https://api.openai.com/v1/chat/completions";
196
175
  const OLLAMA_DEFAULT_CHAT_URL = "http://localhost:11434/api/chat";
197
176
  const ANTHROPIC_DEFAULT_CHAT_URL = "https://api.anthropic.com/v1/messages";
@@ -210,9 +189,7 @@
210
189
  mountAccordionSection("#knx-ai-mount-assistant-advanced", "#knx-ai-tab-advanced");
211
190
  mountAccordionSection("#knx-ai-mount-chat-adapter", "#knx-ai-tab-chat-adapter");
212
191
  mountAccordionSection("#knx-ai-mount-home-intelligence", "#knx-ai-tab-home-intelligence");
213
- mountAccordionSection("#knx-ai-mount-knx-capture", "#knx-ai-tab-capture");
214
192
  mountAccordionSection("#knx-ai-mount-knx-storage", "#knx-ai-tab-storage");
215
- mountAccordionSection("#knx-ai-mount-knx-detection", "#knx-ai-tab-detection");
216
193
  $("#knx-ai-accordion").accordion({
217
194
  active: 0,
218
195
  animate: false,
@@ -234,12 +211,6 @@
234
211
  };
235
212
  $("#node-input-historyStoreToDisk").on("change", toggleHistoryArchive);
236
213
 
237
- const toggleDocsContext = () => {
238
- const enabled = $("#node-input-llmIncludeDocsSnippets").is(":checked");
239
- $("#knx-ai-docs-language-row").toggle(enabled);
240
- };
241
- $("#node-input-llmIncludeDocsSnippets").on("change", toggleDocsContext);
242
-
243
214
  const toggleCommandConfirmation = () => {
244
215
  const enabled = $("#node-input-llmAllowKnxCommands").is(":checked");
245
216
  $("#knx-ai-command-confirmation-row").toggle(enabled);
@@ -248,10 +219,27 @@
248
219
 
249
220
  toggleLLM();
250
221
  toggleHistoryArchive();
251
- toggleDocsContext();
252
222
  toggleCommandConfirmation();
253
223
 
254
- const nodeId = this.id;
224
+ const applyNumericFallback = (selector, fallback) => {
225
+ const $field = $(selector);
226
+ if (!$field.length) return;
227
+ const value = $field.val();
228
+ if (value === undefined || value === null || String(value).trim() === "") {
229
+ $field.val(String(fallback));
230
+ }
231
+ };
232
+ applyNumericFallback("#node-input-analysisWindowSec", 120);
233
+ applyNumericFallback("#node-input-historyWindowSec", 600);
234
+ applyNumericFallback("#node-input-historyStoreRetentionDays", 10);
235
+ applyNumericFallback("#node-input-emitIntervalSec", 0);
236
+ applyNumericFallback("#node-input-topN", 10);
237
+ applyNumericFallback("#node-input-maxEvents", 50000);
238
+ applyNumericFallback("#node-input-proactiveOpenMinutes", 120);
239
+ applyNumericFallback("#node-input-proactiveCooldownMinutes", 360);
240
+ applyNumericFallback("#node-input-homeMemoryMaxKb", 256);
241
+
242
+ const nodeId = node.id;
255
243
  const resolveAdminRoot = () => {
256
244
  const raw = (RED.settings && typeof RED.settings.httpAdminRoot === "string") ? RED.settings.httpAdminRoot : "/";
257
245
  const trimmed = String(raw || "/").trim();
@@ -273,12 +261,115 @@
273
261
  return value;
274
262
  };
275
263
 
264
+ const applyChatEditorOptions = (editor) => {
265
+ try {
266
+ if (!editor) return;
267
+ if (typeof editor.updateOptions === "function") {
268
+ editor.updateOptions({
269
+ lineNumbers: "off",
270
+ minimap: { enabled: false },
271
+ scrollbar: { verticalScrollbarSize: 8, horizontalScrollbarSize: 8 }
272
+ });
273
+ } else if (editor.renderer && typeof editor.renderer.setShowGutter === "function") {
274
+ editor.renderer.setShowGutter(false);
275
+ }
276
+ if (typeof editor.setShowPrintMargin === "function") editor.setShowPrintMargin(false);
277
+ if (typeof editor.setOptions === "function") {
278
+ editor.setOptions({
279
+ enableBasicAutocompletion: true,
280
+ enableLiveAutocompletion: true
281
+ });
282
+ }
283
+ } catch (error) { }
284
+ };
285
+ const setChatEditorValue = (editor, value) => {
286
+ try {
287
+ if (!editor) return;
288
+ if (typeof editor.setValue === "function") {
289
+ editor.setValue(String(value || ""));
290
+ } else if (editor.session && typeof editor.session.setValue === "function") {
291
+ editor.session.setValue(String(value || ""));
292
+ }
293
+ } catch (error) { }
294
+ };
295
+ const destroyChatEditors = () => {
296
+ ["chatInputCodeEditor", "chatOutputCodeEditor"].forEach(function (key) {
297
+ try {
298
+ if (node[key] && typeof node[key].destroy === "function") node[key].destroy();
299
+ } catch (error) { }
300
+ delete node[key];
301
+ });
302
+ };
303
+
304
+ try {
305
+ node.chatInputCodeEditor = RED.editor.createEditor({
306
+ id: "node-input-chatInputCode-editor",
307
+ mode: "ace/mode/nrjavascript",
308
+ value: node.chatInputCode || ""
309
+ });
310
+ } catch (error) { }
311
+ try {
312
+ node.chatOutputCodeEditor = RED.editor.createEditor({
313
+ id: "node-input-chatOutputCode-editor",
314
+ mode: "ace/mode/nrjavascript",
315
+ value: node.chatOutputCode || ""
316
+ });
317
+ } catch (error) { }
318
+ applyChatEditorOptions(node.chatInputCodeEditor);
319
+ applyChatEditorOptions(node.chatOutputCodeEditor);
320
+ if (typeof monaco !== "undefined") {
321
+ try {
322
+ const globalScope = typeof window !== "undefined" ? window : globalThis;
323
+ if (!globalScope._knxAiChatEditorThemesDefined) {
324
+ monaco.editor.defineTheme("knx-ai-chat-input-theme", {
325
+ base: "vs",
326
+ inherit: true,
327
+ rules: [],
328
+ colors: {
329
+ "editor.background": "#e8f5e9",
330
+ "editorGutter.background": "#e8f5e9"
331
+ }
332
+ });
333
+ monaco.editor.defineTheme("knx-ai-chat-output-theme", {
334
+ base: "vs",
335
+ inherit: true,
336
+ rules: [],
337
+ colors: {
338
+ "editor.background": "#fffde7",
339
+ "editorGutter.background": "#fffde7"
340
+ }
341
+ });
342
+ globalScope._knxAiChatEditorThemesDefined = true;
343
+ }
344
+ if (node.chatInputCodeEditor && typeof node.chatInputCodeEditor.updateOptions === "function") {
345
+ node.chatInputCodeEditor.updateOptions({ theme: "knx-ai-chat-input-theme" });
346
+ }
347
+ if (node.chatOutputCodeEditor && typeof node.chatOutputCodeEditor.updateOptions === "function") {
348
+ node.chatOutputCodeEditor.updateOptions({ theme: "knx-ai-chat-output-theme" });
349
+ }
350
+ } catch (error) { }
351
+ }
352
+ node._destroyKnxAiChatEditors = destroyChatEditors;
353
+
276
354
  const configureChatAdapterPicker = () => {
277
355
  const adapters = Array.isArray(window.KNXAIChatAdapterMappings)
278
356
  ? window.KNXAIChatAdapterMappings.slice()
279
357
  : [];
280
358
  const $picker = $("#node-input-chatAdapterPreset");
281
359
  if (!$picker.length) return;
360
+ const updateAdapterFieldsVisibility = (preset) => {
361
+ const visible = String(preset || "none") !== "none";
362
+ $("#knx-ai-chat-adapter-fields").toggle(visible);
363
+ if (visible) {
364
+ const resizeEditors = function () {
365
+ try {
366
+ if (node.chatInputCodeEditor && typeof node.chatInputCodeEditor.resize === "function") node.chatInputCodeEditor.resize();
367
+ if (node.chatOutputCodeEditor && typeof node.chatOutputCodeEditor.resize === "function") node.chatOutputCodeEditor.resize();
368
+ } catch (error) { }
369
+ };
370
+ try { setTimeout(resizeEditors, 0); } catch (error) { resizeEditors(); }
371
+ }
372
+ };
282
373
  $picker.empty();
283
374
  $("<option></option>")
284
375
  .attr("value", "none")
@@ -300,20 +391,23 @@
300
391
  .appendTo($picker);
301
392
  }
302
393
  $picker.val(configuredPreset);
394
+ updateAdapterFieldsVisibility(configuredPreset);
303
395
  $picker.on("change", function () {
304
396
  const selected = String($(this).val() || "none");
305
397
  if (selected === "none") {
306
- $("#node-input-chatInputCode").val("");
307
- $("#node-input-chatOutputCode").val("");
398
+ setChatEditorValue(node.chatInputCodeEditor, "");
399
+ setChatEditorValue(node.chatOutputCodeEditor, "");
400
+ updateAdapterFieldsVisibility(selected);
308
401
  return;
309
402
  }
310
403
  const adapter = adapters.find(item => item && item.id === selected);
311
404
  if (!adapter) return;
312
- $("#node-input-chatInputCode").val(adapter.inputCode || "");
313
- $("#node-input-chatOutputCode").val(adapter.outputCode || "");
405
+ setChatEditorValue(node.chatInputCodeEditor, adapter.inputCode || "");
406
+ setChatEditorValue(node.chatOutputCodeEditor, adapter.outputCode || "");
407
+ updateAdapterFieldsVisibility(selected);
314
408
  });
315
409
  };
316
- configureChatAdapterPicker.call(this);
410
+ configureChatAdapterPicker.call(node);
317
411
 
318
412
  const updateProactiveVisibility = () => {
319
413
  const enabled = $("#node-input-proactiveEnabled").is(":checked");
@@ -335,19 +429,10 @@
335
429
  try { if (wnd && typeof wnd.focus === "function") wnd.focus(); } catch (e) { }
336
430
  });
337
431
 
338
- const setModelsStatus = (text, level) => {
339
- const $status = $("#knx-ai-models-status");
340
- if (!$status || $status.length === 0) return;
341
- $status.text(text || "");
342
- $status.removeClass("knx-ai-models-ok knx-ai-models-warn knx-ai-models-err");
343
- if (level === "ok") $status.addClass("knx-ai-models-ok");
344
- if (level === "warn") $status.addClass("knx-ai-models-warn");
345
- if (level === "err") $status.addClass("knx-ai-models-err");
346
- };
347
-
348
- const setOllamaNoModelsVisible = (visible) => {
349
- if (visible) $("#knx-ai-ollama-empty-help").show();
350
- else $("#knx-ai-ollama-empty-help").hide();
432
+ const setRefreshModelsBusy = (busy) => {
433
+ const $button = $("#knx-ai-refreshModels");
434
+ $button.prop("disabled", !!busy);
435
+ $button.find("i.fa-refresh").toggleClass("fa-spin", !!busy);
351
436
  };
352
437
 
353
438
  const setOllamaInstallUiState = (busy) => {
@@ -399,8 +484,7 @@
399
484
  // Node-RED uses "__PWRD__" as placeholder for stored credentials: don't send it.
400
485
  if (apiKey && apiKey !== "__PWRD__") payload.apiKey = apiKey;
401
486
 
402
- setModelsStatus(t('knxUltimateAI.messages.loadingModels', "Loading models..."), "warn");
403
- $("#knx-ai-refreshModels").prop("disabled", true);
487
+ setRefreshModelsBusy(true);
404
488
 
405
489
  $.ajax({
406
490
  url: "knxUltimateAI/models",
@@ -410,7 +494,6 @@
410
494
  })
411
495
  .done(function (data) {
412
496
  const models = (data && data.models) ? data.models : [];
413
- let msg = "";
414
497
  populateModels(models);
415
498
  if (provider === "ollama" && data && data.ollamaStarted) {
416
499
  try {
@@ -419,15 +502,9 @@
419
502
  } catch (e) { }
420
503
  }
421
504
  if (provider === "ollama" && models.length === 0) {
422
- msg = t('knxUltimateAI.messages.ollamaNoModels', "No local Ollama model found. Install one from https://ollama.com/library.");
423
- setModelsStatus(msg, "warn");
424
- setOllamaNoModelsVisible(true);
425
- } else {
426
- msg = t('knxUltimateAI.messages.loadedModels', "Models loaded") + ": " + models.length;
427
- setModelsStatus(msg, "ok");
428
- setOllamaNoModelsVisible(false);
505
+ const msg = t('knxUltimateAI.messages.ollamaNoModels', "No local Ollama model found. Install one from https://ollama.com/library.");
506
+ try { RED.notify(msg, "warning"); } catch (e) { }
429
507
  }
430
- try { RED.notify(msg, "success"); } catch (e) { }
431
508
  })
432
509
  .fail(function (xhr) {
433
510
  let err = "Failed to load models";
@@ -435,11 +512,10 @@
435
512
  const resp = xhr && xhr.responseJSON;
436
513
  if (resp && resp.error) err = resp.error;
437
514
  } catch (e) { }
438
- setModelsStatus(err, "err");
439
515
  try { RED.notify(err, "error"); } catch (e) { }
440
516
  })
441
517
  .always(function () {
442
- $("#knx-ai-refreshModels").prop("disabled", false);
518
+ setRefreshModelsBusy(false);
443
519
  });
444
520
  };
445
521
 
@@ -450,7 +526,6 @@
450
526
  const model = String($("#node-input-llmModel").val() || "").trim() || OLLAMA_DEFAULT_MODEL;
451
527
  const payload = { nodeId: nodeId, baseUrl: baseUrl, model: model };
452
528
 
453
- setModelsStatus(t('knxUltimateAI.messages.installingOllamaModel', "Installing Ollama model..."), "warn");
454
529
  $("#knx-ai-refreshModels").prop("disabled", true);
455
530
  setOllamaInstallUiState(true);
456
531
 
@@ -477,11 +552,10 @@
477
552
  const resp = xhr && xhr.responseJSON;
478
553
  if (resp && resp.error) err = resp.error;
479
554
  } catch (e) { }
480
- setModelsStatus(err, "err");
481
555
  try { RED.notify(err, "error"); } catch (e) { }
556
+ $("#knx-ai-refreshModels").prop("disabled", false);
482
557
  })
483
558
  .always(function () {
484
- $("#knx-ai-refreshModels").prop("disabled", false);
485
559
  setOllamaInstallUiState(false);
486
560
  });
487
561
  };
@@ -496,16 +570,11 @@
496
570
  if (applyDefaults) applyProviderDefaults(provider);
497
571
  if (provider === "ollama") {
498
572
  $("#knx-ai-apikey-row").hide();
499
- $("#knx-ai-ollama-warning").show();
500
573
  $("#knx-ai-ollama-install-row").show();
501
- $("#knx-ai-ollama-steps").show();
502
574
  if (autoLoadModels) refreshModels();
503
575
  } else {
504
576
  $("#knx-ai-apikey-row").show();
505
- $("#knx-ai-ollama-warning").hide();
506
577
  $("#knx-ai-ollama-install-row").hide();
507
- $("#knx-ai-ollama-steps").hide();
508
- setOllamaNoModelsVisible(false);
509
578
  }
510
579
  };
511
580
  $("#node-input-llmProvider").on("change", function () {
@@ -528,9 +597,35 @@
528
597
  },
529
598
  oneditsave: function () {
530
599
  try { RED.sidebar.show("info"); } catch (error) { }
600
+ try {
601
+ if (this.chatInputCodeEditor && typeof this.chatInputCodeEditor.getValue === "function") {
602
+ this.chatInputCode = this.chatInputCodeEditor.getValue();
603
+ $("#node-input-chatInputCode").val(this.chatInputCode);
604
+ }
605
+ } catch (error) { }
606
+ try {
607
+ if (this.chatOutputCodeEditor && typeof this.chatOutputCodeEditor.getValue === "function") {
608
+ this.chatOutputCode = this.chatOutputCodeEditor.getValue();
609
+ $("#node-input-chatOutputCode").val(this.chatOutputCode);
610
+ }
611
+ } catch (error) { }
612
+ try {
613
+ if (typeof this._destroyKnxAiChatEditors === "function") this._destroyKnxAiChatEditors();
614
+ } catch (error) { }
615
+ delete this._destroyKnxAiChatEditors;
531
616
  },
532
617
  oneditcancel: function () {
533
618
  try { RED.sidebar.show("info"); } catch (error) { }
619
+ try {
620
+ if (typeof this._destroyKnxAiChatEditors === "function") this._destroyKnxAiChatEditors();
621
+ } catch (error) { }
622
+ delete this._destroyKnxAiChatEditors;
623
+ },
624
+ oneditresize: function () {
625
+ try {
626
+ if (this.chatInputCodeEditor && typeof this.chatInputCodeEditor.resize === "function") this.chatInputCodeEditor.resize();
627
+ if (this.chatOutputCodeEditor && typeof this.chatOutputCodeEditor.resize === "function") this.chatOutputCodeEditor.resize();
628
+ } catch (error) { }
534
629
  }
535
630
  })
536
631
  </script>
@@ -597,38 +692,15 @@
597
692
 
598
693
  <h3><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.sections.groupKnxAnalysis"></span></h3>
599
694
  <div>
600
- <div class="knx-ai-accordion-subsection">
601
- <h4><i class="fa fa-sign-in"></i> <span data-i18n="knxUltimateAI.sections.capture"></span></h4>
602
- <div class="knx-ai-accordion-mount" id="knx-ai-mount-knx-capture"></div>
603
- </div>
604
695
  <div class="knx-ai-accordion-subsection">
605
696
  <h4><i class="fa fa-database"></i> <span data-i18n="knxUltimateAI.sections.storage"></span></h4>
606
697
  <div class="knx-ai-accordion-mount" id="knx-ai-mount-knx-storage"></div>
607
698
  </div>
608
- <div class="knx-ai-accordion-subsection">
609
- <h4><i class="fa fa-search"></i> <span data-i18n="knxUltimateAI.sections.detection"></span></h4>
610
- <div class="knx-ai-accordion-mount" id="knx-ai-mount-knx-detection"></div>
611
- </div>
612
699
  </div>
613
700
  </div>
614
701
 
615
702
  <div id="knx-ai-accordion-source" style="display:none;">
616
703
 
617
- <div id="knx-ai-tab-capture">
618
- <div class="form-row">
619
- <input type="checkbox" id="node-input-notifywrite" style="display:inline-block; width:auto; vertical-align:top;">
620
- <label style="width:auto" for="node-input-notifywrite">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.notifywrite"></span></label>
621
- </div>
622
- <div class="form-row">
623
- <input type="checkbox" id="node-input-notifyresponse" style="display:inline-block; width:auto; vertical-align:top;">
624
- <label style="width:auto" for="node-input-notifyresponse">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.notifyresponse"></span></label>
625
- </div>
626
- <div class="form-row">
627
- <input type="checkbox" id="node-input-notifyreadrequest" style="display:inline-block; width:auto; vertical-align:top;">
628
- <label style="width:auto" for="node-input-notifyreadrequest">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.notifyreadrequest"></span></label>
629
- </div>
630
- </div>
631
-
632
704
  <div id="knx-ai-tab-storage">
633
705
  <div class="form-row">
634
706
  <label style="width:290px" for="node-input-historyWindowSec"><i class="fa fa-history"></i> <span data-i18n="knxUltimateAI.properties.historyWindowSec"></span></label>
@@ -660,41 +732,6 @@
660
732
  </div>
661
733
  </div>
662
734
 
663
- <div id="knx-ai-tab-detection">
664
- <div class="form-row">
665
- <input type="checkbox" id="node-input-enablePattern" style="display:inline-block; width:auto; vertical-align:top;">
666
- <label style="width:auto" for="node-input-enablePattern">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.enablePattern"></span></label>
667
- </div>
668
- <div class="form-row">
669
- <label style="width:290px" for="node-input-patternMaxLagMs"><i class="fa fa-arrows-h"></i> <span data-i18n="knxUltimateAI.properties.patternMaxLagMs"></span></label>
670
- <input style="width:120px" type="number" id="node-input-patternMaxLagMs">
671
- </div>
672
- <div class="form-row">
673
- <label style="width:290px" for="node-input-patternMinCount"><i class="fa fa-filter"></i> <span data-i18n="knxUltimateAI.properties.patternMinCount"></span></label>
674
- <input style="width:90px" type="number" id="node-input-patternMinCount">
675
- </div>
676
- <div class="form-row">
677
- <label style="width:290px" for="node-input-rateWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.rateWindowSec"></span></label>
678
- <input style="width:90px" type="number" id="node-input-rateWindowSec">
679
- </div>
680
- <div class="form-row">
681
- <label style="width:290px" for="node-input-maxTelegramPerSecOverall"><i class="fa fa-tachometer"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecOverall"></span></label>
682
- <input style="width:90px" type="number" id="node-input-maxTelegramPerSecOverall">
683
- </div>
684
- <div class="form-row">
685
- <label style="width:290px" for="node-input-maxTelegramPerSecPerGA"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecPerGA"></span></label>
686
- <input style="width:90px" type="number" id="node-input-maxTelegramPerSecPerGA">
687
- </div>
688
- <div class="form-row">
689
- <label style="width:290px" for="node-input-flapWindowSec"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.properties.flapWindowSec"></span></label>
690
- <input style="width:90px" type="number" id="node-input-flapWindowSec">
691
- </div>
692
- <div class="form-row">
693
- <label style="width:290px" for="node-input-flapMaxChanges"><i class="fa fa-random"></i> <span data-i18n="knxUltimateAI.properties.flapMaxChanges"></span></label>
694
- <input style="width:90px" type="number" id="node-input-flapMaxChanges">
695
- </div>
696
- </div>
697
-
698
735
  <div id="knx-ai-tab-llm-connection">
699
736
  <div class="form-row">
700
737
  <input type="checkbox" id="node-input-llmEnabled" style="display:inline-block; width:auto; vertical-align:top;">
@@ -723,7 +760,6 @@
723
760
  </button>
724
761
  </div>
725
762
  <datalist id="knx-ai-llmModels"></datalist>
726
- <div class="form-tips" id="knx-ai-models-status"></div>
727
763
  <div class="form-row">
728
764
  <input type="checkbox" id="node-input-llmAllowKnxCommands" style="display:inline-block; width:auto; vertical-align:top;">
729
765
  <label style="width:auto" for="node-input-llmAllowKnxCommands">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmAllowKnxCommands"></span></label>
@@ -736,32 +772,27 @@
736
772
  </div>
737
773
 
738
774
  <div id="knx-ai-tab-chat-adapter">
739
- <div class="form-tips" style="margin-bottom:12px;">
740
- <span data-i18n="knxUltimateAI.messages.chatAdapterIntro"></span>
741
- </div>
742
775
  <div class="form-row">
743
776
  <label style="width:290px" for="node-input-chatAdapterPreset"><i class="fa fa-plug"></i> <span data-i18n="knxUltimateAI.properties.chatAdapterPreset"></span></label>
744
777
  <select style="width:100%" id="node-input-chatAdapterPreset">
745
778
  <option value="none" data-i18n="knxUltimateAI.selectlists.chatAdapter.none"></option>
746
779
  </select>
747
780
  </div>
748
- <div class="form-row">
749
- <label style="width:100%" for="node-input-chatInputCode"><i class="fa fa-sign-in"></i> <span data-i18n="knxUltimateAI.properties.chatInputCode"></span></label>
750
- <textarea id="node-input-chatInputCode" class="knx-ai-chat-adapter-code" spellcheck="false"></textarea>
751
- </div>
752
- <div class="form-row">
753
- <label style="width:100%" for="node-input-chatOutputCode"><i class="fa fa-sign-out"></i> <span data-i18n="knxUltimateAI.properties.chatOutputCode"></span></label>
754
- <textarea id="node-input-chatOutputCode" class="knx-ai-chat-adapter-code" spellcheck="false"></textarea>
755
- </div>
756
- <div class="form-tips">
757
- <span data-i18n="knxUltimateAI.messages.chatAdapterCodeHelp"></span>
781
+ <div id="knx-ai-chat-adapter-fields" style="display:none;">
782
+ <div class="form-row">
783
+ <label style="width:100%" for="node-input-chatInputCode"><i class="fa fa-sign-in"></i> <span data-i18n="knxUltimateAI.properties.chatInputCode"></span></label>
784
+ <input type="hidden" id="node-input-chatInputCode">
785
+ <div id="node-input-chatInputCode-editor" class="node-text-editor knx-ai-chat-adapter-editor"></div>
786
+ </div>
787
+ <div class="form-row">
788
+ <label style="width:100%" for="node-input-chatOutputCode"><i class="fa fa-sign-out"></i> <span data-i18n="knxUltimateAI.properties.chatOutputCode"></span></label>
789
+ <input type="hidden" id="node-input-chatOutputCode">
790
+ <div id="node-input-chatOutputCode-editor" class="node-text-editor knx-ai-chat-adapter-editor"></div>
791
+ </div>
758
792
  </div>
759
793
  </div>
760
794
 
761
795
  <div id="knx-ai-tab-home-intelligence">
762
- <div class="form-tips" style="margin-bottom:12px;">
763
- <span data-i18n="knxUltimateAI.messages.homeIntelligenceIntro"></span>
764
- </div>
765
796
  <div class="form-row">
766
797
  <input type="checkbox" id="node-input-proactiveEnabled" style="display:inline-block; width:auto; vertical-align:top;">
767
798
  <label style="width:auto" for="node-input-proactiveEnabled">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.proactiveEnabled"></span></label>
@@ -788,9 +819,6 @@
788
819
  <label style="width:100%" for="node-input-aiEducation"><i class="fa fa-graduation-cap"></i> <span data-i18n="knxUltimateAI.properties.aiEducation"></span></label>
789
820
  <textarea id="node-input-aiEducation" maxlength="16000" style="width:100%; min-height:180px; box-sizing:border-box;" data-i18n="[placeholder]knxUltimateAI.placeholder.aiEducation"></textarea>
790
821
  </div>
791
- <div class="form-tips" style="margin-bottom:12px;">
792
- <span data-i18n="knxUltimateAI.messages.aiEducationHelp"></span>
793
- </div>
794
822
  <div class="knx-ai-inline-subsection">
795
823
  <h5><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.sections.homeIntelligenceAdvanced"></span></h5>
796
824
  <div>
@@ -802,30 +830,17 @@
802
830
  <label style="width:290px" for="node-input-homeMemoryMaxKb"><i class="fa fa-hdd-o"></i> <span data-i18n="knxUltimateAI.properties.homeMemoryMaxKb"></span></label>
803
831
  <input style="width:100px" type="number" min="64" max="1024" id="node-input-homeMemoryMaxKb">
804
832
  </div>
805
- <div class="form-tips">
806
- <span data-i18n="knxUltimateAI.messages.homeMemoryLimitHelp"></span>
807
- </div>
808
833
  </div>
809
834
  </div>
810
835
  </div>
811
836
 
812
837
  <div id="knx-ai-tab-llm-context">
813
838
  <div id="knx-ai-llm-context-settings">
814
- <div class="form-row">
815
- <input type="checkbox" id="node-input-llmIncludeRaw" style="display:inline-block; width:auto; vertical-align:top;">
816
- <label style="width:auto" for="node-input-llmIncludeRaw">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmIncludeRaw"></span></label>
817
- </div>
818
-
819
839
  <div class="form-row">
820
840
  <input type="checkbox" id="node-input-llmIncludeFlowContext" style="display:inline-block; width:auto; vertical-align:top;">
821
841
  <label style="width:auto" for="node-input-llmIncludeFlowContext">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmIncludeFlowContext"></span></label>
822
842
  </div>
823
843
 
824
- <div class="form-row">
825
- <input type="checkbox" id="node-input-llmIncludeDocsSnippets" style="display:inline-block; width:auto; vertical-align:top;">
826
- <label style="width:auto" for="node-input-llmIncludeDocsSnippets">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmIncludeDocsSnippets"></span></label>
827
- </div>
828
-
829
844
  <div class="form-row" id="knx-ai-docs-language-row">
830
845
  <label style="width:290px" for="node-input-llmDocsLanguage"><i class="fa fa-language"></i> <span data-i18n="knxUltimateAI.properties.llmDocsLanguage"></span></label>
831
846
  <select style="width:100%" id="node-input-llmDocsLanguage">
@@ -837,10 +852,6 @@
837
852
  <option value="zh-CN">简体中文</option>
838
853
  </select>
839
854
  </div>
840
- <div class="form-row">
841
- <label style="width:290px" for="node-input-llmSystemPrompt"><i class="fa fa-commenting-o"></i> <span data-i18n="knxUltimateAI.properties.llmSystemPrompt"></span></label>
842
- <textarea style="width:100%; height:110px;" id="node-input-llmSystemPrompt" data-i18n="[placeholder]knxUltimateAI.placeholder.llmSystemPrompt"></textarea>
843
- </div>
844
855
  </div>
845
856
  </div>
846
857
 
@@ -850,9 +861,6 @@
850
861
  <label style="width:290px" for="node-input-llmBaseUrl"><i class="fa fa-link"></i> <span data-i18n="knxUltimateAI.properties.llmBaseUrl"></span></label>
851
862
  <input type="text" id="node-input-llmBaseUrl" data-i18n="[placeholder]knxUltimateAI.placeholder.llmBaseUrl">
852
863
  </div>
853
- <div class="form-tips" id="knx-ai-ollama-warning" style="display:none;">
854
- <span data-i18n="knxUltimateAI.messages.ollamaNotSupported"></span>
855
- </div>
856
864
  <div class="form-row" id="knx-ai-ollama-install-row" style="display:none;">
857
865
  <label style="width:290px"><i class="fa fa-download"></i> Ollama</label>
858
866
  <div class="knx-ai-ollama-actions">
@@ -864,14 +872,6 @@
864
872
  </button>
865
873
  </div>
866
874
  </div>
867
- <div class="form-tips" id="knx-ai-ollama-steps" style="display:none;">
868
- <span data-i18n="knxUltimateAI.messages.ollamaInstallSteps"></span>
869
- </div>
870
- <div class="form-tips" id="knx-ai-ollama-empty-help" style="display:none;">
871
- <span data-i18n="knxUltimateAI.messages.ollamaNoModels"></span>
872
- <br>
873
- <code>ollama pull llama3.1</code>
874
- </div>
875
875
  </div>
876
876
  </div>
877
877
  </div>