node-red-contrib-knx-ultimate 6.1.1 → 6.2.1

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,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,73 @@
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
+
43
+ #knx-ai-accordion {
44
+ margin-top: 12px;
45
+ }
46
+
47
+ #knx-ai-accordion>h3 {
48
+ margin-top: 6px;
49
+ font-size: 14px;
50
+ font-weight: 600;
51
+ }
52
+
53
+ #knx-ai-accordion>.ui-accordion-content {
54
+ padding: 14px 16px;
55
+ overflow: visible;
56
+ }
57
+
58
+ #knx-ai-accordion .knx-ai-accordion-subsection {
59
+ padding: 0;
60
+ }
61
+
62
+ #knx-ai-accordion .knx-ai-accordion-subsection+.knx-ai-accordion-subsection {
63
+ margin-top: 18px;
64
+ padding-top: 16px;
65
+ border-top: 1px solid var(--red-ui-form-input-border-color, #ccc);
66
+ }
67
+
68
+ #knx-ai-accordion .knx-ai-accordion-subsection>h4 {
69
+ margin: 0 0 14px;
70
+ color: var(--red-ui-primary-text-color, #555);
71
+ font-size: 14px;
72
+ font-weight: 600;
73
+ }
74
+
75
+ #knx-ai-accordion .knx-ai-accordion-subsection>h4 i {
76
+ width: 18px;
77
+ text-align: center;
78
+ }
79
+
80
+ #knx-ai-accordion .knx-ai-inline-subsection {
81
+ margin-top: 18px;
82
+ padding-top: 14px;
83
+ border-top: 1px dashed var(--red-ui-form-input-border-color, #ccc);
84
+ }
85
+
86
+ #knx-ai-accordion .knx-ai-inline-subsection>h5 {
87
+ margin: 0 0 12px;
88
+ font-size: 13px;
89
+ font-weight: 600;
90
+ }
91
+
92
+ #knx-ai-accordion .knx-ai-accordion-mount>div {
93
+ display: block;
94
+ }
95
+
28
96
  #knx-ai-ollama-steps,
29
97
  #knx-ai-ollama-empty-help {
30
98
  margin-left: 290px;
@@ -89,18 +157,32 @@
89
157
  llmIncludeRaw: { value: false },
90
158
  llmIncludeFlowContext: { value: true },
91
159
  llmIncludeDocsSnippets: { value: true },
92
- llmDocsLanguage: { value: "en" }
160
+ llmDocsLanguage: { value: "en" },
161
+ llmAllowKnxCommands: { value: false },
162
+ llmRequireCommandConfirmation: { value: true },
163
+ chatAdapterPreset: { value: "none" },
164
+ chatInputCode: { value: "" },
165
+ chatOutputCode: { value: "" },
166
+ proactiveEnabled: { value: false },
167
+ proactiveRecipient: { value: "" },
168
+ proactiveOpenMinutes: { value: 120, required: true, validate: RED.validators.number() },
169
+ proactiveCooldownMinutes: { value: 360, required: true, validate: RED.validators.number() },
170
+ proactiveQuietStart: { value: "23:00" },
171
+ proactiveQuietEnd: { value: "07:00" },
172
+ homeMemoryMaxKb: { value: 256, required: true, validate: RED.validators.number() },
173
+ aiEducation: { value: "" }
93
174
  },
94
175
  credentials: {
95
176
  llmApiKey: { type: "password" }
96
177
  },
97
178
  inputs: 1,
98
- outputs: 3,
179
+ outputs: 4,
99
180
  outputLabels: function (i) {
100
181
  switch (i) {
101
182
  case 0: return RED._('knxUltimateAI.outputs.summary');
102
183
  case 1: return RED._('knxUltimateAI.outputs.anomalies');
103
184
  case 2: return RED._('knxUltimateAI.outputs.assistant');
185
+ case 3: return RED._('knxUltimateAI.outputs.knxCommands');
104
186
  }
105
187
  },
106
188
  icon: "node-eye-icon.svg",
@@ -118,17 +200,31 @@
118
200
  const OLLAMA_DEFAULT_MODEL = "llama3.1";
119
201
  const ANTHROPIC_DEFAULT_MODEL = "claude-opus-4-8";
120
202
 
203
+ const mountAccordionSection = (mountSelector, sectionSelector) => {
204
+ const $mount = $(mountSelector);
205
+ const $section = $(sectionSelector);
206
+ if ($mount.length && $section.length) $mount.append($section);
207
+ };
208
+ mountAccordionSection("#knx-ai-mount-assistant-setup", "#knx-ai-tab-llm-connection");
209
+ mountAccordionSection("#knx-ai-mount-assistant-context", "#knx-ai-tab-llm-context");
210
+ mountAccordionSection("#knx-ai-mount-assistant-advanced", "#knx-ai-tab-advanced");
211
+ mountAccordionSection("#knx-ai-mount-chat-adapter", "#knx-ai-tab-chat-adapter");
212
+ mountAccordionSection("#knx-ai-mount-home-intelligence", "#knx-ai-tab-home-intelligence");
213
+ mountAccordionSection("#knx-ai-mount-knx-capture", "#knx-ai-tab-capture");
214
+ mountAccordionSection("#knx-ai-mount-knx-storage", "#knx-ai-tab-storage");
215
+ mountAccordionSection("#knx-ai-mount-knx-detection", "#knx-ai-tab-detection");
121
216
  $("#knx-ai-accordion").accordion({
122
- header: "h3",
123
- heightStyle: "content",
217
+ active: 0,
218
+ animate: false,
124
219
  collapsible: true,
125
- active: 0
220
+ heightStyle: "content"
126
221
  });
127
222
 
128
223
  const toggleLLM = () => {
129
224
  const enabled = $("#node-input-llmEnabled").is(":checked");
130
225
  $("#knx-ai-llm-connection-settings").toggle(enabled);
131
226
  $("#knx-ai-llm-context-settings").toggle(enabled);
227
+ $("#knx-ai-llm-advanced-settings").toggle(enabled);
132
228
  };
133
229
  $("#node-input-llmEnabled").on("change", toggleLLM);
134
230
 
@@ -144,9 +240,16 @@
144
240
  };
145
241
  $("#node-input-llmIncludeDocsSnippets").on("change", toggleDocsContext);
146
242
 
243
+ const toggleCommandConfirmation = () => {
244
+ const enabled = $("#node-input-llmAllowKnxCommands").is(":checked");
245
+ $("#knx-ai-command-confirmation-row").toggle(enabled);
246
+ };
247
+ $("#node-input-llmAllowKnxCommands").on("change", toggleCommandConfirmation);
248
+
147
249
  toggleLLM();
148
250
  toggleHistoryArchive();
149
251
  toggleDocsContext();
252
+ toggleCommandConfirmation();
150
253
 
151
254
  const nodeId = this.id;
152
255
  const resolveAdminRoot = () => {
@@ -170,6 +273,55 @@
170
273
  return value;
171
274
  };
172
275
 
276
+ const configureChatAdapterPicker = () => {
277
+ const adapters = Array.isArray(window.KNXAIChatAdapterMappings)
278
+ ? window.KNXAIChatAdapterMappings.slice()
279
+ : [];
280
+ const $picker = $("#node-input-chatAdapterPreset");
281
+ if (!$picker.length) return;
282
+ $picker.empty();
283
+ $("<option></option>")
284
+ .attr("value", "none")
285
+ .text(t("knxUltimateAI.selectlists.chatAdapter.none", "No adapter"))
286
+ .appendTo($picker);
287
+ adapters.forEach(function (adapter) {
288
+ if (!adapter || !adapter.id) return;
289
+ $("<option></option>")
290
+ .attr("value", adapter.id)
291
+ .text(adapter.title || adapter.id)
292
+ .appendTo($picker);
293
+ });
294
+
295
+ const configuredPreset = String(this.chatAdapterPreset || "none");
296
+ if (configuredPreset !== "none" && !adapters.some(adapter => adapter && adapter.id === configuredPreset)) {
297
+ $("<option></option>")
298
+ .attr("value", configuredPreset)
299
+ .text(configuredPreset)
300
+ .appendTo($picker);
301
+ }
302
+ $picker.val(configuredPreset);
303
+ $picker.on("change", function () {
304
+ const selected = String($(this).val() || "none");
305
+ if (selected === "none") {
306
+ $("#node-input-chatInputCode").val("");
307
+ $("#node-input-chatOutputCode").val("");
308
+ return;
309
+ }
310
+ const adapter = adapters.find(item => item && item.id === selected);
311
+ if (!adapter) return;
312
+ $("#node-input-chatInputCode").val(adapter.inputCode || "");
313
+ $("#node-input-chatOutputCode").val(adapter.outputCode || "");
314
+ });
315
+ };
316
+ configureChatAdapterPicker.call(this);
317
+
318
+ const updateProactiveVisibility = () => {
319
+ const enabled = $("#node-input-proactiveEnabled").is(":checked");
320
+ $("#knx-ai-proactive-settings").toggle(enabled);
321
+ };
322
+ $("#node-input-proactiveEnabled").on("change", updateProactiveVisibility);
323
+ updateProactiveVisibility();
324
+
173
325
  $("#knx-ai-open-web-page-vue").on("click", function (evt) {
174
326
  evt.preventDefault();
175
327
  const adminRoot = resolveAdminRoot();
@@ -415,8 +567,54 @@
415
567
  </div>
416
568
 
417
569
  <div id="knx-ai-accordion">
418
- <h3><span data-i18n="knxUltimateAI.sections.capture"></span></h3>
570
+ <h3><i class="fa fa-magic"></i> <span data-i18n="knxUltimateAI.sections.groupAssistant"></span></h3>
571
+ <div>
572
+ <div class="knx-ai-accordion-subsection">
573
+ <h4><i class="fa fa-cog"></i> <span data-i18n="knxUltimateAI.sections.quickSetup"></span></h4>
574
+ <div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-setup"></div>
575
+ </div>
576
+ <div class="knx-ai-accordion-subsection">
577
+ <h4><i class="fa fa-book"></i> <span data-i18n="knxUltimateAI.sections.llmContext"></span></h4>
578
+ <div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-context"></div>
579
+ </div>
580
+ <div class="knx-ai-accordion-subsection">
581
+ <h4><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.sections.advanced"></span></h4>
582
+ <div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-advanced"></div>
583
+ </div>
584
+ </div>
585
+
586
+ <h3><i class="fa fa-comments"></i> <span data-i18n="knxUltimateAI.sections.groupChatHome"></span></h3>
587
+ <div>
588
+ <div class="knx-ai-accordion-subsection">
589
+ <h4><i class="fa fa-plug"></i> <span data-i18n="knxUltimateAI.sections.chatAdapter"></span></h4>
590
+ <div class="knx-ai-accordion-mount" id="knx-ai-mount-chat-adapter"></div>
591
+ </div>
592
+ <div class="knx-ai-accordion-subsection">
593
+ <h4><i class="fa fa-home"></i> <span data-i18n="knxUltimateAI.sections.homeIntelligence"></span></h4>
594
+ <div class="knx-ai-accordion-mount" id="knx-ai-mount-home-intelligence"></div>
595
+ </div>
596
+ </div>
597
+
598
+ <h3><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.sections.groupKnxAnalysis"></span></h3>
419
599
  <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
+ <div class="knx-ai-accordion-subsection">
605
+ <h4><i class="fa fa-database"></i> <span data-i18n="knxUltimateAI.sections.storage"></span></h4>
606
+ <div class="knx-ai-accordion-mount" id="knx-ai-mount-knx-storage"></div>
607
+ </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
+ </div>
613
+ </div>
614
+
615
+ <div id="knx-ai-accordion-source" style="display:none;">
616
+
617
+ <div id="knx-ai-tab-capture">
420
618
  <div class="form-row">
421
619
  <input type="checkbox" id="node-input-notifywrite" style="display:inline-block; width:auto; vertical-align:top;">
422
620
  <label style="width:auto" for="node-input-notifywrite">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.notifywrite"></span></label>
@@ -431,8 +629,7 @@
431
629
  </div>
432
630
  </div>
433
631
 
434
- <h3><span data-i18n="knxUltimateAI.sections.storage"></span></h3>
435
- <div>
632
+ <div id="knx-ai-tab-storage">
436
633
  <div class="form-row">
437
634
  <label style="width:290px" for="node-input-historyWindowSec"><i class="fa fa-history"></i> <span data-i18n="knxUltimateAI.properties.historyWindowSec"></span></label>
438
635
  <input style="width:90px" type="number" id="node-input-historyWindowSec">
@@ -449,18 +646,56 @@
449
646
  <label style="width:290px" for="node-input-emitIntervalSec"><i class="fa fa-send"></i> <span data-i18n="knxUltimateAI.properties.emitIntervalSec"></span></label>
450
647
  <input style="width:90px" type="number" id="node-input-emitIntervalSec">
451
648
  </div>
649
+ <div class="form-row">
650
+ <label style="width:290px" for="node-input-analysisWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.analysisWindowSec"></span></label>
651
+ <input style="width:90px" type="number" id="node-input-analysisWindowSec">
652
+ </div>
653
+ <div class="form-row">
654
+ <label style="width:290px" for="node-input-maxEvents"><i class="fa fa-bars"></i> <span data-i18n="knxUltimateAI.properties.maxEvents"></span></label>
655
+ <input style="width:90px" type="number" id="node-input-maxEvents">
656
+ </div>
657
+ <div class="form-row">
658
+ <label style="width:290px" for="node-input-topN"><i class="fa fa-list-ol"></i> <span data-i18n="knxUltimateAI.properties.topN"></span></label>
659
+ <input style="width:90px" type="number" id="node-input-topN">
660
+ </div>
452
661
  </div>
453
662
 
454
- <h3><span data-i18n="knxUltimateAI.sections.detection"></span></h3>
455
- <div>
663
+ <div id="knx-ai-tab-detection">
456
664
  <div class="form-row">
457
665
  <input type="checkbox" id="node-input-enablePattern" style="display:inline-block; width:auto; vertical-align:top;">
458
666
  <label style="width:auto" for="node-input-enablePattern">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.enablePattern"></span></label>
459
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>
460
696
  </div>
461
697
 
462
- <h3><span data-i18n="knxUltimateAI.sections.llmConnection"></span></h3>
463
- <div>
698
+ <div id="knx-ai-tab-llm-connection">
464
699
  <div class="form-row">
465
700
  <input type="checkbox" id="node-input-llmEnabled" style="display:inline-block; width:auto; vertical-align:top;">
466
701
  <label style="width:auto" for="node-input-llmEnabled">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmEnabled"></span></label>
@@ -475,15 +710,6 @@
475
710
  <option value="ollama" data-i18n="knxUltimateAI.selectlists.llmProvider.ollama"></option>
476
711
  </select>
477
712
  </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
713
  <div class="form-row" id="knx-ai-apikey-row">
488
714
  <label style="width:290px" for="node-input-llmApiKey"><i class="fa fa-key"></i> <span data-i18n="knxUltimateAI.properties.llmApiKey"></span></label>
489
715
  <input type="password" id="node-input-llmApiKey" data-i18n="[placeholder]knxUltimateAI.placeholder.llmApiKey">
@@ -498,31 +724,92 @@
498
724
  </div>
499
725
  <datalist id="knx-ai-llmModels"></datalist>
500
726
  <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>
727
+ <div class="form-row">
728
+ <input type="checkbox" id="node-input-llmAllowKnxCommands" style="display:inline-block; width:auto; vertical-align:top;">
729
+ <label style="width:auto" for="node-input-llmAllowKnxCommands">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmAllowKnxCommands"></span></label>
514
730
  </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>
731
+ <div class="form-row" id="knx-ai-command-confirmation-row">
732
+ <input type="checkbox" id="node-input-llmRequireCommandConfirmation" style="display:inline-block; width:auto; vertical-align:top;">
733
+ <label style="width:auto" for="node-input-llmRequireCommandConfirmation">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmRequireCommandConfirmation"></span></label>
519
734
  </div>
735
+ </div>
736
+ </div>
520
737
 
738
+ <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
+ <div class="form-row">
743
+ <label style="width:290px" for="node-input-chatAdapterPreset"><i class="fa fa-plug"></i> <span data-i18n="knxUltimateAI.properties.chatAdapterPreset"></span></label>
744
+ <select style="width:100%" id="node-input-chatAdapterPreset">
745
+ <option value="none" data-i18n="knxUltimateAI.selectlists.chatAdapter.none"></option>
746
+ </select>
747
+ </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>
521
758
  </div>
522
759
  </div>
523
760
 
524
- <h3><span data-i18n="knxUltimateAI.sections.llmContext"></span></h3>
525
- <div>
761
+ <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
+ <div class="form-row">
766
+ <input type="checkbox" id="node-input-proactiveEnabled" style="display:inline-block; width:auto; vertical-align:top;">
767
+ <label style="width:auto" for="node-input-proactiveEnabled">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.proactiveEnabled"></span></label>
768
+ </div>
769
+ <div id="knx-ai-proactive-settings">
770
+ <div class="form-row">
771
+ <label style="width:290px" for="node-input-proactiveRecipient"><i class="fa fa-user"></i> <span data-i18n="knxUltimateAI.properties.proactiveRecipient"></span></label>
772
+ <input style="width:100%" type="text" id="node-input-proactiveRecipient" data-i18n="[placeholder]knxUltimateAI.placeholder.proactiveRecipient">
773
+ </div>
774
+ <div class="form-row">
775
+ <label style="width:290px" for="node-input-proactiveOpenMinutes"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveOpenMinutes"></span></label>
776
+ <input style="width:100px" type="number" min="1" max="1440" id="node-input-proactiveOpenMinutes">
777
+ </div>
778
+ <div class="form-row">
779
+ <label style="width:290px" for="node-input-proactiveQuietStart"><i class="fa fa-moon-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveQuietStart"></span></label>
780
+ <input style="width:120px" type="time" id="node-input-proactiveQuietStart">
781
+ </div>
782
+ <div class="form-row">
783
+ <label style="width:290px" for="node-input-proactiveQuietEnd"><i class="fa fa-sun-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveQuietEnd"></span></label>
784
+ <input style="width:120px" type="time" id="node-input-proactiveQuietEnd">
785
+ </div>
786
+ </div>
787
+ <div class="form-row" style="margin-top:14px;">
788
+ <label style="width:100%" for="node-input-aiEducation"><i class="fa fa-graduation-cap"></i> <span data-i18n="knxUltimateAI.properties.aiEducation"></span></label>
789
+ <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
+ </div>
791
+ <div class="form-tips" style="margin-bottom:12px;">
792
+ <span data-i18n="knxUltimateAI.messages.aiEducationHelp"></span>
793
+ </div>
794
+ <div class="knx-ai-inline-subsection">
795
+ <h5><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.sections.homeIntelligenceAdvanced"></span></h5>
796
+ <div>
797
+ <div class="form-row">
798
+ <label style="width:290px" for="node-input-proactiveCooldownMinutes"><i class="fa fa-bell-slash-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveCooldownMinutes"></span></label>
799
+ <input style="width:100px" type="number" min="5" max="10080" id="node-input-proactiveCooldownMinutes">
800
+ </div>
801
+ <div class="form-row">
802
+ <label style="width:290px" for="node-input-homeMemoryMaxKb"><i class="fa fa-hdd-o"></i> <span data-i18n="knxUltimateAI.properties.homeMemoryMaxKb"></span></label>
803
+ <input style="width:100px" type="number" min="64" max="1024" id="node-input-homeMemoryMaxKb">
804
+ </div>
805
+ <div class="form-tips">
806
+ <span data-i18n="knxUltimateAI.messages.homeMemoryLimitHelp"></span>
807
+ </div>
808
+ </div>
809
+ </div>
810
+ </div>
811
+
812
+ <div id="knx-ai-tab-llm-context">
526
813
  <div id="knx-ai-llm-context-settings">
527
814
  <div class="form-row">
528
815
  <input type="checkbox" id="node-input-llmIncludeRaw" style="display:inline-block; width:auto; vertical-align:top;">
@@ -550,54 +837,41 @@
550
837
  <option value="zh-CN">简体中文</option>
551
838
  </select>
552
839
  </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>
553
844
  </div>
554
845
  </div>
555
846
 
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>
847
+ <div id="knx-ai-tab-advanced">
848
+ <div id="knx-ai-llm-advanced-settings">
849
+ <div class="form-row">
850
+ <label style="width:290px" for="node-input-llmBaseUrl"><i class="fa fa-link"></i> <span data-i18n="knxUltimateAI.properties.llmBaseUrl"></span></label>
851
+ <input type="text" id="node-input-llmBaseUrl" data-i18n="[placeholder]knxUltimateAI.placeholder.llmBaseUrl">
852
+ </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
+ <div class="form-row" id="knx-ai-ollama-install-row" style="display:none;">
857
+ <label style="width:290px"><i class="fa fa-download"></i> Ollama</label>
858
+ <div class="knx-ai-ollama-actions">
859
+ <button type="button" class="red-ui-button" id="knx-ai-downloadOllamaModel">
860
+ <i class="fa fa-cloud-download"></i> <span data-i18n="knxUltimateAI.buttons.downloadOllamaModel"></span>
861
+ </button>
862
+ <button type="button" class="red-ui-button" id="knx-ai-installOllamaModel">
863
+ <i class="fa fa-download"></i> <span data-i18n="knxUltimateAI.buttons.installOllamaModel"></span>
864
+ </button>
865
+ </div>
866
+ </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>
601
875
  </div>
602
876
  </div>
603
877
  </div>