node-red-contrib-knx-ultimate 4.1.28 → 4.1.30
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 +21 -0
- package/examples/KNX DateTime - Set Date and Time to Bus.json +89 -0
- package/nodes/knxUltimate-config.js +41 -16
- package/nodes/knxUltimateAI.html +18 -5
- package/nodes/knxUltimateAI.js +1315 -22
- package/nodes/knxUltimateDateTime.html +559 -0
- package/nodes/knxUltimateDateTime.js +306 -0
- package/nodes/knxUltimateHueLight.js +26 -6
- package/nodes/locales/de/knxUltimateAI.html +66 -27
- package/nodes/locales/de/knxUltimateDateTime.html +36 -0
- package/nodes/locales/de/knxUltimateDateTime.json +30 -0
- package/nodes/locales/en/knxUltimateAI.html +66 -27
- package/nodes/locales/en/knxUltimateDateTime.html +45 -0
- package/nodes/locales/en/knxUltimateDateTime.json +30 -0
- package/nodes/locales/es/knxUltimateAI.html +66 -27
- package/nodes/locales/es/knxUltimateDateTime.html +36 -0
- package/nodes/locales/es/knxUltimateDateTime.json +30 -0
- package/nodes/locales/fr/knxUltimateAI.html +61 -22
- package/nodes/locales/fr/knxUltimateDateTime.html +36 -0
- package/nodes/locales/fr/knxUltimateDateTime.json +30 -0
- package/nodes/locales/it/knxUltimateAI.html +59 -22
- package/nodes/locales/it/knxUltimateDateTime.html +45 -0
- package/nodes/locales/it/knxUltimateDateTime.json +30 -0
- package/nodes/locales/zh-CN/knxUltimateAI.html +59 -20
- package/nodes/locales/zh-CN/knxUltimateDateTime.html +36 -0
- package/nodes/locales/zh-CN/knxUltimateDateTime.json +30 -0
- package/nodes/plugins/knxUltimateAI-web-page.html +3571 -0
- package/nodes/plugins/themes/knxUltimateAI-theme-green.css +44 -0
- package/nodes/plugins/themes/knxUltimateAI-theme-lavender.css +44 -0
- package/nodes/plugins/themes/knxUltimateAI-theme-mix.css +41 -0
- package/package.json +3 -3
- package/nodes/plugins/knxUltimateAI-sidebar-plugin.html +0 -561
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
**Version 4.1.30** - March 2026<br/>
|
|
10
|
+
|
|
11
|
+
- NEW: **KNX AI** now uses a full **Web Dashboard** as the main UI, opened from the node editor via **Open Web Page** button.<br/>
|
|
12
|
+
- CHANGE: removed the legacy `knxUltimateAI-sidebar-plugin` file and aligned KNX AI admin routes to the Web Dashboard workflow.<br/>
|
|
13
|
+
- IMPROVE: **KNX AI Flow Map** UX/readability: stable layout reset on page open, clearer legend, active/idle direction rendering and dashboard visual refinements.<br/>
|
|
14
|
+
- UI: flattened the KNX AI Web Dashboard style (removed 3D/shadow-heavy look) for a cleaner production UI.<br/>
|
|
15
|
+
- Docs/help/wiki: refreshed **KNX AI** documentation in all supported languages (EN/IT/DE/FR/ES/zh-CN), updated `KNX-AI-Sidebar` pages to Web Dashboard compatibility notes, and updated docs navigation section naming/ordering.<br/>
|
|
16
|
+
- FIX: **KNX AI Ask chat**: SVG answers are now rendered correctly even with multiple SVG blocks, fenced `svg/xml/html` code blocks, or entity-escaped SVG markup.<br/>
|
|
17
|
+
- FIX: **KNX Hue Light**: KNX read response for brightness now returns `0` when the light is OFF and brightness status is configured to follow HUE on/off.<br/>
|
|
18
|
+
- FIX: **KNX Hue Light**: ignore dimming-only cached brightness updates from Hue while light is OFF, preventing wrong KNX brightness status jumps.<br/>
|
|
19
|
+
- IMPROVE: **KNX Hue Light** grouped light handling: when an ON event arrives without dimming payload, brightness is fetched from bridge snapshot before publishing KNX status.<br/>
|
|
20
|
+
- TEST: extended unit coverage for the new Hue Light brightness/read/grouped-light snapshot behaviors.<br/>
|
|
21
|
+
|
|
22
|
+
**Version 4.1.29** - March 2026<br/>
|
|
23
|
+
|
|
24
|
+
- NEW: **KNX DateTime** node: set date/time on the KNX bus via **DPT 19.001** (DateTime) and optionally **DPT 11.001** (Date) / **DPT 10.001** (Time).<br/>
|
|
25
|
+
- NEW: **KNX DateTime**: send on startup (with delay), periodic send, editor send-now button, and input-triggered send.<br/>
|
|
26
|
+
- UI: **KNX DateTime**: when adding a new node, it can auto-select the first KNX Gateway with an ETS import and pre-fill coherent group addresses.<br/>
|
|
27
|
+
- UI: **KNX DateTime**: auto-fill is also triggered when changing the selected KNX Gateway (it won’t override manual values).<br/>
|
|
28
|
+
- i18n/help/wiki: added **KNX DateTime** help and docs pages in all supported languages, and added the node to the docs navigation.<br/>
|
|
29
|
+
|
|
9
30
|
**Version 4.1.28** - February 2026<br/>
|
|
10
31
|
|
|
11
32
|
- NEW: **KNX Load Control**: added **Mode** selector to disable the internal logic and use only `msg.shedding` commands (`shed`/`unshed`).<br/>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
[{
|
|
2
|
+
"id": "dt_cfg_1",
|
|
3
|
+
"type": "knxUltimate-config",
|
|
4
|
+
"z": "",
|
|
5
|
+
"host": "224.0.23.12",
|
|
6
|
+
"port": "3671",
|
|
7
|
+
"physAddr": "15.15.202",
|
|
8
|
+
"suppressACKRequest": false,
|
|
9
|
+
"csv": "",
|
|
10
|
+
"KNXEthInterface": "Auto",
|
|
11
|
+
"KNXEthInterfaceManuallyInput": "",
|
|
12
|
+
"autoReconnect": "yes"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "dt_inj_now",
|
|
16
|
+
"type": "inject",
|
|
17
|
+
"z": "flow_dt",
|
|
18
|
+
"name": "Send NOW",
|
|
19
|
+
"props": [
|
|
20
|
+
{ "p": "payload" }
|
|
21
|
+
],
|
|
22
|
+
"repeat": "",
|
|
23
|
+
"crontab": "",
|
|
24
|
+
"once": false,
|
|
25
|
+
"onceDelay": 0.1,
|
|
26
|
+
"topic": "",
|
|
27
|
+
"payloadType": "date",
|
|
28
|
+
"x": 150,
|
|
29
|
+
"y": 160,
|
|
30
|
+
"wires": [["dt_node"]]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "dt_inj_custom",
|
|
34
|
+
"type": "inject",
|
|
35
|
+
"z": "flow_dt",
|
|
36
|
+
"name": "Send fixed ISO date",
|
|
37
|
+
"props": [
|
|
38
|
+
{ "p": "payload", "v": "2026-03-02T10:00:00+01:00", "vt": "str" }
|
|
39
|
+
],
|
|
40
|
+
"repeat": "",
|
|
41
|
+
"crontab": "",
|
|
42
|
+
"once": false,
|
|
43
|
+
"onceDelay": 0.1,
|
|
44
|
+
"topic": "",
|
|
45
|
+
"x": 170,
|
|
46
|
+
"y": 220,
|
|
47
|
+
"wires": [["dt_node"]]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "dt_node",
|
|
51
|
+
"type": "knxUltimateDateTime",
|
|
52
|
+
"z": "flow_dt",
|
|
53
|
+
"server": "dt_cfg_1",
|
|
54
|
+
"name": "KNX Clock",
|
|
55
|
+
"outputtopic": "events/knx/datetime",
|
|
56
|
+
"gaDateTime": "1/7/1",
|
|
57
|
+
"nameDateTime": "Bus DateTime",
|
|
58
|
+
"dptDateTime": "19.001",
|
|
59
|
+
"gaDate": "",
|
|
60
|
+
"nameDate": "",
|
|
61
|
+
"dptDate": "11.001",
|
|
62
|
+
"gaTime": "",
|
|
63
|
+
"nameTime": "",
|
|
64
|
+
"dptTime": "10.001",
|
|
65
|
+
"sendOnDeploy": true,
|
|
66
|
+
"sendOnDeployDelay": 2,
|
|
67
|
+
"periodicSend": false,
|
|
68
|
+
"periodicSendInterval": 60,
|
|
69
|
+
"periodicSendUnit": "m",
|
|
70
|
+
"x": 420,
|
|
71
|
+
"y": 190,
|
|
72
|
+
"wires": [["dt_dbg"]]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "dt_dbg",
|
|
76
|
+
"type": "debug",
|
|
77
|
+
"z": "flow_dt",
|
|
78
|
+
"name": "DateTime out",
|
|
79
|
+
"active": true,
|
|
80
|
+
"tosidebar": true,
|
|
81
|
+
"console": false,
|
|
82
|
+
"tostatus": true,
|
|
83
|
+
"complete": "true",
|
|
84
|
+
"targetType": "full",
|
|
85
|
+
"x": 640,
|
|
86
|
+
"y": 190,
|
|
87
|
+
"wires": []
|
|
88
|
+
}]
|
|
89
|
+
|
|
@@ -1412,7 +1412,9 @@ module.exports = (RED) => {
|
|
|
1412
1412
|
destination: _dest,
|
|
1413
1413
|
apdu: { data: _apduData, bitlength: _apduBitLength, hex: _apduHex },
|
|
1414
1414
|
cemi: { hex: _cemiETS },
|
|
1415
|
-
echoed: _echoed
|
|
1415
|
+
echoed: _echoed,
|
|
1416
|
+
repeated: isRepeated,
|
|
1417
|
+
repeat: isRepeated
|
|
1416
1418
|
},
|
|
1417
1419
|
knxMultiRouting: {
|
|
1418
1420
|
gateway: { id: node.id, name: node.name || '', physAddr: node.physAddr || '' },
|
|
@@ -1429,7 +1431,9 @@ module.exports = (RED) => {
|
|
|
1429
1431
|
knx: {
|
|
1430
1432
|
event: _evt,
|
|
1431
1433
|
destination: _dest,
|
|
1432
|
-
rawValue: _rawValue
|
|
1434
|
+
rawValue: _rawValue,
|
|
1435
|
+
repeated: isRepeated,
|
|
1436
|
+
repeat: isRepeated
|
|
1433
1437
|
}
|
|
1434
1438
|
}
|
|
1435
1439
|
_input.handleSend(msg)
|
|
@@ -1449,7 +1453,8 @@ module.exports = (RED) => {
|
|
|
1449
1453
|
_devicename: _input.name ? _input.name : '',
|
|
1450
1454
|
_outputtopic: _input.outputtopic,
|
|
1451
1455
|
_oNode: null,
|
|
1452
|
-
_echoed
|
|
1456
|
+
_echoed,
|
|
1457
|
+
_repeated: isRepeated
|
|
1453
1458
|
})
|
|
1454
1459
|
_input.RecallScene(msgRecall.payload, false)
|
|
1455
1460
|
} catch (error) { }
|
|
@@ -1465,7 +1470,8 @@ module.exports = (RED) => {
|
|
|
1465
1470
|
_devicename: _input.name || '',
|
|
1466
1471
|
_outputtopic: _dest,
|
|
1467
1472
|
_oNode: null,
|
|
1468
|
-
_echoed
|
|
1473
|
+
_echoed,
|
|
1474
|
+
_repeated: isRepeated
|
|
1469
1475
|
})
|
|
1470
1476
|
_input.SaveScene(msgSave.payload, false)
|
|
1471
1477
|
} catch (error) { }
|
|
@@ -1492,7 +1498,8 @@ module.exports = (RED) => {
|
|
|
1492
1498
|
_devicename: oDevice.name || '',
|
|
1493
1499
|
_outputtopic: oDevice.outputtopic,
|
|
1494
1500
|
_oNode: null,
|
|
1495
|
-
_echoed
|
|
1501
|
+
_echoed,
|
|
1502
|
+
_repeated: isRepeated
|
|
1496
1503
|
})
|
|
1497
1504
|
oDevice.currentPayload = msg.payload
|
|
1498
1505
|
_input.setNodeStatus({
|
|
@@ -1532,7 +1539,8 @@ module.exports = (RED) => {
|
|
|
1532
1539
|
_Rawvalue: _rawValue,
|
|
1533
1540
|
_outputtopic: _dest,
|
|
1534
1541
|
_oNode: _input,
|
|
1535
|
-
_echoed
|
|
1542
|
+
_echoed,
|
|
1543
|
+
_repeated: isRepeated
|
|
1536
1544
|
})
|
|
1537
1545
|
_input.setNodeStatus({
|
|
1538
1546
|
fill: 'green',
|
|
@@ -1558,7 +1566,8 @@ module.exports = (RED) => {
|
|
|
1558
1566
|
_devicename: _input.name ? _input.name : '',
|
|
1559
1567
|
_outputtopic: _input.outputtopic,
|
|
1560
1568
|
_oNode: _input,
|
|
1561
|
-
_echoed
|
|
1569
|
+
_echoed,
|
|
1570
|
+
_repeated: isRepeated
|
|
1562
1571
|
})
|
|
1563
1572
|
// Check RBE INPUT from KNX Bus, to avoid send the payload to the flow, if it's equal to the current payload
|
|
1564
1573
|
if (!checkRBEInputFromKNXBusAllowSend(_input, msg.payload)) {
|
|
@@ -1605,7 +1614,9 @@ module.exports = (RED) => {
|
|
|
1605
1614
|
destination: _dest,
|
|
1606
1615
|
apdu: { data: _apduData, bitlength: _apduBitLength, hex: _apduHex },
|
|
1607
1616
|
cemi: { hex: _cemiETS },
|
|
1608
|
-
echoed: _echoed
|
|
1617
|
+
echoed: _echoed,
|
|
1618
|
+
repeated: isRepeated,
|
|
1619
|
+
repeat: isRepeated
|
|
1609
1620
|
},
|
|
1610
1621
|
knxMultiRouting: {
|
|
1611
1622
|
gateway: { id: node.id, name: node.name || '', physAddr: node.physAddr || '' },
|
|
@@ -1634,7 +1645,8 @@ module.exports = (RED) => {
|
|
|
1634
1645
|
_Rawvalue: _rawValue,
|
|
1635
1646
|
_outputtopic: _dest,
|
|
1636
1647
|
_oNode: _input,
|
|
1637
|
-
_echoed
|
|
1648
|
+
_echoed,
|
|
1649
|
+
_repeated: isRepeated
|
|
1638
1650
|
})
|
|
1639
1651
|
_input.setNodeStatus({
|
|
1640
1652
|
fill: 'blue',
|
|
@@ -1661,7 +1673,8 @@ module.exports = (RED) => {
|
|
|
1661
1673
|
_devicename: _input.name ? _input.name : '',
|
|
1662
1674
|
_outputtopic: _input.outputtopic,
|
|
1663
1675
|
_oNode: _input,
|
|
1664
|
-
_echoed
|
|
1676
|
+
_echoed,
|
|
1677
|
+
_repeated: isRepeated
|
|
1665
1678
|
})
|
|
1666
1679
|
// Check RBE INPUT from KNX Bus, to avoid send the payload to the flow, if it's equal to the current payload
|
|
1667
1680
|
if (!checkRBEInputFromKNXBusAllowSend(_input, msg.payload)) {
|
|
@@ -1705,7 +1718,9 @@ module.exports = (RED) => {
|
|
|
1705
1718
|
destination: _dest,
|
|
1706
1719
|
apdu: { data: null, bitlength: 0, hex: '' },
|
|
1707
1720
|
cemi: { hex: _cemiETS },
|
|
1708
|
-
echoed: _echoed
|
|
1721
|
+
echoed: _echoed,
|
|
1722
|
+
repeated: isRepeated,
|
|
1723
|
+
repeat: isRepeated
|
|
1709
1724
|
},
|
|
1710
1725
|
knxMultiRouting: {
|
|
1711
1726
|
gateway: { id: node.id, name: node.name || '', physAddr: node.physAddr || '' },
|
|
@@ -1736,7 +1751,8 @@ module.exports = (RED) => {
|
|
|
1736
1751
|
_Rawvalue: null,
|
|
1737
1752
|
_outputtopic: _dest,
|
|
1738
1753
|
_oNode: _input,
|
|
1739
|
-
_echoed
|
|
1754
|
+
_echoed,
|
|
1755
|
+
_repeated: isRepeated
|
|
1740
1756
|
})
|
|
1741
1757
|
_input.setNodeStatus({
|
|
1742
1758
|
fill: 'grey',
|
|
@@ -1763,7 +1779,8 @@ module.exports = (RED) => {
|
|
|
1763
1779
|
_devicename: _input.name || '',
|
|
1764
1780
|
_outputtopic: _input.outputtopic,
|
|
1765
1781
|
_oNode: _input,
|
|
1766
|
-
_echoed
|
|
1782
|
+
_echoed,
|
|
1783
|
+
_repeated: isRepeated
|
|
1767
1784
|
})
|
|
1768
1785
|
msg.previouspayload = typeof _input.currentPayload !== 'undefined' ? _input.currentPayload : '' // 24/01/2020 Reset previous payload
|
|
1769
1786
|
// 24/09/2019 Autorespond to BUS
|
|
@@ -2002,7 +2019,7 @@ module.exports = (RED) => {
|
|
|
2002
2019
|
}
|
|
2003
2020
|
}
|
|
2004
2021
|
|
|
2005
|
-
function buildInputMessage ({ _srcGA, _destGA, _event, _Rawvalue, _inputDpt, _devicename, _outputtopic, _oNode, _echoed = false }) {
|
|
2022
|
+
function buildInputMessage ({ _srcGA, _destGA, _event, _Rawvalue, _inputDpt, _devicename, _outputtopic, _oNode, _echoed = false, _repeated = false }) {
|
|
2006
2023
|
let sPayloadmeasureunit = 'unknown'
|
|
2007
2024
|
let sDptdesc = 'unknown'
|
|
2008
2025
|
let sPayloadsubtypevalue = 'unknown'
|
|
@@ -2054,13 +2071,17 @@ module.exports = (RED) => {
|
|
|
2054
2071
|
devicename: typeof _devicename !== 'undefined' ? _devicename : '',
|
|
2055
2072
|
payloadmeasureunit: '',
|
|
2056
2073
|
payloadsubtypevalue: '',
|
|
2074
|
+
repeated: _repeated === true,
|
|
2075
|
+
repeat: _repeated === true,
|
|
2057
2076
|
knx: {
|
|
2058
2077
|
event: _event,
|
|
2059
2078
|
dpt: 'unknown',
|
|
2060
2079
|
dptdesc: '',
|
|
2061
2080
|
source: _srcGA,
|
|
2062
2081
|
destination: _destGA,
|
|
2063
|
-
rawValue: _Rawvalue
|
|
2082
|
+
rawValue: _Rawvalue,
|
|
2083
|
+
repeated: _repeated === true,
|
|
2084
|
+
repeat: _repeated === true
|
|
2064
2085
|
}
|
|
2065
2086
|
}
|
|
2066
2087
|
|
|
@@ -2197,13 +2218,17 @@ module.exports = (RED) => {
|
|
|
2197
2218
|
payloadsubtypevalue: sPayloadsubtypevalue,
|
|
2198
2219
|
gainfo,
|
|
2199
2220
|
echoed: _echoed,
|
|
2221
|
+
repeated: _repeated === true,
|
|
2222
|
+
repeat: _repeated === true,
|
|
2200
2223
|
knx: {
|
|
2201
2224
|
event: _event,
|
|
2202
2225
|
dpt: sInputDpt,
|
|
2203
2226
|
dptdesc: sDptdesc,
|
|
2204
2227
|
source: _srcGA,
|
|
2205
2228
|
destination: _destGA,
|
|
2206
|
-
rawValue: _Rawvalue
|
|
2229
|
+
rawValue: _Rawvalue,
|
|
2230
|
+
repeated: _repeated === true,
|
|
2231
|
+
repeat: _repeated === true
|
|
2207
2232
|
}
|
|
2208
2233
|
}
|
|
2209
2234
|
// 11/11/2021 jsValue is null, as well as _Rawvalue, in case of READ REQUEST message.
|
package/nodes/knxUltimateAI.html
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
notifyresponse: { value: true },
|
|
14
14
|
notifywrite: { value: true },
|
|
15
15
|
|
|
16
|
-
analysisWindowSec: { value:
|
|
17
|
-
historyWindowSec: { value:
|
|
16
|
+
analysisWindowSec: { value: 120, required: true, validate: RED.validators.number() },
|
|
17
|
+
historyWindowSec: { value: 600, required: true, validate: RED.validators.number() },
|
|
18
18
|
emitIntervalSec: { value: 0, required: true, validate: RED.validators.number() },
|
|
19
19
|
topN: { value: 10, required: true, validate: RED.validators.number() },
|
|
20
|
-
maxEvents: { value:
|
|
20
|
+
maxEvents: { value: 50000, required: true, validate: RED.validators.number() },
|
|
21
21
|
|
|
22
22
|
rateWindowSec: { value: 10, required: true, validate: RED.validators.number() },
|
|
23
23
|
maxTelegramPerSecOverall: { value: 0, required: true, validate: RED.validators.number() },
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
llmEnabled: { value: false },
|
|
34
34
|
llmProvider: { value: "openai_compat" },
|
|
35
35
|
llmBaseUrl: { value: "https://api.openai.com/v1/chat/completions" },
|
|
36
|
-
llmModel: { value: "gpt-
|
|
36
|
+
llmModel: { value: "gpt-5.4" },
|
|
37
37
|
llmSystemPrompt: { value: "" },
|
|
38
38
|
llmTemperature: { value: 0.2, required: false, validate: RED.validators.number() },
|
|
39
39
|
llmMaxTokens: { value: 600, required: false, validate: RED.validators.number() },
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
llmIncludeFlowContext: { value: true },
|
|
44
44
|
llmMaxFlowNodesInPrompt: { value: 80, required: false, validate: RED.validators.number() },
|
|
45
45
|
llmIncludeDocsSnippets: { value: true },
|
|
46
|
-
llmDocsLanguage: { value: "
|
|
46
|
+
llmDocsLanguage: { value: "en" },
|
|
47
47
|
llmDocsMaxSnippets: { value: 5, required: false, validate: RED.validators.number() },
|
|
48
48
|
llmDocsMaxChars: { value: 3000, required: false, validate: RED.validators.number() }
|
|
49
49
|
},
|
|
@@ -100,6 +100,13 @@
|
|
|
100
100
|
|
|
101
101
|
const nodeId = this.id;
|
|
102
102
|
|
|
103
|
+
$("#knx-ai-open-web-page").on("click", function (evt) {
|
|
104
|
+
evt.preventDefault();
|
|
105
|
+
const target = "knxUltimateAI/sidebar/page" + (nodeId ? ("?nodeId=" + encodeURIComponent(nodeId)) : "");
|
|
106
|
+
const wnd = window.open(target, "_blank", "noopener,noreferrer");
|
|
107
|
+
try { if (wnd && typeof wnd.focus === "function") wnd.focus(); } catch (e) { }
|
|
108
|
+
});
|
|
109
|
+
|
|
103
110
|
const setModelsStatus = (text, level) => {
|
|
104
111
|
const $status = $("#knx-ai-models-status");
|
|
105
112
|
if (!$status || $status.length === 0) return;
|
|
@@ -190,6 +197,12 @@
|
|
|
190
197
|
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="knxUltimateAI.properties.topic"></span></label>
|
|
191
198
|
<input type="text" id="node-input-topic" data-i18n="[placeholder]knxUltimateAI.properties.topic">
|
|
192
199
|
</div>
|
|
200
|
+
<div class="form-row">
|
|
201
|
+
<label><i class="fa fa-external-link"></i> Web UI</label>
|
|
202
|
+
<button type="button" class="red-ui-button" id="knx-ai-open-web-page" style="background-color:#AD9EFF; border-color:#AD9EFF; color:#ffffff !important; -webkit-text-fill-color:#ffffff;">
|
|
203
|
+
<i class="fa fa-magic" style="color:#2b1f4d !important;"></i> <span style="color:#2b1f4d !important;">Open KNX AI Web</span>
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
193
206
|
|
|
194
207
|
<div id="knx-ai-accordion">
|
|
195
208
|
<h3><span data-i18n="knxUltimateAI.sections.capture"></span></h3>
|