@runcore-sh/runcore 0.5.15 → 0.6.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.
- package/brain-template/templates/spawn-policy.yaml +12 -6
- package/dictionary.json +2 -2
- package/dist/agents/autonomous.d.ts.map +1 -1
- package/dist/agents/runtime/config.d.ts.map +1 -1
- package/dist/agents/runtime/driver.d.ts +12 -3
- package/dist/agents/runtime/driver.d.ts.map +1 -1
- package/dist/agents/runtime/types.d.ts +4 -0
- package/dist/agents/runtime/types.d.ts.map +1 -1
- package/dist/agents/runtime.js +3 -3
- package/dist/agents/runtime.js.map +1 -1
- package/dist/agents/spawn-policy.d.ts +15 -7
- package/dist/agents/spawn-policy.d.ts.map +1 -1
- package/dist/agents/spawn.d.ts.map +1 -1
- package/dist/agents/store.d.ts.map +1 -1
- package/dist/agents/store.js +94 -49
- package/dist/agents/store.js.map +1 -1
- package/dist/agents/types.d.ts +6 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/alert.d.ts +6 -0
- package/dist/alert.d.ts.map +1 -1
- package/dist/alert.js +16 -3
- package/dist/alert.js.map +1 -1
- package/dist/capabilities/definitions/email.d.ts +3 -2
- package/dist/capabilities/definitions/email.d.ts.map +1 -1
- package/dist/capabilities/definitions/email.js +74 -60
- package/dist/capabilities/definitions/email.js.map +1 -1
- package/dist/cli.js +49 -12
- package/dist/cli.js.map +1 -1
- package/dist/goals/planner.d.ts +40 -0
- package/dist/goals/planner.d.ts.map +1 -0
- package/dist/goals/planner.js +302 -0
- package/dist/goals/planner.js.map +1 -0
- package/dist/health/alert-defaults.js +6 -6
- package/dist/health/alert-defaults.js.map +1 -1
- package/dist/health/checks.d.ts.map +1 -1
- package/dist/health/checks.js +4 -6
- package/dist/health/checks.js.map +1 -1
- package/dist/llm/tools/handlers.js +1 -1
- package/dist/llm/tools/handlers.js.map +1 -1
- package/dist/llm/tools/loop.d.ts +2 -2
- package/dist/llm/tools/loop.d.ts.map +1 -1
- package/dist/llm/tools/loop.js +36 -8
- package/dist/llm/tools/loop.js.map +1 -1
- package/dist/queue/grooming.d.ts.map +1 -1
- package/dist/queue/grooming.js +27 -3
- package/dist/queue/grooming.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +121 -50
- package/dist/server.js.map +1 -1
- package/dist/services/routine-patterns.d.ts.map +1 -1
- package/dist/services/routine-patterns.js +9 -0
- package/dist/services/routine-patterns.js.map +1 -1
- package/dist/services/traceInsights.d.ts.map +1 -1
- package/dist/services/traceInsights.js +48 -32
- package/dist/services/traceInsights.js.map +1 -1
- package/dist/settings.js +2 -2
- package/dist/settings.js.map +1 -1
- package/dist/tier/token.d.ts.map +1 -1
- package/dist/tier/token.js +4 -1
- package/dist/tier/token.js.map +1 -1
- package/dist/vault/store.d.ts +3 -1
- package/dist/vault/store.d.ts.map +1 -1
- package/package.json +1 -1
- package/public/index.html +102 -16
- package/public/settings.html +1411 -0
- package/public/sw.js +9 -59
|
@@ -0,0 +1,1411 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{INSTANCE_NAME}} — Settings</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0e0e10;
|
|
10
|
+
--surface: #18181b;
|
|
11
|
+
--surface2: #222226;
|
|
12
|
+
--border: #2e2e33;
|
|
13
|
+
--text: #e4e4e7;
|
|
14
|
+
--text-dim: #8b8b94;
|
|
15
|
+
--accent: #6d5dfc;
|
|
16
|
+
--accent-dim: #5a4dd4;
|
|
17
|
+
--green: #22c55e;
|
|
18
|
+
--red: #ef4444;
|
|
19
|
+
--yellow: #eab308;
|
|
20
|
+
--orange: #f97316;
|
|
21
|
+
--blue: #3b82f6;
|
|
22
|
+
--purple: #a78bfa;
|
|
23
|
+
--mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
24
|
+
--radius: 8px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
31
|
+
background: var(--bg);
|
|
32
|
+
color: var(--text);
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* --- Header --- */
|
|
39
|
+
header {
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
padding: 16px 24px;
|
|
44
|
+
border-bottom: 1px solid var(--border);
|
|
45
|
+
background: var(--surface);
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
header h1 {
|
|
50
|
+
font-size: 18px;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
letter-spacing: -0.3px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
header h1 a {
|
|
56
|
+
color: var(--text);
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
header h1 a:hover { opacity: 0.8; }
|
|
61
|
+
header h1 span { color: var(--text-dim); font-weight: 400; }
|
|
62
|
+
|
|
63
|
+
.header-nav {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 8px;
|
|
67
|
+
margin-left: 16px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.header-nav a {
|
|
71
|
+
color: var(--text-dim);
|
|
72
|
+
text-decoration: none;
|
|
73
|
+
font-size: 13px;
|
|
74
|
+
padding: 4px 10px;
|
|
75
|
+
border-radius: 6px;
|
|
76
|
+
border: 1px solid var(--border);
|
|
77
|
+
transition: all 0.15s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.header-nav a:hover { color: var(--text); border-color: var(--accent); background: rgba(109,93,252,0.1); }
|
|
81
|
+
.header-nav a.active { color: var(--text); border-color: var(--border); background: var(--surface2); }
|
|
82
|
+
|
|
83
|
+
.nav-divider {
|
|
84
|
+
width: 1px;
|
|
85
|
+
height: 20px;
|
|
86
|
+
background: var(--border);
|
|
87
|
+
margin: 0 4px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.header-actions {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 12px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.header-actions-right {
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
gap: 4px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.gear-btn {
|
|
103
|
+
background: none;
|
|
104
|
+
border: none;
|
|
105
|
+
color: var(--text-dim);
|
|
106
|
+
font-size: 18px;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
padding: 4px 6px;
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
transition: color 0.15s, background 0.15s;
|
|
111
|
+
line-height: 1;
|
|
112
|
+
}
|
|
113
|
+
.gear-btn:hover { color: var(--text); background: var(--border); }
|
|
114
|
+
a.gear-btn { text-decoration: none; }
|
|
115
|
+
|
|
116
|
+
/* --- Main layout --- */
|
|
117
|
+
main {
|
|
118
|
+
flex: 1;
|
|
119
|
+
max-width: 960px;
|
|
120
|
+
width: 100%;
|
|
121
|
+
margin: 0 auto;
|
|
122
|
+
padding: 32px 24px 64px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.page-title {
|
|
126
|
+
font-size: 24px;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
margin-bottom: 32px;
|
|
129
|
+
letter-spacing: -0.3px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* --- Section cards --- */
|
|
133
|
+
.settings-grid {
|
|
134
|
+
display: grid;
|
|
135
|
+
grid-template-columns: 1fr 1fr;
|
|
136
|
+
gap: 24px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.settings-card {
|
|
140
|
+
background: var(--surface);
|
|
141
|
+
border: 1px solid var(--border);
|
|
142
|
+
border-radius: 12px;
|
|
143
|
+
padding: 24px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.settings-card.full-width {
|
|
147
|
+
grid-column: 1 / -1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.card-title {
|
|
151
|
+
font-size: 15px;
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
text-transform: uppercase;
|
|
154
|
+
letter-spacing: 0.5px;
|
|
155
|
+
color: var(--text-dim);
|
|
156
|
+
margin-bottom: 16px;
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 8px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.card-title a {
|
|
163
|
+
font-size: 11px;
|
|
164
|
+
font-weight: 400;
|
|
165
|
+
color: var(--accent);
|
|
166
|
+
text-decoration: none;
|
|
167
|
+
text-transform: none;
|
|
168
|
+
letter-spacing: 0;
|
|
169
|
+
}
|
|
170
|
+
.card-title a:hover { text-decoration: underline; }
|
|
171
|
+
|
|
172
|
+
/* --- Form elements --- */
|
|
173
|
+
.field-row {
|
|
174
|
+
display: flex;
|
|
175
|
+
gap: 8px;
|
|
176
|
+
align-items: center;
|
|
177
|
+
margin-bottom: 12px;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.field-row:last-child { margin-bottom: 0; }
|
|
181
|
+
|
|
182
|
+
.field-row label {
|
|
183
|
+
font-size: 13px;
|
|
184
|
+
color: var(--text-dim);
|
|
185
|
+
white-space: nowrap;
|
|
186
|
+
min-width: 90px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.field-row input[type="text"],
|
|
190
|
+
.field-row input[type="password"],
|
|
191
|
+
.field-row select {
|
|
192
|
+
flex: 1;
|
|
193
|
+
padding: 8px 10px;
|
|
194
|
+
border-radius: 6px;
|
|
195
|
+
border: 1px solid var(--border);
|
|
196
|
+
background: var(--bg);
|
|
197
|
+
color: var(--text);
|
|
198
|
+
font-size: 14px;
|
|
199
|
+
font-family: inherit;
|
|
200
|
+
outline: none;
|
|
201
|
+
transition: border-color 0.15s;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.field-row input:focus,
|
|
205
|
+
.field-row select:focus { border-color: var(--accent); }
|
|
206
|
+
.field-row input::placeholder { color: var(--text-dim); }
|
|
207
|
+
|
|
208
|
+
.btn {
|
|
209
|
+
padding: 8px 16px;
|
|
210
|
+
background: var(--accent);
|
|
211
|
+
color: #fff;
|
|
212
|
+
border: none;
|
|
213
|
+
border-radius: 6px;
|
|
214
|
+
font-size: 13px;
|
|
215
|
+
font-weight: 500;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
font-family: inherit;
|
|
218
|
+
transition: background 0.15s;
|
|
219
|
+
white-space: nowrap;
|
|
220
|
+
}
|
|
221
|
+
.btn:hover { background: var(--accent-dim); }
|
|
222
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
223
|
+
|
|
224
|
+
.btn-sm {
|
|
225
|
+
padding: 6px 12px;
|
|
226
|
+
font-size: 12px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.btn-danger {
|
|
230
|
+
background: transparent;
|
|
231
|
+
color: var(--red);
|
|
232
|
+
border: 1px solid rgba(239,68,68,0.3);
|
|
233
|
+
}
|
|
234
|
+
.btn-danger:hover { background: rgba(239,68,68,0.1); }
|
|
235
|
+
|
|
236
|
+
.btn-ghost {
|
|
237
|
+
background: transparent;
|
|
238
|
+
color: var(--text-dim);
|
|
239
|
+
border: 1px solid var(--border);
|
|
240
|
+
}
|
|
241
|
+
.btn-ghost:hover { color: var(--text); background: var(--surface2); }
|
|
242
|
+
|
|
243
|
+
.save-status {
|
|
244
|
+
font-size: 12px;
|
|
245
|
+
color: var(--green);
|
|
246
|
+
opacity: 0;
|
|
247
|
+
transition: opacity 0.3s;
|
|
248
|
+
margin-left: 8px;
|
|
249
|
+
}
|
|
250
|
+
.save-status.visible { opacity: 1; }
|
|
251
|
+
.save-status.error { color: var(--red); }
|
|
252
|
+
|
|
253
|
+
.card-actions {
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: 10px;
|
|
257
|
+
margin-top: 16px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* --- Toggle switch --- */
|
|
261
|
+
.toggle-row {
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
justify-content: space-between;
|
|
265
|
+
padding: 8px 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.toggle-label {
|
|
269
|
+
font-size: 14px;
|
|
270
|
+
font-weight: 500;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.toggle-sublabel {
|
|
274
|
+
font-size: 12px;
|
|
275
|
+
color: var(--text-dim);
|
|
276
|
+
margin-top: 2px;
|
|
277
|
+
max-width: 320px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.toggle {
|
|
281
|
+
position: relative;
|
|
282
|
+
width: 44px;
|
|
283
|
+
height: 24px;
|
|
284
|
+
flex-shrink: 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.toggle input { opacity: 0; width: 0; height: 0; }
|
|
288
|
+
|
|
289
|
+
.toggle-track {
|
|
290
|
+
position: absolute;
|
|
291
|
+
inset: 0;
|
|
292
|
+
background: var(--border);
|
|
293
|
+
border-radius: 12px;
|
|
294
|
+
cursor: pointer;
|
|
295
|
+
transition: background 0.2s;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.toggle-track::after {
|
|
299
|
+
content: "";
|
|
300
|
+
position: absolute;
|
|
301
|
+
top: 3px;
|
|
302
|
+
left: 3px;
|
|
303
|
+
width: 18px;
|
|
304
|
+
height: 18px;
|
|
305
|
+
background: var(--text-dim);
|
|
306
|
+
border-radius: 50%;
|
|
307
|
+
transition: transform 0.2s, background 0.2s;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.toggle input:checked + .toggle-track { background: var(--accent); }
|
|
311
|
+
.toggle input:checked + .toggle-track::after { transform: translateX(20px); background: #fff; }
|
|
312
|
+
|
|
313
|
+
/* --- Personality trait sliders --- */
|
|
314
|
+
.personality-traits { display: flex; flex-direction: column; gap: 14px; }
|
|
315
|
+
.trait-row {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
gap: 10px;
|
|
319
|
+
font-size: 12px;
|
|
320
|
+
}
|
|
321
|
+
.trait-label { width: 80px; text-align: right; color: var(--text-dim); flex-shrink: 0; }
|
|
322
|
+
.trait-label-right { width: 80px; text-align: left; color: var(--text-dim); flex-shrink: 0; }
|
|
323
|
+
.trait-slider {
|
|
324
|
+
flex: 1;
|
|
325
|
+
position: relative;
|
|
326
|
+
height: 20px;
|
|
327
|
+
display: flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
justify-content: space-between;
|
|
330
|
+
padding: 0 4px;
|
|
331
|
+
}
|
|
332
|
+
.trait-slider::before {
|
|
333
|
+
content: "";
|
|
334
|
+
position: absolute;
|
|
335
|
+
left: 4px; right: 4px;
|
|
336
|
+
top: 50%;
|
|
337
|
+
height: 2px;
|
|
338
|
+
background: var(--border);
|
|
339
|
+
transform: translateY(-50%);
|
|
340
|
+
border-radius: 1px;
|
|
341
|
+
}
|
|
342
|
+
.trait-dot {
|
|
343
|
+
width: 14px; height: 14px;
|
|
344
|
+
border-radius: 50%;
|
|
345
|
+
border: 2px solid var(--border);
|
|
346
|
+
background: transparent;
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
position: relative;
|
|
349
|
+
z-index: 1;
|
|
350
|
+
transition: background 0.15s, border-color 0.15s;
|
|
351
|
+
padding: 0;
|
|
352
|
+
}
|
|
353
|
+
.trait-dot:hover { border-color: var(--accent); }
|
|
354
|
+
.trait-dot.active { background: var(--accent); border-color: var(--accent); }
|
|
355
|
+
|
|
356
|
+
.custom-rules-toggle { margin-top: 12px; }
|
|
357
|
+
.custom-rules-toggle summary {
|
|
358
|
+
font-size: 12px;
|
|
359
|
+
color: var(--text-dim);
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
user-select: none;
|
|
362
|
+
padding: 4px 0;
|
|
363
|
+
}
|
|
364
|
+
.custom-rules-toggle summary:hover { color: var(--text); }
|
|
365
|
+
|
|
366
|
+
.prompt-textarea {
|
|
367
|
+
width: 100%;
|
|
368
|
+
padding: 10px 12px;
|
|
369
|
+
background: var(--bg);
|
|
370
|
+
border: 1px solid var(--border);
|
|
371
|
+
border-radius: var(--radius);
|
|
372
|
+
color: var(--text);
|
|
373
|
+
font-size: 13px;
|
|
374
|
+
font-family: inherit;
|
|
375
|
+
outline: none;
|
|
376
|
+
resize: vertical;
|
|
377
|
+
min-height: 80px;
|
|
378
|
+
line-height: 1.5;
|
|
379
|
+
transition: border-color 0.15s;
|
|
380
|
+
}
|
|
381
|
+
.prompt-textarea:focus { border-color: var(--accent); }
|
|
382
|
+
.prompt-textarea::placeholder { color: var(--text-dim); }
|
|
383
|
+
|
|
384
|
+
/* --- Model dropdowns --- */
|
|
385
|
+
.model-field {
|
|
386
|
+
margin-bottom: 12px;
|
|
387
|
+
}
|
|
388
|
+
.model-field:last-of-type { margin-bottom: 0; }
|
|
389
|
+
|
|
390
|
+
.model-field label {
|
|
391
|
+
display: block;
|
|
392
|
+
font-size: 12px;
|
|
393
|
+
font-weight: 500;
|
|
394
|
+
color: var(--text-dim);
|
|
395
|
+
margin-bottom: 4px;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.model-field select {
|
|
399
|
+
width: 100%;
|
|
400
|
+
padding: 8px 10px;
|
|
401
|
+
background: var(--bg);
|
|
402
|
+
border: 1px solid var(--border);
|
|
403
|
+
border-radius: var(--radius);
|
|
404
|
+
color: var(--text);
|
|
405
|
+
font-size: 13px;
|
|
406
|
+
font-family: var(--mono);
|
|
407
|
+
outline: none;
|
|
408
|
+
transition: border-color 0.15s;
|
|
409
|
+
}
|
|
410
|
+
.model-field select:focus { border-color: var(--accent); }
|
|
411
|
+
|
|
412
|
+
/* --- Key Vault --- */
|
|
413
|
+
.vault-empty {
|
|
414
|
+
color: var(--text-dim);
|
|
415
|
+
font-size: 13px;
|
|
416
|
+
text-align: center;
|
|
417
|
+
padding: 24px 0;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.vault-key-item {
|
|
421
|
+
display: flex;
|
|
422
|
+
align-items: center;
|
|
423
|
+
padding: 10px 12px;
|
|
424
|
+
background: var(--bg);
|
|
425
|
+
border: 1px solid var(--border);
|
|
426
|
+
border-radius: var(--radius);
|
|
427
|
+
margin-bottom: 8px;
|
|
428
|
+
gap: 8px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.vault-key-info {
|
|
432
|
+
flex: 1;
|
|
433
|
+
min-width: 0;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.vault-key-name {
|
|
437
|
+
font-size: 13px;
|
|
438
|
+
font-weight: 600;
|
|
439
|
+
font-family: var(--mono);
|
|
440
|
+
word-break: break-all;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.vault-key-label {
|
|
444
|
+
font-size: 11px;
|
|
445
|
+
color: var(--text-dim);
|
|
446
|
+
margin-top: 2px;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.vault-key-type {
|
|
450
|
+
font-size: 10px;
|
|
451
|
+
padding: 2px 6px;
|
|
452
|
+
border-radius: 4px;
|
|
453
|
+
text-transform: uppercase;
|
|
454
|
+
letter-spacing: 0.3px;
|
|
455
|
+
font-weight: 600;
|
|
456
|
+
flex-shrink: 0;
|
|
457
|
+
}
|
|
458
|
+
.vault-key-type.secret { background: rgba(239,68,68,0.15); color: var(--red); }
|
|
459
|
+
.vault-key-type.text { background: rgba(34,197,94,0.15); color: var(--green); }
|
|
460
|
+
|
|
461
|
+
.vault-key-value {
|
|
462
|
+
flex: 1;
|
|
463
|
+
min-width: 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.vault-key-value input {
|
|
467
|
+
width: 100%;
|
|
468
|
+
padding: 6px 8px;
|
|
469
|
+
border-radius: 4px;
|
|
470
|
+
border: 1px solid var(--border);
|
|
471
|
+
background: var(--bg);
|
|
472
|
+
color: var(--text);
|
|
473
|
+
font-size: 12px;
|
|
474
|
+
font-family: var(--mono);
|
|
475
|
+
outline: none;
|
|
476
|
+
}
|
|
477
|
+
.vault-key-value input:focus { border-color: var(--accent); }
|
|
478
|
+
|
|
479
|
+
.vault-key-masked {
|
|
480
|
+
font-size: 12px;
|
|
481
|
+
font-family: var(--mono);
|
|
482
|
+
color: var(--text-dim);
|
|
483
|
+
letter-spacing: 2px;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.vault-key-actions {
|
|
487
|
+
display: flex;
|
|
488
|
+
gap: 4px;
|
|
489
|
+
flex-shrink: 0;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.vault-key-actions button {
|
|
493
|
+
background: none;
|
|
494
|
+
border: none;
|
|
495
|
+
color: var(--text-dim);
|
|
496
|
+
font-size: 13px;
|
|
497
|
+
cursor: pointer;
|
|
498
|
+
padding: 4px 6px;
|
|
499
|
+
border-radius: 4px;
|
|
500
|
+
transition: color 0.15s, background 0.15s;
|
|
501
|
+
}
|
|
502
|
+
.vault-key-actions button:hover { color: var(--text); background: var(--surface2); }
|
|
503
|
+
.vault-key-actions button.delete-btn:hover { color: var(--red); background: rgba(239,68,68,0.1); }
|
|
504
|
+
|
|
505
|
+
/* --- Add key form --- */
|
|
506
|
+
.add-key-form {
|
|
507
|
+
display: grid;
|
|
508
|
+
grid-template-columns: 1fr 1fr;
|
|
509
|
+
gap: 8px;
|
|
510
|
+
margin-top: 16px;
|
|
511
|
+
padding-top: 16px;
|
|
512
|
+
border-top: 1px solid var(--border);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.add-key-form input,
|
|
516
|
+
.add-key-form select {
|
|
517
|
+
padding: 8px 10px;
|
|
518
|
+
background: var(--bg);
|
|
519
|
+
border: 1px solid var(--border);
|
|
520
|
+
border-radius: var(--radius);
|
|
521
|
+
color: var(--text);
|
|
522
|
+
font-size: 13px;
|
|
523
|
+
font-family: inherit;
|
|
524
|
+
outline: none;
|
|
525
|
+
}
|
|
526
|
+
.add-key-form input:focus,
|
|
527
|
+
.add-key-form select:focus { border-color: var(--accent); }
|
|
528
|
+
.add-key-form input::placeholder { color: var(--text-dim); }
|
|
529
|
+
|
|
530
|
+
.add-key-form .full-span {
|
|
531
|
+
grid-column: 1 / -1;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.vault-error {
|
|
535
|
+
color: var(--red);
|
|
536
|
+
font-size: 12px;
|
|
537
|
+
display: none;
|
|
538
|
+
grid-column: 1 / -1;
|
|
539
|
+
}
|
|
540
|
+
.vault-error.visible { display: block; }
|
|
541
|
+
|
|
542
|
+
/* --- Integrations --- */
|
|
543
|
+
.integration-row {
|
|
544
|
+
display: flex;
|
|
545
|
+
align-items: center;
|
|
546
|
+
justify-content: space-between;
|
|
547
|
+
padding: 12px 0;
|
|
548
|
+
border-bottom: 1px solid var(--border);
|
|
549
|
+
}
|
|
550
|
+
.integration-row:last-child { border-bottom: none; }
|
|
551
|
+
|
|
552
|
+
.integration-info {
|
|
553
|
+
flex: 1;
|
|
554
|
+
}
|
|
555
|
+
.integration-name {
|
|
556
|
+
font-size: 14px;
|
|
557
|
+
font-weight: 500;
|
|
558
|
+
}
|
|
559
|
+
.integration-status {
|
|
560
|
+
font-size: 12px;
|
|
561
|
+
color: var(--text-dim);
|
|
562
|
+
margin-top: 2px;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.status-dot {
|
|
566
|
+
width: 8px;
|
|
567
|
+
height: 8px;
|
|
568
|
+
border-radius: 50%;
|
|
569
|
+
flex-shrink: 0;
|
|
570
|
+
margin-left: 12px;
|
|
571
|
+
}
|
|
572
|
+
.status-dot.connected { background: var(--green); }
|
|
573
|
+
.status-dot.disconnected { background: var(--text-dim); }
|
|
574
|
+
|
|
575
|
+
/* --- Responsive --- */
|
|
576
|
+
@media (max-width: 768px) {
|
|
577
|
+
.settings-grid {
|
|
578
|
+
grid-template-columns: 1fr;
|
|
579
|
+
}
|
|
580
|
+
main { padding: 20px 16px 48px; }
|
|
581
|
+
header { padding: 12px 16px; }
|
|
582
|
+
.header-nav { display: none; }
|
|
583
|
+
.add-key-form {
|
|
584
|
+
grid-template-columns: 1fr;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
</style>
|
|
588
|
+
</head>
|
|
589
|
+
<body>
|
|
590
|
+
|
|
591
|
+
<header>
|
|
592
|
+
<div style="display:flex;align-items:center;">
|
|
593
|
+
<h1><a href="/">{{INSTANCE_NAME}}</a></h1>
|
|
594
|
+
<nav class="header-nav">
|
|
595
|
+
<a href="/">Chat</a>
|
|
596
|
+
<a href="/library">Library</a>
|
|
597
|
+
<a href="/personal">Personal</a>
|
|
598
|
+
<a href="/life">Life</a>
|
|
599
|
+
<a href="/registry">Registry</a>
|
|
600
|
+
<span class="nav-divider"></span>
|
|
601
|
+
<a href="/observatory">Observatory</a>
|
|
602
|
+
<a href="/ops">Operations</a>
|
|
603
|
+
<a href="/board">Board</a>
|
|
604
|
+
<a href="/roadmap">Roadmap</a>
|
|
605
|
+
</nav>
|
|
606
|
+
</div>
|
|
607
|
+
<div class="header-actions">
|
|
608
|
+
<div class="header-actions-right">
|
|
609
|
+
<button class="gear-btn" onclick="openShareModal()" title="Share Core">✉</button>
|
|
610
|
+
<a class="gear-btn active" href="/settings" title="Settings" style="color:var(--text);background:var(--surface2);border-radius:4px;">⚙</a>
|
|
611
|
+
</div>
|
|
612
|
+
</div>
|
|
613
|
+
</header>
|
|
614
|
+
|
|
615
|
+
<main>
|
|
616
|
+
<h2 class="page-title">Settings</h2>
|
|
617
|
+
|
|
618
|
+
<div class="settings-grid">
|
|
619
|
+
|
|
620
|
+
<!-- Identity -->
|
|
621
|
+
<div class="settings-card">
|
|
622
|
+
<div class="card-title">Identity</div>
|
|
623
|
+
<div class="field-row">
|
|
624
|
+
<label for="settings-your-name">Your Name</label>
|
|
625
|
+
<input type="text" id="settings-your-name" placeholder="Bryant">
|
|
626
|
+
<button class="btn btn-sm" id="save-your-name-btn">Save</button>
|
|
627
|
+
</div>
|
|
628
|
+
<div class="field-row">
|
|
629
|
+
<label for="settings-agent-name">Agent Name</label>
|
|
630
|
+
<input type="text" id="settings-agent-name" placeholder="Core">
|
|
631
|
+
<button class="btn btn-sm" id="save-agent-name-btn">Save</button>
|
|
632
|
+
</div>
|
|
633
|
+
<div style="margin-top:16px;">
|
|
634
|
+
<div class="card-title" style="margin-bottom:12px;">Personality</div>
|
|
635
|
+
<div class="personality-traits" id="personality-traits">
|
|
636
|
+
<div class="trait-row" data-trait="verbosity">
|
|
637
|
+
<span class="trait-label">Concise</span>
|
|
638
|
+
<div class="trait-slider">
|
|
639
|
+
<button class="trait-dot" data-value="1" title="Concise"></button>
|
|
640
|
+
<button class="trait-dot active" data-value="2" title="Balanced"></button>
|
|
641
|
+
<button class="trait-dot" data-value="3" title="Verbose"></button>
|
|
642
|
+
</div>
|
|
643
|
+
<span class="trait-label-right">Verbose</span>
|
|
644
|
+
</div>
|
|
645
|
+
<div class="trait-row" data-trait="assertiveness">
|
|
646
|
+
<span class="trait-label">Gentle</span>
|
|
647
|
+
<div class="trait-slider">
|
|
648
|
+
<button class="trait-dot" data-value="1" title="Gentle"></button>
|
|
649
|
+
<button class="trait-dot active" data-value="2" title="Moderate"></button>
|
|
650
|
+
<button class="trait-dot" data-value="3" title="Pushy"></button>
|
|
651
|
+
</div>
|
|
652
|
+
<span class="trait-label-right">Pushy</span>
|
|
653
|
+
</div>
|
|
654
|
+
<div class="trait-row" data-trait="tone">
|
|
655
|
+
<span class="trait-label">Edgy</span>
|
|
656
|
+
<div class="trait-slider">
|
|
657
|
+
<button class="trait-dot" data-value="1" title="Edgy"></button>
|
|
658
|
+
<button class="trait-dot active" data-value="2" title="Neutral"></button>
|
|
659
|
+
<button class="trait-dot" data-value="3" title="Safe"></button>
|
|
660
|
+
</div>
|
|
661
|
+
<span class="trait-label-right">Safe</span>
|
|
662
|
+
</div>
|
|
663
|
+
<div class="trait-row" data-trait="formality">
|
|
664
|
+
<span class="trait-label">Casual</span>
|
|
665
|
+
<div class="trait-slider">
|
|
666
|
+
<button class="trait-dot" data-value="1" title="Casual"></button>
|
|
667
|
+
<button class="trait-dot active" data-value="2" title="Adaptive"></button>
|
|
668
|
+
<button class="trait-dot" data-value="3" title="Formal"></button>
|
|
669
|
+
</div>
|
|
670
|
+
<span class="trait-label-right">Formal</span>
|
|
671
|
+
</div>
|
|
672
|
+
<div class="trait-row" data-trait="curiosity">
|
|
673
|
+
<span class="trait-label">Task-focused</span>
|
|
674
|
+
<div class="trait-slider">
|
|
675
|
+
<button class="trait-dot" data-value="1" title="Task-focused"></button>
|
|
676
|
+
<button class="trait-dot active" data-value="2" title="Balanced"></button>
|
|
677
|
+
<button class="trait-dot" data-value="3" title="Exploratory"></button>
|
|
678
|
+
</div>
|
|
679
|
+
<span class="trait-label-right">Exploratory</span>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
<details class="custom-rules-toggle">
|
|
683
|
+
<summary>Custom rules</summary>
|
|
684
|
+
<textarea class="prompt-textarea" id="custom-rules-textarea" placeholder="One rule per line, e.g. Don't use emojis Always greet me by name" rows="4"></textarea>
|
|
685
|
+
</details>
|
|
686
|
+
<div class="card-actions">
|
|
687
|
+
<button class="btn btn-sm" id="personality-save-btn">Save Personality</button>
|
|
688
|
+
<span class="save-status" id="personality-save-status">Saved</span>
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
|
|
693
|
+
<!-- Security -->
|
|
694
|
+
<div class="settings-card">
|
|
695
|
+
<div class="card-title">Security</div>
|
|
696
|
+
<div class="toggle-row">
|
|
697
|
+
<div>
|
|
698
|
+
<div class="toggle-label">Auth Mode</div>
|
|
699
|
+
<div class="toggle-sublabel" id="safeword-mode-label">Require password on every page load</div>
|
|
700
|
+
</div>
|
|
701
|
+
<select id="safeword-mode-select" style="background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:13px;outline:none;">
|
|
702
|
+
<option value="always">Every load</option>
|
|
703
|
+
<option value="restart">After restart only</option>
|
|
704
|
+
</select>
|
|
705
|
+
</div>
|
|
706
|
+
<div class="card-actions">
|
|
707
|
+
<button class="btn btn-sm" id="security-save-btn">Save</button>
|
|
708
|
+
<span class="save-status" id="security-save-status">Saved</span>
|
|
709
|
+
</div>
|
|
710
|
+
|
|
711
|
+
<!-- Network / Mesh -->
|
|
712
|
+
<div style="margin-top:24px;" id="mesh-section">
|
|
713
|
+
<div class="card-title">Network</div>
|
|
714
|
+
<div class="toggle-row">
|
|
715
|
+
<div>
|
|
716
|
+
<div class="toggle-label">LAN Announcement</div>
|
|
717
|
+
<div class="toggle-sublabel">Announce this instance on the local network via mDNS</div>
|
|
718
|
+
</div>
|
|
719
|
+
<label class="toggle">
|
|
720
|
+
<input type="checkbox" id="mesh-lan-toggle">
|
|
721
|
+
<span class="toggle-track"></span>
|
|
722
|
+
</label>
|
|
723
|
+
</div>
|
|
724
|
+
<div class="toggle-row">
|
|
725
|
+
<div>
|
|
726
|
+
<div class="toggle-label">Allow Incoming</div>
|
|
727
|
+
<div class="toggle-sublabel">Accept mesh connections from discovered peers</div>
|
|
728
|
+
</div>
|
|
729
|
+
<label class="toggle">
|
|
730
|
+
<input type="checkbox" id="mesh-incoming-toggle">
|
|
731
|
+
<span class="toggle-track"></span>
|
|
732
|
+
</label>
|
|
733
|
+
</div>
|
|
734
|
+
<div class="card-actions">
|
|
735
|
+
<button class="btn btn-sm" id="mesh-save-btn">Save</button>
|
|
736
|
+
<span class="save-status" id="mesh-save-status">Saved</span>
|
|
737
|
+
</div>
|
|
738
|
+
</div>
|
|
739
|
+
</div>
|
|
740
|
+
|
|
741
|
+
<!-- Models -->
|
|
742
|
+
<div class="settings-card">
|
|
743
|
+
<div class="card-title">Models
|
|
744
|
+
<a href="https://ollama.com/library" target="_blank" rel="noopener">Ollama</a>
|
|
745
|
+
<a href="https://openrouter.ai/models" target="_blank" rel="noopener">OpenRouter</a>
|
|
746
|
+
</div>
|
|
747
|
+
<div class="model-field">
|
|
748
|
+
<label>Chat model</label>
|
|
749
|
+
<select id="model-chat">
|
|
750
|
+
<option value="auto">Auto (best available)</option>
|
|
751
|
+
</select>
|
|
752
|
+
</div>
|
|
753
|
+
<div class="model-field">
|
|
754
|
+
<label>Utility model</label>
|
|
755
|
+
<select id="model-utility">
|
|
756
|
+
<option value="auto">Auto (best available)</option>
|
|
757
|
+
</select>
|
|
758
|
+
</div>
|
|
759
|
+
<div class="model-field">
|
|
760
|
+
<label>Agent model</label>
|
|
761
|
+
<select id="model-agent">
|
|
762
|
+
<option value="auto">Auto (best coding model)</option>
|
|
763
|
+
</select>
|
|
764
|
+
</div>
|
|
765
|
+
<div class="toggle-row" style="margin-top:12px;">
|
|
766
|
+
<div>
|
|
767
|
+
<div class="toggle-label">Airplane Mode</div>
|
|
768
|
+
<div class="toggle-sublabel">ON = Ollama (local) · OFF = OpenRouter (cloud)</div>
|
|
769
|
+
</div>
|
|
770
|
+
<label class="toggle">
|
|
771
|
+
<input type="checkbox" id="airplane-toggle">
|
|
772
|
+
<span class="toggle-track"></span>
|
|
773
|
+
</label>
|
|
774
|
+
</div>
|
|
775
|
+
<div class="card-actions">
|
|
776
|
+
<button class="btn btn-sm" id="llm-save-btn">Save</button>
|
|
777
|
+
<span class="save-status" id="llm-save-status">Saved</span>
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
|
|
781
|
+
<!-- Voice -->
|
|
782
|
+
<div class="settings-card" id="voice-section">
|
|
783
|
+
<div class="card-title">Voice</div>
|
|
784
|
+
<div class="toggle-row">
|
|
785
|
+
<div>
|
|
786
|
+
<div class="toggle-label">Text-to-Speech</div>
|
|
787
|
+
<div class="toggle-sublabel" id="tts-status-label">Checking...</div>
|
|
788
|
+
</div>
|
|
789
|
+
<label class="toggle">
|
|
790
|
+
<input type="checkbox" id="tts-toggle">
|
|
791
|
+
<span class="toggle-track"></span>
|
|
792
|
+
</label>
|
|
793
|
+
</div>
|
|
794
|
+
<div class="toggle-row">
|
|
795
|
+
<div>
|
|
796
|
+
<div class="toggle-label">Speech-to-Text</div>
|
|
797
|
+
<div class="toggle-sublabel" id="stt-status-label">Checking...</div>
|
|
798
|
+
</div>
|
|
799
|
+
<label class="toggle">
|
|
800
|
+
<input type="checkbox" id="stt-toggle">
|
|
801
|
+
<span class="toggle-track"></span>
|
|
802
|
+
</label>
|
|
803
|
+
</div>
|
|
804
|
+
<div class="toggle-row">
|
|
805
|
+
<div>
|
|
806
|
+
<div class="toggle-label">Auto-play responses</div>
|
|
807
|
+
<div class="toggle-sublabel">Automatically speak replies</div>
|
|
808
|
+
</div>
|
|
809
|
+
<label class="toggle">
|
|
810
|
+
<input type="checkbox" id="autoplay-toggle">
|
|
811
|
+
<span class="toggle-track"></span>
|
|
812
|
+
</label>
|
|
813
|
+
</div>
|
|
814
|
+
<div class="card-actions">
|
|
815
|
+
<button class="btn btn-sm" id="voice-save-btn">Save</button>
|
|
816
|
+
<span class="save-status" id="voice-save-status">Saved</span>
|
|
817
|
+
</div>
|
|
818
|
+
</div>
|
|
819
|
+
|
|
820
|
+
<!-- Integrations -->
|
|
821
|
+
<div class="settings-card" id="integrations-section">
|
|
822
|
+
<div class="card-title">Integrations
|
|
823
|
+
<a href="/registry">Manage Services →</a>
|
|
824
|
+
</div>
|
|
825
|
+
<div class="integration-row" id="google-integration">
|
|
826
|
+
<div class="integration-info">
|
|
827
|
+
<div class="integration-name">Google Workspace</div>
|
|
828
|
+
<div class="integration-status" id="google-status-label">Checking...</div>
|
|
829
|
+
</div>
|
|
830
|
+
<button class="btn btn-sm btn-ghost" id="google-connect-btn">Connect</button>
|
|
831
|
+
<span class="status-dot disconnected" id="google-status-dot"></span>
|
|
832
|
+
</div>
|
|
833
|
+
<div class="integration-row" id="board-integration">
|
|
834
|
+
<div class="integration-info">
|
|
835
|
+
<div class="integration-name" id="board-provider-label">Task Board</div>
|
|
836
|
+
<div class="integration-status" id="board-status-label">Checking...</div>
|
|
837
|
+
</div>
|
|
838
|
+
<span class="status-dot disconnected" id="board-status-dot"></span>
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
|
|
842
|
+
<!-- Key Vault -->
|
|
843
|
+
<div class="settings-card full-width">
|
|
844
|
+
<div class="card-title">Key Vault</div>
|
|
845
|
+
<div class="vault-empty" id="vault-empty">No keys stored yet.</div>
|
|
846
|
+
<div id="vault-list"></div>
|
|
847
|
+
|
|
848
|
+
<div class="add-key-form" id="add-key-form">
|
|
849
|
+
<div class="vault-error" id="vault-error"></div>
|
|
850
|
+
<input type="text" id="vault-key-name" placeholder="KEY_NAME (e.g. OPENROUTER_API_KEY)">
|
|
851
|
+
<input type="text" id="vault-key-label" placeholder="Label (optional)">
|
|
852
|
+
<input type="password" id="vault-key-value" placeholder="Value" class="full-span">
|
|
853
|
+
<select id="vault-key-type">
|
|
854
|
+
<option value="secret">Secret (masked)</option>
|
|
855
|
+
<option value="text">Text (visible)</option>
|
|
856
|
+
</select>
|
|
857
|
+
<button class="btn" id="vault-add-btn">Add Key</button>
|
|
858
|
+
</div>
|
|
859
|
+
</div>
|
|
860
|
+
|
|
861
|
+
</div>
|
|
862
|
+
</main>
|
|
863
|
+
|
|
864
|
+
<script src="/public/share-modal.js"></script>
|
|
865
|
+
<script>
|
|
866
|
+
(function() {
|
|
867
|
+
var sessionId = sessionStorage.getItem("dash_sid");
|
|
868
|
+
|
|
869
|
+
function authHeaders() {
|
|
870
|
+
return sessionId ? { "x-session-id": sessionId } : {};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async function api(path, opts) {
|
|
874
|
+
opts = opts || {};
|
|
875
|
+
var headers = {};
|
|
876
|
+
if (opts.body) headers["Content-Type"] = "application/json";
|
|
877
|
+
if (sessionId) headers["x-session-id"] = sessionId;
|
|
878
|
+
var res = await fetch(path, {
|
|
879
|
+
method: opts.method || "GET",
|
|
880
|
+
headers: headers,
|
|
881
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
882
|
+
});
|
|
883
|
+
if (res.status === 401) {
|
|
884
|
+
sessionStorage.removeItem("dash_sid");
|
|
885
|
+
location.href = "/";
|
|
886
|
+
throw new Error("Session expired");
|
|
887
|
+
}
|
|
888
|
+
var data = await res.json();
|
|
889
|
+
if (!res.ok) throw new Error(data.error || "Request failed");
|
|
890
|
+
return data;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function flashStatus(el, msg, isError) {
|
|
894
|
+
el.textContent = msg || "Saved";
|
|
895
|
+
el.className = "save-status visible" + (isError ? " error" : "");
|
|
896
|
+
setTimeout(function() { el.classList.remove("visible"); }, 2000);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// --- Check session ---
|
|
900
|
+
if (!sessionId) { location.href = "/"; return; }
|
|
901
|
+
|
|
902
|
+
// Verify session is valid
|
|
903
|
+
api("/api/settings").then(function(data) {
|
|
904
|
+
loadIdentity(data);
|
|
905
|
+
loadSecurity(data);
|
|
906
|
+
loadMesh(data);
|
|
907
|
+
loadLlm(data);
|
|
908
|
+
loadVoice(data);
|
|
909
|
+
loadIntegrations();
|
|
910
|
+
loadVaultKeys();
|
|
911
|
+
}).catch(function() {
|
|
912
|
+
// auth redirect handled by api()
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// --- Identity ---
|
|
916
|
+
function loadIdentity(data) {
|
|
917
|
+
document.getElementById("settings-your-name").value = data.humanName || "";
|
|
918
|
+
document.getElementById("settings-agent-name").value = data.instanceName || "Core";
|
|
919
|
+
loadPersonality();
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
document.getElementById("save-your-name-btn").addEventListener("click", async function() {
|
|
923
|
+
var btn = this;
|
|
924
|
+
var val = document.getElementById("settings-your-name").value.trim();
|
|
925
|
+
if (!val) return;
|
|
926
|
+
btn.disabled = true;
|
|
927
|
+
try {
|
|
928
|
+
await api("/api/settings", { method: "PUT", body: { humanName: val } });
|
|
929
|
+
btn.textContent = "Saved";
|
|
930
|
+
setTimeout(function() { btn.textContent = "Save"; }, 1500);
|
|
931
|
+
} catch (err) {
|
|
932
|
+
btn.textContent = "Error";
|
|
933
|
+
setTimeout(function() { btn.textContent = "Save"; }, 1500);
|
|
934
|
+
} finally {
|
|
935
|
+
btn.disabled = false;
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
document.getElementById("save-agent-name-btn").addEventListener("click", async function() {
|
|
940
|
+
var btn = this;
|
|
941
|
+
var val = document.getElementById("settings-agent-name").value.trim();
|
|
942
|
+
if (!val) return;
|
|
943
|
+
btn.disabled = true;
|
|
944
|
+
try {
|
|
945
|
+
await api("/api/settings", { method: "PUT", body: { agentName: val } });
|
|
946
|
+
btn.textContent = "Saved";
|
|
947
|
+
setTimeout(function() { btn.textContent = "Save"; }, 1500);
|
|
948
|
+
} catch (err) {
|
|
949
|
+
btn.textContent = "Error";
|
|
950
|
+
setTimeout(function() { btn.textContent = "Save"; }, 1500);
|
|
951
|
+
} finally {
|
|
952
|
+
btn.disabled = false;
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
// --- Personality ---
|
|
957
|
+
var personalityTraits = document.getElementById("personality-traits");
|
|
958
|
+
var customRulesTextarea = document.getElementById("custom-rules-textarea");
|
|
959
|
+
|
|
960
|
+
personalityTraits.addEventListener("click", function(e) {
|
|
961
|
+
var dot = e.target.closest(".trait-dot");
|
|
962
|
+
if (!dot) return;
|
|
963
|
+
var slider = dot.closest(".trait-slider");
|
|
964
|
+
slider.querySelectorAll(".trait-dot").forEach(function(d) { d.classList.remove("active"); });
|
|
965
|
+
dot.classList.add("active");
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
async function loadPersonality() {
|
|
969
|
+
try {
|
|
970
|
+
var data = await api("/api/personality?sessionId=" + encodeURIComponent(sessionId));
|
|
971
|
+
if (data.traits) {
|
|
972
|
+
for (var trait in data.traits) {
|
|
973
|
+
var row = personalityTraits.querySelector('[data-trait="' + trait + '"]');
|
|
974
|
+
if (!row) continue;
|
|
975
|
+
row.querySelectorAll(".trait-dot").forEach(function(d) {
|
|
976
|
+
d.classList.toggle("active", d.dataset.value === String(data.traits[trait]));
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
customRulesTextarea.value = (data.customRules || []).join("\n");
|
|
981
|
+
} catch (e) {
|
|
982
|
+
customRulesTextarea.value = "";
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
document.getElementById("personality-save-btn").addEventListener("click", async function() {
|
|
987
|
+
var btn = this;
|
|
988
|
+
var status = document.getElementById("personality-save-status");
|
|
989
|
+
btn.disabled = true;
|
|
990
|
+
var traits = {};
|
|
991
|
+
personalityTraits.querySelectorAll(".trait-row").forEach(function(row) {
|
|
992
|
+
var trait = row.dataset.trait;
|
|
993
|
+
var active = row.querySelector(".trait-dot.active");
|
|
994
|
+
traits[trait] = active ? parseInt(active.dataset.value) : 2;
|
|
995
|
+
});
|
|
996
|
+
var customRules = customRulesTextarea.value.split("\n").map(function(l) { return l.trim(); }).filter(Boolean);
|
|
997
|
+
try {
|
|
998
|
+
await api("/api/personality", { method: "PUT", body: { sessionId: sessionId, traits: traits, customRules: customRules } });
|
|
999
|
+
flashStatus(status);
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
flashStatus(status, err.message, true);
|
|
1002
|
+
} finally {
|
|
1003
|
+
btn.disabled = false;
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// --- Security ---
|
|
1008
|
+
var safewordModeSelect = document.getElementById("safeword-mode-select");
|
|
1009
|
+
|
|
1010
|
+
function loadSecurity(data) {
|
|
1011
|
+
safewordModeSelect.value = data.safeWordMode || "always";
|
|
1012
|
+
updateSafewordLabel();
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function updateSafewordLabel() {
|
|
1016
|
+
document.getElementById("safeword-mode-label").textContent =
|
|
1017
|
+
safewordModeSelect.value === "always"
|
|
1018
|
+
? "Require password on every page load"
|
|
1019
|
+
: "Skip password until server or browser restarts";
|
|
1020
|
+
}
|
|
1021
|
+
safewordModeSelect.addEventListener("change", updateSafewordLabel);
|
|
1022
|
+
|
|
1023
|
+
document.getElementById("security-save-btn").addEventListener("click", async function() {
|
|
1024
|
+
var btn = this;
|
|
1025
|
+
var status = document.getElementById("security-save-status");
|
|
1026
|
+
btn.disabled = true;
|
|
1027
|
+
try {
|
|
1028
|
+
await api("/api/settings", { method: "PUT", body: { safeWordMode: safewordModeSelect.value } });
|
|
1029
|
+
if (safewordModeSelect.value === "always") sessionStorage.removeItem("dash_sid");
|
|
1030
|
+
flashStatus(status);
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
flashStatus(status, err.message, true);
|
|
1033
|
+
} finally {
|
|
1034
|
+
btn.disabled = false;
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
// --- Mesh ---
|
|
1039
|
+
var meshLanToggle = document.getElementById("mesh-lan-toggle");
|
|
1040
|
+
var meshIncomingToggle = document.getElementById("mesh-incoming-toggle");
|
|
1041
|
+
|
|
1042
|
+
function loadMesh(data) {
|
|
1043
|
+
meshLanToggle.checked = data.mesh && data.mesh.lanAnnounce || false;
|
|
1044
|
+
meshIncomingToggle.checked = data.mesh && data.mesh.allowIncoming || false;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
document.getElementById("mesh-save-btn").addEventListener("click", async function() {
|
|
1048
|
+
var btn = this;
|
|
1049
|
+
var status = document.getElementById("mesh-save-status");
|
|
1050
|
+
btn.disabled = true;
|
|
1051
|
+
try {
|
|
1052
|
+
await api("/api/settings", { method: "PUT", body: { mesh: { lanAnnounce: meshLanToggle.checked, allowIncoming: meshIncomingToggle.checked } } });
|
|
1053
|
+
flashStatus(status);
|
|
1054
|
+
} catch (err) {
|
|
1055
|
+
flashStatus(status, "Error", true);
|
|
1056
|
+
} finally {
|
|
1057
|
+
btn.disabled = false;
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
// --- LLM / Models ---
|
|
1062
|
+
var airplaneToggle = document.getElementById("airplane-toggle");
|
|
1063
|
+
var modelChatSelect = document.getElementById("model-chat");
|
|
1064
|
+
var modelUtilitySelect = document.getElementById("model-utility");
|
|
1065
|
+
var modelAgentSelect = document.getElementById("model-agent");
|
|
1066
|
+
|
|
1067
|
+
function loadLlm(data) {
|
|
1068
|
+
airplaneToggle.checked = data.airplaneMode;
|
|
1069
|
+
var chatModel = (data.models && data.models.chat) || "auto";
|
|
1070
|
+
var utilityModel = (data.models && data.models.utility) || "auto";
|
|
1071
|
+
var agentModel = (data.models && data.models.agent) || "auto";
|
|
1072
|
+
populateModelDropdowns(chatModel, utilityModel, agentModel);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
async function populateModelDropdowns(selectedChat, selectedUtility, selectedAgent) {
|
|
1076
|
+
try {
|
|
1077
|
+
var data = await api("/api/models");
|
|
1078
|
+
var models = data.models || [];
|
|
1079
|
+
var ollamaModels = models.filter(function(m) { return m.source === "ollama"; })
|
|
1080
|
+
.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
1081
|
+
var openrouterModels = models.filter(function(m) { return m.source === "openrouter"; })
|
|
1082
|
+
.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
1083
|
+
|
|
1084
|
+
var dropdowns = [
|
|
1085
|
+
{ el: modelChatSelect, current: selectedChat, autoLabel: "Auto (best available)" },
|
|
1086
|
+
{ el: modelUtilitySelect, current: selectedUtility, autoLabel: "Auto (best available)" },
|
|
1087
|
+
{ el: modelAgentSelect, current: selectedAgent, autoLabel: "Auto (best coding model)" },
|
|
1088
|
+
];
|
|
1089
|
+
for (var i = 0; i < dropdowns.length; i++) {
|
|
1090
|
+
var dd = dropdowns[i];
|
|
1091
|
+
dd.el.innerHTML = '<option value="auto">' + dd.autoLabel + '</option>';
|
|
1092
|
+
|
|
1093
|
+
if (ollamaModels.length > 0) {
|
|
1094
|
+
var ollamaGroup = document.createElement("optgroup");
|
|
1095
|
+
ollamaGroup.label = "Local (Ollama)";
|
|
1096
|
+
for (var j = 0; j < ollamaModels.length; j++) {
|
|
1097
|
+
var m = ollamaModels[j];
|
|
1098
|
+
var opt = document.createElement("option");
|
|
1099
|
+
opt.value = m.name;
|
|
1100
|
+
var sizeMB = m.size ? Math.round(m.size / 1024 / 1024) : 0;
|
|
1101
|
+
opt.textContent = m.name + (sizeMB ? " (" + (sizeMB >= 1024 ? (sizeMB / 1024).toFixed(1) + "GB" : sizeMB + "MB") + ")" : "");
|
|
1102
|
+
if (m.name === dd.current) opt.selected = true;
|
|
1103
|
+
ollamaGroup.appendChild(opt);
|
|
1104
|
+
}
|
|
1105
|
+
dd.el.appendChild(ollamaGroup);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (openrouterModels.length > 0) {
|
|
1109
|
+
var orGroup = document.createElement("optgroup");
|
|
1110
|
+
orGroup.label = "Cloud (OpenRouter)";
|
|
1111
|
+
for (var k = 0; k < openrouterModels.length; k++) {
|
|
1112
|
+
var m2 = openrouterModels[k];
|
|
1113
|
+
var opt2 = document.createElement("option");
|
|
1114
|
+
opt2.value = m2.name;
|
|
1115
|
+
opt2.textContent = m2.name;
|
|
1116
|
+
if (m2.name === dd.current) opt2.selected = true;
|
|
1117
|
+
orGroup.appendChild(opt2);
|
|
1118
|
+
}
|
|
1119
|
+
dd.el.appendChild(orGroup);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (dd.current && dd.current !== "auto" && !models.some(function(m3) { return m3.name === dd.current; })) {
|
|
1123
|
+
var opt3 = document.createElement("option");
|
|
1124
|
+
opt3.value = dd.current;
|
|
1125
|
+
opt3.textContent = dd.current + " (not found)";
|
|
1126
|
+
opt3.selected = true;
|
|
1127
|
+
dd.el.appendChild(opt3);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
// Leave default Auto options
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
document.getElementById("llm-save-btn").addEventListener("click", async function() {
|
|
1136
|
+
var btn = this;
|
|
1137
|
+
var status = document.getElementById("llm-save-status");
|
|
1138
|
+
btn.disabled = true;
|
|
1139
|
+
try {
|
|
1140
|
+
await api("/api/settings", {
|
|
1141
|
+
method: "PUT",
|
|
1142
|
+
body: {
|
|
1143
|
+
airplaneMode: airplaneToggle.checked,
|
|
1144
|
+
models: {
|
|
1145
|
+
chat: modelChatSelect.value || "auto",
|
|
1146
|
+
utility: modelUtilitySelect.value || "auto",
|
|
1147
|
+
agent: modelAgentSelect.value || "auto",
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
});
|
|
1151
|
+
flashStatus(status);
|
|
1152
|
+
} catch (err) {
|
|
1153
|
+
flashStatus(status, err.message, true);
|
|
1154
|
+
} finally {
|
|
1155
|
+
btn.disabled = false;
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// --- Voice ---
|
|
1160
|
+
function loadVoice(data) {
|
|
1161
|
+
var tts = data.tts || {};
|
|
1162
|
+
var stt = data.stt || {};
|
|
1163
|
+
document.getElementById("tts-toggle").checked = tts.enabled !== false;
|
|
1164
|
+
document.getElementById("stt-toggle").checked = stt.enabled !== false;
|
|
1165
|
+
document.getElementById("autoplay-toggle").checked = tts.autoPlay !== false;
|
|
1166
|
+
document.getElementById("tts-status-label").textContent = tts.enabled !== false ? "Enabled" : "Disabled";
|
|
1167
|
+
document.getElementById("stt-status-label").textContent = stt.enabled !== false ? "Enabled" : "Disabled";
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
document.getElementById("voice-save-btn").addEventListener("click", async function() {
|
|
1171
|
+
var btn = this;
|
|
1172
|
+
var status = document.getElementById("voice-save-status");
|
|
1173
|
+
btn.disabled = true;
|
|
1174
|
+
try {
|
|
1175
|
+
await api("/api/settings", {
|
|
1176
|
+
method: "PUT",
|
|
1177
|
+
body: {
|
|
1178
|
+
tts: {
|
|
1179
|
+
enabled: document.getElementById("tts-toggle").checked,
|
|
1180
|
+
autoPlay: document.getElementById("autoplay-toggle").checked,
|
|
1181
|
+
},
|
|
1182
|
+
stt: {
|
|
1183
|
+
enabled: document.getElementById("stt-toggle").checked,
|
|
1184
|
+
},
|
|
1185
|
+
},
|
|
1186
|
+
});
|
|
1187
|
+
flashStatus(status);
|
|
1188
|
+
} catch (err) {
|
|
1189
|
+
flashStatus(status, err.message, true);
|
|
1190
|
+
} finally {
|
|
1191
|
+
btn.disabled = false;
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// --- Integrations ---
|
|
1196
|
+
async function loadIntegrations() {
|
|
1197
|
+
// Google Workspace status
|
|
1198
|
+
try {
|
|
1199
|
+
var gData = await api("/api/google/status");
|
|
1200
|
+
var connected = gData && gData.connected;
|
|
1201
|
+
document.getElementById("google-status-label").textContent = connected ? "Connected" : "Not connected";
|
|
1202
|
+
document.getElementById("google-status-dot").className = "status-dot " + (connected ? "connected" : "disconnected");
|
|
1203
|
+
document.getElementById("google-connect-btn").textContent = connected ? "Reconnect" : "Connect";
|
|
1204
|
+
} catch (e) {
|
|
1205
|
+
document.getElementById("google-status-label").textContent = "Not available";
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Board status
|
|
1209
|
+
try {
|
|
1210
|
+
var bData = await api("/api/board/status");
|
|
1211
|
+
var provider = (bData && bData.provider) || "None";
|
|
1212
|
+
var bConnected = bData && bData.connected;
|
|
1213
|
+
document.getElementById("board-provider-label").textContent = "Task Board (" + provider + ")";
|
|
1214
|
+
document.getElementById("board-status-label").textContent = bConnected ? "Connected" : "Not connected";
|
|
1215
|
+
document.getElementById("board-status-dot").className = "status-dot " + (bConnected ? "connected" : "disconnected");
|
|
1216
|
+
} catch (e) {
|
|
1217
|
+
document.getElementById("board-status-label").textContent = "Not available";
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
document.getElementById("google-connect-btn").addEventListener("click", function() {
|
|
1222
|
+
window.open("/api/google/auth?sessionId=" + encodeURIComponent(sessionId), "_blank");
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
// --- Key Vault ---
|
|
1226
|
+
var vaultList = document.getElementById("vault-list");
|
|
1227
|
+
var vaultEmpty = document.getElementById("vault-empty");
|
|
1228
|
+
var vaultError = document.getElementById("vault-error");
|
|
1229
|
+
|
|
1230
|
+
function showVaultError(msg) {
|
|
1231
|
+
vaultError.textContent = msg;
|
|
1232
|
+
vaultError.classList.add("visible");
|
|
1233
|
+
}
|
|
1234
|
+
function clearVaultError() {
|
|
1235
|
+
vaultError.textContent = "";
|
|
1236
|
+
vaultError.classList.remove("visible");
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
async function loadVaultKeys() {
|
|
1240
|
+
try {
|
|
1241
|
+
var data = await api("/api/vault?sessionId=" + encodeURIComponent(sessionId));
|
|
1242
|
+
renderVaultKeys(data.keys || []);
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
vaultList.innerHTML = "";
|
|
1245
|
+
vaultEmpty.style.display = "block";
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function renderVaultKeys(keys) {
|
|
1250
|
+
vaultList.innerHTML = "";
|
|
1251
|
+
if (keys.length === 0) {
|
|
1252
|
+
vaultEmpty.style.display = "block";
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
vaultEmpty.style.display = "none";
|
|
1256
|
+
|
|
1257
|
+
for (var i = 0; i < keys.length; i++) {
|
|
1258
|
+
(function(k) {
|
|
1259
|
+
var item = document.createElement("div");
|
|
1260
|
+
item.className = "vault-key-item";
|
|
1261
|
+
|
|
1262
|
+
var isText = k.type === "text";
|
|
1263
|
+
|
|
1264
|
+
// Key info (name + label)
|
|
1265
|
+
var info = document.createElement("div");
|
|
1266
|
+
info.className = "vault-key-info";
|
|
1267
|
+
var nameEl = document.createElement("div");
|
|
1268
|
+
nameEl.className = "vault-key-name";
|
|
1269
|
+
nameEl.textContent = k.name;
|
|
1270
|
+
info.appendChild(nameEl);
|
|
1271
|
+
if (k.label) {
|
|
1272
|
+
var labelEl = document.createElement("div");
|
|
1273
|
+
labelEl.className = "vault-key-label";
|
|
1274
|
+
labelEl.textContent = k.label;
|
|
1275
|
+
info.appendChild(labelEl);
|
|
1276
|
+
}
|
|
1277
|
+
item.appendChild(info);
|
|
1278
|
+
|
|
1279
|
+
// Type badge
|
|
1280
|
+
var badge = document.createElement("span");
|
|
1281
|
+
badge.className = "vault-key-type " + (isText ? "text" : "secret");
|
|
1282
|
+
badge.textContent = isText ? "text" : "secret";
|
|
1283
|
+
item.appendChild(badge);
|
|
1284
|
+
|
|
1285
|
+
// Value display
|
|
1286
|
+
var valWrap = document.createElement("div");
|
|
1287
|
+
valWrap.className = "vault-key-value";
|
|
1288
|
+
|
|
1289
|
+
if (isText) {
|
|
1290
|
+
var valInput = document.createElement("input");
|
|
1291
|
+
valInput.type = "text";
|
|
1292
|
+
valInput.value = k.value || "";
|
|
1293
|
+
valInput.dataset.keyName = k.name;
|
|
1294
|
+
valInput.title = "Edit value inline";
|
|
1295
|
+
valWrap.appendChild(valInput);
|
|
1296
|
+
|
|
1297
|
+
// Save on blur or Enter
|
|
1298
|
+
valInput.addEventListener("blur", function() {
|
|
1299
|
+
saveTextKeyValue(k.name, valInput.value, k.label);
|
|
1300
|
+
});
|
|
1301
|
+
valInput.addEventListener("keydown", function(e) {
|
|
1302
|
+
if (e.key === "Enter") {
|
|
1303
|
+
valInput.blur();
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
} else {
|
|
1307
|
+
var masked = document.createElement("div");
|
|
1308
|
+
masked.className = "vault-key-masked";
|
|
1309
|
+
masked.textContent = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
1310
|
+
valWrap.appendChild(masked);
|
|
1311
|
+
}
|
|
1312
|
+
item.appendChild(valWrap);
|
|
1313
|
+
|
|
1314
|
+
// Actions
|
|
1315
|
+
var actions = document.createElement("div");
|
|
1316
|
+
actions.className = "vault-key-actions";
|
|
1317
|
+
|
|
1318
|
+
var delBtn = document.createElement("button");
|
|
1319
|
+
delBtn.className = "delete-btn";
|
|
1320
|
+
delBtn.title = "Delete";
|
|
1321
|
+
delBtn.innerHTML = "×";
|
|
1322
|
+
delBtn.addEventListener("click", function() { deleteVaultKey(k.name); });
|
|
1323
|
+
actions.appendChild(delBtn);
|
|
1324
|
+
|
|
1325
|
+
item.appendChild(actions);
|
|
1326
|
+
vaultList.appendChild(item);
|
|
1327
|
+
})(keys[i]);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
async function saveTextKeyValue(name, value, label) {
|
|
1332
|
+
try {
|
|
1333
|
+
await api("/api/vault/" + encodeURIComponent(name) + "?sessionId=" + encodeURIComponent(sessionId), {
|
|
1334
|
+
method: "PUT",
|
|
1335
|
+
body: { value: value, label: label || undefined, type: "text" },
|
|
1336
|
+
});
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
showVaultError("Failed to save " + name + ": " + err.message);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
async function deleteVaultKey(name) {
|
|
1343
|
+
if (!confirm("Delete " + name + " from vault?")) return;
|
|
1344
|
+
try {
|
|
1345
|
+
await api("/api/vault/" + encodeURIComponent(name) + "?sessionId=" + encodeURIComponent(sessionId), {
|
|
1346
|
+
method: "DELETE",
|
|
1347
|
+
});
|
|
1348
|
+
await loadVaultKeys();
|
|
1349
|
+
} catch (err) {
|
|
1350
|
+
showVaultError(err.message);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
document.getElementById("vault-add-btn").addEventListener("click", async function() {
|
|
1355
|
+
clearVaultError();
|
|
1356
|
+
var name = document.getElementById("vault-key-name").value.trim();
|
|
1357
|
+
var value = document.getElementById("vault-key-value").value;
|
|
1358
|
+
var label = document.getElementById("vault-key-label").value.trim();
|
|
1359
|
+
var type = document.getElementById("vault-key-type").value;
|
|
1360
|
+
|
|
1361
|
+
if (!name) { showVaultError("Key name is required"); return; }
|
|
1362
|
+
if (!value) { showVaultError("Key value is required"); return; }
|
|
1363
|
+
|
|
1364
|
+
var btn = this;
|
|
1365
|
+
btn.disabled = true;
|
|
1366
|
+
try {
|
|
1367
|
+
await api("/api/vault/" + encodeURIComponent(name) + "?sessionId=" + encodeURIComponent(sessionId), {
|
|
1368
|
+
method: "PUT",
|
|
1369
|
+
body: { value: value, label: label || undefined, type: type },
|
|
1370
|
+
});
|
|
1371
|
+
document.getElementById("vault-key-name").value = "";
|
|
1372
|
+
document.getElementById("vault-key-value").value = "";
|
|
1373
|
+
document.getElementById("vault-key-label").value = "";
|
|
1374
|
+
document.getElementById("vault-key-type").value = "secret";
|
|
1375
|
+
await loadVaultKeys();
|
|
1376
|
+
} catch (err) {
|
|
1377
|
+
showVaultError(err.message);
|
|
1378
|
+
} finally {
|
|
1379
|
+
btn.disabled = false;
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
// Enter to add key
|
|
1384
|
+
document.getElementById("vault-key-value").addEventListener("keydown", function(e) {
|
|
1385
|
+
if (e.key === "Enter") document.getElementById("vault-add-btn").click();
|
|
1386
|
+
});
|
|
1387
|
+
document.getElementById("vault-key-label").addEventListener("keydown", function(e) {
|
|
1388
|
+
if (e.key === "Enter") document.getElementById("vault-add-btn").click();
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
// --- Capability-based visibility ---
|
|
1392
|
+
api("/api/tier").then(function(data) {
|
|
1393
|
+
var caps = (data && data.capabilities) || {};
|
|
1394
|
+
if (!caps.mesh) {
|
|
1395
|
+
var meshSection = document.getElementById("mesh-section");
|
|
1396
|
+
if (meshSection) meshSection.style.display = "none";
|
|
1397
|
+
}
|
|
1398
|
+
if (!caps.voice) {
|
|
1399
|
+
var voiceSection = document.getElementById("voice-section");
|
|
1400
|
+
if (voiceSection) voiceSection.style.display = "none";
|
|
1401
|
+
}
|
|
1402
|
+
if (!caps.integrations) {
|
|
1403
|
+
var intSection = document.getElementById("integrations-section");
|
|
1404
|
+
if (intSection) intSection.style.display = "none";
|
|
1405
|
+
}
|
|
1406
|
+
}).catch(function() {});
|
|
1407
|
+
|
|
1408
|
+
})();
|
|
1409
|
+
</script>
|
|
1410
|
+
</body>
|
|
1411
|
+
</html>
|