node-red-contrib-knx-ultimate 6.3.21 → 6.3.22

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,16 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ **Version 6.3.22** - August 2026<br/>
10
+
11
+ - **KNX AI — visible context size and real prompt weight**: the “Chat context overview” card now shows the current operational context limit and the actual UTF-8 size of the last operational chat prompt. Ollama, Bionic, OpenAI-compatible and Claude token-usage fields are captured when returned by the provider; otherwise the UI clearly labels the token count as an estimate. Local limits reflect the active context without exceeding KNX AI's 16K cap.<br/>
12
+ - **KNX AI — documentation-free operational chat**: Telegram, RedBot and custom CHAT adapters no longer inject packaged help, README, changelog, wiki or example snippets into model prompts. They retain live and archived KNX data, ETS semantics, Node-RED inventory, conversation and home memory, AI Education, cameras and TTS; the web Assistant keeps documentation access for package-support questions.<br/>
13
+ - **KNX AI — tolerant safe-read normalization**: state queries from small local models no longer become rejected writes when the model returns exact ETS destinations but omits the operation discriminator and payload. Payload-free operations are safely inferred as `GroupValue_Read`; payload-bearing operations remain writes and retain every ETS-role, DPT, payload and confirmation safeguard.<br/>
14
+ - **KNX AI — fixed 16K Ollama runtime and prompt budget**: Ollama's advertised model maximum is now informational instead of being sent back as the requested `num_ctx`. KNX AI caps `num_ctx` and its relevance-selected semantic prompt at 16K, preventing 131K KV-cache allocation and full-context prefill. An audit confirmed that OpenAI and Claude receive only output limits, not a client-selected maximum context window.<br/>
15
+ - **KNX AI — fixed 16K Bionic prompt budget**: Bionic's reported active or maximum context can no longer promote conversations to the full 131K data view. KNX AI always builds a relevance-selected 16K semantic prompt for Bionic while preserving the provider's loaded instance, open-ended reasoning, routines, KNX control, cameras and TTS capabilities.<br/>
16
+ - **KNX AI — preserve Bionic LM Studio context**: model discovery now keeps the context length of an already loaded Bionic instance instead of confusing the model's maximum capability with the desired runtime setting. KNX AI no longer loads inactive models through the management API: the first chat request leaves JIT loading and saved per-model defaults to Bionic, while KNX AI uses a conservative 16K prompt budget until the active configuration can be inspected.<br/>
17
+ - **KNX AI — neutral structured-response example**: the conversational contract now demonstrates empty action arrays instead of a fabricated group address, DPT, camera and payload. This prevents small local models from copying placeholder operations into unrelated replies such as greetings while preserving open-ended reasoning and every existing KNX, camera, TTS and routine capability.<br/>
18
+
9
19
  **Version 6.3.21** - August 2026<br/>
10
20
 
11
21
  - **KNX AI — persistent adapter-event history**: every event published by an automatically detected adapter is now normalized into vendor-neutral metadata and appended to a node-specific daily JSONL archive. The archive follows the 10-day KNX retention, guaranteeing at least 24 hours of camera and future adapter events across Node-RED restarts without storing snapshot image data. The editor context card exposes the actual adapter-history directories.<br/>
@@ -182,12 +182,18 @@
182
182
  #knx-ai-detected-adapters-status,
183
183
  #knx-ai-chat-context-status,
184
184
  .knx-ai-chat-context-intro,
185
+ .knx-ai-chat-context-metric,
185
186
  .knx-ai-chat-context-description,
186
187
  .knx-ai-chat-context-pattern {
187
188
  color: var(--red-ui-secondary-text-color, #777);
188
189
  font-size: 12px;
189
190
  }
190
191
 
192
+ .knx-ai-chat-context-metric {
193
+ margin-top: 6px;
194
+ font-weight: 600;
195
+ }
196
+
191
197
  .knx-ai-chat-context-group-title {
192
198
  margin: 12px 0 2px;
193
199
  font-size: 12px;
@@ -610,6 +616,8 @@
610
616
  const $sources = $("#knx-ai-chat-context-sources-list").empty();
611
617
  const $files = $("#knx-ai-chat-context-files-list").empty();
612
618
  const $directories = $("#knx-ai-chat-context-directories-list").empty();
619
+ const $contextLimit = $("#knx-ai-chat-context-limit");
620
+ const $lastPromptUsage = $("#knx-ai-chat-context-last-prompt");
613
621
  if (!overview || typeof overview !== "object") {
614
622
  $content.hide();
615
623
  $status
@@ -633,12 +641,59 @@
633
641
  $row.appendTo($list);
634
642
  };
635
643
 
644
+ const formatCompactTokens = function (value) {
645
+ const tokens = Math.max(0, Number(value) || 0);
646
+ if (!tokens) return "?";
647
+ if (tokens >= 1024 && tokens % 1024 === 0) return (tokens / 1024) + "K";
648
+ return tokens.toLocaleString();
649
+ };
650
+ const formatBytes = function (value) {
651
+ const bytes = Math.max(0, Number(value) || 0);
652
+ if (bytes >= (1024 * 1024)) return (bytes / (1024 * 1024)).toLocaleString(undefined, { maximumFractionDigits: 1 }) + " MB";
653
+ if (bytes >= 1024) return (bytes / 1024).toLocaleString(undefined, { maximumFractionDigits: 1 }) + " KB";
654
+ return bytes.toLocaleString() + " B";
655
+ };
656
+
657
+ const contextLimit = overview.contextLimit && typeof overview.contextLimit === "object" ? overview.contextLimit : {};
658
+ const currentProvider = String(contextLimit.provider || $("#node-input-llmProvider").val() || "").toLowerCase();
659
+ let contextLimitTokens = Math.max(0, Number(contextLimit.tokens) || 0);
660
+ if (!contextLimitTokens && (currentProvider === "lmstudio" || currentProvider === "ollama")) {
661
+ contextLimitTokens = Math.min(Math.max(0, Number($("#node-input-llmContextLength").val()) || 16384), 16384);
662
+ }
663
+ const contextLimitValue = contextLimitTokens
664
+ ? formatCompactTokens(contextLimitTokens) + " " + t("knxUltimateAI.messages.chatContextTokens", "tokens")
665
+ : t("knxUltimateAI.messages.chatContextProviderManaged", "managed by the selected provider/model");
666
+ $contextLimit
667
+ .text(t("knxUltimateAI.messages.chatContextLimitLabel", "Maximum operational context") + ": " + contextLimitValue)
668
+ .attr("title", contextLimitTokens ? contextLimitTokens.toLocaleString() + " tokens" : contextLimitValue);
669
+
670
+ const promptUsage = overview.lastPromptUsage && typeof overview.lastPromptUsage === "object" ? overview.lastPromptUsage : null;
671
+ if (promptUsage && Number(promptUsage.bytes) > 0) {
672
+ const exactInputTokens = Math.max(0, Number(promptUsage.exactInputTokens) || 0);
673
+ const estimatedInputTokens = Math.max(0, Number(promptUsage.estimatedInputTokens) || 0);
674
+ const tokenText = exactInputTokens
675
+ ? formatCompactTokens(exactInputTokens) + " " + t("knxUltimateAI.messages.chatContextExactInputTokens", "input tokens measured by the provider")
676
+ : "~" + formatCompactTokens(estimatedInputTokens) + " " + t("knxUltimateAI.messages.chatContextEstimatedInputTokens", "estimated input tokens");
677
+ const imageText = Number(promptUsage.imageCount) > 0
678
+ ? " · " + Number(promptUsage.imageCount).toLocaleString() + " " + t("knxUltimateAI.messages.chatContextImages", "images")
679
+ : "";
680
+ $lastPromptUsage.text(
681
+ t("knxUltimateAI.messages.chatContextLastPromptLabel", "Last chat prompt actual size") + ": " +
682
+ formatBytes(promptUsage.bytes) + " · " + tokenText + imageText
683
+ );
684
+ } else {
685
+ $lastPromptUsage.text(
686
+ t("knxUltimateAI.messages.chatContextLastPromptLabel", "Last chat prompt actual size") + ": " +
687
+ t("knxUltimateAI.messages.chatContextLastPromptUnavailable", "not available until the first chat request")
688
+ );
689
+ }
690
+
636
691
  const sourceLabels = {
637
692
  knxTraffic: t("knxUltimateAI.messages.chatContextSourceKnxTraffic", "Live KNX summary, anomalies, topology and selected telegrams."),
638
693
  adapterHistory: t("knxUltimateAI.messages.chatContextSourceAdapterHistory", "Persistent history of automatically detected adapter events."),
639
694
  etsProject: t("knxUltimateAI.messages.chatContextSourceEtsProject", "ETS semantics and the full Node-RED project inventory."),
640
695
  memoryEducation: t("knxUltimateAI.messages.chatContextSourceMemoryEducation", "Session context, AI Education and bounded home memory."),
641
- camerasDocs: t("knxUltimateAI.messages.chatContextSourceCamerasDocs", "Detected cameras and relevant help, README and example snippets."),
696
+ cameras: t("knxUltimateAI.messages.chatContextSourceCameras", "Detected cameras and their available capabilities."),
642
697
  ttsUltimate: t("knxUltimateAI.messages.chatContextSourceTtsUltimate", "Selected TTS Ultimate announcement target.")
643
698
  };
644
699
  (Array.isArray(overview.sources) ? overview.sources : []).forEach(function (sourceId) {
@@ -752,11 +807,14 @@
752
807
  let text = t("knxUltimateAI.messages.lmStudioContextAvailable", "Maximum model context") + ": " + maxTokens + " token";
753
808
  let color = "#555";
754
809
  if (state === "loading") {
755
- text = t("knxUltimateAI.messages.lmStudioContextLoading", "Loading the model with its maximum context") + ": " + maxTokens + " token…";
810
+ text = t("knxUltimateAI.messages.lmStudioContextLoading", "Checking the active model context") + "…";
756
811
  color = "#1565c0";
812
+ } else if (state === "inactive") {
813
+ text = t("knxUltimateAI.messages.lmStudioContextInactive", "Model inactive; Bionic defaults will be used on the first request");
814
+ color = "#555";
757
815
  } else if (state === "configured") {
758
816
  const configuredTokens = formatContextTokens(detail.configuredContextLength || detail.loadedContextLength || detail.maxContextLength);
759
- text = t("knxUltimateAI.messages.lmStudioContextConfigured", "Maximum model context configured") + ": " + configuredTokens + " token";
817
+ text = t("knxUltimateAI.messages.lmStudioContextConfigured", "Active model context") + ": " + configuredTokens + " token";
760
818
  color = "#2e7d32";
761
819
  } else if (state === "error") {
762
820
  text = String(errorText || t("knxUltimateAI.messages.lmStudioContextFailed", "Unable to configure the model context"));
@@ -764,6 +822,9 @@
764
822
  } else if (detail.loadedContextLength > 0) {
765
823
  text += " · " + t("knxUltimateAI.messages.lmStudioContextCurrentlyLoaded", "currently loaded") + ": " + loadedTokens + " token";
766
824
  }
825
+ if (provider === "lmstudio" || provider === "ollama") {
826
+ text += " · " + t("knxUltimateAI.messages.localContextBudget", "KNX AI context budget") + ": 16K";
827
+ }
767
828
  $status.text(text).css("color", color).show();
768
829
  };
769
830
 
@@ -824,9 +885,13 @@
824
885
  }
825
886
 
826
887
  const requestVersion = ++localContextRequestVersion;
827
- $("#node-input-llmContextLength").val(Number(detail.maxContextLength));
888
+ const activeContextLength = Number(detail.loadedContextLength) > 0
889
+ ? Number(detail.loadedContextLength)
890
+ : Number(detail.maxContextLength);
891
+ $("#node-input-llmContextLength").val(activeContextLength);
828
892
  if (provider === "ollama") {
829
- detail.configuredContextLength = Number(detail.maxContextLength);
893
+ detail.configuredContextLength = Math.min(Number(detail.maxContextLength), 16384);
894
+ $("#node-input-llmContextLength").val(detail.configuredContextLength);
830
895
  setLocalModelContextStatus(detail, "configured");
831
896
  return;
832
897
  }
@@ -848,13 +913,14 @@
848
913
  type: "POST",
849
914
  contentType: "application/json",
850
915
  data: JSON.stringify(payload)
851
- })
916
+ })
852
917
  .done(function (data) {
853
918
  if (requestVersion !== localContextRequestVersion) return;
854
- detail.loadedContextLength = Math.max(0, Number(data && data.contextLength) || Number(detail.maxContextLength));
855
- detail.configuredContextLength = detail.loadedContextLength;
856
- $("#node-input-llmContextLength").val(detail.loadedContextLength);
857
- setLocalModelContextStatus(detail, "configured");
919
+ const resolvedContextLength = Math.max(0, Number(data && data.contextLength) || 0);
920
+ detail.loadedContextLength = data && data.active === true ? resolvedContextLength : 0;
921
+ detail.configuredContextLength = resolvedContextLength;
922
+ $("#node-input-llmContextLength").val(resolvedContextLength);
923
+ setLocalModelContextStatus(detail, data && data.active === true ? "configured" : "inactive");
858
924
  })
859
925
  .fail(function (xhr) {
860
926
  if (requestVersion !== localContextRequestVersion) return;
@@ -1120,6 +1186,8 @@
1120
1186
  <div id="knx-ai-chat-context-status" data-i18n="knxUltimateAI.messages.chatContextLoading"></div>
1121
1187
  <div id="knx-ai-chat-context-content" style="display:none;">
1122
1188
  <div class="knx-ai-chat-context-intro" data-i18n="knxUltimateAI.messages.chatContextIntro"></div>
1189
+ <div id="knx-ai-chat-context-limit" class="knx-ai-chat-context-metric"></div>
1190
+ <div id="knx-ai-chat-context-last-prompt" class="knx-ai-chat-context-metric"></div>
1123
1191
  <div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextSourcesTitle"></div>
1124
1192
  <div id="knx-ai-chat-context-sources-list"></div>
1125
1193
  <div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextFilesTitle"></div>