node-red-contrib-knx-ultimate 6.3.21 → 6.3.24
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 +24 -0
- package/examples/KNX AI - Telegrambot Direct Chat.json +1 -17
- package/nodes/knxUltimateAI.html +218 -23
- package/nodes/knxUltimateAI.js +930 -244
- package/nodes/locales/de/knxUltimateAI.html +28 -8
- package/nodes/locales/de/knxUltimateAI.json +24 -4
- package/nodes/locales/en/knxUltimateAI.html +28 -8
- package/nodes/locales/en/knxUltimateAI.json +24 -4
- package/nodes/locales/es/knxUltimateAI.html +28 -8
- package/nodes/locales/es/knxUltimateAI.json +24 -4
- package/nodes/locales/fr/knxUltimateAI.html +28 -8
- package/nodes/locales/fr/knxUltimateAI.json +24 -4
- package/nodes/locales/it/knxUltimateAI.html +28 -8
- package/nodes/locales/it/knxUltimateAI.json +24 -4
- package/nodes/locales/zh-CN/knxUltimateAI.html +28 -8
- package/nodes/locales/zh-CN/knxUltimateAI.json +24 -4
- package/nodes/plugins/knxUltimateAI-vue/assets/app.css +1 -1
- package/nodes/plugins/knxUltimateAI-vue/assets/app.js +4 -4
- package/nodes/utils/knxAiChatContext.js +181 -84
- package/nodes/utils/knxAiEventHistory.js +275 -0
- package/package.json +1 -1
- package/resources/KNXAIChatAdapterMappings.js +14 -12
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,30 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 6.3.24** - August 2026<br/>
|
|
10
|
+
|
|
11
|
+
- **KNX AI — working Telegram confirmation buttons**: changed the `windkh/node-red-contrib-telegrambot` preset from inline callbacks to a one-time Telegram reply keyboard. **Confirm** and **Cancel** now return as normal localized messages through the already connected `telegram receiver`, eliminating the silent no-op caused by a missing `callback_query` event node. Existing saved preset output mappings are upgraded at runtime, the keyboard is removed after the decision, and legacy callback events remain accepted. The importable direct-chat example no longer requires extra callback wiring; help and wiki documentation were updated in EN, IT, DE, FR, ES and zh-CN.<br/>
|
|
12
|
+
- **KNX AI — native CHAT learning memory**: replaced the verbose Markdown/JSON V2 learning file with the direct line-oriented `knxai-chat-context.knxctx` V3 format. Sessions, instructions, turns and camera watches are stored as editable tab-separated `SESSION`, `INSTRUCTION`, `TURN`, `CAMERA_WATCH` and `END_SESSION` records with escaped text fields, without JSON serialization or a regenerated preview. The Web UI keeps copy, download, restore, revision-safe save and protected reinitialization. Previous `.md` Markdown/JSON V2 and Base64 V1 files are deliberately not read, imported, migrated or deleted; KNX AI starts a new native context instead. Help and wiki documentation were updated in EN, IT, DE, FR, ES and zh-CN.<br/>
|
|
13
|
+
- **KNX AI — compact LLM settings layout**: constrained the assistant connection form to a readable responsive width and removed the fixed 290 px indentation that allowed the local-context hint and status line to widen the complete Node-RED properties panel. The model autocomplete now reopens after mouse release, so the full provider list remains visible after a normal click instead of appearing only while the button is held.<br/>
|
|
14
|
+
- **KNX AI — resettable CHAT learning**: added a protected **Reinitialize Memory** action to **Settings → AI Chat Learning**. After explicit confirmation it atomically replaces the shared learning file with a new empty native context, clears sessions, persistent instructions, camera watches and pending chat confirmations across every KNX AI node using the same storage, and refreshes the editor immediately. Concurrent changes remain protected by the existing content revision check.<br/>
|
|
15
|
+
- **KNX AI — complete model picker**: clicking the model field now opens the complete list returned by the selected provider regardless of the text already present, while typed text can still filter the same list and custom model names remain accepted.<br/>
|
|
16
|
+
- **KNX AI — native compact context archives**: replaced JSONL serialization for KNX telegram and detected-adapter history with KNX AI's direct line-oriented `.knxctx` format. Typed payloads, flags and bounded adapter metadata round-trip without repeated JSON property names, while bus and archive summaries are rendered as compact `key=value` context instead of JSON before reaching the model. Existing JSONL archives are deliberately not read or migrated; new daily files use `YYYY-MM-DD.knxctx`. The user-editable CHAT learning file remains separate because it is also a portable user backup surface. Context cards, editor help and wiki documentation were updated in EN, IT, DE, FR, ES and zh-CN.<br/>
|
|
17
|
+
- **KNX AI — user-selectable local chat context**: added a localized **Chat context amount** selector for Ollama and Bionic LM Studio with 4K, 8K and 16K choices; 16K remains the default. The selected budget proportionally bounds KNX traffic, archives, ETS catalog, Node-RED inventory, memory and adapter context, and is also sent as Ollama's `num_ctx`, while the complete agent tool contract stays available without phrase classifiers, keyword gates or linguistic intent routing. The context overview now reports the configured operational limit. Editor help and wiki documentation were updated in EN, IT, DE, FR, ES and zh-CN.<br/>
|
|
18
|
+
- **KNX AI — reliable conversational recall**: verified that session IDs and recent turns were already persisted correctly, then moved the current session memory next to the current request in the operational prompt so small local models do not lose user-supplied facts inside large KNX context. The structured memory tool now explicitly covers durable user facts such as preferred name and language as well as preferences and instructions, while remaining model-selected with no phrase classifiers or intent routing; credentials, security codes and API keys are excluded.<br/>
|
|
19
|
+
- **KNX AI — editable and portable CHAT learning**: added **Settings → AI Chat Learning** to the Vue web UI for viewing the exact absolute path and editing, copying, downloading or restoring the shared learning file. The **Conversations & home** tab in the Node-RED node configuration includes a localized shortcut that opens the Web UI directly on this editor for the current node. Saves are validated, bounded to 512 KB, written atomically and applied immediately to every KNX AI node sharing the same storage, while revision checks prevent stale editors from overwriting newer learning. UI strings, editor help and wiki documentation were updated in EN, IT, DE, FR, ES and zh-CN.<br/>
|
|
20
|
+
- **KNX AI — intent-free agentic chat tools**: removed hard-coded semantic phrase classifiers from conversational routing. Every Ask/CHAT request now lets the model reason directly over the current goal, persistent user guidance, AI Education and the available structured KNX, camera, TTS Ultimate and memory tools. Routine planning no longer depends on a multilingual keyword list, TTS announcements no longer require an action alias, and durable chat instructions are remembered or forgotten through model-selected structured actions instead of regular-expression intent detection. Runtime argument validation, adapter availability checks, ETS/DPT safeguards and command confirmation remain unchanged.<br/>
|
|
21
|
+
- **KNX AI — learned KNX group-address roles**: a `neutral` GA is now treated as unresolved rather than permanently uncontrollable. The conversational model can learn or forget the exact `command`, `status` or `neutral` role from trusted user teaching, persistent chat guidance, AI Education or unequivocal ETS semantics through the structured `gaRoleActions` tool. Learned role, reason and evidence are persisted per node in `knxai-config-<node-id>.json`, synchronized into bounded semantic home memory and reused after restart. A command role learned in the current answer can validate a write in that same turn, while exact ETS membership, immutable DPT, payload validation and configured confirmation still apply. Help and wiki documentation were aligned in EN, IT, DE, FR, ES and zh-CN.<br/>
|
|
22
|
+
|
|
23
|
+
**Version 6.3.22** - August 2026<br/>
|
|
24
|
+
|
|
25
|
+
- **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/>
|
|
26
|
+
- **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/>
|
|
27
|
+
- **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/>
|
|
28
|
+
- **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/>
|
|
29
|
+
- **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/>
|
|
30
|
+
- **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/>
|
|
31
|
+
- **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/>
|
|
32
|
+
|
|
9
33
|
**Version 6.3.21** - August 2026<br/>
|
|
10
34
|
|
|
11
35
|
- **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/>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "tab",
|
|
5
5
|
"label": "KNX AI - Telegrambot direct chat",
|
|
6
6
|
"disabled": false,
|
|
7
|
-
"info": "Direct chat wiring for windkh/node-red-contrib-telegrambot using the KNX AI chat-adapter preset."
|
|
7
|
+
"info": "Direct chat wiring for windkh/node-red-contrib-telegrambot using the KNX AI chat-adapter preset. Confirmation buttons return through the same receiver; no callback event node is required."
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"id": "comment_knx_ai_telegram_setup",
|
|
@@ -53,22 +53,6 @@
|
|
|
53
53
|
[]
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
|
-
{
|
|
57
|
-
"id": "telegram_callback_knx_ai",
|
|
58
|
-
"type": "telegram event",
|
|
59
|
-
"z": "tab_knx_ai_telegram",
|
|
60
|
-
"name": "Confirm / Cancel buttons",
|
|
61
|
-
"bot": "telegram_bot_knx_ai",
|
|
62
|
-
"event": "callback_query",
|
|
63
|
-
"autoanswer": true,
|
|
64
|
-
"x": 190,
|
|
65
|
-
"y": 240,
|
|
66
|
-
"wires": [
|
|
67
|
-
[
|
|
68
|
-
"node_knx_ai_telegram"
|
|
69
|
-
]
|
|
70
|
-
]
|
|
71
|
-
},
|
|
72
56
|
{
|
|
73
57
|
"id": "node_knx_ai_telegram",
|
|
74
58
|
"type": "knxUltimateAI",
|
package/nodes/knxUltimateAI.html
CHANGED
|
@@ -5,6 +5,37 @@
|
|
|
5
5
|
align-items: flex-start;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
#knx-ai-llm-connection-settings {
|
|
9
|
+
width: 100%;
|
|
10
|
+
max-width: 720px;
|
|
11
|
+
min-width: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#knx-ai-llm-connection-settings .form-row {
|
|
15
|
+
min-width: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#knx-ai-local-context-status,
|
|
19
|
+
.knx-ai-prompt-context-hint {
|
|
20
|
+
width: 100%;
|
|
21
|
+
max-width: 720px;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
color: var(--red-ui-secondary-text-color, #777);
|
|
25
|
+
font-size: 12px;
|
|
26
|
+
line-height: 1.4;
|
|
27
|
+
white-space: normal;
|
|
28
|
+
overflow-wrap: break-word;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#knx-ai-local-context-status {
|
|
32
|
+
margin: -8px 0 12px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.knx-ai-prompt-context-hint {
|
|
36
|
+
margin: 6px 0 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
8
39
|
#knx-ai-ollama-install-row>label {
|
|
9
40
|
padding-top: 6px;
|
|
10
41
|
}
|
|
@@ -138,7 +169,8 @@
|
|
|
138
169
|
}
|
|
139
170
|
|
|
140
171
|
#knx-ai-detected-adapters-panel,
|
|
141
|
-
#knx-ai-chat-context-panel
|
|
172
|
+
#knx-ai-chat-context-panel,
|
|
173
|
+
#knx-ai-chat-learning-link-panel {
|
|
142
174
|
margin: 0 0 18px;
|
|
143
175
|
padding: 12px 14px;
|
|
144
176
|
border: 1px solid var(--red-ui-form-input-border-color, #ccc);
|
|
@@ -155,7 +187,8 @@
|
|
|
155
187
|
}
|
|
156
188
|
|
|
157
189
|
#knx-ai-detected-adapters-panel>h4,
|
|
158
|
-
#knx-ai-chat-context-panel>h4
|
|
190
|
+
#knx-ai-chat-context-panel>h4,
|
|
191
|
+
#knx-ai-chat-learning-link-panel>h4 {
|
|
159
192
|
margin: 0 0 9px;
|
|
160
193
|
font-size: 14px;
|
|
161
194
|
font-weight: 600;
|
|
@@ -182,12 +215,29 @@
|
|
|
182
215
|
#knx-ai-detected-adapters-status,
|
|
183
216
|
#knx-ai-chat-context-status,
|
|
184
217
|
.knx-ai-chat-context-intro,
|
|
218
|
+
.knx-ai-chat-context-metric,
|
|
185
219
|
.knx-ai-chat-context-description,
|
|
186
220
|
.knx-ai-chat-context-pattern {
|
|
187
221
|
color: var(--red-ui-secondary-text-color, #777);
|
|
188
222
|
font-size: 12px;
|
|
189
223
|
}
|
|
190
224
|
|
|
225
|
+
#knx-ai-chat-learning-link-panel {
|
|
226
|
+
margin: 18px 0 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.knx-ai-chat-learning-link-copy {
|
|
230
|
+
margin: 0 0 10px;
|
|
231
|
+
color: var(--red-ui-secondary-text-color, #777);
|
|
232
|
+
font-size: 12px;
|
|
233
|
+
line-height: 1.45;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.knx-ai-chat-context-metric {
|
|
237
|
+
margin-top: 6px;
|
|
238
|
+
font-weight: 600;
|
|
239
|
+
}
|
|
240
|
+
|
|
191
241
|
.knx-ai-chat-context-group-title {
|
|
192
242
|
margin: 12px 0 2px;
|
|
193
243
|
font-size: 12px;
|
|
@@ -267,6 +317,7 @@
|
|
|
267
317
|
llmBaseUrl: { value: "https://api.openai.com/v1/chat/completions" },
|
|
268
318
|
llmModel: { value: "gpt-5.4" },
|
|
269
319
|
llmContextLength: { value: 0 },
|
|
320
|
+
llmPromptContextTokens: { value: 16384 },
|
|
270
321
|
llmAllowKnxCommands: { value: false },
|
|
271
322
|
llmRequireCommandConfirmation: { value: true },
|
|
272
323
|
chatAdapterPreset: { value: "none" },
|
|
@@ -392,6 +443,13 @@
|
|
|
392
443
|
} catch (error) { }
|
|
393
444
|
delete node[key];
|
|
394
445
|
});
|
|
446
|
+
try {
|
|
447
|
+
const $model = $("#node-input-llmModel");
|
|
448
|
+
$model.off(".knxUltimateAiModelPicker");
|
|
449
|
+
if (typeof $model.autocomplete === "function" && $model.data("ui-autocomplete")) {
|
|
450
|
+
$model.autocomplete("destroy");
|
|
451
|
+
}
|
|
452
|
+
} catch (error) { }
|
|
395
453
|
};
|
|
396
454
|
|
|
397
455
|
try {
|
|
@@ -610,6 +668,8 @@
|
|
|
610
668
|
const $sources = $("#knx-ai-chat-context-sources-list").empty();
|
|
611
669
|
const $files = $("#knx-ai-chat-context-files-list").empty();
|
|
612
670
|
const $directories = $("#knx-ai-chat-context-directories-list").empty();
|
|
671
|
+
const $contextLimit = $("#knx-ai-chat-context-limit");
|
|
672
|
+
const $lastPromptUsage = $("#knx-ai-chat-context-last-prompt");
|
|
613
673
|
if (!overview || typeof overview !== "object") {
|
|
614
674
|
$content.hide();
|
|
615
675
|
$status
|
|
@@ -633,12 +693,63 @@
|
|
|
633
693
|
$row.appendTo($list);
|
|
634
694
|
};
|
|
635
695
|
|
|
696
|
+
const formatCompactTokens = function (value) {
|
|
697
|
+
const tokens = Math.max(0, Number(value) || 0);
|
|
698
|
+
if (!tokens) return "?";
|
|
699
|
+
if (tokens >= 1024 && tokens % 1024 === 0) return (tokens / 1024) + "K";
|
|
700
|
+
return tokens.toLocaleString();
|
|
701
|
+
};
|
|
702
|
+
const formatBytes = function (value) {
|
|
703
|
+
const bytes = Math.max(0, Number(value) || 0);
|
|
704
|
+
if (bytes >= (1024 * 1024)) return (bytes / (1024 * 1024)).toLocaleString(undefined, { maximumFractionDigits: 1 }) + " MB";
|
|
705
|
+
if (bytes >= 1024) return (bytes / 1024).toLocaleString(undefined, { maximumFractionDigits: 1 }) + " KB";
|
|
706
|
+
return bytes.toLocaleString() + " B";
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
const contextLimit = overview.contextLimit && typeof overview.contextLimit === "object" ? overview.contextLimit : {};
|
|
710
|
+
const currentProvider = String(contextLimit.provider || $("#node-input-llmProvider").val() || "").toLowerCase();
|
|
711
|
+
let contextLimitTokens = Math.max(0, Number(contextLimit.tokens) || 0);
|
|
712
|
+
if (!contextLimitTokens && (currentProvider === "lmstudio" || currentProvider === "ollama")) {
|
|
713
|
+
contextLimitTokens = Math.min(
|
|
714
|
+
Math.max(0, Number($("#node-input-llmContextLength").val()) || 16384),
|
|
715
|
+
Math.max(0, Number($("#node-input-llmPromptContextTokens").val()) || 16384),
|
|
716
|
+
16384
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
const contextLimitValue = contextLimitTokens
|
|
720
|
+
? formatCompactTokens(contextLimitTokens) + " " + t("knxUltimateAI.messages.chatContextTokens", "tokens")
|
|
721
|
+
: t("knxUltimateAI.messages.chatContextProviderManaged", "managed by the selected provider/model");
|
|
722
|
+
$contextLimit
|
|
723
|
+
.text(t("knxUltimateAI.messages.chatContextLimitLabel", "Maximum operational context") + ": " + contextLimitValue)
|
|
724
|
+
.attr("title", contextLimitTokens ? contextLimitTokens.toLocaleString() + " tokens" : contextLimitValue);
|
|
725
|
+
|
|
726
|
+
const promptUsage = overview.lastPromptUsage && typeof overview.lastPromptUsage === "object" ? overview.lastPromptUsage : null;
|
|
727
|
+
if (promptUsage && Number(promptUsage.bytes) > 0) {
|
|
728
|
+
const exactInputTokens = Math.max(0, Number(promptUsage.exactInputTokens) || 0);
|
|
729
|
+
const estimatedInputTokens = Math.max(0, Number(promptUsage.estimatedInputTokens) || 0);
|
|
730
|
+
const tokenText = exactInputTokens
|
|
731
|
+
? formatCompactTokens(exactInputTokens) + " " + t("knxUltimateAI.messages.chatContextExactInputTokens", "input tokens measured by the provider")
|
|
732
|
+
: "~" + formatCompactTokens(estimatedInputTokens) + " " + t("knxUltimateAI.messages.chatContextEstimatedInputTokens", "estimated input tokens");
|
|
733
|
+
const imageText = Number(promptUsage.imageCount) > 0
|
|
734
|
+
? " · " + Number(promptUsage.imageCount).toLocaleString() + " " + t("knxUltimateAI.messages.chatContextImages", "images")
|
|
735
|
+
: "";
|
|
736
|
+
$lastPromptUsage.text(
|
|
737
|
+
t("knxUltimateAI.messages.chatContextLastPromptLabel", "Last chat prompt actual size") + ": " +
|
|
738
|
+
formatBytes(promptUsage.bytes) + " · " + tokenText + imageText
|
|
739
|
+
);
|
|
740
|
+
} else {
|
|
741
|
+
$lastPromptUsage.text(
|
|
742
|
+
t("knxUltimateAI.messages.chatContextLastPromptLabel", "Last chat prompt actual size") + ": " +
|
|
743
|
+
t("knxUltimateAI.messages.chatContextLastPromptUnavailable", "not available until the first chat request")
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
636
747
|
const sourceLabels = {
|
|
637
748
|
knxTraffic: t("knxUltimateAI.messages.chatContextSourceKnxTraffic", "Live KNX summary, anomalies, topology and selected telegrams."),
|
|
638
749
|
adapterHistory: t("knxUltimateAI.messages.chatContextSourceAdapterHistory", "Persistent history of automatically detected adapter events."),
|
|
639
750
|
etsProject: t("knxUltimateAI.messages.chatContextSourceEtsProject", "ETS semantics and the full Node-RED project inventory."),
|
|
640
751
|
memoryEducation: t("knxUltimateAI.messages.chatContextSourceMemoryEducation", "Session context, AI Education and bounded home memory."),
|
|
641
|
-
|
|
752
|
+
cameras: t("knxUltimateAI.messages.chatContextSourceCameras", "Detected cameras and their available capabilities."),
|
|
642
753
|
ttsUltimate: t("knxUltimateAI.messages.chatContextSourceTtsUltimate", "Selected TTS Ultimate announcement target.")
|
|
643
754
|
};
|
|
644
755
|
(Array.isArray(overview.sources) ? overview.sources : []).forEach(function (sourceId) {
|
|
@@ -687,7 +798,7 @@
|
|
|
687
798
|
});
|
|
688
799
|
|
|
689
800
|
$("#knx-ai-chat-context-pattern")
|
|
690
|
-
.text(t("knxUltimateAI.messages.chatContextTelegramPattern", "Daily files") + ": " + String(overview.telegramFilePattern || "YYYY-MM-DD.
|
|
801
|
+
.text(t("knxUltimateAI.messages.chatContextTelegramPattern", "Daily files") + ": " + String(overview.telegramFilePattern || "YYYY-MM-DD.knxctx"));
|
|
691
802
|
$status.hide();
|
|
692
803
|
$content.show();
|
|
693
804
|
};
|
|
@@ -708,17 +819,30 @@
|
|
|
708
819
|
renderChatContextOverview(null);
|
|
709
820
|
});
|
|
710
821
|
|
|
711
|
-
|
|
712
|
-
evt.preventDefault();
|
|
822
|
+
const openKnxAiWebPage = function (view) {
|
|
713
823
|
const adminRoot = resolveAdminRoot();
|
|
714
824
|
const targetBase = adminRoot + "/knxUltimateAI/sidebar/page";
|
|
715
825
|
const params = new URLSearchParams();
|
|
716
826
|
if (nodeId) params.set("nodeId", nodeId);
|
|
827
|
+
if (view === "chat-learning") {
|
|
828
|
+
params.set("tab", "settings");
|
|
829
|
+
params.set("settingsTab", "learning");
|
|
830
|
+
}
|
|
717
831
|
const accessToken = resolveAccessToken();
|
|
718
832
|
if (accessToken) params.set("access_token", accessToken);
|
|
719
833
|
const target = targetBase + (params.toString() ? ("?" + params.toString()) : "");
|
|
720
834
|
const wnd = window.open(target, "_blank", "noopener,noreferrer");
|
|
721
835
|
try { if (wnd && typeof wnd.focus === "function") wnd.focus(); } catch (e) { }
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
$("#knx-ai-open-web-page-vue").on("click", function (evt) {
|
|
839
|
+
evt.preventDefault();
|
|
840
|
+
openKnxAiWebPage();
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
$("#knx-ai-open-chat-learning").on("click", function (evt) {
|
|
844
|
+
evt.preventDefault();
|
|
845
|
+
openKnxAiWebPage("chat-learning");
|
|
722
846
|
});
|
|
723
847
|
|
|
724
848
|
const setRefreshModelsBusy = (busy) => {
|
|
@@ -733,6 +857,8 @@
|
|
|
733
857
|
};
|
|
734
858
|
|
|
735
859
|
let localModelDetailsById = new Map();
|
|
860
|
+
let availableModelItems = [];
|
|
861
|
+
let modelPickerMouseDown = false;
|
|
736
862
|
let localContextRequestVersion = 0;
|
|
737
863
|
|
|
738
864
|
const formatContextTokens = (value) => {
|
|
@@ -752,11 +878,14 @@
|
|
|
752
878
|
let text = t("knxUltimateAI.messages.lmStudioContextAvailable", "Maximum model context") + ": " + maxTokens + " token";
|
|
753
879
|
let color = "#555";
|
|
754
880
|
if (state === "loading") {
|
|
755
|
-
text = t("knxUltimateAI.messages.lmStudioContextLoading", "
|
|
881
|
+
text = t("knxUltimateAI.messages.lmStudioContextLoading", "Checking the active model context") + "…";
|
|
756
882
|
color = "#1565c0";
|
|
883
|
+
} else if (state === "inactive") {
|
|
884
|
+
text = t("knxUltimateAI.messages.lmStudioContextInactive", "Model inactive; Bionic defaults will be used on the first request");
|
|
885
|
+
color = "#555";
|
|
757
886
|
} else if (state === "configured") {
|
|
758
887
|
const configuredTokens = formatContextTokens(detail.configuredContextLength || detail.loadedContextLength || detail.maxContextLength);
|
|
759
|
-
text = t("knxUltimateAI.messages.lmStudioContextConfigured", "
|
|
888
|
+
text = t("knxUltimateAI.messages.lmStudioContextConfigured", "Active model context") + ": " + configuredTokens + " token";
|
|
760
889
|
color = "#2e7d32";
|
|
761
890
|
} else if (state === "error") {
|
|
762
891
|
text = String(errorText || t("knxUltimateAI.messages.lmStudioContextFailed", "Unable to configure the model context"));
|
|
@@ -764,26 +893,64 @@
|
|
|
764
893
|
} else if (detail.loadedContextLength > 0) {
|
|
765
894
|
text += " · " + t("knxUltimateAI.messages.lmStudioContextCurrentlyLoaded", "currently loaded") + ": " + loadedTokens + " token";
|
|
766
895
|
}
|
|
896
|
+
if (provider === "lmstudio" || provider === "ollama") {
|
|
897
|
+
const selectedBudget = Math.max(4096, Number($("#node-input-llmPromptContextTokens").val()) || 16384);
|
|
898
|
+
text += " · " + t("knxUltimateAI.messages.localContextBudget", "KNX AI context budget") + ": " + (selectedBudget / 1024) + "K";
|
|
899
|
+
}
|
|
767
900
|
$status.text(text).css("color", color).show();
|
|
768
901
|
};
|
|
769
902
|
|
|
770
903
|
const populateModels = (models, modelDetails) => {
|
|
771
|
-
const $dl = $("#knx-ai-llmModels");
|
|
772
|
-
if (!$dl || $dl.length === 0) return;
|
|
773
904
|
localModelDetailsById = new Map();
|
|
774
905
|
(modelDetails || []).forEach(function (detail) {
|
|
775
906
|
if (detail && detail.id) localModelDetailsById.set(String(detail.id), detail);
|
|
776
907
|
});
|
|
777
|
-
|
|
778
|
-
(models || []).forEach(function (m) {
|
|
908
|
+
availableModelItems = (models || []).map(function (m) {
|
|
779
909
|
const detail = localModelDetailsById.get(String(m));
|
|
780
910
|
const label = detail
|
|
781
911
|
? String(detail.displayName || detail.id) + " · " + formatContextTokens(detail.maxContextLength) + " token"
|
|
782
912
|
: String(m);
|
|
783
|
-
|
|
913
|
+
return { label: label, value: String(m) };
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
const configureModelPicker = () => {
|
|
918
|
+
const $model = $("#node-input-llmModel");
|
|
919
|
+
if (!$model.length || typeof $model.autocomplete !== "function") return;
|
|
920
|
+
$model.autocomplete({
|
|
921
|
+
minLength: 0,
|
|
922
|
+
source: function (request, response) {
|
|
923
|
+
const term = String(request && request.term ? request.term : "").trim().toLowerCase();
|
|
924
|
+
if (!term) {
|
|
925
|
+
response(availableModelItems.slice());
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
response(availableModelItems.filter(function (item) {
|
|
929
|
+
return item.value.toLowerCase().includes(term) || item.label.toLowerCase().includes(term);
|
|
930
|
+
}));
|
|
931
|
+
},
|
|
932
|
+
select: function (event, ui) {
|
|
933
|
+
$(this).val(ui.item.value).trigger("change");
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
$model.on("mousedown.knxUltimateAiModelPicker", function () {
|
|
938
|
+
modelPickerMouseDown = true;
|
|
939
|
+
}).on("focus.knxUltimateAiModelPicker", function () {
|
|
940
|
+
if (!modelPickerMouseDown) $(this).autocomplete("search", "");
|
|
941
|
+
}).on("click.knxUltimateAiModelPicker", function () {
|
|
942
|
+
modelPickerMouseDown = false;
|
|
943
|
+
const input = this;
|
|
944
|
+
setTimeout(function () {
|
|
945
|
+
try { $(input).autocomplete("search", ""); } catch (error) { }
|
|
946
|
+
}, 0);
|
|
947
|
+
}).on("blur.knxUltimateAiModelPicker", function () {
|
|
948
|
+
modelPickerMouseDown = false;
|
|
784
949
|
});
|
|
785
950
|
};
|
|
786
951
|
|
|
952
|
+
configureModelPicker();
|
|
953
|
+
|
|
787
954
|
const normalizeUrl = (value) => String(value || "").trim().replace(/\/+$/, "").toLowerCase();
|
|
788
955
|
const isOpenAiDefaultUrl = (value) => normalizeUrl(value) === normalizeUrl(OPENAI_COMPAT_DEFAULT_CHAT_URL);
|
|
789
956
|
const isOllamaDefaultUrl = (value) => normalizeUrl(value) === normalizeUrl(OLLAMA_DEFAULT_CHAT_URL);
|
|
@@ -824,9 +991,13 @@
|
|
|
824
991
|
}
|
|
825
992
|
|
|
826
993
|
const requestVersion = ++localContextRequestVersion;
|
|
827
|
-
|
|
994
|
+
const activeContextLength = Number(detail.loadedContextLength) > 0
|
|
995
|
+
? Number(detail.loadedContextLength)
|
|
996
|
+
: Number(detail.maxContextLength);
|
|
997
|
+
$("#node-input-llmContextLength").val(activeContextLength);
|
|
828
998
|
if (provider === "ollama") {
|
|
829
|
-
detail.configuredContextLength = Number(detail.maxContextLength);
|
|
999
|
+
detail.configuredContextLength = Math.min(Number(detail.maxContextLength), 16384);
|
|
1000
|
+
$("#node-input-llmContextLength").val(detail.configuredContextLength);
|
|
830
1001
|
setLocalModelContextStatus(detail, "configured");
|
|
831
1002
|
return;
|
|
832
1003
|
}
|
|
@@ -848,13 +1019,14 @@
|
|
|
848
1019
|
type: "POST",
|
|
849
1020
|
contentType: "application/json",
|
|
850
1021
|
data: JSON.stringify(payload)
|
|
851
|
-
|
|
1022
|
+
})
|
|
852
1023
|
.done(function (data) {
|
|
853
1024
|
if (requestVersion !== localContextRequestVersion) return;
|
|
854
|
-
|
|
855
|
-
detail.
|
|
856
|
-
|
|
857
|
-
|
|
1025
|
+
const resolvedContextLength = Math.max(0, Number(data && data.contextLength) || 0);
|
|
1026
|
+
detail.loadedContextLength = data && data.active === true ? resolvedContextLength : 0;
|
|
1027
|
+
detail.configuredContextLength = resolvedContextLength;
|
|
1028
|
+
$("#node-input-llmContextLength").val(resolvedContextLength);
|
|
1029
|
+
setLocalModelContextStatus(detail, data && data.active === true ? "configured" : "inactive");
|
|
858
1030
|
})
|
|
859
1031
|
.fail(function (xhr) {
|
|
860
1032
|
if (requestVersion !== localContextRequestVersion) return;
|
|
@@ -978,7 +1150,9 @@
|
|
|
978
1150
|
|
|
979
1151
|
const toggleProvider = ({ applyDefaults = false, autoLoadModels = false } = {}) => {
|
|
980
1152
|
const provider = $("#node-input-llmProvider").val();
|
|
1153
|
+
const localProvider = provider === "lmstudio" || provider === "ollama";
|
|
981
1154
|
if (applyDefaults) applyProviderDefaults(provider);
|
|
1155
|
+
$("#knx-ai-prompt-context-row").toggle(localProvider);
|
|
982
1156
|
if (provider === "ollama") {
|
|
983
1157
|
$("#knx-ai-apikey-row").hide();
|
|
984
1158
|
$("#knx-ai-ollama-install-row").show();
|
|
@@ -1005,6 +1179,10 @@
|
|
|
1005
1179
|
$("#node-input-llmModel").on("change", function () {
|
|
1006
1180
|
configureSelectedLocalModel();
|
|
1007
1181
|
});
|
|
1182
|
+
$("#node-input-llmPromptContextTokens").on("change", function () {
|
|
1183
|
+
const detail = localModelDetailsById.get(String($("#node-input-llmModel").val() || "").trim());
|
|
1184
|
+
if (detail) setLocalModelContextStatus(detail, detail.loadedContextLength > 0 ? "configured" : "inactive");
|
|
1185
|
+
});
|
|
1008
1186
|
$("#knx-ai-downloadOllamaModel").on("click", function (evt) {
|
|
1009
1187
|
evt.preventDefault();
|
|
1010
1188
|
openOllamaLibrary();
|
|
@@ -1105,6 +1283,13 @@
|
|
|
1105
1283
|
<h4><i class="fa fa-home"></i> <span data-i18n="knxUltimateAI.sections.homeIntelligence"></span></h4>
|
|
1106
1284
|
<div class="knx-ai-accordion-mount" id="knx-ai-mount-home-intelligence"></div>
|
|
1107
1285
|
</div>
|
|
1286
|
+
<div id="knx-ai-chat-learning-link-panel">
|
|
1287
|
+
<h4><i class="fa fa-graduation-cap"></i> <span data-i18n="knxUltimateAI.sections.chatLearning"></span></h4>
|
|
1288
|
+
<p class="knx-ai-chat-learning-link-copy" data-i18n="knxUltimateAI.messages.chatLearningOpenHint"></p>
|
|
1289
|
+
<button type="button" class="red-ui-button" id="knx-ai-open-chat-learning">
|
|
1290
|
+
<i class="fa fa-external-link"></i> <span data-i18n="knxUltimateAI.buttons.openChatLearning"></span>
|
|
1291
|
+
</button>
|
|
1292
|
+
</div>
|
|
1108
1293
|
<div id="knx-ai-detected-adapters-panel">
|
|
1109
1294
|
<h4><i class="fa fa-puzzle-piece"></i> <span data-i18n="knxUltimateAI.sections.detectedAdapters"></span></h4>
|
|
1110
1295
|
<div id="knx-ai-detected-adapters-status" data-i18n="knxUltimateAI.messages.detectedAdaptersLoading"></div>
|
|
@@ -1120,6 +1305,8 @@
|
|
|
1120
1305
|
<div id="knx-ai-chat-context-status" data-i18n="knxUltimateAI.messages.chatContextLoading"></div>
|
|
1121
1306
|
<div id="knx-ai-chat-context-content" style="display:none;">
|
|
1122
1307
|
<div class="knx-ai-chat-context-intro" data-i18n="knxUltimateAI.messages.chatContextIntro"></div>
|
|
1308
|
+
<div id="knx-ai-chat-context-limit" class="knx-ai-chat-context-metric"></div>
|
|
1309
|
+
<div id="knx-ai-chat-context-last-prompt" class="knx-ai-chat-context-metric"></div>
|
|
1123
1310
|
<div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextSourcesTitle"></div>
|
|
1124
1311
|
<div id="knx-ai-chat-context-sources-list"></div>
|
|
1125
1312
|
<div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextFilesTitle"></div>
|
|
@@ -1158,14 +1345,22 @@
|
|
|
1158
1345
|
|
|
1159
1346
|
<div class="form-row">
|
|
1160
1347
|
<label style="width:290px" for="node-input-llmModel"><i class="fa fa-cube"></i> <span data-i18n="knxUltimateAI.properties.llmModel"></span></label>
|
|
1161
|
-
<input style="width:calc(100% - 110px)" type="text" id="node-input-llmModel"
|
|
1348
|
+
<input style="width:calc(100% - 110px)" type="text" id="node-input-llmModel" autocomplete="off" data-i18n="[placeholder]knxUltimateAI.placeholder.llmModel">
|
|
1162
1349
|
<button type="button" class="red-ui-button" id="knx-ai-refreshModels" style="margin-left:6px;" title="Refresh models">
|
|
1163
1350
|
<i class="fa fa-refresh"></i> <span data-i18n="knxUltimateAI.buttons.refreshModels"></span>
|
|
1164
1351
|
</button>
|
|
1165
1352
|
</div>
|
|
1166
|
-
<datalist id="knx-ai-llmModels"></datalist>
|
|
1167
1353
|
<input type="hidden" id="node-input-llmContextLength">
|
|
1168
|
-
<div id="knx-ai-local-context-status" style="display:none;
|
|
1354
|
+
<div id="knx-ai-local-context-status" style="display:none;"></div>
|
|
1355
|
+
<div class="form-row" id="knx-ai-prompt-context-row" style="display:none;">
|
|
1356
|
+
<label style="width:290px" for="node-input-llmPromptContextTokens"><i class="fa fa-database"></i> <span data-i18n="knxUltimateAI.properties.llmPromptContextTokens"></span></label>
|
|
1357
|
+
<select style="width:100%" id="node-input-llmPromptContextTokens">
|
|
1358
|
+
<option value="4096" data-i18n="knxUltimateAI.selectlists.promptContext.small"></option>
|
|
1359
|
+
<option value="8192" data-i18n="knxUltimateAI.selectlists.promptContext.medium"></option>
|
|
1360
|
+
<option value="16384" data-i18n="knxUltimateAI.selectlists.promptContext.full"></option>
|
|
1361
|
+
</select>
|
|
1362
|
+
<div class="knx-ai-prompt-context-hint" data-i18n="knxUltimateAI.messages.promptContextHint"></div>
|
|
1363
|
+
</div>
|
|
1169
1364
|
<div class="form-row">
|
|
1170
1365
|
<input type="checkbox" id="node-input-llmAllowKnxCommands" style="display:inline-block; width:auto; vertical-align:top;">
|
|
1171
1366
|
<label style="width:auto" for="node-input-llmAllowKnxCommands"> <span data-i18n="knxUltimateAI.properties.llmAllowKnxCommands"></span></label>
|