node-red-contrib-knx-ultimate 4.3.6 → 4.3.8

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 CHANGED
@@ -6,6 +6,17 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 4.3.8** - April 2026<br/>
10
+
11
+ - FIX: **KNX AI Web Assistant** `Ask` tab now truly uses the full available content width on desktop too; removed the leftover `50vw` cap that still kept the panel at half width.<br/>
12
+ - FIX: **KNX DEVICE** in universal mode (`listenAllGA`) now falls back to `raw` instead of raising a datapoint-detection error when no ETS DPT is available and automatic inference fails; raw bytes remain available in `msg.knx.rawValue`.<br/>
13
+ - NEW: **KNX AI** can now optionally archive captured telegrams to disk under `knxultimatestorage`, with configurable retention in days and automatic reuse of the archive for `Ask` queries.<br/>
14
+ - CHANGE: **KNX AI** `Ask` now uses the disk archive by default when enabled, honoring explicit time references and otherwise searching the last 24 hours plus current RAM events.<br/>
15
+ - CHANGE: **KNX AI** flow context for the AI prompt now includes the whole **Node-RED project inventory**, not only KNX nodes or the current flow, so `function`/`change`/`inject`/`template` nodes with KNX-related logic are visible to the assistant.<br/>
16
+ - FIX: **KNX AI Ask** now includes the real JavaScript source of Node-RED `function` nodes for code-review questions, instead of exposing only short flattened snippets that could make the assistant say it could not inspect the full code.<br/>
17
+ - UI: **KNX AI** editor options were reorganized into clearer sections, with technical tuning moved under **Advanced**, several low-value LLM tuning fields removed from the editor, and localized tab labels/help/docs updated in all supported languages.<br/>
18
+ - UI: **KNX AI Web Assistant** `Ask` panel now uses the full available page width instead of being capped to half-page width on desktop.<br/>
19
+
9
20
  **Version 4.3.6** - April 2026<br/>
10
21
 
11
22
  - UI: when **KNX DEVICE** is set to **`dpt = raw`**, the editor now hides decoded-payload options that do not apply to raw telegrams, including **RBE**, **periodic send**, **format numeric values**, manual write button modes, and **notifyreadrequestalsorespondtobus**.<br/>
@@ -2067,6 +2067,15 @@ module.exports = (RED) => {
2067
2067
  }
2068
2068
 
2069
2069
  const isRawMode = typeof _inputDpt === 'string' && _inputDpt.trim().toLowerCase() === 'raw'
2070
+ const isUniversalNode = _oNode?.listenallga === true || _oNode?.listenallga === 'true'
2071
+ const hasKnownInputDpt = !(_inputDpt === null || _inputDpt === undefined || _inputDpt === '')
2072
+ const shouldFallbackToRaw = isUniversalNode && !hasKnownInputDpt
2073
+ const setRawModeMetadata = () => {
2074
+ sInputDpt = 'raw'
2075
+ sPayloadmeasureunit = ''
2076
+ sDptdesc = 'Raw value'
2077
+ sPayloadsubtypevalue = ''
2078
+ }
2070
2079
 
2071
2080
  const errorMessage = {
2072
2081
  topic: _outputtopic,
@@ -2091,14 +2100,43 @@ module.exports = (RED) => {
2091
2100
  // Resolve DPT and convert value if available
2092
2101
  if (_Rawvalue !== null) {
2093
2102
  if (isRawMode) {
2094
- sInputDpt = 'raw'
2095
- sPayloadmeasureunit = ''
2096
- sDptdesc = 'Raw value'
2097
- sPayloadsubtypevalue = ''
2103
+ setRawModeMetadata()
2098
2104
  } else {
2099
2105
  try {
2100
- sInputDpt = _inputDpt === null ? tryToFigureOutDataPointFromRawValue(_Rawvalue) : _inputDpt
2106
+ sInputDpt = !hasKnownInputDpt ? tryToFigureOutDataPointFromRawValue(_Rawvalue) : _inputDpt
2101
2107
  } catch (error) {
2108
+ if (shouldFallbackToRaw) {
2109
+ setRawModeMetadata()
2110
+ jsValue = null
2111
+ node.sysLogger?.debug(`buildInputMessage: falling back to RAW for universal node ${_oNode?.id || ''} destination ${_destGA}`)
2112
+ try {
2113
+ const finalMessage = {
2114
+ topic: _outputtopic,
2115
+ devicename: typeof _devicename !== 'undefined' ? _devicename : '',
2116
+ payload: jsValue,
2117
+ payloadmeasureunit: sPayloadmeasureunit,
2118
+ payloadsubtypevalue: sPayloadsubtypevalue,
2119
+ gainfo,
2120
+ echoed: _echoed,
2121
+ repeated: _repeated === true,
2122
+ repeat: _repeated === true,
2123
+ knx: {
2124
+ event: _event,
2125
+ dpt: sInputDpt,
2126
+ dptdesc: sDptdesc,
2127
+ source: _srcGA,
2128
+ destination: _destGA,
2129
+ rawValue: _Rawvalue,
2130
+ repeated: _repeated === true,
2131
+ repeat: _repeated === true
2132
+ }
2133
+ }
2134
+ return finalMessage
2135
+ } catch (buildError) {
2136
+ node.sysLogger?.error('buildInputMessage error: ' + buildError.message)
2137
+ return errorMessage
2138
+ }
2139
+ }
2102
2140
  // Here comes if no datapoint has beeen found
2103
2141
  node.sysLogger?.error(
2104
2142
  'buildInputMessage: Error returning from tryToFigureOutDataPointFromRawValue. Device ' +
@@ -2216,11 +2254,8 @@ module.exports = (RED) => {
2216
2254
  }
2217
2255
  } else {
2218
2256
  // Don't care, it's a READ REQUEST
2219
- if (isRawMode) {
2220
- sInputDpt = 'raw'
2221
- sPayloadmeasureunit = ''
2222
- sDptdesc = 'Raw value'
2223
- sPayloadsubtypevalue = ''
2257
+ if (isRawMode || shouldFallbackToRaw) {
2258
+ setRawModeMetadata()
2224
2259
  }
2225
2260
  }
2226
2261
 
@@ -9,7 +9,7 @@
9
9
  align-items: flex-start;
10
10
  }
11
11
 
12
- #knx-ai-ollama-install-row > label {
12
+ #knx-ai-ollama-install-row>label {
13
13
  padding-top: 6px;
14
14
  }
15
15
 
@@ -32,7 +32,7 @@
32
32
  }
33
33
 
34
34
  @media (max-width: 900px) {
35
- #knx-ai-ollama-install-row > label {
35
+ #knx-ai-ollama-install-row>label {
36
36
  width: 100% !important;
37
37
  padding-top: 0;
38
38
  margin-bottom: 6px;
@@ -64,6 +64,8 @@
64
64
 
65
65
  analysisWindowSec: { value: 120, required: true, validate: RED.validators.number() },
66
66
  historyWindowSec: { value: 600, required: true, validate: RED.validators.number() },
67
+ historyStoreToDisk: { value: true },
68
+ historyStoreRetentionDays: { value: 10, required: true, validate: RED.validators.number() },
67
69
  emitIntervalSec: { value: 0, required: true, validate: RED.validators.number() },
68
70
  topN: { value: 10, required: true, validate: RED.validators.number() },
69
71
  maxEvents: { value: 50000, required: true, validate: RED.validators.number() },
@@ -84,17 +86,10 @@
84
86
  llmBaseUrl: { value: "https://api.openai.com/v1/chat/completions" },
85
87
  llmModel: { value: "gpt-5.4" },
86
88
  llmSystemPrompt: { value: "" },
87
- llmTemperature: { value: 0.2, required: false, validate: RED.validators.number() },
88
- llmMaxTokens: { value: 50000, required: false, validate: RED.validators.number() },
89
- llmTimeoutMs: { value: 120000, required: false, validate: RED.validators.number() },
90
- llmMaxEventsInPrompt: { value: 120, required: false, validate: RED.validators.number() },
91
89
  llmIncludeRaw: { value: false },
92
90
  llmIncludeFlowContext: { value: true },
93
- llmMaxFlowNodesInPrompt: { value: 400, required: false, validate: RED.validators.number() },
94
91
  llmIncludeDocsSnippets: { value: true },
95
- llmDocsLanguage: { value: "en" },
96
- llmDocsMaxSnippets: { value: 5, required: false, validate: RED.validators.number() },
97
- llmDocsMaxChars: { value: 60000, required: false, validate: RED.validators.number() }
92
+ llmDocsLanguage: { value: "en" }
98
93
  },
99
94
  credentials: {
100
95
  llmApiKey: { type: "password" }
@@ -121,14 +116,6 @@
121
116
  const OPENAI_COMPAT_DEFAULT_MODELS = ["gpt-5.4", "gpt-4o-mini"];
122
117
  const OLLAMA_DEFAULT_MODEL = "llama3.1";
123
118
 
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
- }
131
-
132
119
  $("#knx-ai-accordion").accordion({
133
120
  header: "h3",
134
121
  heightStyle: "content",
@@ -138,14 +125,26 @@
138
125
 
139
126
  const toggleLLM = () => {
140
127
  const enabled = $("#node-input-llmEnabled").is(":checked");
141
- if (enabled) {
142
- $("#knx-ai-llm-settings").show();
143
- } else {
144
- $("#knx-ai-llm-settings").hide();
145
- }
128
+ $("#knx-ai-llm-connection-settings").toggle(enabled);
129
+ $("#knx-ai-llm-context-settings").toggle(enabled);
146
130
  };
147
131
  $("#node-input-llmEnabled").on("change", toggleLLM);
132
+
133
+ const toggleHistoryArchive = () => {
134
+ const enabled = $("#node-input-historyStoreToDisk").is(":checked");
135
+ $("#knx-ai-history-retention-row").toggle(enabled);
136
+ };
137
+ $("#node-input-historyStoreToDisk").on("change", toggleHistoryArchive);
138
+
139
+ const toggleDocsContext = () => {
140
+ const enabled = $("#node-input-llmIncludeDocsSnippets").is(":checked");
141
+ $("#knx-ai-docs-language-row").toggle(enabled);
142
+ };
143
+ $("#node-input-llmIncludeDocsSnippets").on("change", toggleDocsContext);
144
+
148
145
  toggleLLM();
146
+ toggleHistoryArchive();
147
+ toggleDocsContext();
149
148
 
150
149
  const nodeId = this.id;
151
150
  const resolveAdminRoot = () => {
@@ -422,74 +421,42 @@
422
421
  </div>
423
422
  </div>
424
423
 
425
- <h3><span data-i18n="knxUltimateAI.sections.analysis"></span></h3>
424
+ <h3><span data-i18n="knxUltimateAI.sections.storage"></span></h3>
426
425
  <div>
427
- <div class="form-row">
428
- <label style="width:290px" for="node-input-analysisWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.analysisWindowSec"></span></label>
429
- <input style="width:90px" type="number" id="node-input-analysisWindowSec">
430
- </div>
431
426
  <div class="form-row">
432
427
  <label style="width:290px" for="node-input-historyWindowSec"><i class="fa fa-history"></i> <span data-i18n="knxUltimateAI.properties.historyWindowSec"></span></label>
433
428
  <input style="width:90px" type="number" id="node-input-historyWindowSec">
434
429
  </div>
435
430
  <div class="form-row">
436
- <label style="width:290px" for="node-input-maxEvents"><i class="fa fa-bars"></i> <span data-i18n="knxUltimateAI.properties.maxEvents"></span></label>
437
- <input style="width:90px" type="number" id="node-input-maxEvents">
431
+ <input type="checkbox" id="node-input-historyStoreToDisk" style="display:inline-block; width:auto; vertical-align:top;">
432
+ <label style="width:auto" for="node-input-historyStoreToDisk">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.historyStoreToDisk"></span></label>
433
+ </div>
434
+ <div class="form-row" id="knx-ai-history-retention-row">
435
+ <label style="width:290px" for="node-input-historyStoreRetentionDays"><i class="fa fa-calendar"></i> <span data-i18n="knxUltimateAI.properties.historyStoreRetentionDays"></span></label>
436
+ <input style="width:90px" type="number" id="node-input-historyStoreRetentionDays">
438
437
  </div>
439
438
  <div class="form-row">
440
439
  <label style="width:290px" for="node-input-emitIntervalSec"><i class="fa fa-send"></i> <span data-i18n="knxUltimateAI.properties.emitIntervalSec"></span></label>
441
440
  <input style="width:90px" type="number" id="node-input-emitIntervalSec">
442
441
  </div>
443
- <div class="form-row">
444
- <label style="width:290px" for="node-input-topN"><i class="fa fa-list-ol"></i> <span data-i18n="knxUltimateAI.properties.topN"></span></label>
445
- <input style="width:90px" type="number" id="node-input-topN">
446
- </div>
447
- <div class="form-row">
448
- <input type="checkbox" id="node-input-enablePattern" style="display:inline-block; width:auto; vertical-align:top;">
449
- <label style="width:auto" for="node-input-enablePattern">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.enablePattern"></span></label>
450
- </div>
451
- <div class="form-row">
452
- <label style="width:290px" for="node-input-patternMaxLagMs"><i class="fa fa-arrows-h"></i> <span data-i18n="knxUltimateAI.properties.patternMaxLagMs"></span></label>
453
- <input style="width:120px" type="number" id="node-input-patternMaxLagMs">
454
- </div>
455
- <div class="form-row">
456
- <label style="width:290px" for="node-input-patternMinCount"><i class="fa fa-filter"></i> <span data-i18n="knxUltimateAI.properties.patternMinCount"></span></label>
457
- <input style="width:90px" type="number" id="node-input-patternMinCount">
458
- </div>
459
442
  </div>
460
443
 
461
- <h3><span data-i18n="knxUltimateAI.sections.anomalies"></span></h3>
444
+ <h3><span data-i18n="knxUltimateAI.sections.detection"></span></h3>
462
445
  <div>
463
446
  <div class="form-row">
464
- <label style="width:290px" for="node-input-rateWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.rateWindowSec"></span></label>
465
- <input style="width:90px" type="number" id="node-input-rateWindowSec">
466
- </div>
467
- <div class="form-row">
468
- <label style="width:290px" for="node-input-maxTelegramPerSecOverall"><i class="fa fa-tachometer"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecOverall"></span></label>
469
- <input style="width:90px" type="number" id="node-input-maxTelegramPerSecOverall">
470
- </div>
471
- <div class="form-row">
472
- <label style="width:290px" for="node-input-maxTelegramPerSecPerGA"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecPerGA"></span></label>
473
- <input style="width:90px" type="number" id="node-input-maxTelegramPerSecPerGA">
474
- </div>
475
- <div class="form-row">
476
- <label style="width:290px" for="node-input-flapWindowSec"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.properties.flapWindowSec"></span></label>
477
- <input style="width:90px" type="number" id="node-input-flapWindowSec">
478
- </div>
479
- <div class="form-row">
480
- <label style="width:290px" for="node-input-flapMaxChanges"><i class="fa fa-random"></i> <span data-i18n="knxUltimateAI.properties.flapMaxChanges"></span></label>
481
- <input style="width:90px" type="number" id="node-input-flapMaxChanges">
447
+ <input type="checkbox" id="node-input-enablePattern" style="display:inline-block; width:auto; vertical-align:top;">
448
+ <label style="width:auto" for="node-input-enablePattern">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.enablePattern"></span></label>
482
449
  </div>
483
450
  </div>
484
451
 
485
- <h3><span data-i18n="knxUltimateAI.sections.llm"></span></h3>
452
+ <h3><span data-i18n="knxUltimateAI.sections.llmConnection"></span></h3>
486
453
  <div>
487
454
  <div class="form-row">
488
455
  <input type="checkbox" id="node-input-llmEnabled" style="display:inline-block; width:auto; vertical-align:top;">
489
456
  <label style="width:auto" for="node-input-llmEnabled">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmEnabled"></span></label>
490
457
  </div>
491
458
 
492
- <div id="knx-ai-llm-settings">
459
+ <div id="knx-ai-llm-connection-settings">
493
460
  <div class="form-row">
494
461
  <label style="width:290px" for="node-input-llmProvider"><i class="fa fa-cog"></i> <span data-i18n="knxUltimateAI.properties.llmProvider"></span></label>
495
462
  <select style="width:100%" id="node-input-llmProvider">
@@ -540,31 +507,12 @@
540
507
  <code>ollama pull llama3.1</code>
541
508
  </div>
542
509
 
543
- <div class="form-row">
544
- <label style="width:290px" for="node-input-llmSystemPrompt"><i class="fa fa-commenting-o"></i> <span data-i18n="knxUltimateAI.properties.llmSystemPrompt"></span></label>
545
- <textarea style="width:100%; height:110px;" id="node-input-llmSystemPrompt" data-i18n="[placeholder]knxUltimateAI.placeholder.llmSystemPrompt"></textarea>
546
- </div>
547
-
548
- <div class="form-row">
549
- <label style="width:290px" for="node-input-llmTemperature"><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.properties.llmTemperature"></span></label>
550
- <input style="width:90px" type="number" id="node-input-llmTemperature">
551
- </div>
552
-
553
- <div class="form-row">
554
- <label style="width:290px" for="node-input-llmMaxTokens"><i class="fa fa-align-left"></i> <span data-i18n="knxUltimateAI.properties.llmMaxTokens"></span></label>
555
- <input style="width:90px" type="number" id="node-input-llmMaxTokens">
556
- </div>
557
-
558
- <div class="form-row">
559
- <label style="width:290px" for="node-input-llmTimeoutMs"><i class="fa fa-hourglass-half"></i> <span data-i18n="knxUltimateAI.properties.llmTimeoutMs"></span></label>
560
- <input style="width:120px" type="number" id="node-input-llmTimeoutMs">
561
- </div>
562
-
563
- <div class="form-row">
564
- <label style="width:290px" for="node-input-llmMaxEventsInPrompt"><i class="fa fa-list"></i> <span data-i18n="knxUltimateAI.properties.llmMaxEventsInPrompt"></span></label>
565
- <input style="width:90px" type="number" id="node-input-llmMaxEventsInPrompt">
566
- </div>
510
+ </div>
511
+ </div>
567
512
 
513
+ <h3><span data-i18n="knxUltimateAI.sections.llmContext"></span></h3>
514
+ <div>
515
+ <div id="knx-ai-llm-context-settings">
568
516
  <div class="form-row">
569
517
  <input type="checkbox" id="node-input-llmIncludeRaw" style="display:inline-block; width:auto; vertical-align:top;">
570
518
  <label style="width:auto" for="node-input-llmIncludeRaw">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmIncludeRaw"></span></label>
@@ -575,17 +523,12 @@
575
523
  <label style="width:auto" for="node-input-llmIncludeFlowContext">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmIncludeFlowContext"></span></label>
576
524
  </div>
577
525
 
578
- <div class="form-row">
579
- <label style="width:290px" for="node-input-llmMaxFlowNodesInPrompt"><i class="fa fa-sitemap"></i> <span data-i18n="knxUltimateAI.properties.llmMaxFlowNodesInPrompt"></span></label>
580
- <input style="width:90px" type="number" id="node-input-llmMaxFlowNodesInPrompt">
581
- </div>
582
-
583
526
  <div class="form-row">
584
527
  <input type="checkbox" id="node-input-llmIncludeDocsSnippets" style="display:inline-block; width:auto; vertical-align:top;">
585
528
  <label style="width:auto" for="node-input-llmIncludeDocsSnippets">&nbsp;&nbsp;<span data-i18n="knxUltimateAI.properties.llmIncludeDocsSnippets"></span></label>
586
529
  </div>
587
530
 
588
- <div class="form-row">
531
+ <div class="form-row" id="knx-ai-docs-language-row">
589
532
  <label style="width:290px" for="node-input-llmDocsLanguage"><i class="fa fa-language"></i> <span data-i18n="knxUltimateAI.properties.llmDocsLanguage"></span></label>
590
533
  <select style="width:100%" id="node-input-llmDocsLanguage">
591
534
  <option value="it">Italiano</option>
@@ -596,16 +539,54 @@
596
539
  <option value="zh-CN">简体中文</option>
597
540
  </select>
598
541
  </div>
542
+ </div>
543
+ </div>
599
544
 
600
- <div class="form-row">
601
- <label style="width:290px" for="node-input-llmDocsMaxSnippets"><i class="fa fa-files-o"></i> <span data-i18n="knxUltimateAI.properties.llmDocsMaxSnippets"></span></label>
602
- <input style="width:90px" type="number" id="node-input-llmDocsMaxSnippets">
603
- </div>
604
-
605
- <div class="form-row">
606
- <label style="width:290px" for="node-input-llmDocsMaxChars"><i class="fa fa-text-width"></i> <span data-i18n="knxUltimateAI.properties.llmDocsMaxChars"></span></label>
607
- <input style="width:110px" type="number" id="node-input-llmDocsMaxChars">
608
- </div>
545
+ <h3><span data-i18n="knxUltimateAI.sections.advanced"></span></h3>
546
+ <div>
547
+ <div class="form-row">
548
+ <label style="width:290px" for="node-input-analysisWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.analysisWindowSec"></span></label>
549
+ <input style="width:90px" type="number" id="node-input-analysisWindowSec">
550
+ </div>
551
+ <div class="form-row">
552
+ <label style="width:290px" for="node-input-maxEvents"><i class="fa fa-bars"></i> <span data-i18n="knxUltimateAI.properties.maxEvents"></span></label>
553
+ <input style="width:90px" type="number" id="node-input-maxEvents">
554
+ </div>
555
+ <div class="form-row">
556
+ <label style="width:290px" for="node-input-topN"><i class="fa fa-list-ol"></i> <span data-i18n="knxUltimateAI.properties.topN"></span></label>
557
+ <input style="width:90px" type="number" id="node-input-topN">
558
+ </div>
559
+ <div class="form-row">
560
+ <label style="width:290px" for="node-input-patternMaxLagMs"><i class="fa fa-arrows-h"></i> <span data-i18n="knxUltimateAI.properties.patternMaxLagMs"></span></label>
561
+ <input style="width:120px" type="number" id="node-input-patternMaxLagMs">
562
+ </div>
563
+ <div class="form-row">
564
+ <label style="width:290px" for="node-input-patternMinCount"><i class="fa fa-filter"></i> <span data-i18n="knxUltimateAI.properties.patternMinCount"></span></label>
565
+ <input style="width:90px" type="number" id="node-input-patternMinCount">
566
+ </div>
567
+ <div class="form-row">
568
+ <label style="width:290px" for="node-input-rateWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.rateWindowSec"></span></label>
569
+ <input style="width:90px" type="number" id="node-input-rateWindowSec">
570
+ </div>
571
+ <div class="form-row">
572
+ <label style="width:290px" for="node-input-maxTelegramPerSecOverall"><i class="fa fa-tachometer"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecOverall"></span></label>
573
+ <input style="width:90px" type="number" id="node-input-maxTelegramPerSecOverall">
574
+ </div>
575
+ <div class="form-row">
576
+ <label style="width:290px" for="node-input-maxTelegramPerSecPerGA"><i class="fa fa-bolt"></i> <span data-i18n="knxUltimateAI.properties.maxTelegramPerSecPerGA"></span></label>
577
+ <input style="width:90px" type="number" id="node-input-maxTelegramPerSecPerGA">
578
+ </div>
579
+ <div class="form-row">
580
+ <label style="width:290px" for="node-input-flapWindowSec"><i class="fa fa-exchange"></i> <span data-i18n="knxUltimateAI.properties.flapWindowSec"></span></label>
581
+ <input style="width:90px" type="number" id="node-input-flapWindowSec">
582
+ </div>
583
+ <div class="form-row">
584
+ <label style="width:290px" for="node-input-flapMaxChanges"><i class="fa fa-random"></i> <span data-i18n="knxUltimateAI.properties.flapMaxChanges"></span></label>
585
+ <input style="width:90px" type="number" id="node-input-flapMaxChanges">
586
+ </div>
587
+ <div class="form-row">
588
+ <label style="width:290px" for="node-input-llmSystemPrompt"><i class="fa fa-commenting-o"></i> <span data-i18n="knxUltimateAI.properties.llmSystemPrompt"></span></label>
589
+ <textarea style="width:100%; height:110px;" id="node-input-llmSystemPrompt" data-i18n="[placeholder]knxUltimateAI.placeholder.llmSystemPrompt"></textarea>
609
590
  </div>
610
591
  </div>
611
592
  </div>