iobroker.javascript 9.1.0 → 9.2.0

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.
Files changed (83) hide show
  1. package/README.md +16 -8
  2. package/admin/assets/AiChatPanel-sgt697A2.js +296 -0
  3. package/admin/assets/AiDatapointProvider-B0i3I5Fm.js +2 -0
  4. package/admin/assets/AiDiffView-RPozjjHf.js +1 -0
  5. package/admin/assets/AiInlineProvider-Dd7eKxk4.js +7 -0
  6. package/admin/assets/{Save-Cld9qv9i.js → Save-CkuNSiWL.js} +1 -1
  7. package/admin/assets/{ScriptEditor-BVaX7Hcs.js → ScriptEditor-CH4qJzqq.js} +2 -2
  8. package/admin/assets/ScriptEditorVanillaMonaco-cB_90T0P.js +1 -0
  9. package/admin/assets/{createSvgIcon-DPvF8xqu.js → createSvgIcon-DYs_tNrE.js} +1 -1
  10. package/admin/assets/docs-compact-CKNGiYh2.js +336 -0
  11. package/admin/assets/index-BI3JsAz_.js +1 -0
  12. package/admin/assets/{index-D8iYqogF.js → index-DAkyON1r.js} +5 -5
  13. package/admin/assets/index-DW5CwIma.js +1256 -0
  14. package/admin/assets/index-SHs7_wDh.js +1 -0
  15. package/admin/assets/{index-DlDUIfpA.js → index-dN73sbyQ.js} +1 -1
  16. package/admin/assets/index-hwplVCgv.js +986 -0
  17. package/admin/assets/index-wHNMOf-c.js +5 -0
  18. package/admin/assets/inheritsLoose-CIXm_zPk.js +29 -0
  19. package/admin/assets/{iobroker_javascript__loadShare___mf_0_mui_mf_1_material__loadShare__.mjs-QWG5Arqe.js → iobroker_javascript__loadShare___mf_0_mui_mf_1_material__loadShare__.mjs-CV1_X-Lf.js} +1 -1
  20. package/admin/assets/{localSharedImportMap-AxgBkrOR.js → localSharedImportMap-CqvxvyGG.js} +1 -1
  21. package/admin/assets/useTheme-DuTJsNQ-.js +1 -0
  22. package/admin/assets/{virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-C9suQF96.js → virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-BJ03G6Pu.js} +2 -2
  23. package/admin/custom/assets/{Components-FfVbavBM.js → Components-B9ZyjeRI.js} +1 -1
  24. package/admin/custom/assets/{index-Be7j5wvd.js → index-BVwMgafs.js} +1 -1
  25. package/admin/custom/assets/index-CVIpIckw.js +960 -0
  26. package/admin/custom/assets/{index-DNdWk-cT.js → index-DsIn9jH_.js} +1 -1
  27. package/admin/custom/assets/{localSharedImportMap-BRxFonIn.js → localSharedImportMap-C4qXtGUd.js} +1 -1
  28. package/admin/custom/assets/{virtualExposes-DyedJotb.js → virtualExposes-jSvr-kK3.js} +1 -1
  29. package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-BtJxKB87.js → virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-DFSXB0v2.js} +2 -2
  30. package/admin/custom/customComponents.js +1 -1
  31. package/admin/google-blockly/own/blocks_words.js +7 -0
  32. package/admin/google-blockly/own/field_oid.js +311 -37
  33. package/admin/i18n/de.json +8 -0
  34. package/admin/i18n/en.json +8 -0
  35. package/admin/i18n/es.json +8 -0
  36. package/admin/i18n/flat.txt +427 -0
  37. package/admin/i18n/fr.json +8 -0
  38. package/admin/i18n/it.json +8 -0
  39. package/admin/i18n/nl.json +8 -0
  40. package/admin/i18n/pl.json +8 -0
  41. package/admin/i18n/pt.json +8 -0
  42. package/admin/i18n/ru.json +8 -0
  43. package/admin/i18n/uk.json +8 -0
  44. package/admin/i18n/zh-cn.json +8 -0
  45. package/admin/img/anthropic.svg +1 -0
  46. package/admin/img/custom.svg +1 -0
  47. package/admin/img/deepseek.svg +1 -0
  48. package/admin/img/gemini.svg +1 -0
  49. package/admin/img/openai.svg +1 -0
  50. package/admin/jsonConfig.json +76 -14
  51. package/admin/mf-manifest.json +1 -1
  52. package/admin/remoteEntry.js +1 -1
  53. package/admin/tab.html +6 -6
  54. package/build/lib/debugger.js +2 -2
  55. package/build/lib/debugger.js.map +1 -1
  56. package/build/lib/inspect.js.map +1 -1
  57. package/build/lib/nodeModulesManagement.js +2 -2
  58. package/build/lib/nodeModulesManagement.js.map +1 -1
  59. package/build/lib/protectFs.js +5 -0
  60. package/build/lib/protectFs.js.map +1 -1
  61. package/build/lib/sandbox.js +2 -2
  62. package/build/lib/sandbox.js.map +1 -1
  63. package/build/main.js +72 -17
  64. package/build/main.js.map +1 -1
  65. package/build/types.d.ts +2 -0
  66. package/docs/de/README.md +21 -8
  67. package/docs/de/blockly.md +34 -0
  68. package/docs/en/README.md +21 -8
  69. package/docs/en/blockly.md +34 -0
  70. package/install/installTypings.js +1 -1
  71. package/io-package.json +27 -66
  72. package/package.json +7 -6
  73. package/admin/assets/OpenAiDialog-Dx-XNET0.js +0 -13
  74. package/admin/assets/ScriptEditorVanillaMonaco-yq11eIYC.js +0 -1
  75. package/admin/assets/docs-BrInh2Pn.js +0 -2036
  76. package/admin/assets/index-CLJ6-h9Q.js +0 -1
  77. package/admin/assets/index-COZhHfBn.js +0 -1230
  78. package/admin/assets/index-Cu-TydZj.js +0 -5
  79. package/admin/assets/index-Dnbw7_L3.js +0 -986
  80. package/admin/assets/index-gIA8x-8f.js +0 -1
  81. package/admin/assets/inheritsLoose-B0Ba5G9z.js +0 -29
  82. package/admin/assets/useTheme-hy6f3gra.js +0 -1
  83. package/admin/custom/assets/index-CYc31Gwg.js +0 -960
@@ -19,6 +19,7 @@ class FieldOID extends Blockly.Field {
19
19
  this.CURSOR = 'pointer';
20
20
  this.SERIALIZABLE = true;
21
21
  this.spellcheck_ = false;
22
+ this.maxDisplayLength = 200;
22
23
  }
23
24
 
24
25
  dispose() {
@@ -26,6 +27,235 @@ class FieldOID extends Blockly.Field {
26
27
  super.dispose();
27
28
  }
28
29
 
30
+ initView() {
31
+ super.initView();
32
+ const id = this.getValue();
33
+ if (id) {
34
+ this.updateIcon_(id);
35
+ }
36
+ }
37
+
38
+ static _resolveObjectName(id) {
39
+ const objects = window.main.objects;
40
+ if (!objects || !objects[id] || !objects[id].common || !objects[id].common.name) {
41
+ return null;
42
+ }
43
+ let name = objects[id].common.name;
44
+ if (typeof name === 'object') {
45
+ name = name[systemLang] || name.en;
46
+ }
47
+ return name || null;
48
+ }
49
+
50
+ static computeDisplayText(id, maxLen) {
51
+ const mode = FieldOID.displayMode;
52
+ const objects = window.main.objects;
53
+ let text;
54
+
55
+ switch (mode) {
56
+ case 1: { // Path: all parent names joined with "."
57
+ const parts = id.split('.');
58
+ const names = [];
59
+ for (let i = 0; i < parts.length; i++) {
60
+ const ancestorId = parts.slice(0, i + 1).join('.');
61
+ const name = FieldOID._resolveObjectName(ancestorId);
62
+ names.push(name || parts[i]);
63
+ }
64
+ text = names.join('.');
65
+ break;
66
+ }
67
+ case 2: // State ID: last segment only
68
+ text = id.split('.').pop() || id;
69
+ break;
70
+ case 3: // Full ID
71
+ text = id;
72
+ break;
73
+ default: { // Mode 0: Name (current behavior)
74
+ text = FieldOID._resolveObjectName(id) || id;
75
+ break;
76
+ }
77
+ }
78
+
79
+ // Append type suffix for non-state objects
80
+ if (objects && objects[id]?.type && !['state', 'meta', 'script'].includes(objects[id].type)) {
81
+ text += ` (${objects[id].type})`;
82
+ }
83
+
84
+ if (maxLen && text.length > maxLen) {
85
+ text = text.substring(0, maxLen - 2) + '\u2026';
86
+ }
87
+
88
+ // Replace whitespace with non-breaking spaces so the text doesn't collapse.
89
+ text = text.replace(/\s/g, Blockly.Field.NBSP);
90
+
91
+ return text || Blockly.Field.NBSP;
92
+ }
93
+
94
+ refreshDisplay() {
95
+ const id = this.getValue();
96
+ if (id) {
97
+ this._idName = FieldOID.computeDisplayText(id, this.maxDisplayLength);
98
+ this.updateIcon_(id);
99
+ this.forceRerender();
100
+ // For path mode, async-fetch parent objects to resolve names
101
+ if (FieldOID.displayMode === 1) {
102
+ this._resolvePathNamesAsync(id);
103
+ }
104
+ }
105
+ }
106
+
107
+ _resolvePathNamesAsync(id) {
108
+ if (typeof window.main.getObject !== 'function') return;
109
+ const objects = window.main.objects;
110
+ const parts = id.split('.');
111
+ let pending = 0;
112
+ let updated = false;
113
+ for (let i = 0; i < parts.length; i++) {
114
+ const ancestorId = parts.slice(0, i + 1).join('.');
115
+ if (objects[ancestorId]) continue; // already cached
116
+ pending++;
117
+ window.main.getObject(ancestorId, (err, obj) => {
118
+ if (obj) {
119
+ objects[obj._id] = objects[obj._id] || obj;
120
+ updated = true;
121
+ }
122
+ pending--;
123
+ if (pending === 0 && updated) {
124
+ this._idName = FieldOID.computeDisplayText(id, this.maxDisplayLength);
125
+ this.forceRerender();
126
+ }
127
+ });
128
+ }
129
+ }
130
+
131
+ _getIconOffset() {
132
+ return (FieldOID.showIcon && this._iconElement) ? 20 : 0;
133
+ }
134
+
135
+ // Override Blockly's size + position to account for icon
136
+ updateSize_(margin) {
137
+ const constants = this.getConstants();
138
+ const xPad = margin !== undefined ? margin :
139
+ (this.isFullBlockField() ? 0 : constants.FIELD_BORDER_RECT_X_PADDING);
140
+ const iconOffset = this._getIconOffset();
141
+
142
+ let textWidth = 0;
143
+ if (this.textElement_) {
144
+ textWidth = this.textElement_.getComputedTextLength();
145
+ }
146
+
147
+ let totalWidth = 2 * xPad + textWidth + iconOffset;
148
+ let totalHeight = constants.FIELD_TEXT_HEIGHT;
149
+ if (!this.isFullBlockField()) {
150
+ totalHeight = Math.max(totalHeight, constants.FIELD_BORDER_RECT_HEIGHT);
151
+ }
152
+
153
+ this.size_.height = totalHeight;
154
+ this.size_.width = totalWidth;
155
+
156
+ this.positionTextElement_(xPad + iconOffset, textWidth);
157
+ this.positionBorderRect_();
158
+ }
159
+
160
+ updateIcon_(id) {
161
+ if (!this.fieldGroup_) {
162
+ return;
163
+ }
164
+ if (!FieldOID.showIcon) {
165
+ if (this._iconElement) {
166
+ this._iconElement.remove();
167
+ this._iconElement = null;
168
+ this._iconUrl = null;
169
+ }
170
+ return;
171
+ }
172
+ // First try synchronous resolve with cached objects
173
+ const iconUrl = FieldOID.resolveIcon(id);
174
+ if (iconUrl) {
175
+ this._applyIcon(iconUrl);
176
+ }
177
+ // Then async-fetch parent objects to find a closer (more specific) icon
178
+ this._resolveIconAsync(id);
179
+ }
180
+
181
+ _applyIcon(iconUrl) {
182
+ if (!this.fieldGroup_) return;
183
+ if (this._iconUrl === iconUrl && this._iconElement) {
184
+ return;
185
+ }
186
+ this._iconUrl = iconUrl;
187
+ const iconSize = 16;
188
+ if (!this._iconElement) {
189
+ this._iconElement = document.createElementNS('http://www.w3.org/2000/svg', 'image');
190
+ this._iconElement.setAttribute('width', String(iconSize));
191
+ this._iconElement.setAttribute('height', String(iconSize));
192
+ if (this.textElement_) {
193
+ this.fieldGroup_.insertBefore(this._iconElement, this.textElement_);
194
+ } else {
195
+ this.fieldGroup_.appendChild(this._iconElement);
196
+ }
197
+ }
198
+ this._iconElement.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', iconUrl);
199
+ this._positionIcon();
200
+ }
201
+
202
+ _positionIcon() {
203
+ if (!this._iconElement || !this.textElement_) return;
204
+ const iconSize = 16;
205
+ try {
206
+ // Position icon at the start of the field content area
207
+ const constants = this.getConstants();
208
+ const xPad = this.borderRect_ ? constants.FIELD_BORDER_RECT_X_PADDING : 0;
209
+ this._iconElement.setAttribute('x', String(xPad + 2));
210
+
211
+ // Vertically center icon with text
212
+ const textBBox = this.textElement_.getBBox();
213
+ if (textBBox.height > 0) {
214
+ const textMidY = textBBox.y + textBBox.height / 2;
215
+ this._iconElement.setAttribute('y', String(textMidY - iconSize / 2));
216
+ }
217
+ } catch (e) {
218
+ // getBBox can fail if element is not in DOM yet
219
+ }
220
+ }
221
+
222
+ _resolveIconAsync(id) {
223
+ if (!FieldOID.showIcon || typeof window.main.getObject !== 'function') {
224
+ return;
225
+ }
226
+ const objects = window.main.objects;
227
+ const parts = id.split('.');
228
+ const adapterName = parts[0];
229
+ // Fetch ancestors from state level up to adapter instance level
230
+ // Start from the state itself, go up - first icon found wins
231
+ let remaining = parts.length;
232
+ const tryNext = (idx) => {
233
+ if (idx < 1) return;
234
+ const ancestorId = parts.slice(0, idx).join('.');
235
+ // Already in cache?
236
+ if (objects[ancestorId]) {
237
+ if (objects[ancestorId].common && objects[ancestorId].common.icon) {
238
+ this._applyIcon(FieldOID._buildIconUrl(objects[ancestorId].common.icon, adapterName));
239
+ return; // Found closest icon, stop
240
+ }
241
+ tryNext(idx - 1); // No icon here, try parent
242
+ return;
243
+ }
244
+ // Fetch this ancestor
245
+ window.main.getObject(ancestorId, (err, obj) => {
246
+ if (obj) {
247
+ objects[obj._id] = objects[obj._id] || obj;
248
+ if (obj.common && obj.common.icon) {
249
+ this._applyIcon(FieldOID._buildIconUrl(obj.common.icon, adapterName));
250
+ return; // Found closest icon, stop
251
+ }
252
+ }
253
+ tryNext(idx - 1); // Try parent
254
+ });
255
+ };
256
+ tryNext(remaining);
257
+ }
258
+
29
259
  setValue(id) {
30
260
  if (id === null) {
31
261
  return; // No change if null.
@@ -38,50 +268,26 @@ class FieldOID extends Blockly.Field {
38
268
  window.main.getObject(id, (err, obj) => {
39
269
  if (obj) {
40
270
  objects[obj._id] = objects[obj._id] || obj;
41
- let text = objects[obj._id].common && objects[obj._id].common.name && objects[obj._id].common.name;
271
+ const text = FieldOID.computeDisplayText(id, this.maxDisplayLength);
42
272
  if (text) {
43
- if (typeof text === 'object') {
44
- text = text[systemLang] || text.en;
45
- }
46
- if (text.length > this.maxDisplayLength) {
47
- // Truncate displayed string and add an ellipsis ('...').
48
- text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
49
- }
50
- if (objects && objects[id] && objects[id]?.type && !['state', 'meta', 'script'].includes(objects[id].type)) {
51
- text += ` (${objects[id].type})`;
52
- }
53
- // Replace whitespace with non-breaking spaces so the text doesn't collapse.
54
- text = text.replace(/\s/g, Blockly.Field.NBSP);
55
-
56
- if (text) {
57
- this._idName = text;
58
- this.forceRerender();
59
- }
273
+ this._idName = text;
274
+ this.forceRerender();
275
+ }
276
+ // Resolve parent names for path mode
277
+ if (FieldOID.displayMode === 1) {
278
+ this._resolvePathNamesAsync(id);
60
279
  }
61
280
  }
62
281
  });
63
282
  } else {
64
- let text = objects && objects[id] && objects[id].common && objects[id].common.name ? objects[id].common.name : id;
65
- if (typeof text === 'object') {
66
- text = text[systemLang] || text.en;
283
+ this._idName = FieldOID.computeDisplayText(id, this.maxDisplayLength);
284
+ // Resolve parent names for path mode
285
+ if (FieldOID.displayMode === 1) {
286
+ this._resolvePathNamesAsync(id);
67
287
  }
68
- if (text.length > this.maxDisplayLength) {
69
- // Truncate the displayed string and add an ellipsis ('...').
70
- text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
71
- }
72
- if (objects && objects[id] && objects[id]?.type && !['state', 'meta', 'script'].includes(objects[id].type)) {
73
- text += ` (${objects[id].type})`;
74
- }
75
- // Replace whitespace with non-breaking spaces so the text doesn't collapse.
76
- text = text.replace(/\s/g, Blockly.Field.NBSP);
77
-
78
- if (!text) {
79
- // Prevent the field from disappearing if empty.
80
- text = Blockly.Field.NBSP;
81
- }
82
- this._idName = text;
83
288
  }
84
289
 
290
+ this.updateIcon_(id);
85
291
  super.setValue(id);
86
292
  super.setTooltip(id);
87
293
  }
@@ -98,7 +304,6 @@ class FieldOID extends Blockly.Field {
98
304
  }
99
305
  // Replace whitespace with non-breaking spaces so the text doesn't collapse.
100
306
  text = text.replace(/\s/g, Blockly.Field.NBSP);
101
-
102
307
  if (this.sourceBlock_.RTL) {
103
308
  // The SVG is LTR, force text to be RTL.
104
309
  text += '\u200F';
@@ -214,6 +419,75 @@ class FieldOID extends Blockly.Field {
214
419
  }
215
420
  }
216
421
 
422
+ FieldOID.DISPLAY_MODE_KEYS = ['oid_display_name', 'oid_display_path', 'oid_display_id', 'oid_display_full_id'];
423
+ FieldOID.displayMode = parseInt(localStorage.getItem('Blockly.FieldOID.displayMode') || '0', 10) || 0;
424
+ FieldOID.showIcon = localStorage.getItem('Blockly.FieldOID.showIcon') === 'true';
425
+
426
+ FieldOID._buildIconUrl = function (icon, adapterName) {
427
+ if (!icon) return null;
428
+ // base64 data URIs and absolute URLs are used as-is
429
+ if (icon.startsWith('data:') || icon.startsWith('http://') || icon.startsWith('https://') || icon.startsWith('/')) {
430
+ return icon;
431
+ }
432
+ return '/adapter/' + adapterName + '/' + icon;
433
+ };
434
+
435
+ FieldOID.resolveIcon = function (id) {
436
+ const objects = window.main.objects;
437
+ if (!objects) return null;
438
+ const parts = id.split('.');
439
+ const adapterName = parts[0];
440
+ // Walk up the object hierarchy looking for common.icon
441
+ for (let i = parts.length; i >= 1; i--) {
442
+ const ancestorId = parts.slice(0, i).join('.');
443
+ const obj = objects[ancestorId];
444
+ if (obj && obj.common && obj.common.icon) {
445
+ return FieldOID._buildIconUrl(obj.common.icon, adapterName);
446
+ }
447
+ }
448
+ // Fallback: check system.adapter.{adapterName}.{instance} (e.g. system.adapter.zigbee2mqtt.0)
449
+ if (parts.length >= 2) {
450
+ const instanceId = 'system.adapter.' + parts[0] + '.' + parts[1];
451
+ const sysInstance = objects[instanceId];
452
+ if (sysInstance && sysInstance.common && sysInstance.common.icon) {
453
+ return FieldOID._buildIconUrl(sysInstance.common.icon, adapterName);
454
+ }
455
+ }
456
+ return null;
457
+ };
458
+
459
+ FieldOID.setDisplayMode = function (mode, workspace) {
460
+ FieldOID.displayMode = mode;
461
+ localStorage.setItem('Blockly.FieldOID.displayMode', String(mode));
462
+
463
+ const blocks = workspace.getAllBlocks(false);
464
+ for (const block of blocks) {
465
+ for (const input of block.inputList) {
466
+ for (const field of input.fieldRow) {
467
+ if (field instanceof FieldOID) {
468
+ field.refreshDisplay();
469
+ }
470
+ }
471
+ }
472
+ }
473
+ };
474
+
475
+ FieldOID.setShowIcon = function (show, workspace) {
476
+ FieldOID.showIcon = show;
477
+ localStorage.setItem('Blockly.FieldOID.showIcon', String(show));
478
+
479
+ const blocks = workspace.getAllBlocks(false);
480
+ for (const block of blocks) {
481
+ for (const input of block.inputList) {
482
+ for (const field of input.fieldRow) {
483
+ if (field instanceof FieldOID) {
484
+ field.refreshDisplay();
485
+ }
486
+ }
487
+ }
488
+ }
489
+ };
490
+
217
491
  Blockly.FieldOID = FieldOID;
218
492
 
219
493
  //Blockly.Field.register('field_oid', FieldOID);
@@ -21,6 +21,7 @@
21
21
  "Maximum setState requests per Minute per Script": "Maximale setState-Anfragen pro Minute pro Skript",
22
22
  "Maximum triggers per Script (until warning)": "Maximale Auslöser/Trigger pro Skript (bis zur Warnung)",
23
23
  "Mirror scripts to file path": "Spiegeln von Skripten in den Dateipfad",
24
+ "Sort global scripts alphabetically": "Globale Skripte alphabetisch sortieren",
24
25
  "Module names": "NPM-Modulnamen",
25
26
  "Next sunrise": "Nächster Tagesbeginn:",
26
27
  "Next sunset": "Nächstes Tagesende:",
@@ -61,6 +62,13 @@
61
62
  "Custom API Base URL": "Eigene API Base-URL (z.B. für Ollama, LM Studio)",
62
63
  "Custom API Base URL help": "Leer lassen für OpenAI. Für Ollama: http://localhost:11434/v1",
63
64
  "Test API connection": "API-Verbindung testen",
65
+ "Test OpenAI": "OpenAI testen",
66
+ "Test Anthropic": "Anthropic testen",
67
+ "Test Gemini": "Gemini testen",
68
+ "Test DeepSeek": "DeepSeek testen",
69
+ "Test Custom API": "Eigene API testen",
70
+ "Custom API key": "API-Schlüssel (optional)",
71
+ "Custom API key help": "Nur nötig wenn der Anbieter Authentifizierung erfordert",
64
72
  "AI settings": "KI-Einstellungen",
65
73
  "AI settings description": "Wenn Sie die KI-Codegenerierung nutzen möchten, können Sie hier die API-Schlüssel für die entsprechenden Anbieter hinterlegen. Es wird nur der Schlüssel für den verwendeten Anbieter benötigt.",
66
74
  "ChatGPT API key": "ChatGPT API-Schlüssel",
@@ -21,6 +21,7 @@
21
21
  "Maximum setState requests per Minute per Script": "Maximum setState requests per Minute per Script",
22
22
  "Maximum triggers per Script (until warning)": "Maximum triggers per Script (until warning)",
23
23
  "Mirror scripts to file path": "Mirror scripts to file path",
24
+ "Sort global scripts alphabetically": "Sort global scripts alphabetically",
24
25
  "Module names": "NPM module names",
25
26
  "Next sunrise": "Next sunrise:",
26
27
  "Next sunset": "Next sunset:",
@@ -61,6 +62,13 @@
61
62
  "Custom API Base URL": "Custom API Base URL (e.g. for Ollama, LM Studio)",
62
63
  "Custom API Base URL help": "Leave empty for OpenAI. For Ollama use http://localhost:11434/v1",
63
64
  "Test API connection": "Test API connection",
65
+ "Test OpenAI": "Test OpenAI",
66
+ "Test Anthropic": "Test Anthropic",
67
+ "Test Gemini": "Test Gemini",
68
+ "Test DeepSeek": "Test DeepSeek",
69
+ "Test Custom API": "Test Custom API",
70
+ "Custom API key": "API key (optional)",
71
+ "Custom API key help": "Only needed if your provider requires authentication",
64
72
  "AI settings": "AI settings",
65
73
  "AI settings description": "If you want to use AI code generation, you can define API keys for the corresponding providers here. Only the key for the provider you use is required.",
66
74
  "ChatGPT API key": "ChatGPT API key",
@@ -21,6 +21,7 @@
21
21
  "Maximum setState requests per Minute per Script": "Máximas solicitudes de Estados por minuto por script",
22
22
  "Maximum triggers per Script (until warning)": "Activadores máximos por script (hasta el aviso)",
23
23
  "Mirror scripts to file path": "Guiones espejo a la ruta del archivo",
24
+ "Sort global scripts alphabetically": "Ordenar scripts globales alfabéticamente",
24
25
  "Module names": "Nombres de módulos",
25
26
  "Next sunrise": "Próximo amanecer:",
26
27
  "Next sunset": "Próxima puesta de sol:",
@@ -61,6 +62,13 @@
61
62
  "Custom API Base URL": "URL base de API personalizada (p. ej. para Ollama, LM Studio)",
62
63
  "Custom API Base URL help": "Dejar vacío para OpenAI. Para Ollama usar http://localhost:11434/v1",
63
64
  "Test API connection": "Probar conexión API",
65
+ "Test OpenAI": "Test OpenAI",
66
+ "Test Anthropic": "Test Anthropic",
67
+ "Test Gemini": "Test Gemini",
68
+ "Test DeepSeek": "Test DeepSeek",
69
+ "Test Custom API": "Test Custom API",
70
+ "Custom API key": "API key (optional)",
71
+ "Custom API key help": "Only needed if your provider requires authentication",
64
72
  "AI settings": "Configuración de IA",
65
73
  "AI settings description": "Si desea utilizar la generación de código con IA, puede definir aquí las claves API de los proveedores correspondientes. Solo se necesita la clave del proveedor que utilice.",
66
74
  "ChatGPT API key": "Clave API de ChatGPT",