node-red-contrib-knx-ultimate 6.1.0 → 6.2.0

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/examples/KNX AI - Conversational Control with Confirmation.json +374 -0
  3. package/examples/KNX AI - Telegrambot Direct Chat.json +221 -0
  4. package/examples/Matter Controller - Semantic Flow Input.json +248 -0
  5. package/nodes/commonFunctions.js +0 -174
  6. package/nodes/knxUltimateAI.html +254 -90
  7. package/nodes/knxUltimateAI.js +1286 -61
  8. package/nodes/knxUltimateMatterBridge.html +144 -11
  9. package/nodes/knxUltimateMatterControllerDevice.html +80 -55
  10. package/nodes/locales/de/knxUltimateAI.html +38 -2
  11. package/nodes/locales/de/knxUltimateAI.json +16 -3
  12. package/nodes/locales/de/knxUltimateMatterBridge.html +1 -1
  13. package/nodes/locales/de/knxUltimateMatterBridge.json +14 -0
  14. package/nodes/locales/de/knxUltimateMatterControllerDevice.html +2 -2
  15. package/nodes/locales/de/knxUltimateMatterControllerDevice.json +2 -1
  16. package/nodes/locales/en/knxUltimateAI.html +38 -2
  17. package/nodes/locales/en/knxUltimateAI.json +16 -3
  18. package/nodes/locales/en/knxUltimateMatterBridge.html +1 -1
  19. package/nodes/locales/en/knxUltimateMatterBridge.json +14 -0
  20. package/nodes/locales/en/knxUltimateMatterControllerDevice.html +2 -2
  21. package/nodes/locales/en/knxUltimateMatterControllerDevice.json +2 -1
  22. package/nodes/locales/es/knxUltimateAI.html +38 -2
  23. package/nodes/locales/es/knxUltimateAI.json +16 -3
  24. package/nodes/locales/es/knxUltimateMatterBridge.html +1 -1
  25. package/nodes/locales/es/knxUltimateMatterBridge.json +14 -0
  26. package/nodes/locales/es/knxUltimateMatterControllerDevice.html +2 -2
  27. package/nodes/locales/es/knxUltimateMatterControllerDevice.json +2 -1
  28. package/nodes/locales/fr/knxUltimateAI.html +38 -2
  29. package/nodes/locales/fr/knxUltimateAI.json +16 -3
  30. package/nodes/locales/fr/knxUltimateMatterBridge.html +1 -1
  31. package/nodes/locales/fr/knxUltimateMatterBridge.json +14 -0
  32. package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +2 -2
  33. package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +2 -1
  34. package/nodes/locales/it/knxUltimateAI.html +38 -2
  35. package/nodes/locales/it/knxUltimateAI.json +16 -3
  36. package/nodes/locales/it/knxUltimateMatterBridge.html +1 -1
  37. package/nodes/locales/it/knxUltimateMatterBridge.json +14 -0
  38. package/nodes/locales/it/knxUltimateMatterControllerDevice.html +2 -2
  39. package/nodes/locales/it/knxUltimateMatterControllerDevice.json +2 -1
  40. package/nodes/locales/zh-CN/knxUltimateAI.html +38 -2
  41. package/nodes/locales/zh-CN/knxUltimateAI.json +16 -3
  42. package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +1 -1
  43. package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +14 -0
  44. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +2 -2
  45. package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +2 -1
  46. package/nodes/utils/sysLogger.js +0 -109
  47. package/package.json +1 -2
  48. package/resources/KNXAIChatAdapterMappings.js +87 -0
  49. package/nodes/plugins/knxUltimateMonitor-sidebar-plugin.html +0 -922
@@ -1,4 +1,5 @@
1
1
  <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/htmlUtils.js"></script>
2
+ <script type="text/javascript" src="resources/node-red-contrib-knx-ultimate/KNXAIChatAdapterMappings.js"></script>
2
3
  <style type="text/css">
3
4
  #knx-ai-models-status {
4
5
  margin-top: 6px;
@@ -25,6 +26,20 @@
25
26
  margin: 0 !important;
26
27
  }
27
28
 
29
+ .knx-ai-chat-adapter-code {
30
+ display: block;
31
+ width: 100% !important;
32
+ min-width: 100%;
33
+ max-width: none !important;
34
+ min-height: 190px;
35
+ 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;
41
+ }
42
+
28
43
  #knx-ai-ollama-steps,
29
44
  #knx-ai-ollama-empty-help {
30
45
  margin-left: 290px;
@@ -89,18 +104,24 @@
89
104
  llmIncludeRaw: { value: false },
90
105
  llmIncludeFlowContext: { value: true },
91
106
  llmIncludeDocsSnippets: { value: true },
92
- llmDocsLanguage: { value: "en" }
107
+ llmDocsLanguage: { value: "en" },
108
+ llmAllowKnxCommands: { value: false },
109
+ llmRequireCommandConfirmation: { value: true },
110
+ chatAdapterPreset: { value: "none" },
111
+ chatInputCode: { value: "" },
112
+ chatOutputCode: { value: "" }
93
113
  },
94
114
  credentials: {
95
115
  llmApiKey: { type: "password" }
96
116
  },
97
117
  inputs: 1,
98
- outputs: 3,
118
+ outputs: 4,
99
119
  outputLabels: function (i) {
100
120
  switch (i) {
101
121
  case 0: return RED._('knxUltimateAI.outputs.summary');
102
122
  case 1: return RED._('knxUltimateAI.outputs.anomalies');
103
123
  case 2: return RED._('knxUltimateAI.outputs.assistant');
124
+ case 3: return RED._('knxUltimateAI.outputs.knxCommands');
104
125
  }
105
126
  },
106
127
  icon: "node-eye-icon.svg",
@@ -118,17 +139,77 @@
118
139
  const OLLAMA_DEFAULT_MODEL = "llama3.1";
119
140
  const ANTHROPIC_DEFAULT_MODEL = "claude-opus-4-8";
120
141
 
121
- $("#knx-ai-accordion").accordion({
122
- header: "h3",
123
- heightStyle: "content",
124
- collapsible: true,
125
- active: 0
126
- });
142
+ // Reuse the same left-hand vertical tabs as the Matter nodes.
143
+ if ($("#knxUltimateMatterControllerDeviceVerticalTabs").length === 0) {
144
+ $("head").append(`
145
+ <style id="knxUltimateMatterControllerDeviceVerticalTabs">
146
+ .hue-vertical-tabs.ui-tabs.ui-widget.ui-widget-content.ui-corner-all {
147
+ display: flex;
148
+ border: none;
149
+ padding: 0;
150
+ }
151
+ .hue-vertical-tabs > ul.ui-tabs-nav {
152
+ flex: 0 0 180px;
153
+ border-right: 1px solid #ccc;
154
+ border-left: none;
155
+ border-top: none;
156
+ border-bottom: none;
157
+ padding: 0.5em 0.3em;
158
+ }
159
+ .hue-vertical-tabs > ul.ui-tabs-nav li {
160
+ float: none;
161
+ width: 100%;
162
+ margin: 0 0 2px 0;
163
+ }
164
+ .hue-vertical-tabs > ul.ui-tabs-nav li a {
165
+ display: block;
166
+ width: 100%;
167
+ white-space: normal;
168
+ position: relative;
169
+ border-bottom: none !important;
170
+ }
171
+ .hue-vertical-tabs > ul.ui-tabs-nav li.ui-tabs-active {
172
+ border-bottom: none !important;
173
+ }
174
+ .hue-vertical-tabs > ul.ui-tabs-nav li.ui-tabs-active a::after {
175
+ content: "";
176
+ position: absolute;
177
+ left: 0;
178
+ bottom: 0;
179
+ width: 50%;
180
+ height: 3px;
181
+ background: currentColor;
182
+ }
183
+ .hue-vertical-tabs .ui-tabs-panel {
184
+ flex: 1;
185
+ padding: 0.8em 1em;
186
+ box-sizing: border-box;
187
+ border: none;
188
+ background: transparent;
189
+ }
190
+ .hue-vertical-tabs .form-row > dt {
191
+ flex: 1 1 auto;
192
+ margin: 0;
193
+ }
194
+ .hue-vertical-tabs hr {
195
+ width: 100%;
196
+ border: 0;
197
+ border-top: 1px solid #ccc;
198
+ margin: 8px 0;
199
+ }
200
+ </style>`);
201
+ }
202
+ const $tabs = $("#knx-ai-tabs");
203
+ $tabs.addClass("hue-vertical-tabs");
204
+ $tabs.tabs();
205
+ $tabs.find("ul").addClass("ui-tabs-nav");
206
+ $tabs.find("li").removeClass("ui-corner-top").addClass("ui-corner-left");
127
207
 
128
208
  const toggleLLM = () => {
129
209
  const enabled = $("#node-input-llmEnabled").is(":checked");
130
210
  $("#knx-ai-llm-connection-settings").toggle(enabled);
131
211
  $("#knx-ai-llm-context-settings").toggle(enabled);
212
+ $("#knx-ai-llm-advanced-settings").toggle(enabled);
132
213
  };
133
214
  $("#node-input-llmEnabled").on("change", toggleLLM);
134
215
 
@@ -144,9 +225,16 @@
144
225
  };
145
226
  $("#node-input-llmIncludeDocsSnippets").on("change", toggleDocsContext);
146
227
 
228
+ const toggleCommandConfirmation = () => {
229
+ const enabled = $("#node-input-llmAllowKnxCommands").is(":checked");
230
+ $("#knx-ai-command-confirmation-row").toggle(enabled);
231
+ };
232
+ $("#node-input-llmAllowKnxCommands").on("change", toggleCommandConfirmation);
233
+
147
234
  toggleLLM();
148
235
  toggleHistoryArchive();
149
236
  toggleDocsContext();
237
+ toggleCommandConfirmation();
150
238
 
151
239
  const nodeId = this.id;
152
240
  const resolveAdminRoot = () => {
@@ -170,6 +258,48 @@
170
258
  return value;
171
259
  };
172
260
 
261
+ const configureChatAdapterPicker = () => {
262
+ const adapters = Array.isArray(window.KNXAIChatAdapterMappings)
263
+ ? window.KNXAIChatAdapterMappings.slice()
264
+ : [];
265
+ const $picker = $("#node-input-chatAdapterPreset");
266
+ if (!$picker.length) return;
267
+ $picker.empty();
268
+ $("<option></option>")
269
+ .attr("value", "none")
270
+ .text(t("knxUltimateAI.selectlists.chatAdapter.none", "No adapter"))
271
+ .appendTo($picker);
272
+ adapters.forEach(function (adapter) {
273
+ if (!adapter || !adapter.id) return;
274
+ $("<option></option>")
275
+ .attr("value", adapter.id)
276
+ .text(adapter.title || adapter.id)
277
+ .appendTo($picker);
278
+ });
279
+
280
+ const configuredPreset = String(this.chatAdapterPreset || "none");
281
+ if (configuredPreset !== "none" && !adapters.some(adapter => adapter && adapter.id === configuredPreset)) {
282
+ $("<option></option>")
283
+ .attr("value", configuredPreset)
284
+ .text(configuredPreset)
285
+ .appendTo($picker);
286
+ }
287
+ $picker.val(configuredPreset);
288
+ $picker.on("change", function () {
289
+ const selected = String($(this).val() || "none");
290
+ if (selected === "none") {
291
+ $("#node-input-chatInputCode").val("");
292
+ $("#node-input-chatOutputCode").val("");
293
+ return;
294
+ }
295
+ const adapter = adapters.find(item => item && item.id === selected);
296
+ if (!adapter) return;
297
+ $("#node-input-chatInputCode").val(adapter.inputCode || "");
298
+ $("#node-input-chatOutputCode").val(adapter.outputCode || "");
299
+ });
300
+ };
301
+ configureChatAdapterPicker.call(this);
302
+
173
303
  $("#knx-ai-open-web-page-vue").on("click", function (evt) {
174
304
  evt.preventDefault();
175
305
  const adminRoot = resolveAdminRoot();
@@ -414,9 +544,18 @@
414
544
  </div>
415
545
  </div>
416
546
 
417
- <div id="knx-ai-accordion">
418
- <h3><span data-i18n="knxUltimateAI.sections.capture"></span></h3>
419
- <div>
547
+ <div id="knx-ai-tabs" style="margin-top:12px;">
548
+ <ul>
549
+ <li><a href="#knx-ai-tab-llm-connection"><i class="fa fa-magic"></i> <span data-i18n="knxUltimateAI.sections.quickSetup"></span></a></li>
550
+ <li><a href="#knx-ai-tab-capture"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.sections.capture"></span></a></li>
551
+ <li><a href="#knx-ai-tab-storage"><i class="fa fa-database"></i> <span data-i18n="knxUltimateAI.sections.storage"></span></a></li>
552
+ <li><a href="#knx-ai-tab-detection"><i class="fa fa-search"></i> <span data-i18n="knxUltimateAI.sections.detection"></span></a></li>
553
+ <li><a href="#knx-ai-tab-chat-adapter"><i class="fa fa-plug"></i> <span data-i18n="knxUltimateAI.sections.chatAdapter"></span></a></li>
554
+ <li><a href="#knx-ai-tab-llm-context"><i class="fa fa-comments"></i> <span data-i18n="knxUltimateAI.sections.llmContext"></span></a></li>
555
+ <li><a href="#knx-ai-tab-advanced"><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.sections.advanced"></span></a></li>
556
+ </ul>
557
+
558
+ <div id="knx-ai-tab-capture">
420
559
  <div class="form-row">
421
560
  <input type="checkbox" id="node-input-notifywrite" style="display:inline-block; width:auto; vertical-align:top;">
422
561
  <label style="width:auto" for="node-input-notifywrite">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.notifywrite"></span></label>
@@ -431,8 +570,7 @@
431
570
  </div>
432
571
  </div>
433
572
 
434
- <h3><span data-i18n="knxUltimateAI.sections.storage"></span></h3>
435
- <div>
573
+ <div id="knx-ai-tab-storage">
436
574
  <div class="form-row">
437
575
  <label style="width:290px" for="node-input-historyWindowSec"><i class="fa fa-history"></i> <span data-i18n="knxUltimateAI.properties.historyWindowSec"></span></label>
438
576
  <input style="width:90px" type="number" id="node-input-historyWindowSec">
@@ -449,18 +587,56 @@
449
587
  <label style="width:290px" for="node-input-emitIntervalSec"><i class="fa fa-send"></i> <span data-i18n="knxUltimateAI.properties.emitIntervalSec"></span></label>
450
588
  <input style="width:90px" type="number" id="node-input-emitIntervalSec">
451
589
  </div>
590
+ <div class="form-row">
591
+ <label style="width:290px" for="node-input-analysisWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.analysisWindowSec"></span></label>
592
+ <input style="width:90px" type="number" id="node-input-analysisWindowSec">
593
+ </div>
594
+ <div class="form-row">
595
+ <label style="width:290px" for="node-input-maxEvents"><i class="fa fa-bars"></i> <span data-i18n="knxUltimateAI.properties.maxEvents"></span></label>
596
+ <input style="width:90px" type="number" id="node-input-maxEvents">
597
+ </div>
598
+ <div class="form-row">
599
+ <label style="width:290px" for="node-input-topN"><i class="fa fa-list-ol"></i> <span data-i18n="knxUltimateAI.properties.topN"></span></label>
600
+ <input style="width:90px" type="number" id="node-input-topN">
601
+ </div>
452
602
  </div>
453
603
 
454
- <h3><span data-i18n="knxUltimateAI.sections.detection"></span></h3>
455
- <div>
604
+ <div id="knx-ai-tab-detection">
456
605
  <div class="form-row">
457
606
  <input type="checkbox" id="node-input-enablePattern" style="display:inline-block; width:auto; vertical-align:top;">
458
607
  <label style="width:auto" for="node-input-enablePattern">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.enablePattern"></span></label>
459
608
  </div>
609
+ <div class="form-row">
610
+ <label style="width:290px" for="node-input-patternMaxLagMs"><i class="fa fa-arrows-h"></i> <span data-i18n="knxUltimateAI.properties.patternMaxLagMs"></span></label>
611
+ <input style="width:120px" type="number" id="node-input-patternMaxLagMs">
612
+ </div>
613
+ <div class="form-row">
614
+ <label style="width:290px" for="node-input-patternMinCount"><i class="fa fa-filter"></i> <span data-i18n="knxUltimateAI.properties.patternMinCount"></span></label>
615
+ <input style="width:90px" type="number" id="node-input-patternMinCount">
616
+ </div>
617
+ <div class="form-row">
618
+ <label style="width:290px" for="node-input-rateWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.rateWindowSec"></span></label>
619
+ <input style="width:90px" type="number" id="node-input-rateWindowSec">
620
+ </div>
621
+ <div class="form-row">
622
+ <label style="width:290px" for="node-input-maxTelegramPerSecOverall"><i class="fa fa-tachometer"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecOverall"></span></label>
623
+ <input style="width:90px" type="number" id="node-input-maxTelegramPerSecOverall">
624
+ </div>
625
+ <div class="form-row">
626
+ <label style="width:290px" for="node-input-maxTelegramPerSecPerGA"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecPerGA"></span></label>
627
+ <input style="width:90px" type="number" id="node-input-maxTelegramPerSecPerGA">
628
+ </div>
629
+ <div class="form-row">
630
+ <label style="width:290px" for="node-input-flapWindowSec"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.properties.flapWindowSec"></span></label>
631
+ <input style="width:90px" type="number" id="node-input-flapWindowSec">
632
+ </div>
633
+ <div class="form-row">
634
+ <label style="width:290px" for="node-input-flapMaxChanges"><i class="fa fa-random"></i> <span data-i18n="knxUltimateAI.properties.flapMaxChanges"></span></label>
635
+ <input style="width:90px" type="number" id="node-input-flapMaxChanges">
636
+ </div>
460
637
  </div>
461
638
 
462
- <h3><span data-i18n="knxUltimateAI.sections.llmConnection"></span></h3>
463
- <div>
639
+ <div id="knx-ai-tab-llm-connection">
464
640
  <div class="form-row">
465
641
  <input type="checkbox" id="node-input-llmEnabled" style="display:inline-block; width:auto; vertical-align:top;">
466
642
  <label style="width:auto" for="node-input-llmEnabled">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmEnabled"></span></label>
@@ -475,15 +651,6 @@
475
651
  <option value="ollama" data-i18n="knxUltimateAI.selectlists.llmProvider.ollama"></option>
476
652
  </select>
477
653
  </div>
478
- <div class="form-tips" id="knx-ai-ollama-warning" style="display:none;">
479
- <span data-i18n="knxUltimateAI.messages.ollamaNotSupported"></span>
480
- </div>
481
-
482
- <div class="form-row">
483
- <label style="width:290px" for="node-input-llmBaseUrl"><i class="fa fa-link"></i> <span data-i18n="knxUltimateAI.properties.llmBaseUrl"></span></label>
484
- <input type="text" id="node-input-llmBaseUrl" data-i18n="[placeholder]knxUltimateAI.placeholder.llmBaseUrl">
485
- </div>
486
-
487
654
  <div class="form-row" id="knx-ai-apikey-row">
488
655
  <label style="width:290px" for="node-input-llmApiKey"><i class="fa fa-key"></i> <span data-i18n="knxUltimateAI.properties.llmApiKey"></span></label>
489
656
  <input type="password" id="node-input-llmApiKey" data-i18n="[placeholder]knxUltimateAI.placeholder.llmApiKey">
@@ -498,31 +665,41 @@
498
665
  </div>
499
666
  <datalist id="knx-ai-llmModels"></datalist>
500
667
  <div class="form-tips" id="knx-ai-models-status"></div>
501
- <div class="form-row" id="knx-ai-ollama-install-row" style="display:none;">
502
- <label style="width:290px"><i class="fa fa-download"></i> Ollama</label>
503
- <div class="knx-ai-ollama-actions">
504
- <button type="button" class="red-ui-button" id="knx-ai-downloadOllamaModel">
505
- <i class="fa fa-cloud-download"></i> <span data-i18n="knxUltimateAI.buttons.downloadOllamaModel"></span>
506
- </button>
507
- <button type="button" class="red-ui-button" id="knx-ai-installOllamaModel">
508
- <i class="fa fa-download"></i> <span data-i18n="knxUltimateAI.buttons.installOllamaModel"></span>
509
- </button>
510
- </div>
511
- </div>
512
- <div class="form-tips" id="knx-ai-ollama-steps" style="display:none;">
513
- <span data-i18n="knxUltimateAI.messages.ollamaInstallSteps"></span>
668
+ <div class="form-row">
669
+ <input type="checkbox" id="node-input-llmAllowKnxCommands" style="display:inline-block; width:auto; vertical-align:top;">
670
+ <label style="width:auto" for="node-input-llmAllowKnxCommands">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmAllowKnxCommands"></span></label>
514
671
  </div>
515
- <div class="form-tips" id="knx-ai-ollama-empty-help" style="display:none;">
516
- <span data-i18n="knxUltimateAI.messages.ollamaNoModels"></span>
517
- <br>
518
- <code>ollama pull llama3.1</code>
672
+ <div class="form-row" id="knx-ai-command-confirmation-row">
673
+ <input type="checkbox" id="node-input-llmRequireCommandConfirmation" style="display:inline-block; width:auto; vertical-align:top;">
674
+ <label style="width:auto" for="node-input-llmRequireCommandConfirmation">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmRequireCommandConfirmation"></span></label>
519
675
  </div>
676
+ </div>
677
+ </div>
520
678
 
679
+ <div id="knx-ai-tab-chat-adapter">
680
+ <div class="form-tips" style="margin-bottom:12px;">
681
+ <span data-i18n="knxUltimateAI.messages.chatAdapterIntro"></span>
682
+ </div>
683
+ <div class="form-row">
684
+ <label style="width:290px" for="node-input-chatAdapterPreset"><i class="fa fa-plug"></i> <span data-i18n="knxUltimateAI.properties.chatAdapterPreset"></span></label>
685
+ <select style="width:100%" id="node-input-chatAdapterPreset">
686
+ <option value="none" data-i18n="knxUltimateAI.selectlists.chatAdapter.none"></option>
687
+ </select>
688
+ </div>
689
+ <div class="form-row">
690
+ <label style="width:100%" for="node-input-chatInputCode"><i class="fa fa-sign-in"></i> <span data-i18n="knxUltimateAI.properties.chatInputCode"></span></label>
691
+ <textarea id="node-input-chatInputCode" class="knx-ai-chat-adapter-code" spellcheck="false"></textarea>
692
+ </div>
693
+ <div class="form-row">
694
+ <label style="width:100%" for="node-input-chatOutputCode"><i class="fa fa-sign-out"></i> <span data-i18n="knxUltimateAI.properties.chatOutputCode"></span></label>
695
+ <textarea id="node-input-chatOutputCode" class="knx-ai-chat-adapter-code" spellcheck="false"></textarea>
696
+ </div>
697
+ <div class="form-tips">
698
+ <span data-i18n="knxUltimateAI.messages.chatAdapterCodeHelp"></span>
521
699
  </div>
522
700
  </div>
523
701
 
524
- <h3><span data-i18n="knxUltimateAI.sections.llmContext"></span></h3>
525
- <div>
702
+ <div id="knx-ai-tab-llm-context">
526
703
  <div id="knx-ai-llm-context-settings">
527
704
  <div class="form-row">
528
705
  <input type="checkbox" id="node-input-llmIncludeRaw" style="display:inline-block; width:auto; vertical-align:top;">
@@ -550,54 +727,41 @@
550
727
  <option value="zh-CN">简体中文</option>
551
728
  </select>
552
729
  </div>
730
+ <div class="form-row">
731
+ <label style="width:290px" for="node-input-llmSystemPrompt"><i class="fa fa-commenting-o"></i> <span data-i18n="knxUltimateAI.properties.llmSystemPrompt"></span></label>
732
+ <textarea style="width:100%; height:110px;" id="node-input-llmSystemPrompt" data-i18n="[placeholder]knxUltimateAI.placeholder.llmSystemPrompt"></textarea>
733
+ </div>
553
734
  </div>
554
735
  </div>
555
736
 
556
- <h3><span data-i18n="knxUltimateAI.sections.advanced"></span></h3>
557
- <div>
558
- <div class="form-row">
559
- <label style="width:290px" for="node-input-analysisWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.analysisWindowSec"></span></label>
560
- <input style="width:90px" type="number" id="node-input-analysisWindowSec">
561
- </div>
562
- <div class="form-row">
563
- <label style="width:290px" for="node-input-maxEvents"><i class="fa fa-bars"></i> <span data-i18n="knxUltimateAI.properties.maxEvents"></span></label>
564
- <input style="width:90px" type="number" id="node-input-maxEvents">
565
- </div>
566
- <div class="form-row">
567
- <label style="width:290px" for="node-input-topN"><i class="fa fa-list-ol"></i> <span data-i18n="knxUltimateAI.properties.topN"></span></label>
568
- <input style="width:90px" type="number" id="node-input-topN">
569
- </div>
570
- <div class="form-row">
571
- <label style="width:290px" for="node-input-patternMaxLagMs"><i class="fa fa-arrows-h"></i> <span data-i18n="knxUltimateAI.properties.patternMaxLagMs"></span></label>
572
- <input style="width:120px" type="number" id="node-input-patternMaxLagMs">
573
- </div>
574
- <div class="form-row">
575
- <label style="width:290px" for="node-input-patternMinCount"><i class="fa fa-filter"></i> <span data-i18n="knxUltimateAI.properties.patternMinCount"></span></label>
576
- <input style="width:90px" type="number" id="node-input-patternMinCount">
577
- </div>
578
- <div class="form-row">
579
- <label style="width:290px" for="node-input-rateWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.rateWindowSec"></span></label>
580
- <input style="width:90px" type="number" id="node-input-rateWindowSec">
581
- </div>
582
- <div class="form-row">
583
- <label style="width:290px" for="node-input-maxTelegramPerSecOverall"><i class="fa fa-tachometer"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecOverall"></span></label>
584
- <input style="width:90px" type="number" id="node-input-maxTelegramPerSecOverall">
585
- </div>
586
- <div class="form-row">
587
- <label style="width:290px" for="node-input-maxTelegramPerSecPerGA"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecPerGA"></span></label>
588
- <input style="width:90px" type="number" id="node-input-maxTelegramPerSecPerGA">
589
- </div>
590
- <div class="form-row">
591
- <label style="width:290px" for="node-input-flapWindowSec"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.properties.flapWindowSec"></span></label>
592
- <input style="width:90px" type="number" id="node-input-flapWindowSec">
593
- </div>
594
- <div class="form-row">
595
- <label style="width:290px" for="node-input-flapMaxChanges"><i class="fa fa-random"></i> <span data-i18n="knxUltimateAI.properties.flapMaxChanges"></span></label>
596
- <input style="width:90px" type="number" id="node-input-flapMaxChanges">
597
- </div>
598
- <div class="form-row">
599
- <label style="width:290px" for="node-input-llmSystemPrompt"><i class="fa fa-commenting-o"></i> <span data-i18n="knxUltimateAI.properties.llmSystemPrompt"></span></label>
600
- <textarea style="width:100%; height:110px;" id="node-input-llmSystemPrompt" data-i18n="[placeholder]knxUltimateAI.placeholder.llmSystemPrompt"></textarea>
737
+ <div id="knx-ai-tab-advanced">
738
+ <div id="knx-ai-llm-advanced-settings">
739
+ <div class="form-row">
740
+ <label style="width:290px" for="node-input-llmBaseUrl"><i class="fa fa-link"></i> <span data-i18n="knxUltimateAI.properties.llmBaseUrl"></span></label>
741
+ <input type="text" id="node-input-llmBaseUrl" data-i18n="[placeholder]knxUltimateAI.placeholder.llmBaseUrl">
742
+ </div>
743
+ <div class="form-tips" id="knx-ai-ollama-warning" style="display:none;">
744
+ <span data-i18n="knxUltimateAI.messages.ollamaNotSupported"></span>
745
+ </div>
746
+ <div class="form-row" id="knx-ai-ollama-install-row" style="display:none;">
747
+ <label style="width:290px"><i class="fa fa-download"></i> Ollama</label>
748
+ <div class="knx-ai-ollama-actions">
749
+ <button type="button" class="red-ui-button" id="knx-ai-downloadOllamaModel">
750
+ <i class="fa fa-cloud-download"></i> <span data-i18n="knxUltimateAI.buttons.downloadOllamaModel"></span>
751
+ </button>
752
+ <button type="button" class="red-ui-button" id="knx-ai-installOllamaModel">
753
+ <i class="fa fa-download"></i> <span data-i18n="knxUltimateAI.buttons.installOllamaModel"></span>
754
+ </button>
755
+ </div>
756
+ </div>
757
+ <div class="form-tips" id="knx-ai-ollama-steps" style="display:none;">
758
+ <span data-i18n="knxUltimateAI.messages.ollamaInstallSteps"></span>
759
+ </div>
760
+ <div class="form-tips" id="knx-ai-ollama-empty-help" style="display:none;">
761
+ <span data-i18n="knxUltimateAI.messages.ollamaNoModels"></span>
762
+ <br>
763
+ <code>ollama pull llama3.1</code>
764
+ </div>
601
765
  </div>
602
766
  </div>
603
767
  </div>