node-red-contrib-knx-ultimate 6.3.30 → 6.3.32
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 +12 -0
- package/nodes/knxUltimateAI.html +191 -77
- package/nodes/knxUltimateAI.js +1113 -1549
- package/nodes/locales/de/knxUltimateAI.html +19 -16
- package/nodes/locales/de/knxUltimateAI.json +35 -29
- package/nodes/locales/en/knxUltimateAI.html +17 -16
- package/nodes/locales/en/knxUltimateAI.json +35 -29
- package/nodes/locales/es/knxUltimateAI.html +19 -16
- package/nodes/locales/es/knxUltimateAI.json +35 -29
- package/nodes/locales/fr/knxUltimateAI.html +19 -16
- package/nodes/locales/fr/knxUltimateAI.json +35 -29
- package/nodes/locales/it/knxUltimateAI.html +17 -16
- package/nodes/locales/it/knxUltimateAI.json +35 -29
- package/nodes/locales/zh-CN/knxUltimateAI.html +19 -16
- package/nodes/locales/zh-CN/knxUltimateAI.json +35 -29
- package/nodes/plugins/knxUltimateAI-vue/assets/app.css +1 -1
- package/nodes/plugins/knxUltimateAI-vue/assets/app.js +4 -4
- package/nodes/utils/knxAiCatalogRetrieval.js +349 -0
- package/nodes/utils/knxAiChatContext.js +43 -1
- package/nodes/utils/knxAiHomeMemory.js +15 -7
- package/nodes/utils/knxAiScheduler.js +4 -1
- package/nodes/utils/knxAiSemanticContext.js +662 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 6.3.32** - August 2026<br/>
|
|
10
|
+
|
|
11
|
+
- **KNX AI**: AI-first chat with complete cloud ETS context, selectable local windows and guided retrieval; selected ETS access is now the sole read/write authority, with no neutral role.<br/>
|
|
12
|
+
|
|
13
|
+
**Version 6.3.31** - August 2026<br/>
|
|
14
|
+
|
|
15
|
+
- **KNX AI — adaptive prompts for 8K local models**: the conversational prompt now reserves response space from the active model window before every request. For an 8K model it uses a compact tool contract and automatically bounds the newest conversation turns, KNX/adapter events, home memory, Web results, schedules, Function source, retrieved ETS objects and camera metadata. The complete selected ETS catalog remains local and available through deterministic retrieval; it is not copied wholesale into the prompt. The prompt-debug file records the final bounded system/user text actually sent to the model.<br/>
|
|
16
|
+
- **KNX AI — model-guided local ETS retrieval**: the conversational model now receives `catalogActions` for deterministic searches over the complete selected ETS catalog held inside the node instead of receiving the full catalog in every prompt. Bounded multi-pass retrieval supports ranked search across exact GAs, ETS names, aliases, hierarchy, areas, semantics, DPTs and value labels; accent-insensitive and typo-tolerant matches; exact-address lookup; area discovery/browsing; and related command/status discovery. No socket or second classifier is used: LM Studio returns structured requests, KNX AI executes them locally and calls the same model again with a deduplicated subset of at most 24 objects. Commands and GA-role learning are accepted only for retrieved objects and still pass the complete local ETS/DPT/access validation. The final prompt debug file exposes the retrieval summaries and exact retrieved subset.<br/>
|
|
17
|
+
- **KNX AI — complete ETS with deduplicated operational context**: removed the 4K/8K/16K/unlimited context-size selector and its persisted runtime property while keeping the complete selected ETS catalog locally searchable from every conversation. The implicit KNX and adapter archive range is now 20 minutes instead of 24 hours; an explicit user time range still takes precedence. Packaged help, README, wiki, examples and changelog text are no longer embedded in prompts and can instead be retrieved from public GitHub through the request-driven Web tool. Retrieved ETS metadata, archive rows and the current request each have one canonical prompt location; duplicated archive summaries, ETS-derived area lists, per-GA live snapshots and the always-on Node-RED project inventory were removed. Complete Function source is supplied only for explicit Function-code review requests. A node-specific, locally protected `knxai-last-chat-prompt-<node-id>.txt` debug file is overwritten before every conversational model call with the exact system/user prompt text and its estimated size, excluding API keys and HTTP headers; its path appears in the editor context card. Ollama receives the model's full declared window as `num_ctx`; KNX AI never retries an oversized request by compacting its input.<br/>
|
|
18
|
+
- **KNX AI — explicit ETS catalog access and read-only policy**: added the same filterable group-address selector used by IoT Bridge’s MQTT profile, with Select all/none and bulk or per-row Read only controls. Every selected ETS object is available to conversational retrieval, while Flow Builder keeps its complete selected design inventory. Excluded objects are absent from AI tools, while read-only objects remain searchable and readable but are rejected locally for every write even if their inferred or learned role is command. Complete ETS names, hierarchy paths and duplicate-address aliases are preserved in the local catalog and retrieved object records. There is intentionally no backward-compatibility migration: an existing node without a newly saved explicit ETS selection exposes no group addresses until it is opened, configured, saved and deployed.<br/>
|
|
19
|
+
- **KNX AI — request-driven Web access with clarification-first tool use**: removed the fixed proactive Web polling option, interval selector, background timer, synthetic recipient routing and related Setup Doctor state. During a normal conversation the model now decides semantically whether the current request needs fresh public information; when a missing goal, scope, place, time window or desired outcome would materially change the answer or query, it must ask one concise clarification and emit no tool action until the user answers. Explicit one-time or recurring Web checks remain available through the user-authorized natural-language scheduler and share the existing hourly budget, bounded research loop, verified citations and security controls. Editor strings, help, wiki documentation and tests were aligned in EN, IT, DE, FR, ES and zh-CN.<br/>
|
|
20
|
+
|
|
9
21
|
**Version 6.3.30** - August 2026<br/>
|
|
10
22
|
|
|
11
23
|
- **KNX AI — two-way RedBot Telegram voice chat**: the `RedBot / node-red-contrib-chatbot (Telegram)` preset now accepts RedBot's native inbound `audio` payload and its already-downloaded OGG/Opus `Buffer`, applies the same 20 MB and five-minute safeguards without downloading the file a second time, and transcribes it through the selected OpenAI-compatible provider. Successful requests receive a native RedBot `audio` reply with the localized AI-generated-voice disclosure and text caption; KNX confirmations deliberately remain RedBot `inline-buttons` text messages because its Telegram voice sender cannot attach that keyboard to `sendVoice`. Existing saved RedBot input/output mappings are upgraded at runtime. The separate `windkh/node-red-contrib-telegrambot` voice contract and its tests remain unchanged.<br/>
|
package/nodes/knxUltimateAI.html
CHANGED
|
@@ -480,12 +480,13 @@
|
|
|
480
480
|
llmModel: { value: "gpt-5.4" },
|
|
481
481
|
llmReasoningEffort: { value: "default" },
|
|
482
482
|
llmContextLength: { value: 0 },
|
|
483
|
-
|
|
483
|
+
llmLocalContextTokens: { value: 0 },
|
|
484
484
|
llmAllowKnxCommands: { value: false },
|
|
485
485
|
llmRequireCommandConfirmation: { value: true },
|
|
486
|
+
etsExposedGAs: { value: [] },
|
|
487
|
+
etsReadOnlyGAs: { value: [] },
|
|
488
|
+
etsExposeConfigured: { value: false },
|
|
486
489
|
webAccessEnabled: { value: true },
|
|
487
|
-
webProactiveEnabled: { value: true },
|
|
488
|
-
webProactiveIntervalMinutes: { value: 5 },
|
|
489
490
|
webMaxCallsPerHour: { value: 40 },
|
|
490
491
|
chatAdapterPreset: { value: "none" },
|
|
491
492
|
chatInputCode: { value: "" },
|
|
@@ -529,6 +530,7 @@
|
|
|
529
530
|
if ($mount.length && $section.length) $mount.append($section);
|
|
530
531
|
};
|
|
531
532
|
mountTabSection("#knx-ai-mount-assistant-setup", "#knx-ai-tab-llm-connection");
|
|
533
|
+
mountTabSection("#knx-ai-mount-ets-access", "#knx-ai-tab-ets-access");
|
|
532
534
|
mountTabSection("#knx-ai-mount-assistant-advanced", "#knx-ai-tab-advanced");
|
|
533
535
|
mountTabSection("#knx-ai-mount-chat-adapter", "#knx-ai-tab-chat-adapter");
|
|
534
536
|
mountTabSection("#knx-ai-mount-home-intelligence", "#knx-ai-tab-home-intelligence");
|
|
@@ -572,6 +574,78 @@
|
|
|
572
574
|
return value;
|
|
573
575
|
};
|
|
574
576
|
|
|
577
|
+
// Same ETS access selector used by the MQTT profile of IoT Bridge.
|
|
578
|
+
try {
|
|
579
|
+
const savedSet = new Set(Array.isArray(node.etsExposedGAs) ? node.etsExposedGAs : []);
|
|
580
|
+
const readOnlySet = new Set(Array.isArray(node.etsReadOnlyGAs) ? node.etsReadOnlyGAs : []);
|
|
581
|
+
const exposeConfigured = node.etsExposeConfigured === true;
|
|
582
|
+
|
|
583
|
+
const updateEtsCount = () => {
|
|
584
|
+
const total = $("#knx-ai-ets-ga-list input.knx-ai-ets-ga-cb").length;
|
|
585
|
+
const selected = $("#knx-ai-ets-ga-list input.knx-ai-ets-ga-cb:checked").length;
|
|
586
|
+
$("#knx-ai-ets-ga-count").text(selected + " / " + total + " " + t("knxUltimateAI.messages.etsSelected", "selected"));
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const buildEtsGaList = () => {
|
|
590
|
+
const list = $("#knx-ai-ets-ga-list").empty();
|
|
591
|
+
const serverId = String($("#node-input-server").val() || "").trim();
|
|
592
|
+
if (!serverId) {
|
|
593
|
+
list.append($("<div/>").css("color", "#999").text(t("knxUltimateAI.messages.etsNoGateway", "Select a KNX gateway.")));
|
|
594
|
+
updateEtsCount();
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
$.getJSON("knxUltimatecsv?nodeID=" + encodeURIComponent(serverId), function (data) {
|
|
598
|
+
list.empty();
|
|
599
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
600
|
+
list.append($("<div/>").css("color", "#999").text(t("knxUltimateAI.messages.etsNoGa", "No group addresses found. Import the ETS list in the KNX gateway.")));
|
|
601
|
+
updateEtsCount();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
data.forEach(function (item) {
|
|
605
|
+
if (!item || !item.ga) return;
|
|
606
|
+
const row = $("<div/>").addClass("knx-ai-ets-ga-row").css({
|
|
607
|
+
display: "flex", gap: "8px", alignItems: "center", padding: "3px 2px",
|
|
608
|
+
width: "100%", whiteSpace: "nowrap", cursor: "pointer", borderBottom: "1px solid #f3f3f3"
|
|
609
|
+
}).appendTo(list);
|
|
610
|
+
const include = $("<input/>", { type: "checkbox", class: "knx-ai-ets-ga-cb", value: item.ga, style: "width:auto; margin:0; flex:0 0 auto;" }).appendTo(row);
|
|
611
|
+
include.prop("checked", exposeConfigured ? savedSet.has(item.ga) : true);
|
|
612
|
+
$("<span/>").css({ flex: "0 0 auto", fontFamily: "monospace", minWidth: "64px" }).text(item.ga).appendTo(row);
|
|
613
|
+
const name = String(item.devicename || item.ga);
|
|
614
|
+
$("<span/>").addClass("knx-ai-ets-ga-name").css({ flex: "1 1 auto", minWidth: "0", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "#444" }).text(name).attr("title", name).appendTo(row);
|
|
615
|
+
$("<span/>").css({ flex: "0 0 auto", color: "#888", fontSize: "11px" }).text("DPT" + (item.dpt || "")).appendTo(row);
|
|
616
|
+
const readOnlyLabel = $("<label/>").css({ flex: "0 0 auto", display: "flex", alignItems: "center", gap: "3px", margin: "0", fontSize: "11px", color: "#888", cursor: "pointer" }).appendTo(row);
|
|
617
|
+
const readOnly = $("<input/>", { type: "checkbox", class: "knx-ai-ets-ga-ro", value: item.ga, style: "width:auto; margin:0;" }).appendTo(readOnlyLabel);
|
|
618
|
+
readOnly.prop("checked", readOnlySet.has(item.ga));
|
|
619
|
+
$("<span/>").text(t("knxUltimateAI.messages.etsReadOnly", "Read only")).appendTo(readOnlyLabel);
|
|
620
|
+
readOnlyLabel.on("click", function (event) { event.stopPropagation(); });
|
|
621
|
+
row.on("click", function (event) {
|
|
622
|
+
if (event.target === include[0] || event.target === readOnly[0]) return;
|
|
623
|
+
include.prop("checked", !include.prop("checked"));
|
|
624
|
+
updateEtsCount();
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
$("#knx-ai-ets-ga-list input.knx-ai-ets-ga-cb").on("change", updateEtsCount);
|
|
628
|
+
updateEtsCount();
|
|
629
|
+
}).fail(function () {
|
|
630
|
+
list.empty().append($("<div/>").css("color", "#c0392b").text(t("knxUltimateAI.messages.etsCsvError", "Unable to load the group address list from the gateway.")));
|
|
631
|
+
updateEtsCount();
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
$("#knx-ai-ets-ga-all").on("click", function (event) { event.preventDefault(); $("#knx-ai-ets-ga-list .knx-ai-ets-ga-row:visible").find("input.knx-ai-ets-ga-cb").prop("checked", true); updateEtsCount(); });
|
|
636
|
+
$("#knx-ai-ets-ga-none").on("click", function (event) { event.preventDefault(); $("#knx-ai-ets-ga-list .knx-ai-ets-ga-row:visible").find("input.knx-ai-ets-ga-cb").prop("checked", false); updateEtsCount(); });
|
|
637
|
+
$("#knx-ai-ets-ga-ro-all").on("click", function (event) { event.preventDefault(); $("#knx-ai-ets-ga-list .knx-ai-ets-ga-row:visible").find("input.knx-ai-ets-ga-ro").prop("checked", true); });
|
|
638
|
+
$("#knx-ai-ets-ga-ro-none").on("click", function (event) { event.preventDefault(); $("#knx-ai-ets-ga-list .knx-ai-ets-ga-row:visible").find("input.knx-ai-ets-ga-ro").prop("checked", false); });
|
|
639
|
+
$("#knx-ai-ets-ga-filter").on("input", function () {
|
|
640
|
+
const term = String($(this).val() || "").toLowerCase();
|
|
641
|
+
$("#knx-ai-ets-ga-list .knx-ai-ets-ga-row").each(function () {
|
|
642
|
+
$(this).toggle($(this).text().toLowerCase().indexOf(term) !== -1);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
$("#node-input-server").on("change.knxAiEtsAccess", buildEtsGaList);
|
|
646
|
+
buildEtsGaList();
|
|
647
|
+
} catch (error) { }
|
|
648
|
+
|
|
575
649
|
const applyChatEditorOptions = (editor) => {
|
|
576
650
|
try {
|
|
577
651
|
if (!editor) return;
|
|
@@ -808,6 +882,21 @@
|
|
|
808
882
|
$content.show();
|
|
809
883
|
};
|
|
810
884
|
|
|
885
|
+
let lastChatContextOverview = null;
|
|
886
|
+
const selectedLocalContextTokens = () => Math.max(0, Number($("#node-input-llmLocalContextTokens").val()) || 0);
|
|
887
|
+
const formatCompactContextTokens = (value) => {
|
|
888
|
+
const tokens = Math.max(0, Number(value) || 0);
|
|
889
|
+
if (!tokens) return "?";
|
|
890
|
+
if (tokens >= 1024 && tokens % 1024 === 0) return (tokens / 1024) + "K";
|
|
891
|
+
return tokens.toLocaleString();
|
|
892
|
+
};
|
|
893
|
+
const selectedLocalContextLabel = () => {
|
|
894
|
+
const selectedTokens = selectedLocalContextTokens();
|
|
895
|
+
return selectedTokens
|
|
896
|
+
? formatCompactContextTokens(selectedTokens)
|
|
897
|
+
: t("knxUltimateAI.selectlists.localContext.maximum", "Maximum");
|
|
898
|
+
};
|
|
899
|
+
|
|
811
900
|
const renderChatContextOverview = (overview) => {
|
|
812
901
|
const $status = $("#knx-ai-chat-context-status");
|
|
813
902
|
const $content = $("#knx-ai-chat-context-content");
|
|
@@ -817,12 +906,14 @@
|
|
|
817
906
|
const $contextLimit = $("#knx-ai-chat-context-limit");
|
|
818
907
|
const $lastPromptUsage = $("#knx-ai-chat-context-last-prompt");
|
|
819
908
|
if (!overview || typeof overview !== "object") {
|
|
909
|
+
lastChatContextOverview = null;
|
|
820
910
|
$content.hide();
|
|
821
911
|
$status
|
|
822
912
|
.text(t("knxUltimateAI.messages.chatContextUnavailable", "The chat context summary is temporarily unavailable."))
|
|
823
913
|
.show();
|
|
824
914
|
return;
|
|
825
915
|
}
|
|
916
|
+
lastChatContextOverview = overview;
|
|
826
917
|
|
|
827
918
|
const addRow = function ($list, title, description, pathText, badge) {
|
|
828
919
|
const $row = $("<div></div>").addClass("knx-ai-detected-adapter");
|
|
@@ -839,12 +930,6 @@
|
|
|
839
930
|
$row.appendTo($list);
|
|
840
931
|
};
|
|
841
932
|
|
|
842
|
-
const formatCompactTokens = function (value) {
|
|
843
|
-
const tokens = Math.max(0, Number(value) || 0);
|
|
844
|
-
if (!tokens) return "?";
|
|
845
|
-
if (tokens >= 1024 && tokens % 1024 === 0) return (tokens / 1024) + "K";
|
|
846
|
-
return tokens.toLocaleString();
|
|
847
|
-
};
|
|
848
933
|
const formatBytes = function (value) {
|
|
849
934
|
const bytes = Math.max(0, Number(value) || 0);
|
|
850
935
|
if (bytes >= (1024 * 1024)) return (bytes / (1024 * 1024)).toLocaleString(undefined, { maximumFractionDigits: 1 }) + " MB";
|
|
@@ -853,24 +938,27 @@
|
|
|
853
938
|
};
|
|
854
939
|
|
|
855
940
|
const contextLimit = overview.contextLimit && typeof overview.contextLimit === "object" ? overview.contextLimit : {};
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
contextLimitTokens =
|
|
866
|
-
? modelContextTokens
|
|
867
|
-
:
|
|
941
|
+
const deployedProvider = String(contextLimit.provider || "").toLowerCase();
|
|
942
|
+
const currentProvider = String($("#node-input-llmProvider").val() || deployedProvider).toLowerCase();
|
|
943
|
+
let contextLimitTokens = currentProvider === deployedProvider
|
|
944
|
+
? Math.max(0, Number(contextLimit.tokens) || 0)
|
|
945
|
+
: 0;
|
|
946
|
+
const localProvider = currentProvider === "lmstudio" || currentProvider === "ollama";
|
|
947
|
+
if (localProvider) {
|
|
948
|
+
const modelContextTokens = contextLimitTokens || Math.max(0, Number($("#node-input-llmContextLength").val()) || 0);
|
|
949
|
+
const selectedTokens = selectedLocalContextTokens();
|
|
950
|
+
contextLimitTokens = selectedTokens > 0
|
|
951
|
+
? (modelContextTokens > 0 ? Math.min(modelContextTokens, selectedTokens) : selectedTokens)
|
|
952
|
+
: modelContextTokens;
|
|
868
953
|
}
|
|
869
954
|
const contextLimitValue = contextLimitTokens
|
|
870
|
-
?
|
|
955
|
+
? formatCompactContextTokens(contextLimitTokens) + " " + t("knxUltimateAI.messages.chatContextTokens", "tokens")
|
|
871
956
|
: t("knxUltimateAI.messages.chatContextProviderManaged", "managed by the selected provider/model");
|
|
957
|
+
const localBudgetValue = localProvider
|
|
958
|
+
? " · " + t("knxUltimateAI.messages.localContextBudget", "Local chat context") + ": " + selectedLocalContextLabel()
|
|
959
|
+
: "";
|
|
872
960
|
$contextLimit
|
|
873
|
-
.text(t("knxUltimateAI.messages.chatContextLimitLabel", "Maximum operational context") + ": " + contextLimitValue)
|
|
961
|
+
.text(t("knxUltimateAI.messages.chatContextLimitLabel", "Maximum operational context") + ": " + contextLimitValue + localBudgetValue)
|
|
874
962
|
.attr("title", contextLimitTokens ? contextLimitTokens.toLocaleString() + " tokens" : contextLimitValue);
|
|
875
963
|
|
|
876
964
|
const promptUsage = overview.lastPromptUsage && typeof overview.lastPromptUsage === "object" ? overview.lastPromptUsage : null;
|
|
@@ -878,8 +966,8 @@
|
|
|
878
966
|
const exactInputTokens = Math.max(0, Number(promptUsage.exactInputTokens) || 0);
|
|
879
967
|
const estimatedInputTokens = Math.max(0, Number(promptUsage.estimatedInputTokens) || 0);
|
|
880
968
|
const tokenText = exactInputTokens
|
|
881
|
-
?
|
|
882
|
-
: "~" +
|
|
969
|
+
? formatCompactContextTokens(exactInputTokens) + " " + t("knxUltimateAI.messages.chatContextExactInputTokens", "input tokens measured by the provider")
|
|
970
|
+
: "~" + formatCompactContextTokens(estimatedInputTokens) + " " + t("knxUltimateAI.messages.chatContextEstimatedInputTokens", "estimated input tokens");
|
|
883
971
|
const imageText = Number(promptUsage.imageCount) > 0
|
|
884
972
|
? " · " + Number(promptUsage.imageCount).toLocaleString() + " " + t("knxUltimateAI.messages.chatContextImages", "images")
|
|
885
973
|
: "";
|
|
@@ -895,9 +983,9 @@
|
|
|
895
983
|
}
|
|
896
984
|
|
|
897
985
|
const sourceLabels = {
|
|
898
|
-
knxTraffic: t("knxUltimateAI.messages.chatContextSourceKnxTraffic", "
|
|
899
|
-
adapterHistory: t("knxUltimateAI.messages.chatContextSourceAdapterHistory", "
|
|
900
|
-
etsProject: t("knxUltimateAI.messages.chatContextSourceEtsProject", "ETS
|
|
986
|
+
knxTraffic: t("knxUltimateAI.messages.chatContextSourceKnxTraffic", "Derived KNX analysis plus the latest exact events from the default 20-minute or explicit interval, automatically bounded for the active local-model window."),
|
|
987
|
+
adapterHistory: t("knxUltimateAI.messages.chatContextSourceAdapterHistory", "Latest exact adapter events from the same interval, automatically bounded for the active local-model window."),
|
|
988
|
+
etsProject: t("knxUltimateAI.messages.chatContextSourceEtsProject", "Complete selected semantic ETS catalog for cloud models. Local models receive the full catalog when it fits; otherwise they receive a window-sized manifest and exact model-requested details. Function source only for explicit code review."),
|
|
901
989
|
memoryEducation: t("knxUltimateAI.messages.chatContextSourceMemoryEducation", "Session context, AI Education, bounded home memory and active plans."),
|
|
902
990
|
cameras: t("knxUltimateAI.messages.chatContextSourceCameras", "Detected cameras and their available capabilities.")
|
|
903
991
|
};
|
|
@@ -916,7 +1004,8 @@
|
|
|
916
1004
|
homeMemory: t("knxUltimateAI.messages.chatContextFileHomeMemory", "Bounded learned home memory only; AI Education remains in the node property."),
|
|
917
1005
|
schedules: t("knxUltimateAI.messages.chatContextFileSchedules", "Authoritative structured plans and reminders for this node."),
|
|
918
1006
|
schedulesReadable: t("knxUltimateAI.messages.chatContextFileSchedulesReadable", "Human-readable plans and reminders generated from the scheduler state."),
|
|
919
|
-
assistantConfig: t("knxUltimateAI.messages.chatContextFileAssistantConfig", "Persistent web Assistant configuration and semantic areas for this node.")
|
|
1007
|
+
assistantConfig: t("knxUltimateAI.messages.chatContextFileAssistantConfig", "Persistent web Assistant configuration and semantic areas for this node."),
|
|
1008
|
+
lastChatPrompt: t("knxUltimateAI.messages.chatContextFileLastChatPrompt", "Temporary local copy of the latest system and user messages sent to the chat model; overwritten on every chat request.")
|
|
920
1009
|
};
|
|
921
1010
|
(Array.isArray(overview.files) ? overview.files : []).forEach(function (item) {
|
|
922
1011
|
if (!item || !item.name || !item.path) return;
|
|
@@ -1037,9 +1126,25 @@
|
|
|
1037
1126
|
return tokens ? tokens.toLocaleString() : "?";
|
|
1038
1127
|
};
|
|
1039
1128
|
|
|
1129
|
+
const updateLocalContextTokenOptions = (detail) => {
|
|
1130
|
+
const $select = $("#node-input-llmLocalContextTokens");
|
|
1131
|
+
if (!$select.length) return;
|
|
1132
|
+
const maxContextLength = Math.max(0, Number(detail && detail.maxContextLength) || 0);
|
|
1133
|
+
$select.find("option[data-context-tokens]").each(function () {
|
|
1134
|
+
const optionTokens = Math.max(0, Number($(this).attr("data-context-tokens")) || 0);
|
|
1135
|
+
const unavailable = maxContextLength > 0 && optionTokens > maxContextLength;
|
|
1136
|
+
$(this).prop("disabled", unavailable).prop("hidden", unavailable).toggle(!unavailable);
|
|
1137
|
+
});
|
|
1138
|
+
if (maxContextLength > 0 && selectedLocalContextTokens() > maxContextLength) {
|
|
1139
|
+
$select.val("0");
|
|
1140
|
+
}
|
|
1141
|
+
if (lastChatContextOverview) renderChatContextOverview(lastChatContextOverview);
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1040
1144
|
const setLocalModelContextStatus = (detail, state, errorText) => {
|
|
1041
1145
|
const $status = $("#knx-ai-local-context-status");
|
|
1042
1146
|
const provider = $("#node-input-llmProvider").val();
|
|
1147
|
+
updateLocalContextTokenOptions(detail);
|
|
1043
1148
|
if (!$status.length || (provider !== "lmstudio" && provider !== "ollama") || !detail) {
|
|
1044
1149
|
$status.hide();
|
|
1045
1150
|
return;
|
|
@@ -1064,13 +1169,7 @@
|
|
|
1064
1169
|
} else if (detail.loadedContextLength > 0) {
|
|
1065
1170
|
text += " · " + t("knxUltimateAI.messages.lmStudioContextCurrentlyLoaded", "currently loaded") + ": " + loadedTokens + " token";
|
|
1066
1171
|
}
|
|
1067
|
-
|
|
1068
|
-
const selectedContextValue = String($("#node-input-llmPromptContextTokens").val() || "16384");
|
|
1069
|
-
const selectedBudget = selectedContextValue === "0"
|
|
1070
|
-
? t("knxUltimateAI.selectlists.promptContext.unlimited", "No KNX AI limit (model context)")
|
|
1071
|
-
: (Math.max(4096, Number(selectedContextValue) || 16384) / 1024) + "K";
|
|
1072
|
-
text += " · " + t("knxUltimateAI.messages.localContextBudget", "KNX AI context budget") + ": " + selectedBudget;
|
|
1073
|
-
}
|
|
1172
|
+
text += " · " + t("knxUltimateAI.messages.localContextBudget", "Local chat context") + ": " + selectedLocalContextLabel();
|
|
1074
1173
|
$status.text(text).css("color", color).show();
|
|
1075
1174
|
};
|
|
1076
1175
|
|
|
@@ -1160,6 +1259,7 @@
|
|
|
1160
1259
|
const detail = localModelDetailsById.get(model);
|
|
1161
1260
|
if (!detail || !(Number(detail.maxContextLength) > 0)) {
|
|
1162
1261
|
$("#node-input-llmContextLength").val(0);
|
|
1262
|
+
updateLocalContextTokenOptions(null);
|
|
1163
1263
|
setLocalModelContextStatus(null);
|
|
1164
1264
|
return;
|
|
1165
1265
|
}
|
|
@@ -1170,11 +1270,7 @@
|
|
|
1170
1270
|
: Number(detail.maxContextLength);
|
|
1171
1271
|
$("#node-input-llmContextLength").val(activeContextLength);
|
|
1172
1272
|
if (provider === "ollama") {
|
|
1173
|
-
|
|
1174
|
-
const selectedContextTokens = selectedContextValue === "0"
|
|
1175
|
-
? Number(detail.maxContextLength)
|
|
1176
|
-
: Math.min(16384, Math.max(4096, Number(selectedContextValue) || 16384));
|
|
1177
|
-
detail.configuredContextLength = Math.min(Number(detail.maxContextLength), selectedContextTokens);
|
|
1273
|
+
detail.configuredContextLength = Number(detail.maxContextLength);
|
|
1178
1274
|
$("#node-input-llmContextLength").val(detail.configuredContextLength);
|
|
1179
1275
|
setLocalModelContextStatus(detail);
|
|
1180
1276
|
return;
|
|
@@ -1187,7 +1283,8 @@
|
|
|
1187
1283
|
const payload = {
|
|
1188
1284
|
nodeId: nodeId,
|
|
1189
1285
|
baseUrl: $("#node-input-llmBaseUrl").val() || LMSTUDIO_DEFAULT_CHAT_URL,
|
|
1190
|
-
model: model
|
|
1286
|
+
model: model,
|
|
1287
|
+
localContextTokens: selectedLocalContextTokens()
|
|
1191
1288
|
};
|
|
1192
1289
|
const apiKey = $("#node-input-llmApiKey").val() || "";
|
|
1193
1290
|
if (apiKey && apiKey !== "__PWRD__") payload.apiKey = apiKey;
|
|
@@ -1330,7 +1427,10 @@
|
|
|
1330
1427
|
const provider = $("#node-input-llmProvider").val();
|
|
1331
1428
|
const localProvider = provider === "lmstudio" || provider === "ollama";
|
|
1332
1429
|
if (applyDefaults) applyProviderDefaults(provider);
|
|
1333
|
-
$("#knx-ai-
|
|
1430
|
+
$("#knx-ai-local-context-row").toggle(localProvider);
|
|
1431
|
+
if (localProvider) {
|
|
1432
|
+
updateLocalContextTokenOptions(localModelDetailsById.get(String($("#node-input-llmModel").val() || "").trim()));
|
|
1433
|
+
}
|
|
1334
1434
|
if (provider === "ollama") {
|
|
1335
1435
|
$("#knx-ai-apikey-row").hide();
|
|
1336
1436
|
$("#knx-ai-ollama-install-row").show();
|
|
@@ -1347,11 +1447,9 @@
|
|
|
1347
1447
|
};
|
|
1348
1448
|
const toggleWebAccess = () => {
|
|
1349
1449
|
const enabled = $("#node-input-webAccessEnabled").prop("checked") === true;
|
|
1350
|
-
const proactive = enabled && $("#node-input-webProactiveEnabled").prop("checked") === true;
|
|
1351
1450
|
$("#knx-ai-web-access-settings").toggle(enabled);
|
|
1352
|
-
$("#knx-ai-web-proactive-interval-row").toggle(proactive);
|
|
1353
1451
|
};
|
|
1354
|
-
$("#node-input-webAccessEnabled
|
|
1452
|
+
$("#node-input-webAccessEnabled").on("change", toggleWebAccess);
|
|
1355
1453
|
toggleWebAccess();
|
|
1356
1454
|
$("#node-input-llmProvider").on("change", function () {
|
|
1357
1455
|
toggleProvider({ applyDefaults: true, autoLoadModels: true });
|
|
@@ -1365,14 +1463,10 @@
|
|
|
1365
1463
|
$("#node-input-llmModel").on("change", function () {
|
|
1366
1464
|
configureSelectedLocalModel();
|
|
1367
1465
|
});
|
|
1368
|
-
$("#node-input-
|
|
1466
|
+
$("#node-input-llmLocalContextTokens").on("change", function () {
|
|
1369
1467
|
const detail = localModelDetailsById.get(String($("#node-input-llmModel").val() || "").trim());
|
|
1370
|
-
|
|
1371
|
-
if (
|
|
1372
|
-
configureSelectedLocalModel();
|
|
1373
|
-
return;
|
|
1374
|
-
}
|
|
1375
|
-
setLocalModelContextStatus(detail, detail.loadedContextLength > 0 ? "configured" : "inactive");
|
|
1468
|
+
updateLocalContextTokenOptions(detail);
|
|
1469
|
+
if (detail) setLocalModelContextStatus(detail, detail.loadedContextLength > 0 ? "configured" : undefined);
|
|
1376
1470
|
});
|
|
1377
1471
|
$("#knx-ai-downloadOllamaModel").on("click", function (evt) {
|
|
1378
1472
|
evt.preventDefault();
|
|
@@ -1385,6 +1479,18 @@
|
|
|
1385
1479
|
},
|
|
1386
1480
|
oneditsave: function () {
|
|
1387
1481
|
try { RED.sidebar.show("info"); } catch (error) { }
|
|
1482
|
+
try {
|
|
1483
|
+
const checkboxes = $("#knx-ai-ets-ga-list input.knx-ai-ets-ga-cb");
|
|
1484
|
+
if (checkboxes.length > 0) {
|
|
1485
|
+
const exposed = [];
|
|
1486
|
+
const readOnly = [];
|
|
1487
|
+
checkboxes.each(function () { if ($(this).is(":checked")) exposed.push($(this).val()); });
|
|
1488
|
+
$("#knx-ai-ets-ga-list input.knx-ai-ets-ga-ro").each(function () { if ($(this).is(":checked")) readOnly.push($(this).val()); });
|
|
1489
|
+
this.etsExposedGAs = exposed;
|
|
1490
|
+
this.etsReadOnlyGAs = readOnly;
|
|
1491
|
+
this.etsExposeConfigured = true;
|
|
1492
|
+
}
|
|
1493
|
+
} catch (error) { }
|
|
1388
1494
|
try {
|
|
1389
1495
|
if (this.chatInputCodeEditor && typeof this.chatInputCodeEditor.getValue === "function") {
|
|
1390
1496
|
this.chatInputCode = this.chatInputCodeEditor.getValue();
|
|
@@ -1459,6 +1565,10 @@
|
|
|
1459
1565
|
<h4><i class="fa fa-cog"></i> <span data-i18n="knxUltimateAI.sections.quickSetup"></span></h4>
|
|
1460
1566
|
<div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-setup"></div>
|
|
1461
1567
|
</div>
|
|
1568
|
+
<div class="knx-ai-accordion-subsection">
|
|
1569
|
+
<h4><i class="fa fa-list"></i> <span data-i18n="knxUltimateAI.sections.etsAccess"></span></h4>
|
|
1570
|
+
<div class="knx-ai-accordion-mount" id="knx-ai-mount-ets-access"></div>
|
|
1571
|
+
</div>
|
|
1462
1572
|
<div class="knx-ai-accordion-subsection">
|
|
1463
1573
|
<h4><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.sections.advanced"></span></h4>
|
|
1464
1574
|
<div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-advanced"></div>
|
|
@@ -1584,15 +1694,19 @@
|
|
|
1584
1694
|
</div>
|
|
1585
1695
|
<input type="hidden" id="node-input-llmContextLength">
|
|
1586
1696
|
<div id="knx-ai-local-context-status" style="display:none;"></div>
|
|
1587
|
-
<div class="form-row" id="knx-ai-
|
|
1588
|
-
<label style="width:290px" for="node-input-
|
|
1589
|
-
<select style="width:100%" id="node-input-
|
|
1590
|
-
<option value="
|
|
1591
|
-
<option value="
|
|
1592
|
-
<option value="
|
|
1593
|
-
<option value="
|
|
1697
|
+
<div class="form-row" id="knx-ai-local-context-row" style="display:none;">
|
|
1698
|
+
<label style="width:290px" for="node-input-llmLocalContextTokens"><i class="fa fa-database"></i> <span data-i18n="knxUltimateAI.properties.llmLocalContextTokens"></span></label>
|
|
1699
|
+
<select style="width:100%" id="node-input-llmLocalContextTokens">
|
|
1700
|
+
<option value="0" data-i18n="knxUltimateAI.selectlists.localContext.maximum"></option>
|
|
1701
|
+
<option value="4096" data-context-tokens="4096" data-i18n="knxUltimateAI.selectlists.localContext.k4"></option>
|
|
1702
|
+
<option value="8192" data-context-tokens="8192" data-i18n="knxUltimateAI.selectlists.localContext.k8"></option>
|
|
1703
|
+
<option value="16384" data-context-tokens="16384" data-i18n="knxUltimateAI.selectlists.localContext.k16"></option>
|
|
1704
|
+
<option value="32768" data-context-tokens="32768" data-i18n="knxUltimateAI.selectlists.localContext.k32"></option>
|
|
1705
|
+
<option value="65536" data-context-tokens="65536" data-i18n="knxUltimateAI.selectlists.localContext.k64"></option>
|
|
1706
|
+
<option value="131072" data-context-tokens="131072" data-i18n="knxUltimateAI.selectlists.localContext.k128"></option>
|
|
1707
|
+
<option value="262144" data-context-tokens="262144" data-i18n="knxUltimateAI.selectlists.localContext.k256"></option>
|
|
1594
1708
|
</select>
|
|
1595
|
-
<div class="knx-ai-prompt-context-hint" data-i18n="knxUltimateAI.messages.
|
|
1709
|
+
<div class="knx-ai-prompt-context-hint" data-i18n="knxUltimateAI.messages.localContextHint"></div>
|
|
1596
1710
|
</div>
|
|
1597
1711
|
<div class="form-row">
|
|
1598
1712
|
<input type="checkbox" id="node-input-llmAllowKnxCommands" style="display:inline-block; width:auto; vertical-align:top;">
|
|
@@ -1641,22 +1755,6 @@
|
|
|
1641
1755
|
<input type="number" id="node-input-webMaxCallsPerHour" min="1" max="60" step="1" style="width:100%;">
|
|
1642
1756
|
<!-- <div class="knx-ai-prompt-context-hint" data-i18n="knxUltimateAI.messages.webBudgetHint"></div> -->
|
|
1643
1757
|
</div>
|
|
1644
|
-
<div class="form-row">
|
|
1645
|
-
<input type="checkbox" id="node-input-webProactiveEnabled" style="display:inline-block; width:auto; vertical-align:top;">
|
|
1646
|
-
<label style="width:auto" for="node-input-webProactiveEnabled"> <span data-i18n="knxUltimateAI.properties.webProactiveEnabled"></span></label>
|
|
1647
|
-
<div class="knx-ai-prompt-context-hint" data-i18n="knxUltimateAI.messages.webProactiveHint"></div>
|
|
1648
|
-
</div>
|
|
1649
|
-
<div class="form-row" id="knx-ai-web-proactive-interval-row">
|
|
1650
|
-
<label style="width:290px" for="node-input-webProactiveIntervalMinutes"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.webProactiveIntervalMinutes"></span></label>
|
|
1651
|
-
<select style="width:100%" id="node-input-webProactiveIntervalMinutes">
|
|
1652
|
-
<option value="5" data-i18n="knxUltimateAI.selectlists.webProactiveInterval.5"></option>
|
|
1653
|
-
<option value="10" data-i18n="knxUltimateAI.selectlists.webProactiveInterval.10"></option>
|
|
1654
|
-
<option value="15" data-i18n="knxUltimateAI.selectlists.webProactiveInterval.15"></option>
|
|
1655
|
-
<option value="30" data-i18n="knxUltimateAI.selectlists.webProactiveInterval.30"></option>
|
|
1656
|
-
<option value="60" data-i18n="knxUltimateAI.selectlists.webProactiveInterval.60"></option>
|
|
1657
|
-
<option value="180" data-i18n="knxUltimateAI.selectlists.webProactiveInterval.180"></option>
|
|
1658
|
-
</select>
|
|
1659
|
-
</div>
|
|
1660
1758
|
</div>
|
|
1661
1759
|
<hr>
|
|
1662
1760
|
<div class="form-row">
|
|
@@ -1665,6 +1763,22 @@
|
|
|
1665
1763
|
</div>
|
|
1666
1764
|
</div>
|
|
1667
1765
|
|
|
1766
|
+
<div id="knx-ai-tab-ets-access">
|
|
1767
|
+
<div class="form-row">
|
|
1768
|
+
<div class="knx-ai-prompt-context-hint" data-i18n="knxUltimateAI.messages.etsAccessHint"></div>
|
|
1769
|
+
<div style="margin:8px 0 4px; display:flex; gap:6px; align-items:center;">
|
|
1770
|
+
<input type="text" id="knx-ai-ets-ga-filter" data-i18n="[placeholder]knxUltimateAI.messages.etsFilterPlaceholder" style="flex:1 1 auto;">
|
|
1771
|
+
<button type="button" id="knx-ai-ets-ga-all" class="ui-button ui-corner-all ui-widget" style="width:auto;" data-i18n="knxUltimateAI.buttons.etsSelectAll"></button>
|
|
1772
|
+
<button type="button" id="knx-ai-ets-ga-none" class="ui-button ui-corner-all ui-widget" style="width:auto;" data-i18n="knxUltimateAI.buttons.etsSelectNone"></button>
|
|
1773
|
+
<span style="border-left:1px solid #ccc; align-self:stretch;"></span>
|
|
1774
|
+
<button type="button" id="knx-ai-ets-ga-ro-all" class="ui-button ui-corner-all ui-widget" style="width:auto;" data-i18n="[title]knxUltimateAI.messages.etsReadOnlyBulk;knxUltimateAI.buttons.etsReadOnlyAll"></button>
|
|
1775
|
+
<button type="button" id="knx-ai-ets-ga-ro-none" class="ui-button ui-corner-all ui-widget" style="width:auto;" data-i18n="[title]knxUltimateAI.messages.etsReadOnlyBulk;knxUltimateAI.buttons.etsReadOnlyNone"></button>
|
|
1776
|
+
</div>
|
|
1777
|
+
<div id="knx-ai-ets-ga-count" style="font-size:11px; color:#666; margin:2px 0;"></div>
|
|
1778
|
+
<div id="knx-ai-ets-ga-list" style="max-height:220px; overflow:auto; border:1px solid #ccc; padding:6px; border-radius:4px; background:#fff;"></div>
|
|
1779
|
+
</div>
|
|
1780
|
+
</div>
|
|
1781
|
+
|
|
1668
1782
|
<div id="knx-ai-tab-advanced">
|
|
1669
1783
|
<div id="knx-ai-llm-advanced-settings">
|
|
1670
1784
|
<div class="form-row">
|