node-red-contrib-knx-ultimate 6.3.16 → 6.3.18
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 +11 -0
- package/examples/KNX AI - Conversational Control with Confirmation.json +0 -20
- package/examples/KNX AI - Summary Anomalies and Ask.json +0 -17
- package/examples/KNX AI - Telegrambot Direct Chat.json +3 -29
- package/nodes/knxUltimateAI.html +458 -206
- package/nodes/knxUltimateAI.js +1191 -163
- package/nodes/locales/de/knxUltimateAI.html +34 -48
- package/nodes/locales/de/knxUltimateAI.json +42 -39
- package/nodes/locales/en/knxUltimateAI.html +38 -51
- package/nodes/locales/en/knxUltimateAI.json +42 -39
- package/nodes/locales/es/knxUltimateAI.html +34 -48
- package/nodes/locales/es/knxUltimateAI.json +42 -39
- package/nodes/locales/fr/knxUltimateAI.html +34 -48
- package/nodes/locales/fr/knxUltimateAI.json +42 -39
- package/nodes/locales/it/knxUltimateAI.html +38 -51
- package/nodes/locales/it/knxUltimateAI.json +42 -39
- package/nodes/locales/zh-CN/knxUltimateAI.html +34 -48
- package/nodes/locales/zh-CN/knxUltimateAI.json +42 -39
- package/package.json +1 -1
package/nodes/knxUltimateAI.html
CHANGED
|
@@ -46,60 +46,99 @@
|
|
|
46
46
|
background-color: #fffde7 !important;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
#knx-ai-
|
|
50
|
-
|
|
49
|
+
#knx-ai-tabs.ui-tabs {
|
|
50
|
+
background: transparent;
|
|
51
|
+
border: none;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
#knx-ai-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
#knx-ai-tabs>.ui-tabs-nav {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
align-items: flex-end;
|
|
58
|
+
margin: 0;
|
|
59
|
+
padding: 0;
|
|
60
|
+
background: transparent;
|
|
61
|
+
border: none;
|
|
62
|
+
border-bottom: 1px solid #c0c0c0;
|
|
63
|
+
border-radius: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#knx-ai-tabs>.ui-tabs-nav>li.ui-state-default {
|
|
67
|
+
float: none;
|
|
68
|
+
margin: 0 2px -1px 0;
|
|
69
|
+
background: transparent;
|
|
70
|
+
border: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#knx-ai-tabs>.ui-tabs-nav>li.ui-state-default>a {
|
|
74
|
+
padding: 0.5em 1em;
|
|
75
|
+
color: #878787;
|
|
76
|
+
outline: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#knx-ai-tabs>.ui-tabs-nav>li.ui-state-active {
|
|
80
|
+
background: transparent no-repeat bottom center;
|
|
81
|
+
border: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#knx-ai-tabs>.ui-tabs-nav>li.ui-state-active>a {
|
|
85
|
+
color: #377e00;
|
|
86
|
+
border-bottom: 1px solid #377e00;
|
|
87
|
+
outline: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#knx-ai-tabs>.ui-tabs-nav>li>a>i {
|
|
91
|
+
color: inherit;
|
|
57
92
|
}
|
|
58
93
|
|
|
59
|
-
#knx-ai-
|
|
60
|
-
padding:
|
|
94
|
+
#knx-ai-tabs>.ui-tabs-panel {
|
|
95
|
+
padding: 1em 1.4em;
|
|
61
96
|
overflow: visible;
|
|
97
|
+
background: transparent;
|
|
98
|
+
border: none;
|
|
99
|
+
border-radius: 0;
|
|
62
100
|
}
|
|
63
101
|
|
|
64
|
-
#knx-ai-
|
|
102
|
+
#knx-ai-tabs .knx-ai-accordion-subsection {
|
|
65
103
|
padding: 0;
|
|
66
104
|
}
|
|
67
105
|
|
|
68
|
-
#knx-ai-
|
|
106
|
+
#knx-ai-tabs .knx-ai-accordion-subsection+.knx-ai-accordion-subsection {
|
|
69
107
|
margin-top: 18px;
|
|
70
108
|
padding-top: 16px;
|
|
71
109
|
border-top: 1px solid var(--red-ui-form-input-border-color, #ccc);
|
|
72
110
|
}
|
|
73
111
|
|
|
74
|
-
#knx-ai-
|
|
112
|
+
#knx-ai-tabs .knx-ai-accordion-subsection>h4 {
|
|
75
113
|
margin: 0 0 14px;
|
|
76
114
|
color: var(--red-ui-primary-text-color, #555);
|
|
77
115
|
font-size: 14px;
|
|
78
116
|
font-weight: 600;
|
|
79
117
|
}
|
|
80
118
|
|
|
81
|
-
#knx-ai-
|
|
119
|
+
#knx-ai-tabs .knx-ai-accordion-subsection>h4 i {
|
|
82
120
|
width: 18px;
|
|
83
121
|
text-align: center;
|
|
84
122
|
}
|
|
85
123
|
|
|
86
|
-
#knx-ai-
|
|
124
|
+
#knx-ai-tabs .knx-ai-inline-subsection {
|
|
87
125
|
margin-top: 18px;
|
|
88
126
|
padding-top: 14px;
|
|
89
127
|
border-top: 1px dashed var(--red-ui-form-input-border-color, #ccc);
|
|
90
128
|
}
|
|
91
129
|
|
|
92
|
-
#knx-ai-
|
|
130
|
+
#knx-ai-tabs .knx-ai-inline-subsection>h5 {
|
|
93
131
|
margin: 0 0 12px;
|
|
94
132
|
font-size: 13px;
|
|
95
133
|
font-weight: 600;
|
|
96
134
|
}
|
|
97
135
|
|
|
98
|
-
#knx-ai-
|
|
136
|
+
#knx-ai-tabs .knx-ai-accordion-mount>div {
|
|
99
137
|
display: block;
|
|
100
138
|
}
|
|
101
139
|
|
|
102
|
-
#knx-ai-detected-adapters-panel
|
|
140
|
+
#knx-ai-detected-adapters-panel,
|
|
141
|
+
#knx-ai-chat-context-panel {
|
|
103
142
|
margin: 0 0 18px;
|
|
104
143
|
padding: 12px 14px;
|
|
105
144
|
border: 1px solid var(--red-ui-form-input-border-color, #ccc);
|
|
@@ -107,7 +146,16 @@
|
|
|
107
146
|
background: var(--red-ui-secondary-background, #f7f7f7);
|
|
108
147
|
}
|
|
109
148
|
|
|
110
|
-
#knx-ai-detected-adapters-panel
|
|
149
|
+
#knx-ai-tabs-chat-home>#knx-ai-detected-adapters-panel {
|
|
150
|
+
margin: 18px 0 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#knx-ai-tabs-chat-home>#knx-ai-detected-adapters-panel+#knx-ai-chat-context-panel {
|
|
154
|
+
margin: 18px 0 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#knx-ai-detected-adapters-panel>h4,
|
|
158
|
+
#knx-ai-chat-context-panel>h4 {
|
|
111
159
|
margin: 0 0 9px;
|
|
112
160
|
font-size: 14px;
|
|
113
161
|
font-weight: 600;
|
|
@@ -131,11 +179,33 @@
|
|
|
131
179
|
|
|
132
180
|
.knx-ai-detected-adapter-package,
|
|
133
181
|
.knx-ai-detected-adapter-counts,
|
|
134
|
-
#knx-ai-detected-adapters-status
|
|
182
|
+
#knx-ai-detected-adapters-status,
|
|
183
|
+
#knx-ai-chat-context-status,
|
|
184
|
+
.knx-ai-chat-context-intro,
|
|
185
|
+
.knx-ai-chat-context-description,
|
|
186
|
+
.knx-ai-chat-context-pattern {
|
|
135
187
|
color: var(--red-ui-secondary-text-color, #777);
|
|
136
188
|
font-size: 12px;
|
|
137
189
|
}
|
|
138
190
|
|
|
191
|
+
.knx-ai-chat-context-group-title {
|
|
192
|
+
margin: 12px 0 2px;
|
|
193
|
+
font-size: 12px;
|
|
194
|
+
font-weight: 600;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.knx-ai-chat-context-path {
|
|
198
|
+
margin-top: 2px;
|
|
199
|
+
color: var(--red-ui-secondary-text-color, #777);
|
|
200
|
+
font-family: monospace;
|
|
201
|
+
font-size: 11px;
|
|
202
|
+
overflow-wrap: anywhere;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.knx-ai-chat-context-pattern {
|
|
206
|
+
margin-top: 8px;
|
|
207
|
+
}
|
|
208
|
+
|
|
139
209
|
.knx-ai-detected-adapter-badge {
|
|
140
210
|
flex: 0 0 auto;
|
|
141
211
|
padding: 2px 7px;
|
|
@@ -146,6 +216,29 @@
|
|
|
146
216
|
font-weight: 600;
|
|
147
217
|
}
|
|
148
218
|
|
|
219
|
+
#knx-ai-tts-ultimate-picker {
|
|
220
|
+
margin-top: 8px;
|
|
221
|
+
padding-top: 12px;
|
|
222
|
+
border-top: 1px solid var(--red-ui-form-input-border-color, #ddd);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#knx-ai-tts-ultimate-picker label {
|
|
226
|
+
display: block;
|
|
227
|
+
width: 100%;
|
|
228
|
+
margin-bottom: 6px;
|
|
229
|
+
font-weight: 600;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#node-input-ttsUltimateNodeId {
|
|
233
|
+
width: 100%;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.knx-ai-tts-ultimate-hint {
|
|
237
|
+
margin-top: 6px;
|
|
238
|
+
color: var(--red-ui-secondary-text-color, #777);
|
|
239
|
+
font-size: 12px;
|
|
240
|
+
}
|
|
241
|
+
|
|
149
242
|
@media (max-width: 900px) {
|
|
150
243
|
#knx-ai-ollama-install-row>label {
|
|
151
244
|
width: 100% !important;
|
|
@@ -169,31 +262,17 @@
|
|
|
169
262
|
name: { value: "KNX AI", required: false },
|
|
170
263
|
topic: { value: "", required: false },
|
|
171
264
|
|
|
172
|
-
analysisWindowSec: { value: 120 },
|
|
173
|
-
historyWindowSec: { value: 600 },
|
|
174
|
-
historyStoreToDisk: { value: true },
|
|
175
|
-
historyStoreRetentionDays: { value: 10 },
|
|
176
|
-
emitIntervalSec: { value: 0 },
|
|
177
|
-
topN: { value: 10 },
|
|
178
|
-
maxEvents: { value: 50000 },
|
|
179
|
-
|
|
180
265
|
llmEnabled: { value: false },
|
|
181
266
|
llmProvider: { value: "openai_compat" },
|
|
182
267
|
llmBaseUrl: { value: "https://api.openai.com/v1/chat/completions" },
|
|
183
268
|
llmModel: { value: "gpt-5.4" },
|
|
184
|
-
|
|
185
|
-
llmDocsLanguage: { value: "en" },
|
|
269
|
+
llmContextLength: { value: 0 },
|
|
186
270
|
llmAllowKnxCommands: { value: false },
|
|
187
271
|
llmRequireCommandConfirmation: { value: true },
|
|
188
272
|
chatAdapterPreset: { value: "none" },
|
|
189
273
|
chatInputCode: { value: "" },
|
|
190
274
|
chatOutputCode: { value: "" },
|
|
191
|
-
|
|
192
|
-
proactiveRecipient: { value: "" },
|
|
193
|
-
proactiveOpenMinutes: { value: 120 },
|
|
194
|
-
proactiveCooldownMinutes: { value: 360 },
|
|
195
|
-
proactiveQuietStart: { value: "23:00" },
|
|
196
|
-
proactiveQuietEnd: { value: "07:00" },
|
|
275
|
+
ttsUltimateNodeId: { value: "" },
|
|
197
276
|
aiEducation: { value: "" }
|
|
198
277
|
},
|
|
199
278
|
credentials: {
|
|
@@ -220,43 +299,30 @@
|
|
|
220
299
|
const OPENAI_COMPAT_DEFAULT_CHAT_URL = "https://api.openai.com/v1/chat/completions";
|
|
221
300
|
const OLLAMA_DEFAULT_CHAT_URL = "http://localhost:11434/api/chat";
|
|
222
301
|
const ANTHROPIC_DEFAULT_CHAT_URL = "https://api.anthropic.com/v1/messages";
|
|
302
|
+
const LMSTUDIO_DEFAULT_CHAT_URL = "http://localhost:1234/v1/chat/completions";
|
|
223
303
|
const OLLAMA_LIBRARY_URL = "https://ollama.com/library";
|
|
224
304
|
const OPENAI_COMPAT_DEFAULT_MODELS = ["gpt-5.4", "gpt-4o-mini"];
|
|
225
305
|
const OLLAMA_DEFAULT_MODEL = "llama3.1";
|
|
226
306
|
const ANTHROPIC_DEFAULT_MODEL = "claude-opus-4-8";
|
|
227
307
|
|
|
228
|
-
const
|
|
308
|
+
const mountTabSection = (mountSelector, sectionSelector) => {
|
|
229
309
|
const $mount = $(mountSelector);
|
|
230
310
|
const $section = $(sectionSelector);
|
|
231
311
|
if ($mount.length && $section.length) $mount.append($section);
|
|
232
312
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
mountAccordionSection("#knx-ai-mount-knx-storage", "#knx-ai-tab-storage");
|
|
239
|
-
$("#knx-ai-accordion").accordion({
|
|
240
|
-
active: 0,
|
|
241
|
-
animate: false,
|
|
242
|
-
collapsible: true,
|
|
243
|
-
heightStyle: "content"
|
|
244
|
-
});
|
|
313
|
+
mountTabSection("#knx-ai-mount-assistant-setup", "#knx-ai-tab-llm-connection");
|
|
314
|
+
mountTabSection("#knx-ai-mount-assistant-advanced", "#knx-ai-tab-advanced");
|
|
315
|
+
mountTabSection("#knx-ai-mount-chat-adapter", "#knx-ai-tab-chat-adapter");
|
|
316
|
+
mountTabSection("#knx-ai-mount-home-intelligence", "#knx-ai-tab-home-intelligence");
|
|
317
|
+
$("#knx-ai-tabs").tabs({ active: 0 });
|
|
245
318
|
|
|
246
319
|
const toggleLLM = () => {
|
|
247
320
|
const enabled = $("#node-input-llmEnabled").is(":checked");
|
|
248
321
|
$("#knx-ai-llm-connection-settings").toggle(enabled);
|
|
249
|
-
$("#knx-ai-llm-context-settings").toggle(enabled);
|
|
250
322
|
$("#knx-ai-llm-advanced-settings").toggle(enabled);
|
|
251
323
|
};
|
|
252
324
|
$("#node-input-llmEnabled").on("change", toggleLLM);
|
|
253
325
|
|
|
254
|
-
const toggleHistoryArchive = () => {
|
|
255
|
-
const enabled = $("#node-input-historyStoreToDisk").is(":checked");
|
|
256
|
-
$("#knx-ai-history-retention-row").toggle(enabled);
|
|
257
|
-
};
|
|
258
|
-
$("#node-input-historyStoreToDisk").on("change", toggleHistoryArchive);
|
|
259
|
-
|
|
260
326
|
const toggleCommandConfirmation = () => {
|
|
261
327
|
const enabled = $("#node-input-llmAllowKnxCommands").is(":checked");
|
|
262
328
|
$("#knx-ai-command-confirmation-row").toggle(enabled);
|
|
@@ -264,26 +330,8 @@
|
|
|
264
330
|
$("#node-input-llmAllowKnxCommands").on("change", toggleCommandConfirmation);
|
|
265
331
|
|
|
266
332
|
toggleLLM();
|
|
267
|
-
toggleHistoryArchive();
|
|
268
333
|
toggleCommandConfirmation();
|
|
269
334
|
|
|
270
|
-
const applyNumericFallback = (selector, fallback) => {
|
|
271
|
-
const $field = $(selector);
|
|
272
|
-
if (!$field.length) return;
|
|
273
|
-
const value = $field.val();
|
|
274
|
-
if (value === undefined || value === null || String(value).trim() === "") {
|
|
275
|
-
$field.val(String(fallback));
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
applyNumericFallback("#node-input-analysisWindowSec", 120);
|
|
279
|
-
applyNumericFallback("#node-input-historyWindowSec", 600);
|
|
280
|
-
applyNumericFallback("#node-input-historyStoreRetentionDays", 10);
|
|
281
|
-
applyNumericFallback("#node-input-emitIntervalSec", 0);
|
|
282
|
-
applyNumericFallback("#node-input-topN", 10);
|
|
283
|
-
applyNumericFallback("#node-input-maxEvents", 50000);
|
|
284
|
-
applyNumericFallback("#node-input-proactiveOpenMinutes", 120);
|
|
285
|
-
applyNumericFallback("#node-input-proactiveCooldownMinutes", 360);
|
|
286
|
-
|
|
287
335
|
const nodeId = node.id;
|
|
288
336
|
const resolveAdminRoot = () => {
|
|
289
337
|
const raw = (RED.settings && typeof RED.settings.httpAdminRoot === "string") ? RED.settings.httpAdminRoot : "/";
|
|
@@ -402,18 +450,8 @@
|
|
|
402
450
|
: [];
|
|
403
451
|
const $picker = $("#node-input-chatAdapterPreset");
|
|
404
452
|
if (!$picker.length) return;
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
$("#knx-ai-chat-adapter-fields").toggle(visible);
|
|
408
|
-
if (visible) {
|
|
409
|
-
const resizeEditors = function () {
|
|
410
|
-
try {
|
|
411
|
-
if (node.chatInputCodeEditor && typeof node.chatInputCodeEditor.resize === "function") node.chatInputCodeEditor.resize();
|
|
412
|
-
if (node.chatOutputCodeEditor && typeof node.chatOutputCodeEditor.resize === "function") node.chatOutputCodeEditor.resize();
|
|
413
|
-
} catch (error) { }
|
|
414
|
-
};
|
|
415
|
-
try { setTimeout(resizeEditors, 0); } catch (error) { resizeEditors(); }
|
|
416
|
-
}
|
|
453
|
+
const hideAdapterMappingFields = () => {
|
|
454
|
+
$("#knx-ai-chat-adapter-fields").hide();
|
|
417
455
|
};
|
|
418
456
|
$picker.empty();
|
|
419
457
|
$("<option></option>")
|
|
@@ -436,24 +474,99 @@
|
|
|
436
474
|
.appendTo($picker);
|
|
437
475
|
}
|
|
438
476
|
$picker.val(configuredPreset);
|
|
439
|
-
|
|
477
|
+
hideAdapterMappingFields();
|
|
440
478
|
$picker.on("change", function () {
|
|
441
479
|
const selected = String($(this).val() || "none");
|
|
442
480
|
if (selected === "none") {
|
|
443
481
|
setChatEditorValue(node.chatInputCodeEditor, "");
|
|
444
482
|
setChatEditorValue(node.chatOutputCodeEditor, "");
|
|
445
|
-
|
|
483
|
+
hideAdapterMappingFields();
|
|
446
484
|
return;
|
|
447
485
|
}
|
|
448
486
|
const adapter = adapters.find(item => item && item.id === selected);
|
|
449
487
|
if (!adapter) return;
|
|
450
488
|
setChatEditorValue(node.chatInputCodeEditor, adapter.inputCode || "");
|
|
451
489
|
setChatEditorValue(node.chatOutputCodeEditor, adapter.outputCode || "");
|
|
452
|
-
|
|
490
|
+
hideAdapterMappingFields();
|
|
453
491
|
});
|
|
454
492
|
};
|
|
455
493
|
configureChatAdapterPicker.call(node);
|
|
456
494
|
|
|
495
|
+
const collectEditorTtsUltimateNodes = () => {
|
|
496
|
+
const tabById = new Map();
|
|
497
|
+
const entries = [];
|
|
498
|
+
try {
|
|
499
|
+
RED.nodes.eachNode(function (candidate) {
|
|
500
|
+
if (!candidate || String(candidate.type || "") !== "tab") return;
|
|
501
|
+
tabById.set(String(candidate.id || ""), String(candidate.label || candidate.name || ""));
|
|
502
|
+
});
|
|
503
|
+
RED.nodes.eachNode(function (candidate) {
|
|
504
|
+
if (!candidate || String(candidate.type || "") !== "ttsultimate") return;
|
|
505
|
+
const id = String(candidate.id || "").trim();
|
|
506
|
+
if (!id) return;
|
|
507
|
+
entries.push({
|
|
508
|
+
id: id,
|
|
509
|
+
name: String(candidate.name || "TTS Ultimate"),
|
|
510
|
+
flowId: String(candidate.z || ""),
|
|
511
|
+
flowName: tabById.get(String(candidate.z || "")) || "",
|
|
512
|
+
playerType: String(candidate.playertype || "sonos")
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
} catch (error) { }
|
|
516
|
+
return entries;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const populateTtsUltimatePicker = (runtimeSummary) => {
|
|
520
|
+
const $pickerContainer = $("#knx-ai-tts-ultimate-picker");
|
|
521
|
+
const $picker = $("#node-input-ttsUltimateNodeId");
|
|
522
|
+
if (!$picker.length) return;
|
|
523
|
+
const byId = new Map();
|
|
524
|
+
const runtimeNodes = runtimeSummary && Array.isArray(runtimeSummary.nodes) ? runtimeSummary.nodes : [];
|
|
525
|
+
runtimeNodes.forEach(function (item) {
|
|
526
|
+
if (item && item.id) byId.set(String(item.id), item);
|
|
527
|
+
});
|
|
528
|
+
collectEditorTtsUltimateNodes().forEach(function (item) {
|
|
529
|
+
byId.set(String(item.id), item);
|
|
530
|
+
});
|
|
531
|
+
const entries = Array.from(byId.values()).sort(function (left, right) {
|
|
532
|
+
return String((left.flowName || "") + "\u0000" + (left.name || "") + "\u0000" + left.id)
|
|
533
|
+
.localeCompare(String((right.flowName || "") + "\u0000" + (right.name || "") + "\u0000" + right.id));
|
|
534
|
+
});
|
|
535
|
+
const configuredId = String(node.ttsUltimateNodeId || "").trim();
|
|
536
|
+
$picker.empty();
|
|
537
|
+
$("<option></option>")
|
|
538
|
+
.attr("value", "")
|
|
539
|
+
.text(entries.length
|
|
540
|
+
? t("knxUltimateAI.selectlists.ttsUltimate.select", "Select a TTS Ultimate node")
|
|
541
|
+
: t("knxUltimateAI.selectlists.ttsUltimate.none", "No TTS Ultimate node found"))
|
|
542
|
+
.appendTo($picker);
|
|
543
|
+
entries.forEach(function (item) {
|
|
544
|
+
const flowName = String(item.flowName || t("knxUltimateAI.messages.ttsUltimateUnknownFlow", "Flow without name"));
|
|
545
|
+
const playerType = String(item.playerType || "sonos");
|
|
546
|
+
$("<option></option>")
|
|
547
|
+
.attr("value", item.id)
|
|
548
|
+
.text(flowName + " · " + String(item.name || "TTS Ultimate") + " · " + playerType)
|
|
549
|
+
.appendTo($picker);
|
|
550
|
+
});
|
|
551
|
+
if (configuredId && !byId.has(configuredId)) {
|
|
552
|
+
$("<option></option>")
|
|
553
|
+
.attr("value", configuredId)
|
|
554
|
+
.text(t("knxUltimateAI.messages.ttsUltimateUnavailable", "Unavailable TTS Ultimate node") + " · " + configuredId)
|
|
555
|
+
.appendTo($picker);
|
|
556
|
+
}
|
|
557
|
+
if (configuredId) {
|
|
558
|
+
$picker.val(configuredId);
|
|
559
|
+
} else if (entries.length === 1) {
|
|
560
|
+
$picker.val(entries[0].id);
|
|
561
|
+
} else {
|
|
562
|
+
$picker.val("");
|
|
563
|
+
}
|
|
564
|
+
const detected = entries.length > 0 || !!(runtimeSummary && runtimeSummary.detected);
|
|
565
|
+
$picker.prop("disabled", entries.length === 0 && !configuredId);
|
|
566
|
+
$pickerContainer.toggle(detected || !!configuredId);
|
|
567
|
+
};
|
|
568
|
+
populateTtsUltimatePicker(null);
|
|
569
|
+
|
|
457
570
|
const renderDetectedAdapters = (adapters) => {
|
|
458
571
|
const $list = $("#knx-ai-detected-adapters-list");
|
|
459
572
|
const $status = $("#knx-ai-detected-adapters-status");
|
|
@@ -469,6 +582,7 @@
|
|
|
469
582
|
if (!adapter || !adapter.id) return;
|
|
470
583
|
const providerCount = Number(adapter.providerCount) || 0;
|
|
471
584
|
const cameraCount = Number(adapter.cameraCount) || 0;
|
|
585
|
+
const nodeCount = Number(adapter.nodeCount) || 0;
|
|
472
586
|
const $row = $("<div></div>").addClass("knx-ai-detected-adapter");
|
|
473
587
|
const $main = $("<div></div>").addClass("knx-ai-detected-adapter-main");
|
|
474
588
|
$("<div></div>").text(adapter.title || adapter.id).appendTo($main);
|
|
@@ -477,7 +591,9 @@
|
|
|
477
591
|
}
|
|
478
592
|
$("<div></div>")
|
|
479
593
|
.addClass("knx-ai-detected-adapter-counts")
|
|
480
|
-
.text(
|
|
594
|
+
.text(adapter.kind === "tts"
|
|
595
|
+
? t("knxUltimateAI.messages.detectedAdapterNodes", "Nodes") + ": " + nodeCount
|
|
596
|
+
: t("knxUltimateAI.messages.detectedAdapterControllers", "Controllers") + ": " + providerCount + " · " + t("knxUltimateAI.messages.detectedAdapterCameras", "Cameras") + ": " + cameraCount)
|
|
481
597
|
.appendTo($main);
|
|
482
598
|
$main.appendTo($row);
|
|
483
599
|
$("<span></span>")
|
|
@@ -487,6 +603,89 @@
|
|
|
487
603
|
$row.appendTo($list);
|
|
488
604
|
});
|
|
489
605
|
};
|
|
606
|
+
|
|
607
|
+
const renderChatContextOverview = (overview) => {
|
|
608
|
+
const $status = $("#knx-ai-chat-context-status");
|
|
609
|
+
const $content = $("#knx-ai-chat-context-content");
|
|
610
|
+
const $sources = $("#knx-ai-chat-context-sources-list").empty();
|
|
611
|
+
const $files = $("#knx-ai-chat-context-files-list").empty();
|
|
612
|
+
const $directories = $("#knx-ai-chat-context-directories-list").empty();
|
|
613
|
+
if (!overview || typeof overview !== "object") {
|
|
614
|
+
$content.hide();
|
|
615
|
+
$status
|
|
616
|
+
.text(t("knxUltimateAI.messages.chatContextUnavailable", "The chat context summary is temporarily unavailable."))
|
|
617
|
+
.show();
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const addRow = function ($list, title, description, pathText, badge) {
|
|
622
|
+
const $row = $("<div></div>").addClass("knx-ai-detected-adapter");
|
|
623
|
+
const $main = $("<div></div>").addClass("knx-ai-detected-adapter-main");
|
|
624
|
+
$("<div></div>").text(title || "").appendTo($main);
|
|
625
|
+
if (description) {
|
|
626
|
+
$("<div></div>").addClass("knx-ai-chat-context-description").text(description).appendTo($main);
|
|
627
|
+
}
|
|
628
|
+
if (pathText) {
|
|
629
|
+
$("<div></div>").addClass("knx-ai-chat-context-path").text(pathText).appendTo($main);
|
|
630
|
+
}
|
|
631
|
+
$main.appendTo($row);
|
|
632
|
+
$("<span></span>").addClass("knx-ai-detected-adapter-badge").text(badge).appendTo($row);
|
|
633
|
+
$row.appendTo($list);
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const sourceLabels = {
|
|
637
|
+
knxTraffic: t("knxUltimateAI.messages.chatContextSourceKnxTraffic", "Live KNX summary, anomalies, topology and selected telegrams."),
|
|
638
|
+
etsProject: t("knxUltimateAI.messages.chatContextSourceEtsProject", "ETS semantics and the full Node-RED project inventory."),
|
|
639
|
+
memoryEducation: t("knxUltimateAI.messages.chatContextSourceMemoryEducation", "Session context, AI Education and bounded home memory."),
|
|
640
|
+
camerasDocs: t("knxUltimateAI.messages.chatContextSourceCamerasDocs", "Detected cameras and relevant help, README and example snippets."),
|
|
641
|
+
ttsUltimate: t("knxUltimateAI.messages.chatContextSourceTtsUltimate", "Selected TTS Ultimate announcement target.")
|
|
642
|
+
};
|
|
643
|
+
(Array.isArray(overview.sources) ? overview.sources : []).forEach(function (sourceId) {
|
|
644
|
+
addRow(
|
|
645
|
+
$sources,
|
|
646
|
+
sourceLabels[sourceId] || String(sourceId),
|
|
647
|
+
"",
|
|
648
|
+
"",
|
|
649
|
+
t("knxUltimateAI.messages.chatContextSourceBadge", "Source")
|
|
650
|
+
);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
const fileDescriptions = {
|
|
654
|
+
chatContext: t("knxUltimateAI.messages.chatContextFileChatContext", "Persistent conversation turns, instructions and camera notification rules."),
|
|
655
|
+
homeMemory: t("knxUltimateAI.messages.chatContextFileHomeMemory", "AI Education and bounded learned home memory."),
|
|
656
|
+
assistantConfig: t("knxUltimateAI.messages.chatContextFileAssistantConfig", "Persistent web Assistant configuration and semantic areas for this node.")
|
|
657
|
+
};
|
|
658
|
+
(Array.isArray(overview.files) ? overview.files : []).forEach(function (item) {
|
|
659
|
+
if (!item || !item.name || !item.path) return;
|
|
660
|
+
addRow(
|
|
661
|
+
$files,
|
|
662
|
+
item.name,
|
|
663
|
+
fileDescriptions[item.id] || "",
|
|
664
|
+
item.path,
|
|
665
|
+
t("knxUltimateAI.messages.chatContextFileBadge", "File")
|
|
666
|
+
);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
const directoryLabels = {
|
|
670
|
+
archiveRoot: t("knxUltimateAI.messages.chatContextDirectoryRoot", "Telegram archive root"),
|
|
671
|
+
nodeArchive: t("knxUltimateAI.messages.chatContextDirectoryNode", "This node's telegram archive")
|
|
672
|
+
};
|
|
673
|
+
(Array.isArray(overview.telegramDirectories) ? overview.telegramDirectories : []).forEach(function (item) {
|
|
674
|
+
if (!item || !item.path) return;
|
|
675
|
+
addRow(
|
|
676
|
+
$directories,
|
|
677
|
+
directoryLabels[item.id] || String(item.id || ""),
|
|
678
|
+
"",
|
|
679
|
+
item.path,
|
|
680
|
+
t("knxUltimateAI.messages.chatContextDirectoryBadge", "KNX")
|
|
681
|
+
);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
$("#knx-ai-chat-context-pattern")
|
|
685
|
+
.text(t("knxUltimateAI.messages.chatContextTelegramPattern", "Daily files") + ": " + String(overview.telegramFilePattern || "YYYY-MM-DD.jsonl"));
|
|
686
|
+
$status.hide();
|
|
687
|
+
$content.show();
|
|
688
|
+
};
|
|
490
689
|
$.ajax({
|
|
491
690
|
url: "knxUltimateAI/adapters",
|
|
492
691
|
type: "GET",
|
|
@@ -494,20 +693,16 @@
|
|
|
494
693
|
})
|
|
495
694
|
.done(function (data) {
|
|
496
695
|
renderDetectedAdapters(data && data.adapters);
|
|
696
|
+
populateTtsUltimatePicker(data && data.ttsUltimate);
|
|
697
|
+
renderChatContextOverview(data && data.chatContext);
|
|
497
698
|
})
|
|
498
699
|
.fail(function () {
|
|
499
700
|
$("#knx-ai-detected-adapters-status")
|
|
500
701
|
.text(t("knxUltimateAI.messages.detectedAdaptersUnavailable", "Automatic adapter detection is temporarily unavailable."))
|
|
501
702
|
.show();
|
|
703
|
+
renderChatContextOverview(null);
|
|
502
704
|
});
|
|
503
705
|
|
|
504
|
-
const updateProactiveVisibility = () => {
|
|
505
|
-
const enabled = $("#node-input-proactiveEnabled").is(":checked");
|
|
506
|
-
$("#knx-ai-proactive-settings").toggle(enabled);
|
|
507
|
-
};
|
|
508
|
-
$("#node-input-proactiveEnabled").on("change", updateProactiveVisibility);
|
|
509
|
-
updateProactiveVisibility();
|
|
510
|
-
|
|
511
706
|
$("#knx-ai-open-web-page-vue").on("click", function (evt) {
|
|
512
707
|
evt.preventDefault();
|
|
513
708
|
const adminRoot = resolveAdminRoot();
|
|
@@ -532,12 +727,55 @@
|
|
|
532
727
|
$("#knx-ai-downloadOllamaModel").prop("disabled", !!busy);
|
|
533
728
|
};
|
|
534
729
|
|
|
535
|
-
|
|
730
|
+
let localModelDetailsById = new Map();
|
|
731
|
+
let localContextRequestVersion = 0;
|
|
732
|
+
|
|
733
|
+
const formatContextTokens = (value) => {
|
|
734
|
+
const tokens = Math.max(0, Number(value) || 0);
|
|
735
|
+
return tokens ? tokens.toLocaleString() : "?";
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const setLocalModelContextStatus = (detail, state, errorText) => {
|
|
739
|
+
const $status = $("#knx-ai-local-context-status");
|
|
740
|
+
const provider = $("#node-input-llmProvider").val();
|
|
741
|
+
if (!$status.length || (provider !== "lmstudio" && provider !== "ollama") || !detail) {
|
|
742
|
+
$status.hide();
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const maxTokens = formatContextTokens(detail.maxContextLength);
|
|
746
|
+
const loadedTokens = formatContextTokens(detail.loadedContextLength);
|
|
747
|
+
let text = t("knxUltimateAI.messages.lmStudioContextAvailable", "Maximum model context") + ": " + maxTokens + " token";
|
|
748
|
+
let color = "#555";
|
|
749
|
+
if (state === "loading") {
|
|
750
|
+
text = t("knxUltimateAI.messages.lmStudioContextLoading", "Loading the model with its maximum context") + ": " + maxTokens + " token…";
|
|
751
|
+
color = "#1565c0";
|
|
752
|
+
} else if (state === "configured") {
|
|
753
|
+
const configuredTokens = formatContextTokens(detail.configuredContextLength || detail.loadedContextLength || detail.maxContextLength);
|
|
754
|
+
text = t("knxUltimateAI.messages.lmStudioContextConfigured", "Maximum model context configured") + ": " + configuredTokens + " token";
|
|
755
|
+
color = "#2e7d32";
|
|
756
|
+
} else if (state === "error") {
|
|
757
|
+
text = String(errorText || t("knxUltimateAI.messages.lmStudioContextFailed", "Unable to configure the model context"));
|
|
758
|
+
color = "#c62828";
|
|
759
|
+
} else if (detail.loadedContextLength > 0) {
|
|
760
|
+
text += " · " + t("knxUltimateAI.messages.lmStudioContextCurrentlyLoaded", "currently loaded") + ": " + loadedTokens + " token";
|
|
761
|
+
}
|
|
762
|
+
$status.text(text).css("color", color).show();
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const populateModels = (models, modelDetails) => {
|
|
536
766
|
const $dl = $("#knx-ai-llmModels");
|
|
537
767
|
if (!$dl || $dl.length === 0) return;
|
|
768
|
+
localModelDetailsById = new Map();
|
|
769
|
+
(modelDetails || []).forEach(function (detail) {
|
|
770
|
+
if (detail && detail.id) localModelDetailsById.set(String(detail.id), detail);
|
|
771
|
+
});
|
|
538
772
|
$dl.empty();
|
|
539
773
|
(models || []).forEach(function (m) {
|
|
540
|
-
|
|
774
|
+
const detail = localModelDetailsById.get(String(m));
|
|
775
|
+
const label = detail
|
|
776
|
+
? String(detail.displayName || detail.id) + " · " + formatContextTokens(detail.maxContextLength) + " token"
|
|
777
|
+
: String(m);
|
|
778
|
+
$("<option>").attr("value", m).attr("label", label).appendTo($dl);
|
|
541
779
|
});
|
|
542
780
|
};
|
|
543
781
|
|
|
@@ -545,7 +783,7 @@
|
|
|
545
783
|
const isOpenAiDefaultUrl = (value) => normalizeUrl(value) === normalizeUrl(OPENAI_COMPAT_DEFAULT_CHAT_URL);
|
|
546
784
|
const isOllamaDefaultUrl = (value) => normalizeUrl(value) === normalizeUrl(OLLAMA_DEFAULT_CHAT_URL);
|
|
547
785
|
const isOpenAiDefaultModel = (value) => OPENAI_COMPAT_DEFAULT_MODELS.indexOf(String(value || "").trim().toLowerCase()) >= 0;
|
|
548
|
-
const DEFAULT_CHAT_URLS = [OPENAI_COMPAT_DEFAULT_CHAT_URL, OLLAMA_DEFAULT_CHAT_URL, ANTHROPIC_DEFAULT_CHAT_URL];
|
|
786
|
+
const DEFAULT_CHAT_URLS = [OPENAI_COMPAT_DEFAULT_CHAT_URL, OLLAMA_DEFAULT_CHAT_URL, ANTHROPIC_DEFAULT_CHAT_URL, LMSTUDIO_DEFAULT_CHAT_URL];
|
|
549
787
|
const DEFAULT_MODELS = OPENAI_COMPAT_DEFAULT_MODELS.concat([OLLAMA_DEFAULT_MODEL, ANTHROPIC_DEFAULT_MODEL]);
|
|
550
788
|
const isAnyDefaultUrl = (value) => DEFAULT_CHAT_URLS.some((u) => normalizeUrl(u) === normalizeUrl(value));
|
|
551
789
|
const isAnyDefaultModel = (value) => DEFAULT_MODELS.indexOf(String(value || "").trim().toLowerCase()) >= 0;
|
|
@@ -556,10 +794,12 @@
|
|
|
556
794
|
const currentBaseUrl = String($baseUrl.val() || "").trim();
|
|
557
795
|
const currentModel = String($model.val() || "").trim();
|
|
558
796
|
|
|
559
|
-
const urlByProvider = { ollama: OLLAMA_DEFAULT_CHAT_URL, anthropic: ANTHROPIC_DEFAULT_CHAT_URL, openai_compat: OPENAI_COMPAT_DEFAULT_CHAT_URL };
|
|
560
|
-
const modelByProvider = { ollama: OLLAMA_DEFAULT_MODEL, anthropic: ANTHROPIC_DEFAULT_MODEL, openai_compat: OPENAI_COMPAT_DEFAULT_MODELS[0] };
|
|
797
|
+
const urlByProvider = { ollama: OLLAMA_DEFAULT_CHAT_URL, anthropic: ANTHROPIC_DEFAULT_CHAT_URL, lmstudio: LMSTUDIO_DEFAULT_CHAT_URL, openai_compat: OPENAI_COMPAT_DEFAULT_CHAT_URL };
|
|
798
|
+
const modelByProvider = { ollama: OLLAMA_DEFAULT_MODEL, anthropic: ANTHROPIC_DEFAULT_MODEL, lmstudio: "", openai_compat: OPENAI_COMPAT_DEFAULT_MODELS[0] };
|
|
561
799
|
const targetUrl = urlByProvider[provider] || OPENAI_COMPAT_DEFAULT_CHAT_URL;
|
|
562
|
-
const targetModel = modelByProvider
|
|
800
|
+
const targetModel = Object.prototype.hasOwnProperty.call(modelByProvider, provider)
|
|
801
|
+
? modelByProvider[provider]
|
|
802
|
+
: OPENAI_COMPAT_DEFAULT_MODELS[0];
|
|
563
803
|
|
|
564
804
|
// Only overwrite when the field is empty or still holds another provider's default,
|
|
565
805
|
// so a user's custom base URL / model is never clobbered when switching providers.
|
|
@@ -567,6 +807,66 @@
|
|
|
567
807
|
if (!currentModel || isAnyDefaultModel(currentModel)) $model.val(targetModel);
|
|
568
808
|
};
|
|
569
809
|
|
|
810
|
+
const configureSelectedLocalModel = () => {
|
|
811
|
+
const provider = $("#node-input-llmProvider").val();
|
|
812
|
+
if (provider !== "lmstudio" && provider !== "ollama") return;
|
|
813
|
+
const model = String($("#node-input-llmModel").val() || "").trim();
|
|
814
|
+
const detail = localModelDetailsById.get(model);
|
|
815
|
+
if (!detail || !(Number(detail.maxContextLength) > 0)) {
|
|
816
|
+
$("#node-input-llmContextLength").val(0);
|
|
817
|
+
setLocalModelContextStatus(null);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const requestVersion = ++localContextRequestVersion;
|
|
822
|
+
$("#node-input-llmContextLength").val(Number(detail.maxContextLength));
|
|
823
|
+
if (provider === "ollama") {
|
|
824
|
+
detail.configuredContextLength = Number(detail.maxContextLength);
|
|
825
|
+
setLocalModelContextStatus(detail, "configured");
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
setLocalModelContextStatus(detail, "loading");
|
|
830
|
+
$("#node-input-llmModel").prop("disabled", true);
|
|
831
|
+
$("#knx-ai-refreshModels").prop("disabled", true);
|
|
832
|
+
|
|
833
|
+
const payload = {
|
|
834
|
+
nodeId: nodeId,
|
|
835
|
+
baseUrl: $("#node-input-llmBaseUrl").val() || LMSTUDIO_DEFAULT_CHAT_URL,
|
|
836
|
+
model: model
|
|
837
|
+
};
|
|
838
|
+
const apiKey = $("#node-input-llmApiKey").val() || "";
|
|
839
|
+
if (apiKey && apiKey !== "__PWRD__") payload.apiKey = apiKey;
|
|
840
|
+
|
|
841
|
+
$.ajax({
|
|
842
|
+
url: "knxUltimateAI/lmstudio/select-model",
|
|
843
|
+
type: "POST",
|
|
844
|
+
contentType: "application/json",
|
|
845
|
+
data: JSON.stringify(payload)
|
|
846
|
+
})
|
|
847
|
+
.done(function (data) {
|
|
848
|
+
if (requestVersion !== localContextRequestVersion) return;
|
|
849
|
+
detail.loadedContextLength = Math.max(0, Number(data && data.contextLength) || Number(detail.maxContextLength));
|
|
850
|
+
detail.configuredContextLength = detail.loadedContextLength;
|
|
851
|
+
$("#node-input-llmContextLength").val(detail.loadedContextLength);
|
|
852
|
+
setLocalModelContextStatus(detail, "configured");
|
|
853
|
+
})
|
|
854
|
+
.fail(function (xhr) {
|
|
855
|
+
if (requestVersion !== localContextRequestVersion) return;
|
|
856
|
+
let errorText = t("knxUltimateAI.messages.lmStudioContextFailed", "Unable to configure the model context");
|
|
857
|
+
try {
|
|
858
|
+
if (xhr && xhr.responseJSON && xhr.responseJSON.error) errorText = xhr.responseJSON.error;
|
|
859
|
+
} catch (e) { }
|
|
860
|
+
setLocalModelContextStatus(detail, "error", errorText);
|
|
861
|
+
try { RED.notify(errorText, "error"); } catch (e) { }
|
|
862
|
+
})
|
|
863
|
+
.always(function () {
|
|
864
|
+
if (requestVersion !== localContextRequestVersion) return;
|
|
865
|
+
$("#node-input-llmModel").prop("disabled", false);
|
|
866
|
+
$("#knx-ai-refreshModels").prop("disabled", false);
|
|
867
|
+
});
|
|
868
|
+
};
|
|
869
|
+
|
|
570
870
|
const refreshModels = () => {
|
|
571
871
|
const provider = $("#node-input-llmProvider").val();
|
|
572
872
|
const baseUrl = $("#node-input-llmBaseUrl").val() || "";
|
|
@@ -586,7 +886,21 @@
|
|
|
586
886
|
})
|
|
587
887
|
.done(function (data) {
|
|
588
888
|
const models = (data && data.models) ? data.models : [];
|
|
589
|
-
populateModels(models);
|
|
889
|
+
populateModels(models, data && data.modelDetails);
|
|
890
|
+
let selectedAutomatically = false;
|
|
891
|
+
if (provider === "lmstudio" && models.length > 0 && !String($("#node-input-llmModel").val() || "").trim()) {
|
|
892
|
+
$("#node-input-llmModel").val(models[0]);
|
|
893
|
+
selectedAutomatically = true;
|
|
894
|
+
}
|
|
895
|
+
if (provider === "lmstudio" || provider === "ollama") {
|
|
896
|
+
const selectedDetail = localModelDetailsById.get(String($("#node-input-llmModel").val() || ""));
|
|
897
|
+
if (selectedDetail) {
|
|
898
|
+
setLocalModelContextStatus(selectedDetail);
|
|
899
|
+
configureSelectedLocalModel();
|
|
900
|
+
} else if (selectedAutomatically) {
|
|
901
|
+
configureSelectedLocalModel();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
590
904
|
if (provider === "ollama" && data && data.ollamaStarted) {
|
|
591
905
|
try {
|
|
592
906
|
const startedMsg = t('knxUltimateAI.messages.ollamaStartedAuto', "Ollama server started automatically.");
|
|
@@ -667,6 +981,11 @@
|
|
|
667
981
|
} else {
|
|
668
982
|
$("#knx-ai-apikey-row").show();
|
|
669
983
|
$("#knx-ai-ollama-install-row").hide();
|
|
984
|
+
if (provider === "lmstudio" && autoLoadModels) refreshModels();
|
|
985
|
+
if (provider !== "lmstudio") {
|
|
986
|
+
$("#node-input-llmContextLength").val(0);
|
|
987
|
+
if (provider !== "ollama") $("#knx-ai-local-context-status").hide();
|
|
988
|
+
}
|
|
670
989
|
}
|
|
671
990
|
};
|
|
672
991
|
$("#node-input-llmProvider").on("change", function () {
|
|
@@ -678,6 +997,9 @@
|
|
|
678
997
|
evt.preventDefault();
|
|
679
998
|
refreshModels();
|
|
680
999
|
});
|
|
1000
|
+
$("#node-input-llmModel").on("change", function () {
|
|
1001
|
+
configureSelectedLocalModel();
|
|
1002
|
+
});
|
|
681
1003
|
$("#knx-ai-downloadOllamaModel").on("click", function (evt) {
|
|
682
1004
|
evt.preventDefault();
|
|
683
1005
|
openOllamaLibrary();
|
|
@@ -753,30 +1075,23 @@
|
|
|
753
1075
|
</div>
|
|
754
1076
|
</div>
|
|
755
1077
|
|
|
756
|
-
<div id="knx-ai-
|
|
757
|
-
<
|
|
758
|
-
|
|
1078
|
+
<div id="knx-ai-tabs">
|
|
1079
|
+
<ul>
|
|
1080
|
+
<li><a href="#knx-ai-tabs-assistant"><i class="fa fa-magic"></i> <span data-i18n="knxUltimateAI.sections.groupAssistant"></span></a></li>
|
|
1081
|
+
<li><a href="#knx-ai-tabs-chat-home"><i class="fa fa-comments"></i> <span data-i18n="knxUltimateAI.sections.groupChatHome"></span></a></li>
|
|
1082
|
+
</ul>
|
|
1083
|
+
<div id="knx-ai-tabs-assistant">
|
|
759
1084
|
<div class="knx-ai-accordion-subsection">
|
|
760
1085
|
<h4><i class="fa fa-cog"></i> <span data-i18n="knxUltimateAI.sections.quickSetup"></span></h4>
|
|
761
1086
|
<div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-setup"></div>
|
|
762
1087
|
</div>
|
|
763
|
-
<div class="knx-ai-accordion-subsection">
|
|
764
|
-
<h4><i class="fa fa-book"></i> <span data-i18n="knxUltimateAI.sections.llmContext"></span></h4>
|
|
765
|
-
<div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-context"></div>
|
|
766
|
-
</div>
|
|
767
1088
|
<div class="knx-ai-accordion-subsection">
|
|
768
1089
|
<h4><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.sections.advanced"></span></h4>
|
|
769
1090
|
<div class="knx-ai-accordion-mount" id="knx-ai-mount-assistant-advanced"></div>
|
|
770
1091
|
</div>
|
|
771
1092
|
</div>
|
|
772
1093
|
|
|
773
|
-
<
|
|
774
|
-
<div>
|
|
775
|
-
<div id="knx-ai-detected-adapters-panel">
|
|
776
|
-
<h4><i class="fa fa-puzzle-piece"></i> <span data-i18n="knxUltimateAI.sections.detectedAdapters"></span></h4>
|
|
777
|
-
<div id="knx-ai-detected-adapters-status" data-i18n="knxUltimateAI.messages.detectedAdaptersLoading"></div>
|
|
778
|
-
<div id="knx-ai-detected-adapters-list"></div>
|
|
779
|
-
</div>
|
|
1094
|
+
<div id="knx-ai-tabs-chat-home">
|
|
780
1095
|
<div class="knx-ai-accordion-subsection">
|
|
781
1096
|
<h4><i class="fa fa-plug"></i> <span data-i18n="knxUltimateAI.sections.chatAdapter"></span></h4>
|
|
782
1097
|
<div class="knx-ai-accordion-mount" id="knx-ai-mount-chat-adapter"></div>
|
|
@@ -785,50 +1100,36 @@
|
|
|
785
1100
|
<h4><i class="fa fa-home"></i> <span data-i18n="knxUltimateAI.sections.homeIntelligence"></span></h4>
|
|
786
1101
|
<div class="knx-ai-accordion-mount" id="knx-ai-mount-home-intelligence"></div>
|
|
787
1102
|
</div>
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1103
|
+
<div id="knx-ai-detected-adapters-panel">
|
|
1104
|
+
<h4><i class="fa fa-puzzle-piece"></i> <span data-i18n="knxUltimateAI.sections.detectedAdapters"></span></h4>
|
|
1105
|
+
<div id="knx-ai-detected-adapters-status" data-i18n="knxUltimateAI.messages.detectedAdaptersLoading"></div>
|
|
1106
|
+
<div id="knx-ai-detected-adapters-list"></div>
|
|
1107
|
+
<div id="knx-ai-tts-ultimate-picker" style="display:none;">
|
|
1108
|
+
<label for="node-input-ttsUltimateNodeId"><i class="fa fa-volume-up"></i> <span data-i18n="knxUltimateAI.properties.ttsUltimateNodeId"></span></label>
|
|
1109
|
+
<select id="node-input-ttsUltimateNodeId"></select>
|
|
1110
|
+
<div class="knx-ai-tts-ultimate-hint" data-i18n="knxUltimateAI.messages.ttsUltimateHint"></div>
|
|
1111
|
+
</div>
|
|
1112
|
+
</div>
|
|
1113
|
+
<div id="knx-ai-chat-context-panel">
|
|
1114
|
+
<h4><i class="fa fa-database"></i> <span data-i18n="knxUltimateAI.sections.chatContextOverview"></span></h4>
|
|
1115
|
+
<div id="knx-ai-chat-context-status" data-i18n="knxUltimateAI.messages.chatContextLoading"></div>
|
|
1116
|
+
<div id="knx-ai-chat-context-content" style="display:none;">
|
|
1117
|
+
<div class="knx-ai-chat-context-intro" data-i18n="knxUltimateAI.messages.chatContextIntro"></div>
|
|
1118
|
+
<div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextSourcesTitle"></div>
|
|
1119
|
+
<div id="knx-ai-chat-context-sources-list"></div>
|
|
1120
|
+
<div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextFilesTitle"></div>
|
|
1121
|
+
<div id="knx-ai-chat-context-files-list"></div>
|
|
1122
|
+
<div class="knx-ai-chat-context-group-title" data-i18n="knxUltimateAI.messages.chatContextDirectoriesTitle"></div>
|
|
1123
|
+
<div id="knx-ai-chat-context-directories-list"></div>
|
|
1124
|
+
<div id="knx-ai-chat-context-pattern" class="knx-ai-chat-context-pattern"></div>
|
|
1125
|
+
</div>
|
|
795
1126
|
</div>
|
|
796
1127
|
</div>
|
|
1128
|
+
|
|
797
1129
|
</div>
|
|
798
1130
|
|
|
799
1131
|
<div id="knx-ai-accordion-source" style="display:none;">
|
|
800
1132
|
|
|
801
|
-
<div id="knx-ai-tab-storage">
|
|
802
|
-
<div class="form-row">
|
|
803
|
-
<label style="width:290px" for="node-input-historyWindowSec"><i class="fa fa-history"></i> <span data-i18n="knxUltimateAI.properties.historyWindowSec"></span></label>
|
|
804
|
-
<input style="width:90px" type="number" id="node-input-historyWindowSec">
|
|
805
|
-
</div>
|
|
806
|
-
<div class="form-row">
|
|
807
|
-
<input type="checkbox" id="node-input-historyStoreToDisk" style="display:inline-block; width:auto; vertical-align:top;">
|
|
808
|
-
<label style="width:auto" for="node-input-historyStoreToDisk"> <span data-i18n="knxUltimateAI.properties.historyStoreToDisk"></span></label>
|
|
809
|
-
</div>
|
|
810
|
-
<div class="form-row" id="knx-ai-history-retention-row">
|
|
811
|
-
<label style="width:290px" for="node-input-historyStoreRetentionDays"><i class="fa fa-calendar"></i> <span data-i18n="knxUltimateAI.properties.historyStoreRetentionDays"></span></label>
|
|
812
|
-
<input style="width:90px" type="number" id="node-input-historyStoreRetentionDays">
|
|
813
|
-
</div>
|
|
814
|
-
<div class="form-row">
|
|
815
|
-
<label style="width:290px" for="node-input-emitIntervalSec"><i class="fa fa-send"></i> <span data-i18n="knxUltimateAI.properties.emitIntervalSec"></span></label>
|
|
816
|
-
<input style="width:90px" type="number" id="node-input-emitIntervalSec">
|
|
817
|
-
</div>
|
|
818
|
-
<div class="form-row">
|
|
819
|
-
<label style="width:290px" for="node-input-analysisWindowSec"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.analysisWindowSec"></span></label>
|
|
820
|
-
<input style="width:90px" type="number" id="node-input-analysisWindowSec">
|
|
821
|
-
</div>
|
|
822
|
-
<div class="form-row">
|
|
823
|
-
<label style="width:290px" for="node-input-maxEvents"><i class="fa fa-bars"></i> <span data-i18n="knxUltimateAI.properties.maxEvents"></span></label>
|
|
824
|
-
<input style="width:90px" type="number" id="node-input-maxEvents">
|
|
825
|
-
</div>
|
|
826
|
-
<div class="form-row">
|
|
827
|
-
<label style="width:290px" for="node-input-topN"><i class="fa fa-list-ol"></i> <span data-i18n="knxUltimateAI.properties.topN"></span></label>
|
|
828
|
-
<input style="width:90px" type="number" id="node-input-topN">
|
|
829
|
-
</div>
|
|
830
|
-
</div>
|
|
831
|
-
|
|
832
1133
|
<div id="knx-ai-tab-llm-connection">
|
|
833
1134
|
<div class="form-row">
|
|
834
1135
|
<input type="checkbox" id="node-input-llmEnabled" style="display:inline-block; width:auto; vertical-align:top;">
|
|
@@ -842,6 +1143,7 @@
|
|
|
842
1143
|
<option value="openai_compat" data-i18n="knxUltimateAI.selectlists.llmProvider.openai_compat"></option>
|
|
843
1144
|
<option value="anthropic" data-i18n="knxUltimateAI.selectlists.llmProvider.anthropic"></option>
|
|
844
1145
|
<option value="ollama" data-i18n="knxUltimateAI.selectlists.llmProvider.ollama"></option>
|
|
1146
|
+
<option value="lmstudio" data-i18n="knxUltimateAI.selectlists.llmProvider.lmstudio"></option>
|
|
845
1147
|
</select>
|
|
846
1148
|
</div>
|
|
847
1149
|
<div class="form-row" id="knx-ai-apikey-row">
|
|
@@ -857,6 +1159,8 @@
|
|
|
857
1159
|
</button>
|
|
858
1160
|
</div>
|
|
859
1161
|
<datalist id="knx-ai-llmModels"></datalist>
|
|
1162
|
+
<input type="hidden" id="node-input-llmContextLength">
|
|
1163
|
+
<div id="knx-ai-local-context-status" style="display:none; margin:-8px 0 12px 290px; font-size:12px;"></div>
|
|
860
1164
|
<div class="form-row">
|
|
861
1165
|
<input type="checkbox" id="node-input-llmAllowKnxCommands" style="display:inline-block; width:auto; vertical-align:top;">
|
|
862
1166
|
<label style="width:auto" for="node-input-llmAllowKnxCommands"> <span data-i18n="knxUltimateAI.properties.llmAllowKnxCommands"></span></label>
|
|
@@ -891,61 +1195,9 @@
|
|
|
891
1195
|
|
|
892
1196
|
<div id="knx-ai-tab-home-intelligence">
|
|
893
1197
|
<div class="form-row">
|
|
894
|
-
<input type="checkbox" id="node-input-proactiveEnabled" style="display:inline-block; width:auto; vertical-align:top;">
|
|
895
|
-
<label style="width:auto" for="node-input-proactiveEnabled"> <span data-i18n="knxUltimateAI.properties.proactiveEnabled"></span></label>
|
|
896
|
-
</div>
|
|
897
|
-
<div id="knx-ai-proactive-settings">
|
|
898
|
-
<div class="form-row">
|
|
899
|
-
<label style="width:290px" for="node-input-proactiveRecipient"><i class="fa fa-user"></i> <span data-i18n="knxUltimateAI.properties.proactiveRecipient"></span></label>
|
|
900
|
-
<input style="width:100%" type="text" id="node-input-proactiveRecipient" data-i18n="[placeholder]knxUltimateAI.placeholder.proactiveRecipient">
|
|
901
|
-
</div>
|
|
902
|
-
<div class="form-row">
|
|
903
|
-
<label style="width:290px" for="node-input-proactiveOpenMinutes"><i class="fa fa-clock-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveOpenMinutes"></span></label>
|
|
904
|
-
<input style="width:100px" type="number" min="1" max="1440" id="node-input-proactiveOpenMinutes">
|
|
905
|
-
</div>
|
|
906
|
-
<div class="form-row">
|
|
907
|
-
<label style="width:290px" for="node-input-proactiveQuietStart"><i class="fa fa-moon-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveQuietStart"></span></label>
|
|
908
|
-
<input style="width:120px" type="time" id="node-input-proactiveQuietStart">
|
|
909
|
-
</div>
|
|
910
|
-
<div class="form-row">
|
|
911
|
-
<label style="width:290px" for="node-input-proactiveQuietEnd"><i class="fa fa-sun-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveQuietEnd"></span></label>
|
|
912
|
-
<input style="width:120px" type="time" id="node-input-proactiveQuietEnd">
|
|
913
|
-
</div>
|
|
914
|
-
</div>
|
|
915
|
-
<div class="form-row" style="margin-top:14px;">
|
|
916
1198
|
<label style="width:100%" for="node-input-aiEducation"><i class="fa fa-graduation-cap"></i> <span data-i18n="knxUltimateAI.properties.aiEducation"></span></label>
|
|
917
1199
|
<textarea id="node-input-aiEducation" maxlength="16000" style="width:100%; min-height:180px; box-sizing:border-box;" data-i18n="[placeholder]knxUltimateAI.placeholder.aiEducation"></textarea>
|
|
918
1200
|
</div>
|
|
919
|
-
<div class="knx-ai-inline-subsection">
|
|
920
|
-
<h5><i class="fa fa-sliders"></i> <span data-i18n="knxUltimateAI.sections.homeIntelligenceAdvanced"></span></h5>
|
|
921
|
-
<div>
|
|
922
|
-
<div class="form-row">
|
|
923
|
-
<label style="width:290px" for="node-input-proactiveCooldownMinutes"><i class="fa fa-bell-slash-o"></i> <span data-i18n="knxUltimateAI.properties.proactiveCooldownMinutes"></span></label>
|
|
924
|
-
<input style="width:100px" type="number" min="5" max="10080" id="node-input-proactiveCooldownMinutes">
|
|
925
|
-
</div>
|
|
926
|
-
</div>
|
|
927
|
-
</div>
|
|
928
|
-
</div>
|
|
929
|
-
|
|
930
|
-
<div id="knx-ai-tab-llm-context">
|
|
931
|
-
<div id="knx-ai-llm-context-settings">
|
|
932
|
-
<div class="form-row">
|
|
933
|
-
<input type="checkbox" id="node-input-llmIncludeFlowContext" style="display:inline-block; width:auto; vertical-align:top;">
|
|
934
|
-
<label style="width:auto" for="node-input-llmIncludeFlowContext"> <span data-i18n="knxUltimateAI.properties.llmIncludeFlowContext"></span></label>
|
|
935
|
-
</div>
|
|
936
|
-
|
|
937
|
-
<div class="form-row" id="knx-ai-docs-language-row">
|
|
938
|
-
<label style="width:290px" for="node-input-llmDocsLanguage"><i class="fa fa-language"></i> <span data-i18n="knxUltimateAI.properties.llmDocsLanguage"></span></label>
|
|
939
|
-
<select style="width:100%" id="node-input-llmDocsLanguage">
|
|
940
|
-
<option value="it">Italiano</option>
|
|
941
|
-
<option value="en">English</option>
|
|
942
|
-
<option value="de">Deutsch</option>
|
|
943
|
-
<option value="fr">Français</option>
|
|
944
|
-
<option value="es">Español</option>
|
|
945
|
-
<option value="zh-CN">简体中文</option>
|
|
946
|
-
</select>
|
|
947
|
-
</div>
|
|
948
|
-
</div>
|
|
949
1201
|
</div>
|
|
950
1202
|
|
|
951
1203
|
<div id="knx-ai-tab-advanced">
|