@vesk/runtime 0.2.10 → 0.2.11
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/dist/hmr-client.d.ts +323 -0
- package/dist/hmr-client.d.ts.map +1 -1
- package/dist/hmr-client.js +3869 -197
- package/dist/md.d.ts +4 -2
- package/dist/md.d.ts.map +1 -1
- package/dist/md.js +53 -23
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +7 -3
- package/package.json +2 -2
package/dist/hmr-client.js
CHANGED
|
@@ -1,140 +1,1801 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Vesk dev HMR client — modular dev panel + error overlay.
|
|
3
|
+
*
|
|
4
|
+
* Served standalone at `/_vesk/hmr.js` (module) after `stripCodeTypes` — fully
|
|
5
|
+
* self-contained, NO runtime imports. Top-level `export`s are preserved by the
|
|
6
|
+
* reprint; the bottom-of-file `createDevClient()` call boots the client when a
|
|
7
|
+
* real DOM is present. Pure render/html helpers live at module top so they can
|
|
8
|
+
* be unit-tested DOM-free.
|
|
9
|
+
*
|
|
10
|
+
* Terminal aesthetic: black-and-white, sharp 1px borders, `border-radius: 0`,
|
|
11
|
+
* no blur / backdrop / glass shadows, uppercase labels, `>` cursors, monospace.
|
|
12
|
+
*/
|
|
13
|
+
export function escapeHtml(s) {
|
|
14
|
+
if (!s)
|
|
15
|
+
return '';
|
|
16
|
+
return String(s)
|
|
17
|
+
.replace(/&/g, '&')
|
|
18
|
+
.replace(/</g, '<')
|
|
19
|
+
.replace(/>/g, '>')
|
|
20
|
+
.replace(/"/g, '"');
|
|
21
|
+
}
|
|
22
|
+
export function padNum(n, len) {
|
|
23
|
+
let str = String(n);
|
|
24
|
+
while (str.length < len)
|
|
25
|
+
str = ' ' + str;
|
|
26
|
+
return str;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Render a codeframe's `code` lines directly (gutter numbers + text). The
|
|
30
|
+
* server supplies the ±context window (5 above / 5 below). The exact error
|
|
31
|
+
* line is highlighted with inverse-video styling (via `kf-err`) plus a `>`
|
|
32
|
+
* gutter marker, and a caret is drawn at `frame.column` when present.
|
|
33
|
+
*/
|
|
34
|
+
export function renderCodeframe(frame) {
|
|
35
|
+
if (!frame || !Array.isArray(frame.code) || frame.code.length === 0)
|
|
36
|
+
return '';
|
|
37
|
+
let maxNo = 0;
|
|
38
|
+
for (const l of frame.code) {
|
|
39
|
+
if (l.no > maxNo)
|
|
40
|
+
maxNo = l.no;
|
|
41
|
+
}
|
|
42
|
+
const gutterW = String(maxNo).length;
|
|
43
|
+
let out = '';
|
|
44
|
+
for (const l of frame.code) {
|
|
45
|
+
const no = padNum(l.no, gutterW);
|
|
46
|
+
const marker = l.isError ? '>' : ' ';
|
|
47
|
+
let caret = '';
|
|
48
|
+
if (l.isError && frame.column != null && frame.column > 0) {
|
|
49
|
+
caret = '<span class="kf-caret">' + '^'.padStart(frame.column, ' ') + '</span>';
|
|
50
|
+
}
|
|
51
|
+
out +=
|
|
52
|
+
'<div' +
|
|
53
|
+
(l.isError ? ' class="kf-err"' : '') +
|
|
54
|
+
'>' +
|
|
55
|
+
marker +
|
|
56
|
+
' <span class="kf-ln">' +
|
|
57
|
+
no +
|
|
58
|
+
'</span> ' +
|
|
59
|
+
escapeHtml(l.text) +
|
|
60
|
+
caret +
|
|
61
|
+
'</div>';
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
function section(title, items) {
|
|
66
|
+
let html = '<div class="kf-sec"><div class="kf-sec-t">> ' + escapeHtml(title) + '</div>';
|
|
67
|
+
for (const item of items.slice(0, 6)) {
|
|
68
|
+
html += '<div class="kf-item">' + escapeHtml(item) + '</div>';
|
|
69
|
+
}
|
|
70
|
+
return html + '</div>';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build every markup piece of the error overlay/panel from a payload. Pure —
|
|
74
|
+
* safe to unit test without a DOM.
|
|
75
|
+
*/
|
|
76
|
+
export function buildErrorNodes(payload) {
|
|
77
|
+
const fileRaw = payload.file || payload.filePath || '';
|
|
78
|
+
let file = fileRaw ? escapeHtml(fileRaw) : 'unknown file';
|
|
79
|
+
if (payload.line != null) {
|
|
80
|
+
file += ':' + payload.line;
|
|
81
|
+
if (payload.column != null)
|
|
82
|
+
file += ':' + payload.column;
|
|
83
|
+
}
|
|
84
|
+
const message = payload.message || 'Unknown error';
|
|
85
|
+
let lists = '';
|
|
86
|
+
if (payload.tips && payload.tips.length)
|
|
87
|
+
lists += section('TIPS', payload.tips);
|
|
88
|
+
if (payload.suggestions && payload.suggestions.length)
|
|
89
|
+
lists += section('SUGGESTIONS', payload.suggestions);
|
|
90
|
+
if (payload.nextSteps && payload.nextSteps.length)
|
|
91
|
+
lists += section('NEXT STEPS', payload.nextSteps);
|
|
92
|
+
return {
|
|
93
|
+
file,
|
|
94
|
+
message: escapeHtml(message),
|
|
95
|
+
codeframe: renderCodeframe(payload.codeframe),
|
|
96
|
+
lists,
|
|
97
|
+
stack: payload.stack || '(no stack trace)',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function renderPluginRow(p, index) {
|
|
101
|
+
const name = p.name || ('plugin#' + index);
|
|
102
|
+
const installed = p.installed ? 'true' : 'false';
|
|
103
|
+
const active = p.active ? 'true' : 'false';
|
|
104
|
+
let err = '';
|
|
105
|
+
if (p.error)
|
|
106
|
+
err = ' [error] ' + escapeHtml(String(p.error));
|
|
107
|
+
return ('<div class="kp-row">> ' +
|
|
108
|
+
escapeHtml(name) +
|
|
109
|
+
' [installed ' +
|
|
110
|
+
installed +
|
|
111
|
+
'] [active ' +
|
|
112
|
+
active +
|
|
113
|
+
']' +
|
|
114
|
+
err +
|
|
115
|
+
'</div>');
|
|
116
|
+
}
|
|
117
|
+
export function renderLogRow(entry) {
|
|
118
|
+
const type = entry.type || '?';
|
|
119
|
+
const time = entry.ms != null ? String(entry.ms) : entry.time != null ? String(entry.time) : '?';
|
|
120
|
+
const stamp = entry.ts ? new Date(entry.ts).toLocaleTimeString() : '--:--:--';
|
|
121
|
+
return '<div class="kl-row">' + escapeHtml(stamp) + ' ' + escapeHtml(type) + ' ' + time + 'ms' + '</div>';
|
|
122
|
+
}
|
|
123
|
+
export const DEV_TABS = ['overview', 'agentic', 'errors', 'diagnostics', 'plugins', 'log', 'settings'];
|
|
124
|
+
export const DEV_STATE_KEY = 'veskDevPrefs';
|
|
125
|
+
export const PANEL_MIN_W = 320;
|
|
126
|
+
export const PANEL_MIN_H = 200;
|
|
127
|
+
export const PANEL_MARGIN = 8;
|
|
128
|
+
export const PANEL_EDGE = 16;
|
|
129
|
+
export const AGENTIC_PROVIDERS = ['openai', 'anthropic', 'google', 'ollama', 'opencode', 'opencode-go', 'openrouter', 'loopers', 'custom'];
|
|
130
|
+
export const AGENTIC_MODES = ['explore', 'debug', 'agent'];
|
|
131
|
+
export const AGENTIC_SLASH_COMMANDS = ['/help', '/clear', '/checkpoint', '/rollback', '/provider', '/model', '/models', '/tools', '/commands', '/mode', '/config', '/history'];
|
|
132
|
+
export const AGENTIC_MODELS_URL = '/__vesk/agent/models';
|
|
133
|
+
export function isAgenticMode(v) {
|
|
134
|
+
return AGENTIC_MODES.indexOf(v) !== -1;
|
|
135
|
+
}
|
|
136
|
+
export const SETTINGS_SUB_TABS = ['devtools', 'agentic', 'vesk'];
|
|
137
|
+
export function isSettingsSubTab(v) {
|
|
138
|
+
return SETTINGS_SUB_TABS.indexOf(v) !== -1;
|
|
139
|
+
}
|
|
140
|
+
export function defaultDevtoolState() {
|
|
141
|
+
return {
|
|
142
|
+
theme: 'system',
|
|
143
|
+
pos: 'right',
|
|
144
|
+
activeTab: 'overview',
|
|
145
|
+
open: false,
|
|
146
|
+
w: 520,
|
|
147
|
+
h: 420,
|
|
148
|
+
maxed: false,
|
|
149
|
+
pluginsView: 'cards',
|
|
150
|
+
sidebarMode: 'expanded',
|
|
151
|
+
agenticProvider: 'openai',
|
|
152
|
+
agenticModel: '',
|
|
153
|
+
agenticMode: 'explore',
|
|
154
|
+
agenticModels: [],
|
|
155
|
+
settingsSubTab: 'devtools',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export function loadDevtoolState(store) {
|
|
159
|
+
const state = defaultDevtoolState();
|
|
160
|
+
if (!store)
|
|
161
|
+
return state;
|
|
162
|
+
try {
|
|
163
|
+
const raw = store.getItem(DEV_STATE_KEY);
|
|
164
|
+
if (raw) {
|
|
165
|
+
const parsed = JSON.parse(raw);
|
|
166
|
+
if (parsed.theme === 'light' || parsed.theme === 'dark')
|
|
167
|
+
state.theme = parsed.theme;
|
|
168
|
+
if (parsed.pos === 'left' || parsed.pos === 'right')
|
|
169
|
+
state.pos = parsed.pos;
|
|
170
|
+
if (typeof parsed.activeTab === 'string' && DEV_TABS.indexOf(parsed.activeTab) !== -1) {
|
|
171
|
+
state.activeTab = parsed.activeTab;
|
|
172
|
+
}
|
|
173
|
+
if (typeof parsed.open === 'boolean')
|
|
174
|
+
state.open = parsed.open;
|
|
175
|
+
if (typeof parsed.w === 'number')
|
|
176
|
+
state.w = Math.max(PANEL_MIN_W, parsed.w);
|
|
177
|
+
if (typeof parsed.h === 'number')
|
|
178
|
+
state.h = Math.max(PANEL_MIN_H, parsed.h);
|
|
179
|
+
if (typeof parsed.maxed === 'boolean')
|
|
180
|
+
state.maxed = parsed.maxed;
|
|
181
|
+
if (parsed.pluginsView === 'cards' || parsed.pluginsView === 'list')
|
|
182
|
+
state.pluginsView = parsed.pluginsView;
|
|
183
|
+
if (parsed.sidebarMode === 'expanded' || parsed.sidebarMode === 'rail')
|
|
184
|
+
state.sidebarMode = parsed.sidebarMode;
|
|
185
|
+
if (typeof parsed.agenticProvider === 'string' && AGENTIC_PROVIDERS.indexOf(parsed.agenticProvider) !== -1) {
|
|
186
|
+
state.agenticProvider = parsed.agenticProvider;
|
|
187
|
+
}
|
|
188
|
+
if (typeof parsed.agenticModel === 'string' && parsed.agenticModel.length > 0) {
|
|
189
|
+
state.agenticModel = parsed.agenticModel;
|
|
190
|
+
}
|
|
191
|
+
if (typeof parsed.agenticMode === 'string' && isAgenticMode(parsed.agenticMode)) {
|
|
192
|
+
state.agenticMode = parsed.agenticMode;
|
|
193
|
+
}
|
|
194
|
+
if (Array.isArray(parsed.agenticModels)) {
|
|
195
|
+
state.agenticModels = parsed.agenticModels.filter((v) => typeof v === 'string' && v.length > 0);
|
|
196
|
+
}
|
|
197
|
+
if (typeof parsed.settingsSubTab === 'string' && isSettingsSubTab(parsed.settingsSubTab)) {
|
|
198
|
+
state.settingsSubTab = parsed.settingsSubTab;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
/* devtool state is optional */
|
|
204
|
+
}
|
|
205
|
+
return state;
|
|
206
|
+
}
|
|
207
|
+
export function saveDevtoolState(store, state) {
|
|
208
|
+
if (!store)
|
|
209
|
+
return;
|
|
210
|
+
try {
|
|
211
|
+
store.setItem(DEV_STATE_KEY, JSON.stringify(state));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
/* devtool state is optional */
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
export function defaultAgenticConfigState() {
|
|
218
|
+
return {
|
|
219
|
+
provider: 'openai',
|
|
220
|
+
model: '',
|
|
221
|
+
models: [],
|
|
222
|
+
modelsLoading: false,
|
|
223
|
+
modelsError: null,
|
|
224
|
+
baseUrl: '',
|
|
225
|
+
mode: 'explore',
|
|
226
|
+
maxSteps: 25,
|
|
227
|
+
apiKeys: {},
|
|
228
|
+
hasKeys: {},
|
|
229
|
+
keyPreviews: {},
|
|
230
|
+
loading: false,
|
|
231
|
+
error: null,
|
|
232
|
+
saving: false,
|
|
233
|
+
saveError: null,
|
|
234
|
+
saveOk: null,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function maskApiKey(key) {
|
|
238
|
+
if (!key)
|
|
239
|
+
return '';
|
|
240
|
+
const s = String(key).trim();
|
|
241
|
+
if (s.length <= 8)
|
|
242
|
+
return '***';
|
|
243
|
+
return s.slice(0, 4) + '***' + s.slice(-4);
|
|
244
|
+
}
|
|
245
|
+
export const TAB_GLYPHS = {
|
|
246
|
+
overview: 'Ov',
|
|
247
|
+
agentic: 'Ag',
|
|
248
|
+
errors: 'Er',
|
|
249
|
+
plugins: 'Pl',
|
|
250
|
+
log: 'Lg',
|
|
251
|
+
diagnostics: 'Dg',
|
|
252
|
+
settings: 'St',
|
|
253
|
+
};
|
|
254
|
+
export function renderTabBar(active, mode = 'expanded') {
|
|
255
|
+
if (mode === 'rail') {
|
|
256
|
+
let out = '<div class="__kp_tabs __kp_tabs_rail" data-sidebar="rail">';
|
|
257
|
+
for (const tab of DEV_TABS) {
|
|
258
|
+
const glyph = TAB_GLYPHS[tab] || tab.slice(0, 2).toUpperCase();
|
|
259
|
+
out +=
|
|
260
|
+
'<button class="__kp_tab' +
|
|
261
|
+
(tab === active ? ' active' : '') +
|
|
262
|
+
'" data-tab="' +
|
|
263
|
+
tab +
|
|
264
|
+
'" data-rail="1">' +
|
|
265
|
+
'<span class="__kp_tab_glyph">' +
|
|
266
|
+
escapeHtml(glyph) +
|
|
267
|
+
'</span>' +
|
|
268
|
+
'<span class="__kp_tab_label">' +
|
|
269
|
+
escapeHtml(tab) +
|
|
270
|
+
'</span>' +
|
|
271
|
+
'</button>';
|
|
272
|
+
}
|
|
273
|
+
return out + '</div>';
|
|
274
|
+
}
|
|
275
|
+
let out = '<div class="__kp_tabs" data-sidebar="expanded">';
|
|
276
|
+
for (const tab of DEV_TABS) {
|
|
277
|
+
out +=
|
|
278
|
+
'<button class="__kp_tab' +
|
|
279
|
+
(tab === active ? ' active' : '') +
|
|
280
|
+
'" data-tab="' +
|
|
281
|
+
tab +
|
|
282
|
+
'">' +
|
|
283
|
+
escapeHtml(tab) +
|
|
284
|
+
'</button>';
|
|
285
|
+
}
|
|
286
|
+
return out + '</div>';
|
|
287
|
+
}
|
|
288
|
+
export function renderOverviewPanel(overview) {
|
|
289
|
+
const err = overview.lastError ? escapeHtml(overview.lastError.message || 'error') : 'none';
|
|
290
|
+
return ('<div class="__kp_sec">> STATUS</div>' +
|
|
291
|
+
'<div class="__kp_line">DEV SERVER: <b>' +
|
|
292
|
+
escapeHtml(overview.host) +
|
|
293
|
+
'</b></div>' +
|
|
294
|
+
'<div class="__kp_line">LAST COMPILE: <b>' +
|
|
295
|
+
(overview.lastCompileMs > 0 ? overview.lastCompileMs + 'ms' : 'pending') +
|
|
296
|
+
'</b></div>' +
|
|
297
|
+
'<div class="__kp_line">LAST ERROR: <b>' +
|
|
298
|
+
err +
|
|
299
|
+
'</b></div>');
|
|
300
|
+
}
|
|
301
|
+
export function renderErrorsPanel(err) {
|
|
302
|
+
if (!err) {
|
|
303
|
+
return '<div class="__kp_sec">> ERRORS</div><div class="__kp_line">no errors</div>';
|
|
304
|
+
}
|
|
305
|
+
const nodes = buildErrorNodes(err);
|
|
306
|
+
return ('<div class="__kp_sec">> ERRORS</div>' +
|
|
307
|
+
'<div class="__kp_line"><b>' +
|
|
308
|
+
nodes.file +
|
|
309
|
+
'</b></div>' +
|
|
310
|
+
'<div class="__kp_line">' +
|
|
311
|
+
nodes.message +
|
|
312
|
+
'</div>' +
|
|
313
|
+
(nodes.codeframe ? '<div class="__kp_code">' + nodes.codeframe + '</div>' : '') +
|
|
314
|
+
nodes.lists +
|
|
315
|
+
'<details class="__kp_stack"><summary>Stack trace</summary><pre>' +
|
|
316
|
+
escapeHtml(nodes.stack) +
|
|
317
|
+
'</pre></details>');
|
|
318
|
+
}
|
|
319
|
+
export function pluginIconHtml(iconUrl, name) {
|
|
320
|
+
const initial = escapeHtml((name || '?').charAt(0).toUpperCase());
|
|
321
|
+
let inner = escapeHtml(initial);
|
|
322
|
+
if (iconUrl)
|
|
323
|
+
inner = '<img src="' + escapeHtml(iconUrl) + '" alt="">';
|
|
324
|
+
return '<span class="__kp_pl_icon">' + inner + '</span>';
|
|
325
|
+
}
|
|
326
|
+
export function pluginBadge(p) {
|
|
327
|
+
if (!p.installed) {
|
|
328
|
+
return '<span class="__kp_pl_badge off">not installed</span>';
|
|
329
|
+
}
|
|
330
|
+
return '<span class="__kp_pl_badge ok">' + (p.active ? 'active' : 'inactive') + '</span>';
|
|
331
|
+
}
|
|
332
|
+
export function pluginVersions(p) {
|
|
333
|
+
let out = p.version ? 'v' + escapeHtml(String(p.version)) : 'no version';
|
|
334
|
+
if (p.installed && p.latest && p.latest !== p.version) {
|
|
335
|
+
out += ' \u2192 ' + escapeHtml(String(p.latest));
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
export function renderPluginCard(p) {
|
|
340
|
+
let html = '<div class="__kp_pl_card" data-plugin="' +
|
|
341
|
+
escapeHtml(p.name) +
|
|
342
|
+
'">' +
|
|
343
|
+
'<div class="__kp_pl_head">' +
|
|
344
|
+
pluginIconHtml(p.iconUrl, p.name) +
|
|
345
|
+
'<div class="__kp_pl_name">' +
|
|
346
|
+
escapeHtml(p.name) +
|
|
347
|
+
'</div>' +
|
|
348
|
+
pluginBadge(p) +
|
|
349
|
+
'</div>' +
|
|
350
|
+
'<div class="__kp_pl_versions">' +
|
|
351
|
+
pluginVersions(p) +
|
|
352
|
+
'</div>';
|
|
353
|
+
if (p.description)
|
|
354
|
+
html += '<div class="__kp_pl_desc">' + escapeHtml(p.description) + '</div>';
|
|
355
|
+
let meta = '';
|
|
356
|
+
if (p.author)
|
|
357
|
+
meta += 'author: ' + escapeHtml(p.author) + ' ';
|
|
358
|
+
if (p.license)
|
|
359
|
+
meta += 'license: ' + escapeHtml(p.license) + ' ';
|
|
360
|
+
if (p.homepage)
|
|
361
|
+
meta += 'homepage: ' + escapeHtml(p.homepage) + ' ';
|
|
362
|
+
if (p.repository)
|
|
363
|
+
meta += 'repo: ' + escapeHtml(p.repository) + ' ';
|
|
364
|
+
if (p.updatedAt)
|
|
365
|
+
meta += 'updated: ' + escapeHtml(String(p.updatedAt)) + ' ';
|
|
366
|
+
if (p.metaSource && p.metaSource !== 'none')
|
|
367
|
+
meta += 'meta: ' + escapeHtml(p.metaSource) + ' ';
|
|
368
|
+
if (meta)
|
|
369
|
+
html += '<div class="__kp_pl_meta">' + meta + '</div>';
|
|
370
|
+
if (p.keywords && p.keywords.length) {
|
|
371
|
+
html += '<div class="__kp_pl_keywords">';
|
|
372
|
+
for (const kw of p.keywords)
|
|
373
|
+
html += '<span class="__kp_pl_kw">' + escapeHtml(String(kw)) + '</span>';
|
|
374
|
+
html += '</div>';
|
|
375
|
+
}
|
|
376
|
+
if (p.error)
|
|
377
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(p.error) + '</div>';
|
|
378
|
+
return html + '</div>';
|
|
379
|
+
}
|
|
380
|
+
export function renderPluginListRow(p) {
|
|
381
|
+
let sub = pluginVersions(p);
|
|
382
|
+
if (p.description)
|
|
383
|
+
sub += ' ' + escapeHtml(p.description);
|
|
384
|
+
return ('<div class="__kp_pl_row" data-plugin="' +
|
|
385
|
+
escapeHtml(p.name) +
|
|
386
|
+
'">' +
|
|
387
|
+
pluginIconHtml(p.iconUrl, p.name) +
|
|
388
|
+
'<div class="__kp_pl_row_info">' +
|
|
389
|
+
'<div class="__kp_pl_row_name">' +
|
|
390
|
+
escapeHtml(p.name) +
|
|
391
|
+
'</div>' +
|
|
392
|
+
'<div class="__kp_pl_row_sub">' +
|
|
393
|
+
sub +
|
|
394
|
+
'</div>' +
|
|
395
|
+
'</div>' +
|
|
396
|
+
pluginBadge(p) +
|
|
397
|
+
'</div>');
|
|
398
|
+
}
|
|
399
|
+
export function renderPluginListView(plugins) {
|
|
400
|
+
return '<div class="__kp_pl_list">' + plugins.map(renderPluginListRow).join('') + '</div>';
|
|
401
|
+
}
|
|
402
|
+
export function renderPluginCardsView(plugins) {
|
|
403
|
+
return '<div class="__kp_pl_grid">' + plugins.map(renderPluginCard).join('') + '</div>';
|
|
404
|
+
}
|
|
405
|
+
export function renderPluginsPanel(plugins, error, view = 'cards') {
|
|
406
|
+
let html = '<div class="__kp_sec">> PLUGINS</div>';
|
|
407
|
+
html += '<div class="__kp_pl_toolbar">';
|
|
408
|
+
html +=
|
|
409
|
+
'<button class="__kp_pl_view' +
|
|
410
|
+
(view === 'cards' ? ' active' : '') +
|
|
411
|
+
'" data-pl-view="cards">cards</button>';
|
|
412
|
+
html +=
|
|
413
|
+
'<button class="__kp_pl_view' +
|
|
414
|
+
(view === 'list' ? ' active' : '') +
|
|
415
|
+
'" data-pl-view="list">list</button>';
|
|
416
|
+
html += '<button class="__kp_pl_btn" data-pl-search="1">install / search</button>';
|
|
417
|
+
html += '</div>';
|
|
418
|
+
if (error) {
|
|
419
|
+
html += '<div class="kp-err">' + escapeHtml(error) + '</div>';
|
|
420
|
+
}
|
|
421
|
+
else if (plugins.length === 0) {
|
|
422
|
+
html += '<div class="__kp_line">no plugins reported</div>';
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
html += view === 'list' ? renderPluginListView(plugins) : renderPluginCardsView(plugins);
|
|
426
|
+
}
|
|
427
|
+
return html;
|
|
428
|
+
}
|
|
429
|
+
export function renderPluginDetail(p, message) {
|
|
430
|
+
let html = '<div class="__kp_sec">> PLUGIN</div>' +
|
|
431
|
+
'<button class="__kp_pl_btn" data-pl-back="1">[back]</button>' +
|
|
432
|
+
'<div class="__kp_pl_detail">' +
|
|
433
|
+
'<div class="__kp_pl_head">' +
|
|
434
|
+
pluginIconHtml(p.iconUrl, p.name) +
|
|
435
|
+
'<div class="__kp_pl_name">' +
|
|
436
|
+
escapeHtml(p.name) +
|
|
437
|
+
'</div>' +
|
|
438
|
+
pluginBadge(p) +
|
|
439
|
+
'</div>' +
|
|
440
|
+
'<div class="__kp_pl_versions">' +
|
|
441
|
+
pluginVersions(p) +
|
|
442
|
+
'</div>' +
|
|
443
|
+
'<div class="__kp_pl_pkg">package: ' +
|
|
444
|
+
escapeHtml(p.package || p.name) +
|
|
445
|
+
'</div>';
|
|
446
|
+
if (p.description)
|
|
447
|
+
html += '<div class="__kp_pl_desc">' + escapeHtml(p.description) + '</div>';
|
|
448
|
+
let meta = '';
|
|
449
|
+
if (p.author)
|
|
450
|
+
meta += '<div>author: <b>' + escapeHtml(p.author) + '</b></div>';
|
|
451
|
+
if (p.license)
|
|
452
|
+
meta += '<div>license: <b>' + escapeHtml(p.license) + '</b></div>';
|
|
453
|
+
if (p.homepage)
|
|
454
|
+
meta += '<div>homepage: <b>' + escapeHtml(p.homepage) + '</b></div>';
|
|
455
|
+
if (p.repository)
|
|
456
|
+
meta += '<div>repository: <b>' + escapeHtml(p.repository) + '</b></div>';
|
|
457
|
+
if (p.updatedAt)
|
|
458
|
+
meta += '<div>updated: <b>' + escapeHtml(String(p.updatedAt)) + '</b></div>';
|
|
459
|
+
if (p.path)
|
|
460
|
+
meta += '<div>path: <b>' + escapeHtml(p.path) + '</b></div>';
|
|
461
|
+
if (p.metaSource && p.metaSource !== 'none')
|
|
462
|
+
meta += '<div>meta: <b>' + escapeHtml(p.metaSource) + '</b></div>';
|
|
463
|
+
if (p.keywords && p.keywords.length)
|
|
464
|
+
meta += '<div>keywords: <b>' + escapeHtml(p.keywords.join(', ')) + '</b></div>';
|
|
465
|
+
if (meta)
|
|
466
|
+
html += '<div class="__kp_pl_meta">' + meta + '</div>';
|
|
467
|
+
if (p.error)
|
|
468
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(p.error) + '</div>';
|
|
469
|
+
if (message)
|
|
470
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(message) + '</div>';
|
|
471
|
+
html += '<div class="__kp_pl_actions">';
|
|
472
|
+
const canUpdate = p.installed && p.latest && p.latest !== p.version;
|
|
473
|
+
if (p.installed) {
|
|
474
|
+
html +=
|
|
475
|
+
p.active
|
|
476
|
+
? '<button class="__kp_pl_btn" data-pl-act="deactivate" data-name="' +
|
|
477
|
+
escapeHtml(p.name) +
|
|
478
|
+
'">deactivate</button>'
|
|
479
|
+
: '<button class="__kp_pl_btn" data-pl-act="activate" data-name="' +
|
|
480
|
+
escapeHtml(p.name) +
|
|
481
|
+
'">activate</button>';
|
|
482
|
+
html +=
|
|
483
|
+
'<button class="__kp_pl_btn" data-pl-act="update" data-package="' +
|
|
484
|
+
escapeHtml(p.package || p.name) +
|
|
485
|
+
'"' +
|
|
486
|
+
(canUpdate ? '' : ' disabled') +
|
|
487
|
+
'>update' +
|
|
488
|
+
(canUpdate ? ' \u2192 ' + escapeHtml(String(p.latest)) : '') +
|
|
489
|
+
'</button>';
|
|
490
|
+
html +=
|
|
491
|
+
'<button class="__kp_pl_btn danger" data-pl-act="uninstall" data-package="' +
|
|
492
|
+
escapeHtml(p.package || p.name) +
|
|
493
|
+
'">uninstall</button>';
|
|
494
|
+
}
|
|
495
|
+
html +=
|
|
496
|
+
'<button class="__kp_pl_btn" data-pl-exports="1" data-name="' +
|
|
497
|
+
escapeHtml(p.name) +
|
|
498
|
+
'">view exports & types</button>';
|
|
499
|
+
html += '</div>';
|
|
500
|
+
html += '</div>';
|
|
501
|
+
return html;
|
|
502
|
+
}
|
|
503
|
+
export function renderPluginExports(data, error) {
|
|
504
|
+
let html = '<div class="__kp_sec">> EXPORTS & TYPES</div>' +
|
|
505
|
+
'<button class="__kp_pl_btn" data-pl-back="1">[back]</button>' +
|
|
506
|
+
'<div class="__kp_pl_detail">';
|
|
507
|
+
if (error) {
|
|
508
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(error) + '</div>';
|
|
509
|
+
html += '</div>';
|
|
510
|
+
return html;
|
|
511
|
+
}
|
|
512
|
+
html += '<div class="__kp_pl_export"><dl>';
|
|
513
|
+
if (data && data.entry)
|
|
514
|
+
html += '<dt>entry</dt><dd>' + escapeHtml(data.entry) + '</dd>';
|
|
515
|
+
if (data && data.dtsPath)
|
|
516
|
+
html += '<dt>d.ts path</dt><dd>' + escapeHtml(data.dtsPath) + '</dd>';
|
|
517
|
+
if (data && data.packageJsonExports && Object.keys(data.packageJsonExports).length) {
|
|
518
|
+
html += '<dt>package exports</dt>';
|
|
519
|
+
for (const k of Object.keys(data.packageJsonExports)) {
|
|
520
|
+
html += '<dd>' + escapeHtml(k) + ' \u2192 ' + escapeHtml(String(data.packageJsonExports[k])) + '</dd>';
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (data && data.dtsExports && data.dtsExports.length) {
|
|
524
|
+
html += '<dt>declared exports</dt>';
|
|
525
|
+
for (const ex of data.dtsExports) {
|
|
526
|
+
html += '<div class="__kp_pl_ex_item">> ' + escapeHtml(String(ex)) + '</div>';
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
html += '</dl></div></div>';
|
|
530
|
+
return html;
|
|
531
|
+
}
|
|
532
|
+
export const PLUGIN_SEARCH_SUGGESTIONS = ['@vesk', 'keyword:vesk', '@vesk/plugin-'];
|
|
533
|
+
export function renderPluginSearch(query, results, error, loading, installing = null, installed = []) {
|
|
534
|
+
const installedSet = new Set(installed);
|
|
535
|
+
let html = '<div class="__kp_sec">> INSTALL / SEARCH</div>' +
|
|
536
|
+
'<button class="__kp_pl_btn" data-pl-back="1">[back]</button>' +
|
|
537
|
+
'<div class="__kp_pl_search">' +
|
|
538
|
+
'<input class="__kp_pl_q" id="__kp_pl_q" placeholder="search npm for plugins" value="' +
|
|
539
|
+
escapeHtml(query) +
|
|
540
|
+
'">' +
|
|
541
|
+
'<button class="__kp_pl_btn" data-pl-search-go="1">search</button>' +
|
|
542
|
+
'</div>' +
|
|
543
|
+
'<div class="__kp_pl_suggest">browse: ';
|
|
544
|
+
for (const s of PLUGIN_SEARCH_SUGGESTIONS) {
|
|
545
|
+
html += '<button class="__kp_pl_btn" data-pl-suggest="' + escapeHtml(s) + '">' + escapeHtml(s) + '</button>';
|
|
546
|
+
}
|
|
547
|
+
html += '</div>';
|
|
548
|
+
if (error)
|
|
549
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(error) + '</div>';
|
|
550
|
+
if (loading)
|
|
551
|
+
html += '<div class="__kp_line">searching...</div>';
|
|
552
|
+
if (results.length === 0 && !loading && !error) {
|
|
553
|
+
html += '<div class="__kp_line">no results</div>';
|
|
554
|
+
}
|
|
555
|
+
if (results.length) {
|
|
556
|
+
html += '<div class="__kp_pl_list">';
|
|
557
|
+
for (const r of results) {
|
|
558
|
+
let sub = '';
|
|
559
|
+
if (r.version)
|
|
560
|
+
sub += 'v' + escapeHtml(r.version);
|
|
561
|
+
if (r.description)
|
|
562
|
+
sub += ' ' + escapeHtml(r.description);
|
|
563
|
+
if (r.author)
|
|
564
|
+
sub += ' by ' + escapeHtml(r.author);
|
|
565
|
+
if (r.date)
|
|
566
|
+
sub += ' ' + escapeHtml(r.date);
|
|
567
|
+
const busy = installing === r.name;
|
|
568
|
+
const isInstalled = installedSet.has(r.name);
|
|
569
|
+
html +=
|
|
570
|
+
'<div class="__kp_pl_row" data-search-pkg="' +
|
|
571
|
+
escapeHtml(r.name) +
|
|
572
|
+
'">' +
|
|
573
|
+
'<div class="__kp_pl_row_info">' +
|
|
574
|
+
'<div class="__kp_pl_row_name">' +
|
|
575
|
+
escapeHtml(r.name) +
|
|
576
|
+
(isInstalled ? '<span class="__kp_pl_badge on">installed</span>' : '') +
|
|
577
|
+
'</div>' +
|
|
578
|
+
'<div class="__kp_pl_row_sub">' +
|
|
579
|
+
sub +
|
|
580
|
+
'</div>' +
|
|
581
|
+
(r.keywords && r.keywords.length
|
|
582
|
+
? '<div class="__kp_pl_keywords">' +
|
|
583
|
+
r.keywords.map((kw) => '<span class="__kp_pl_kw">' + escapeHtml(String(kw)) + '</span>').join('') +
|
|
584
|
+
'</div>'
|
|
585
|
+
: '') +
|
|
586
|
+
'</div>' +
|
|
587
|
+
'<button class="__kp_pl_btn" data-search-pkg-open="' +
|
|
588
|
+
escapeHtml(r.name) +
|
|
589
|
+
'">details</button>' +
|
|
590
|
+
'<button class="__kp_pl_btn" data-pl-install="' +
|
|
591
|
+
escapeHtml(r.name) +
|
|
592
|
+
'"' +
|
|
593
|
+
(busy || isInstalled ? ' disabled' : '') +
|
|
594
|
+
'>' +
|
|
595
|
+
(busy ? 'installing...' : isInstalled ? 'installed' : 'install') +
|
|
596
|
+
'</button>' +
|
|
597
|
+
'</div>';
|
|
598
|
+
}
|
|
599
|
+
html += '</div>';
|
|
600
|
+
}
|
|
601
|
+
if (installing)
|
|
602
|
+
html += '<div class="__kp_line">installing ' + escapeHtml(installing) + ' \u2014 please wait...</div>';
|
|
603
|
+
return html;
|
|
604
|
+
}
|
|
605
|
+
export function renderSearchPluginDetail(r, installing, installed = []) {
|
|
606
|
+
const busy = installing === r.name;
|
|
607
|
+
const isInstalled = installed.indexOf(r.name) >= 0;
|
|
608
|
+
let html = '<div class="__kp_sec">> PLUGIN (npm)</div>' +
|
|
609
|
+
'<button class="__kp_pl_btn" data-pl-back="1">[back]</button>' +
|
|
610
|
+
'<div class="__kp_pl_detail">' +
|
|
611
|
+
'<div class="__kp_pl_head">' +
|
|
612
|
+
'<div class="__kp_pl_icon">' +
|
|
613
|
+
escapeHtml((r.name[0] || '?').toUpperCase()) +
|
|
614
|
+
'</div>' +
|
|
615
|
+
'<div class="__kp_pl_name">' +
|
|
616
|
+
escapeHtml(r.name) +
|
|
617
|
+
'</div>' +
|
|
618
|
+
'<span class="__kp_pl_badge ' +
|
|
619
|
+
(isInstalled ? 'on' : 'off') +
|
|
620
|
+
'">' +
|
|
621
|
+
(isInstalled ? 'installed' : 'not installed') +
|
|
622
|
+
'</span>' +
|
|
623
|
+
'</div>' +
|
|
624
|
+
(r.version ? '<div class="__kp_pl_versions">v' + escapeHtml(r.version) + '</div>' : '') +
|
|
625
|
+
(r.description ? '<div class="__kp_pl_desc">' + escapeHtml(r.description) + '</div>' : '') +
|
|
626
|
+
'<div class="__kp_pl_meta">' +
|
|
627
|
+
(r.author ? '<div>author: <b>' + escapeHtml(r.author) + '</b></div>' : '') +
|
|
628
|
+
(r.date ? '<div>published: <b>' + escapeHtml(r.date) + '</b></div>' : '') +
|
|
629
|
+
'</div>' +
|
|
630
|
+
(r.keywords && r.keywords.length
|
|
631
|
+
? '<div class="__kp_pl_keywords">' +
|
|
632
|
+
r.keywords.map((k) => '<span class="__kp_pl_kw">' + escapeHtml(String(k)) + '</span>').join('') +
|
|
633
|
+
'</div>'
|
|
634
|
+
: '') +
|
|
635
|
+
'<div class="__kp_pl_actions">' +
|
|
636
|
+
'<button class="__kp_pl_btn" data-pl-install="' +
|
|
637
|
+
escapeHtml(r.name) +
|
|
638
|
+
'"' +
|
|
639
|
+
(busy || isInstalled ? ' disabled' : '') +
|
|
640
|
+
'>' +
|
|
641
|
+
(busy ? 'installing...' : isInstalled ? 'installed' : 'install') +
|
|
642
|
+
'</button>' +
|
|
643
|
+
'</div>' +
|
|
644
|
+
'</div>';
|
|
645
|
+
if (busy)
|
|
646
|
+
html += '<div class="__kp_line">installing ' + escapeHtml(r.name) + ' \u2014 please wait...</div>';
|
|
647
|
+
return html;
|
|
648
|
+
}
|
|
649
|
+
export function renderLogPanel(log) {
|
|
650
|
+
let html = '<div class="__kp_sec">> LOG</div>';
|
|
651
|
+
if (log.length === 0) {
|
|
652
|
+
html += '<div class="__kp_line">no events yet</div>';
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
html += log.map(renderLogRow).join('');
|
|
656
|
+
}
|
|
657
|
+
return html;
|
|
658
|
+
}
|
|
659
|
+
export function renderDiagnosticsPanel(findings) {
|
|
660
|
+
let html = '<div class="__kp_sec">> DIAGNOSTICS</div>';
|
|
661
|
+
if (findings.length === 0) {
|
|
662
|
+
html += '<div class="__kp_line">no findings — all clear</div>';
|
|
663
|
+
return html;
|
|
664
|
+
}
|
|
665
|
+
const order = ['error', 'warning', 'info'];
|
|
666
|
+
const grouped = order.map((sev) => ({ sev, list: findings.filter((f) => f.severity === sev) }));
|
|
667
|
+
let count = 0;
|
|
668
|
+
for (const { sev, list } of grouped) {
|
|
669
|
+
if (list.length === 0)
|
|
670
|
+
continue;
|
|
671
|
+
html += '<div class="__kp_sec">> ' + sev.toUpperCase() + ' (' + list.length + ')</div>';
|
|
672
|
+
for (const f of list) {
|
|
673
|
+
count++;
|
|
674
|
+
html +=
|
|
675
|
+
'<div class="__kp_diag" data-severity="' +
|
|
676
|
+
sev +
|
|
677
|
+
'">' +
|
|
678
|
+
'<div class="__kp_diag_head">' +
|
|
679
|
+
'<span class="__kp_diag_badge ' +
|
|
680
|
+
sev +
|
|
681
|
+
'">' +
|
|
682
|
+
sev.toUpperCase() +
|
|
683
|
+
'</span>' +
|
|
684
|
+
'<span class="__kp_diag_code">' +
|
|
685
|
+
escapeHtml(f.code) +
|
|
686
|
+
'</span>' +
|
|
687
|
+
'<span class="__kp_diag_msg">' +
|
|
688
|
+
escapeHtml(f.message) +
|
|
689
|
+
'</span>' +
|
|
690
|
+
'</div>' +
|
|
691
|
+
(f.file
|
|
692
|
+
? '<div class="__kp_diag_loc">' +
|
|
693
|
+
escapeHtml(f.file) +
|
|
694
|
+
(f.line != null ? ':' + f.line + (f.column != null ? ':' + f.column : '') : '') +
|
|
695
|
+
'</div>'
|
|
696
|
+
: '') +
|
|
697
|
+
(f.hint ? '<div class="__kp_diag_hint">' + escapeHtml(f.hint) + '</div>' : '') +
|
|
698
|
+
'</div>';
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
void count;
|
|
702
|
+
return html;
|
|
703
|
+
}
|
|
704
|
+
function settingsOptRow(label, key, options, current) {
|
|
705
|
+
let html = '<div class="__kp_setlabel">' + label + '</div><div class="__kp_optrow">';
|
|
706
|
+
for (const opt of options) {
|
|
707
|
+
html +=
|
|
708
|
+
'<button class="__kp_opt' +
|
|
709
|
+
(opt.val === current ? ' active' : '') +
|
|
710
|
+
'" data-key="' +
|
|
711
|
+
key +
|
|
712
|
+
'" data-val="' +
|
|
713
|
+
opt.val +
|
|
714
|
+
'">' +
|
|
715
|
+
escapeHtml(opt.label) +
|
|
716
|
+
'</button>';
|
|
717
|
+
}
|
|
718
|
+
return html + '</div>';
|
|
719
|
+
}
|
|
720
|
+
export function renderSettingsDevToolsSubtab(prefs) {
|
|
721
|
+
const themes = ['system', 'light', 'dark'];
|
|
722
|
+
const positions = ['left', 'right'];
|
|
723
|
+
const pluginViews = ['cards', 'list'];
|
|
724
|
+
let html = '<div class="__kp_sec">> DEVTOOLS</div>';
|
|
725
|
+
html += settingsOptRow('THEME', 'theme', themes.map((t) => ({ val: t, label: t })), prefs.theme);
|
|
726
|
+
html += settingsOptRow('PANEL POSITION', 'pos', positions.map((p) => ({ val: p, label: p })), prefs.pos);
|
|
727
|
+
html += settingsOptRow('PLUGIN VIEW', 'pluginsView', pluginViews.map((v) => ({ val: v, label: v })), prefs.pluginsView);
|
|
728
|
+
html += settingsOptRow('SIDEBAR MODE', 'sidebarMode', [
|
|
729
|
+
{ val: 'expanded', label: 'top' },
|
|
730
|
+
{ val: 'rail', label: 'sidebar' },
|
|
731
|
+
], prefs.sidebarMode);
|
|
732
|
+
return html;
|
|
733
|
+
}
|
|
734
|
+
export function renderSettingsAgenticSubtab(state) {
|
|
735
|
+
const s = state || defaultAgenticConfigState();
|
|
736
|
+
const provider = typeof s.provider === 'string' && AGENTIC_PROVIDERS.indexOf(s.provider) !== -1 ? s.provider : 'openai';
|
|
737
|
+
const model = typeof s.model === 'string' ? s.model : '';
|
|
738
|
+
const models = Array.isArray(s.models) ? s.models : [];
|
|
739
|
+
const baseUrl = typeof s.baseUrl === 'string' ? s.baseUrl : '';
|
|
740
|
+
const mode = isAgenticMode(s.mode) ? s.mode : 'explore';
|
|
741
|
+
const maxSteps = typeof s.maxSteps === 'number' && Number.isFinite(s.maxSteps) ? s.maxSteps : 25;
|
|
742
|
+
const apiKeys = s.apiKeys && typeof s.apiKeys === 'object' ? s.apiKeys : {};
|
|
743
|
+
const hasKeys = s.hasKeys && typeof s.hasKeys === 'object' ? s.hasKeys : {};
|
|
744
|
+
const keyPreviews = s.keyPreviews && typeof s.keyPreviews === 'object' ? s.keyPreviews : {};
|
|
745
|
+
let html = '<div class="__kp_sec">> AGENTIC</div>';
|
|
746
|
+
if (s.loading)
|
|
747
|
+
html += '<div class="__kp_line">loading agentic config...</div>';
|
|
748
|
+
if (s.error)
|
|
749
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(String(s.error)) + '</div>';
|
|
750
|
+
if (s.saveOk)
|
|
751
|
+
html += '<div class="__kp_line" style="color:var(--vk-fg)"><b>' + escapeHtml(String(s.saveOk)) + '</b></div>';
|
|
752
|
+
if (s.saveError)
|
|
753
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(String(s.saveError)) + '</div>';
|
|
754
|
+
if (s.saving)
|
|
755
|
+
html += '<div class="__kp_line">saving agentic config...</div>';
|
|
756
|
+
html += renderAgenticProviderSelect(provider);
|
|
757
|
+
html += renderAgenticModelSelect(model, models, !!s.modelsLoading, s.modelsError || null);
|
|
758
|
+
html += '<div class="__kp_setlabel">BASE URL</div>';
|
|
759
|
+
html += '<input class="__kp_cfg_input" data-agentic-key="baseUrl" data-agentic-baseurl placeholder="https://api.openai.com/v1" value="' + escapeHtml(baseUrl) + '">';
|
|
760
|
+
html += '<div class="__kp_cfg_hint">override provider baseUrl ()</div>';
|
|
761
|
+
html += renderAgenticModeToggle(mode);
|
|
762
|
+
html += '<div class="__kp_setlabel">MAX STEPS</div>';
|
|
763
|
+
html += '<input class="__kp_cfg_input" data-agentic-key="maxSteps" data-agentic-maxsteps type="number" min="1" max="200" step="1" value="' + escapeHtml(String(maxSteps)) + '">';
|
|
764
|
+
html += '<div class="__kp_cfg_hint">steps per run (1-200) — auto-extends past this while the agent still makes progress</div>';
|
|
765
|
+
html += '<div class="__kp_sec">> API KEYS</div>';
|
|
766
|
+
html += '<div class="__kp_cfg_hint">per-provider keys — stored server-side, previews are masked</div>';
|
|
767
|
+
for (const p of AGENTIC_PROVIDERS) {
|
|
768
|
+
const rawKey = typeof apiKeys[p] === 'string' ? String(apiKeys[p]) : '';
|
|
769
|
+
const preview = keyPreviews[p] != null ? String(keyPreviews[p]) : hasKeys[p] ? '••••' : '';
|
|
770
|
+
const masked = rawKey ? maskApiKey(rawKey) : preview ? String(preview) : '';
|
|
771
|
+
const hasKey = !!hasKeys[p];
|
|
772
|
+
html += '<div class="__kp_ag_key_row" data-provider="' + escapeHtml(p) + '">';
|
|
773
|
+
html += '<div class="__kp_setlabel">' + escapeHtml(p.toUpperCase()) + ' API KEY' + (hasKey ? ' <span class="__kp_key_has" data-has-key="' + escapeHtml(p) + '">● hasKey</span>' : ' <span class="__kp_key_nohas" data-has-key="' + escapeHtml(p) + '">○ no key</span>') + (masked ? ' <span class="__kp_key_preview" data-key-preview="' + escapeHtml(p) + '">' + escapeHtml(masked) + '</span>' : '') + '</div>';
|
|
774
|
+
html += '<div class="__kp_ag_key_input_row">';
|
|
775
|
+
html += '<input class="__kp_cfg_input __kp_ag_key_input" data-agentic-key-input="' + escapeHtml(p) + '" data-provider="' + escapeHtml(p) + '" type="password" placeholder="sk-..." value="' + escapeHtml(rawKey) + '" autocomplete="off">';
|
|
776
|
+
html += '<button class="__kp_pl_btn" data-agentic-save-key="' + escapeHtml(p) + '" data-provider="' + escapeHtml(p) + '">save</button>';
|
|
777
|
+
html += '</div>';
|
|
778
|
+
if (masked)
|
|
779
|
+
html += '<div class="__kp_cfg_hint">preview: ' + escapeHtml(masked) + ' ' + (hasKey ? '[hasKey=true]' : '[hasKey=false]') + '</div>';
|
|
780
|
+
html += '</div>';
|
|
781
|
+
}
|
|
782
|
+
return html;
|
|
783
|
+
}
|
|
784
|
+
export function renderSettingsVeskConfigSubtab(state) {
|
|
785
|
+
return renderVeskConfigPanel(state);
|
|
786
|
+
}
|
|
787
|
+
export function renderSettingsSubtabs(active) {
|
|
788
|
+
const tabs = [
|
|
789
|
+
{ id: 'devtools', label: 'DEVTOOLS' },
|
|
790
|
+
{ id: 'agentic', label: 'AGENTIC' },
|
|
791
|
+
{ id: 'vesk', label: 'VESK CONFIG' },
|
|
792
|
+
];
|
|
793
|
+
let html = '<nav class="__kp_settings_tabs" data-settings-subtabs="1">';
|
|
794
|
+
for (const t of tabs) {
|
|
795
|
+
html +=
|
|
796
|
+
'<button class="__kp_settings_tab' +
|
|
797
|
+
(t.id === active ? ' active' : '') +
|
|
798
|
+
'" data-settings-subtab="' +
|
|
799
|
+
t.id +
|
|
800
|
+
'" data-settings-tab="' +
|
|
801
|
+
t.id +
|
|
802
|
+
'">' +
|
|
803
|
+
escapeHtml(t.label) +
|
|
804
|
+
'</button>';
|
|
805
|
+
}
|
|
806
|
+
html += '</nav>';
|
|
807
|
+
return html;
|
|
808
|
+
}
|
|
809
|
+
export function renderSettingsPanel(prefs, agentic, vesk, activeSubTab) {
|
|
810
|
+
const sub = isSettingsSubTab(activeSubTab || '') ? activeSubTab : 'devtools';
|
|
811
|
+
// Keep backward compat: if only prefs supplied, render full panel with nav defaulting to devtools
|
|
812
|
+
let html = '<div class="__kp_sec">> SETTINGS</div>';
|
|
813
|
+
html += renderSettingsSubtabs(sub);
|
|
814
|
+
html += '<div class="__kp_settings_pane" data-settings-pane="' + escapeHtml(sub) + '">';
|
|
815
|
+
if (sub === 'agentic') {
|
|
816
|
+
html += renderSettingsAgenticSubtab(agentic || null);
|
|
817
|
+
}
|
|
818
|
+
else if (sub === 'vesk') {
|
|
819
|
+
html += renderSettingsVeskConfigSubtab(vesk || null);
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
html += renderSettingsDevToolsSubtab(prefs);
|
|
823
|
+
}
|
|
824
|
+
html += '</div>';
|
|
825
|
+
return html;
|
|
826
|
+
}
|
|
827
|
+
export function renderVeskConfigPanel(state) {
|
|
828
|
+
if (!state) {
|
|
829
|
+
return ('<div class="__kp_sec">> VESK CONFIG</div>' +
|
|
830
|
+
'<div class="__kp_line">loading config...</div>' +
|
|
831
|
+
'<div class="__kp_cfg_hint">fetching GET /__vesk/config</div>');
|
|
832
|
+
}
|
|
833
|
+
if (state.loading) {
|
|
834
|
+
return ('<div class="__kp_sec">> VESK CONFIG</div>' +
|
|
835
|
+
'<div class="__kp_line">loading config...</div>');
|
|
836
|
+
}
|
|
837
|
+
if (state.error) {
|
|
838
|
+
return ('<div class="__kp_sec">> VESK CONFIG</div>' +
|
|
839
|
+
'<div class="__kp_pl_err">' +
|
|
840
|
+
escapeHtml(String(state.error)) +
|
|
841
|
+
'</div>' +
|
|
842
|
+
'<button class="__kp_pl_btn" data-cfg-reload="1">retry</button>');
|
|
843
|
+
}
|
|
844
|
+
const cfg = state.config || {};
|
|
845
|
+
const md = (cfg.md || {});
|
|
846
|
+
const secRaw = cfg.security;
|
|
847
|
+
let sec = null;
|
|
848
|
+
let secIsObject = false;
|
|
849
|
+
let secPreset = null;
|
|
850
|
+
if (secRaw === false) {
|
|
851
|
+
secPreset = 'off';
|
|
852
|
+
}
|
|
853
|
+
else if (typeof secRaw === 'string') {
|
|
854
|
+
secPreset = String(secRaw);
|
|
855
|
+
}
|
|
856
|
+
else if (secRaw && typeof secRaw === 'object') {
|
|
857
|
+
sec = secRaw;
|
|
858
|
+
secIsObject = true;
|
|
859
|
+
}
|
|
860
|
+
else if (secRaw == null) {
|
|
861
|
+
sec = {};
|
|
862
|
+
secIsObject = true;
|
|
863
|
+
}
|
|
864
|
+
const appDir = typeof cfg.appDir === 'string' ? cfg.appDir : '';
|
|
865
|
+
const outDir = typeof cfg.outDir === 'string' ? cfg.outDir : '';
|
|
866
|
+
const publicDir = typeof cfg.publicDir === 'string' ? cfg.publicDir : '';
|
|
867
|
+
const routeDataCache = typeof cfg.routeDataCache === 'number' ? cfg.routeDataCache : 0;
|
|
868
|
+
const mdHtml = typeof md.html === 'string' ? md.html : 'escape';
|
|
869
|
+
const allowTags = Array.isArray(md.allowTags) ? md.allowTags.join(', ') : '';
|
|
870
|
+
let html = '<div class="__kp_sec">> VESK CONFIG</div>';
|
|
871
|
+
const pathLabel = state.path ? escapeHtml(state.path) : 'vesk.config.ts (not found)';
|
|
872
|
+
html += '<div class="__kp_cfg_meta">PATH: <b>' + pathLabel + '</b> ' + (state.exists ? '' : '(will create on save)') + '</div>';
|
|
873
|
+
if (state.saveOk)
|
|
874
|
+
html += '<div class="__kp_line" style="color:var(--vk-fg)"><b>' + escapeHtml(String(state.saveOk)) + '</b></div>';
|
|
875
|
+
if (state.saveError)
|
|
876
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(String(state.saveError)) + '</div>';
|
|
877
|
+
if (state.saving)
|
|
878
|
+
html += '<div class="__kp_line">saving...</div>';
|
|
879
|
+
// ── core dirs + cache ─────────────────────────────────────────────
|
|
880
|
+
html += '<div class="__kp_sec">> CORE</div>';
|
|
881
|
+
html += '<div class="__kp_setlabel">APP DIR</div>';
|
|
882
|
+
html += '<input class="__kp_cfg_input" data-cfg-key="appDir" placeholder="app" value="' + escapeHtml(appDir) + '">';
|
|
883
|
+
html += '<div class="__kp_setlabel">OUT DIR</div>';
|
|
884
|
+
html += '<input class="__kp_cfg_input" data-cfg-key="outDir" placeholder=".vesk" value="' + escapeHtml(outDir) + '">';
|
|
885
|
+
html += '<div class="__kp_setlabel">PUBLIC DIR</div>';
|
|
886
|
+
html += '<input class="__kp_cfg_input" data-cfg-key="publicDir" placeholder="public" value="' + escapeHtml(publicDir) + '">';
|
|
887
|
+
html += '<div class="__kp_setlabel">ROUTE DATA CACHE (ms)</div>';
|
|
888
|
+
html += '<input class="__kp_cfg_input" data-cfg-key="routeDataCache" type="number" min="0" step="100" value="' + escapeHtml(String(routeDataCache)) + '">';
|
|
889
|
+
html += '<div class="__kp_cfg_hint">0 = always fetch fresh on SPA nav</div>';
|
|
890
|
+
// ── Markdown ──────────────────────────────────────────────────────
|
|
891
|
+
html += '<div class="__kp_sec">> MARKDOWN (MD)</div>';
|
|
892
|
+
html += '<div class="__kp_setlabel">MD.HTML</div>';
|
|
893
|
+
html += '<select class="__kp_cfg_select" data-cfg-md-html aria-label="md html">';
|
|
894
|
+
for (const opt of ['escape', 'allow', 'allowlist']) {
|
|
895
|
+
html += '<option value="' + opt + '"' + (mdHtml === opt ? ' selected' : '') + '>' + escapeHtml(opt) + '</option>';
|
|
896
|
+
}
|
|
897
|
+
html += '</select>';
|
|
898
|
+
html += '<div class="__kp_setlabel">MD.ALLOW TAGS</div>';
|
|
899
|
+
html += '<input class="__kp_cfg_input" data-cfg-md-tags placeholder="a, em, strong, code" value="' + escapeHtml(allowTags) + '"' + (mdHtml !== 'allowlist' ? ' disabled' : '') + '>';
|
|
900
|
+
html += '<div class="__kp_cfg_hint">comma-separated, only used when html=allowlist</div>';
|
|
901
|
+
if (mdHtml === 'allowlist' && !allowTags) {
|
|
902
|
+
html += '<div class="__kp_cfg_hint">default allowlist: ' + escapeHtml('a, abbr, b, code, em, strong ...') + '</div>';
|
|
903
|
+
}
|
|
904
|
+
// ── Security ──────────────────────────────────────────────────────
|
|
905
|
+
html += '<div class="__kp_sec">> SECURITY</div>';
|
|
906
|
+
if (secPreset) {
|
|
907
|
+
html += '<div class="__kp_cfg_meta">SECURITY PRESET: <b>' + escapeHtml(secPreset) + '</b></div>';
|
|
908
|
+
html += '<div class="__kp_cfg_hint">object editing disabled for preset/false. Save raw source to change preset or switch to object.</div>';
|
|
909
|
+
html += '<div class="__kp_setlabel">SECURITY PRESET</div>';
|
|
910
|
+
html += '<select class="__kp_cfg_select" data-cfg-sec-preset aria-label="security preset">';
|
|
911
|
+
for (const p of ['strict', 'default', 'minimal', 'off', 'custom']) {
|
|
912
|
+
const sel = (secPreset === p || (p === 'custom' && !secPreset)) ? ' selected' : '';
|
|
913
|
+
html += '<option value="' + p + '"' + sel + '>' + escapeHtml(p) + '</option>';
|
|
914
|
+
}
|
|
915
|
+
html += '</select>';
|
|
916
|
+
// still render object fields disabled when preset
|
|
917
|
+
}
|
|
918
|
+
if (secIsObject || !secPreset) {
|
|
919
|
+
const s = (sec || {});
|
|
920
|
+
const xfo = typeof s.xFrameOptions === 'string' ? s.xFrameOptions : s.xFrameOptions === false ? '' : (s.xFrameOptions == null ? 'DENY' : String(s.xFrameOptions));
|
|
921
|
+
const xfoDisabled = s.xFrameOptions === false;
|
|
922
|
+
html += '<div class="__kp_setlabel">X-FRAME-OPTIONS</div>';
|
|
923
|
+
html += '<div class="__kp_cfg_row">';
|
|
924
|
+
html += '<input class="__kp_cfg_input" data-sec-key="xFrameOptions" placeholder="DENY" value="' + escapeHtml(xfo) + '"' + (xfoDisabled ? ' disabled' : '') + '>';
|
|
925
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="xFrameOptions" ' + (xfoDisabled ? 'checked' : '') + '> disable</label>';
|
|
926
|
+
html += '</div>';
|
|
927
|
+
const hsts = typeof s.hsts === 'string' ? s.hsts : s.hsts === false ? '' : (s.hsts == null ? '' : String(s.hsts));
|
|
928
|
+
const hstsDisabled = s.hsts === false;
|
|
929
|
+
html += '<div class="__kp_setlabel">HSTS</div>';
|
|
930
|
+
html += '<div class="__kp_cfg_row">';
|
|
931
|
+
html += '<input class="__kp_cfg_input" data-sec-key="hsts" placeholder="max-age=31536000; includeSubDomains" value="' + escapeHtml(hsts) + '"' + (hstsDisabled ? ' disabled' : '') + '>';
|
|
932
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="hsts" ' + (hstsDisabled ? 'checked' : '') + '> disable</label>';
|
|
933
|
+
html += '</div>';
|
|
934
|
+
const rp = typeof s.referrerPolicy === 'string' ? s.referrerPolicy : s.referrerPolicy === false ? '' : (s.referrerPolicy == null ? '' : String(s.referrerPolicy));
|
|
935
|
+
const rpDisabled = s.referrerPolicy === false;
|
|
936
|
+
html += '<div class="__kp_setlabel">REFERRER-POLICY</div>';
|
|
937
|
+
html += '<div class="__kp_cfg_row">';
|
|
938
|
+
html += '<input class="__kp_cfg_input" data-sec-key="referrerPolicy" placeholder="strict-origin-when-cross-origin" value="' + escapeHtml(rp) + '"' + (rpDisabled ? ' disabled' : '') + '>';
|
|
939
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="referrerPolicy" ' + (rpDisabled ? 'checked' : '') + '> disable</label>';
|
|
940
|
+
html += '</div>';
|
|
941
|
+
const csp = s.contentSecurityPolicy;
|
|
942
|
+
let cspVal = '';
|
|
943
|
+
let cspDisabled = false;
|
|
944
|
+
if (csp === false)
|
|
945
|
+
cspDisabled = true;
|
|
946
|
+
else if (typeof csp === 'string')
|
|
947
|
+
cspVal = csp;
|
|
948
|
+
else if (csp === true)
|
|
949
|
+
cspVal = "default-src 'self'";
|
|
950
|
+
html += '<div class="__kp_setlabel">CONTENT-SECURITY-POLICY</div>';
|
|
951
|
+
html += '<div class="__kp_cfg_row">';
|
|
952
|
+
html += '<input class="__kp_cfg_input" data-sec-key="contentSecurityPolicy" placeholder="default-src \'self\' ..." value="' + escapeHtml(cspVal) + '"' + (cspDisabled ? ' disabled' : '') + '>';
|
|
953
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="contentSecurityPolicy" ' + (cspDisabled ? 'checked' : '') + '> disable</label>';
|
|
954
|
+
html += '</div>';
|
|
955
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="autoEscape" ' + (s.autoEscape !== false ? 'checked' : '') + '> AUTO ESCAPE</label>';
|
|
956
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="csrf" ' + (s.csrf !== false ? 'checked' : '') + '> CSRF</label>';
|
|
957
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="redactLogs" ' + (s.redactLogs !== false ? 'checked' : '') + '> REDACT LOGS</label>';
|
|
958
|
+
const tp = s.trustProxy;
|
|
959
|
+
let tpVal = '';
|
|
960
|
+
let tpChecked = false;
|
|
961
|
+
if (typeof tp === 'string')
|
|
962
|
+
tpVal = tp;
|
|
963
|
+
else if (tp === true)
|
|
964
|
+
tpChecked = true;
|
|
965
|
+
html += '<div class="__kp_setlabel">TRUST PROXY</div>';
|
|
966
|
+
html += '<div class="__kp_cfg_row">';
|
|
967
|
+
html += '<input class="__kp_cfg_input" data-sec-key="trustProxy" placeholder="127.0.0.1 or CIDR" value="' + escapeHtml(tpVal) + '">';
|
|
968
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="trustProxy" ' + (tpChecked ? 'checked' : '') + '> enabled</label>';
|
|
969
|
+
html += '</div>';
|
|
970
|
+
// cors
|
|
971
|
+
const cors = (s.cors || {});
|
|
972
|
+
const corsOrigin = Array.isArray(cors.origin) ? cors.origin.join(', ') : typeof cors.origin === 'string' ? cors.origin : '';
|
|
973
|
+
html += '<div class="__kp_setlabel">CORS ORIGIN</div>';
|
|
974
|
+
html += '<input class="__kp_cfg_input" data-sec-key="cors.origin" placeholder="* or https://example.com" value="' + escapeHtml(corsOrigin) + '">';
|
|
975
|
+
html += '<div class="__kp_setlabel">CORS METHODS</div>';
|
|
976
|
+
html += '<input class="__kp_cfg_input" data-sec-key="cors.methods" placeholder="GET, POST" value="' + escapeHtml(typeof cors.methods === 'string' ? cors.methods : '') + '">';
|
|
977
|
+
html += '<div class="__kp_setlabel">CORS HEADERS</div>';
|
|
978
|
+
html += '<input class="__kp_cfg_input" data-sec-key="cors.headers" placeholder="Content-Type" value="' + escapeHtml(typeof cors.headers === 'string' ? cors.headers : '') + '">';
|
|
979
|
+
html += '<label class="__kp_cfg_check"><input type="checkbox" data-sec-toggle="cors.credentials" ' + (cors.credentials ? 'checked' : '') + '> CORS CREDENTIALS</label>';
|
|
980
|
+
html += '<div class="__kp_setlabel">CORS MAX AGE</div>';
|
|
981
|
+
html += '<input class="__kp_cfg_input" data-sec-key="cors.maxAge" type="number" min="0" value="' + escapeHtml(String(cors.maxAge != null ? cors.maxAge : '')) + '">';
|
|
982
|
+
// rateLimit
|
|
983
|
+
const rl = (s.rateLimit || {});
|
|
984
|
+
html += '<div class="__kp_setlabel">RATE LIMIT WINDOW MS</div>';
|
|
985
|
+
html += '<input class="__kp_cfg_input" data-sec-key="rateLimit.windowMs" type="number" min="0" value="' + escapeHtml(String(rl.windowMs != null ? rl.windowMs : '')) + '">';
|
|
986
|
+
html += '<div class="__kp_setlabel">RATE LIMIT MAX</div>';
|
|
987
|
+
html += '<input class="__kp_cfg_input" data-sec-key="rateLimit.max" type="number" min="0" value="' + escapeHtml(String(rl.max != null ? rl.max : '')) + '">';
|
|
988
|
+
}
|
|
989
|
+
// ── Raw editor ────────────────────────────────────────────────────
|
|
990
|
+
html += '<div class="__kp_sec">> RAW vesk.config.ts</div>';
|
|
991
|
+
const src = state.draftSource != null ? state.draftSource : state.source;
|
|
992
|
+
html += '<textarea class="__kp_cfg_textarea" data-cfg-source rows="12" spellcheck="false">' + escapeHtml(src) + '</textarea>';
|
|
993
|
+
html += '<div class="__kp_cfg_row">';
|
|
994
|
+
html += '<button class="__kp_pl_btn" data-cfg-save="1"' + (state.saving ? ' disabled' : '') + '>' + (state.saving ? 'saving...' : 'save source') + '</button>';
|
|
995
|
+
html += '<button class="__kp_pl_btn" data-cfg-reload="1">reload</button>';
|
|
996
|
+
html += '</div>';
|
|
997
|
+
html += '<div class="__kp_cfg_hint">POST /__vesk/config {source} — invalid source returns 400 and never clobbers</div>';
|
|
998
|
+
return html;
|
|
999
|
+
}
|
|
1000
|
+
export function renderSettingsWithConfig(prefs, activeSubtab, cfgState) {
|
|
1001
|
+
const norm = activeSubtab === 'prefs' ? 'devtools' : activeSubtab === 'config' ? 'vesk' : activeSubtab;
|
|
1002
|
+
if (isSettingsSubTab(norm)) {
|
|
1003
|
+
return renderSettingsPanel(prefs, null, cfgState || null, norm);
|
|
1004
|
+
}
|
|
1005
|
+
return renderSettingsPanel(prefs, null, cfgState || null, 'devtools');
|
|
1006
|
+
}
|
|
1007
|
+
export function agenticProviderLabel(p) {
|
|
1008
|
+
switch (p) {
|
|
1009
|
+
case 'openai': return 'OpenAI';
|
|
1010
|
+
case 'anthropic': return 'Anthropic';
|
|
1011
|
+
case 'google': return 'Google Gemini';
|
|
1012
|
+
case 'ollama': return 'Ollama';
|
|
1013
|
+
default: return p;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
export function buildAgenticModelsUrl(provider, baseUrl) {
|
|
1017
|
+
let url = AGENTIC_MODELS_URL + '?provider=' + encodeURIComponent(provider);
|
|
1018
|
+
if (baseUrl && baseUrl.trim())
|
|
1019
|
+
url += '&baseUrl=' + encodeURIComponent(baseUrl.trim());
|
|
1020
|
+
return url;
|
|
1021
|
+
}
|
|
1022
|
+
export function filterAgenticModels(models, query) {
|
|
1023
|
+
if (!query)
|
|
1024
|
+
return models.slice();
|
|
1025
|
+
const q = query.toLowerCase();
|
|
1026
|
+
const out = [];
|
|
1027
|
+
for (const m of models) {
|
|
1028
|
+
if (m.toLowerCase().indexOf(q) !== -1)
|
|
1029
|
+
out.push(m);
|
|
1030
|
+
}
|
|
1031
|
+
return out;
|
|
1032
|
+
}
|
|
1033
|
+
export function renderAgenticProviderSelect(provider) {
|
|
1034
|
+
let html = '<div class="__kp_setlabel">PROVIDER</div><div class="__kp_optrow" data-agentic="provider">';
|
|
1035
|
+
for (const p of AGENTIC_PROVIDERS) {
|
|
1036
|
+
html +=
|
|
1037
|
+
'<button class="__kp_opt' +
|
|
1038
|
+
(p === provider ? ' active' : '') +
|
|
1039
|
+
'" data-agentic-provider="' +
|
|
1040
|
+
escapeHtml(p) +
|
|
1041
|
+
'" data-key="agenticProvider" data-val="' +
|
|
1042
|
+
escapeHtml(p) +
|
|
1043
|
+
'">' +
|
|
1044
|
+
escapeHtml(agenticProviderLabel(p)) +
|
|
1045
|
+
'</button>';
|
|
1046
|
+
}
|
|
1047
|
+
html += '</div>';
|
|
1048
|
+
return html;
|
|
1049
|
+
}
|
|
1050
|
+
export function renderAgenticModelSelect(model, models, loading, error) {
|
|
1051
|
+
let html = '<div class="__kp_setlabel">MODEL <span class="__kp_ag_hint">via ' + escapeHtml(AGENTIC_MODELS_URL) + '</span></div>';
|
|
1052
|
+
html += '<div class="__kp_ag_model_row">';
|
|
1053
|
+
html +=
|
|
1054
|
+
'<button class="__kp_pl_btn" data-agentic-refresh-models="1"' + (loading ? ' disabled' : '') + '>' + (loading ? 'loading...' : 'refresh') + '</button>';
|
|
1055
|
+
html += '</div>';
|
|
1056
|
+
if (error)
|
|
1057
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(String(error)) + '</div>';
|
|
1058
|
+
if (loading)
|
|
1059
|
+
html += '<div class="__kp_line">loading models from ' + escapeHtml(AGENTIC_MODELS_URL) + '...</div>';
|
|
1060
|
+
html += '<div class="__kp_ag_models" data-agentic="models">';
|
|
1061
|
+
if (models.length === 0 && !loading) {
|
|
1062
|
+
html += '<div class="__kp_line">no models — click refresh</div>';
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
for (const m of models) {
|
|
1066
|
+
html +=
|
|
1067
|
+
'<button class="__kp_opt' +
|
|
1068
|
+
(m === model ? ' active' : '') +
|
|
1069
|
+
'" data-agentic-model="' +
|
|
1070
|
+
escapeHtml(m) +
|
|
1071
|
+
'" data-key="agenticModel" data-val="' +
|
|
1072
|
+
escapeHtml(m) +
|
|
1073
|
+
'">' +
|
|
1074
|
+
escapeHtml(m) +
|
|
1075
|
+
'</button>';
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
html += '</div>';
|
|
1079
|
+
return html;
|
|
1080
|
+
}
|
|
1081
|
+
export function renderAgenticModeToggle(mode) {
|
|
1082
|
+
let html = '<div class="__kp_setlabel">MODE</div><div class="__kp_optrow" data-agentic="mode">';
|
|
1083
|
+
for (const m of AGENTIC_MODES) {
|
|
1084
|
+
html +=
|
|
1085
|
+
'<button class="__kp_opt' +
|
|
1086
|
+
(m === mode ? ' active' : '') +
|
|
1087
|
+
'" data-agentic-mode="' +
|
|
1088
|
+
escapeHtml(m) +
|
|
1089
|
+
'" data-key="agenticMode" data-val="' +
|
|
1090
|
+
escapeHtml(m) +
|
|
1091
|
+
'">' +
|
|
1092
|
+
escapeHtml(m) +
|
|
1093
|
+
'</button>';
|
|
1094
|
+
}
|
|
1095
|
+
html += '</div>';
|
|
1096
|
+
return html;
|
|
1097
|
+
}
|
|
1098
|
+
export function renderAgenticMessages(messages) {
|
|
1099
|
+
if (!messages || messages.length === 0) {
|
|
1100
|
+
return '<div class="__kp_line">no messages yet — start a conversation</div>';
|
|
1101
|
+
}
|
|
1102
|
+
let html = '<div class="__kp_ag_messages" data-agentic="messages">';
|
|
1103
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1104
|
+
const m = messages[i];
|
|
1105
|
+
const role = m.role || 'user';
|
|
1106
|
+
const time = m.ts ? new Date(m.ts).toLocaleTimeString() : '';
|
|
1107
|
+
html +=
|
|
1108
|
+
'<div class="__kp_ag_msg" data-role="' +
|
|
1109
|
+
escapeHtml(role) +
|
|
1110
|
+
'" data-idx="' +
|
|
1111
|
+
i +
|
|
1112
|
+
'">' +
|
|
1113
|
+
'<div class="__kp_ag_msg_head"><span class="__kp_ag_role">' +
|
|
1114
|
+
escapeHtml(role) +
|
|
1115
|
+
'</span>' +
|
|
1116
|
+
(time ? '<span class="__kp_ag_time">' + escapeHtml(time) + '</span>' : '') +
|
|
1117
|
+
'</div>' +
|
|
1118
|
+
'<div class="__kp_ag_msg_body">' +
|
|
1119
|
+
escapeHtml(m.content) +
|
|
1120
|
+
'</div>' +
|
|
1121
|
+
'</div>';
|
|
1122
|
+
}
|
|
1123
|
+
html += '</div>';
|
|
1124
|
+
return html;
|
|
1125
|
+
}
|
|
1126
|
+
export function renderAgenticHistory(history, loading) {
|
|
1127
|
+
// Single stable block node so the client can refresh checkpoints in place
|
|
1128
|
+
// without re-rendering the whole panel (which would drop the chat messages
|
|
1129
|
+
// host and make the panel "jump").
|
|
1130
|
+
let html = '<div class="__kp_ag_hist_block" data-agentic="history-block">';
|
|
1131
|
+
html += '<div class="__kp_sec">> HISTORY</div>';
|
|
1132
|
+
if (loading)
|
|
1133
|
+
html += '<div class="__kp_line">loading history...</div>';
|
|
1134
|
+
if (!history || history.length === 0) {
|
|
1135
|
+
if (!loading)
|
|
1136
|
+
html += '<div class="__kp_line">no checkpoints yet</div>';
|
|
1137
|
+
html += '</div>';
|
|
1138
|
+
return html;
|
|
1139
|
+
}
|
|
1140
|
+
html += '<div class="__kp_ag_history" data-agentic="history">';
|
|
1141
|
+
for (const cp of history) {
|
|
1142
|
+
const when = cp.timestamp ? new Date(cp.timestamp).toLocaleString() : '';
|
|
1143
|
+
const label = cp.label || cp.message || cp.id;
|
|
1144
|
+
html +=
|
|
1145
|
+
'<div class="__kp_ag_hist_row" data-checkpoint="' +
|
|
1146
|
+
escapeHtml(cp.id) +
|
|
1147
|
+
'">' +
|
|
1148
|
+
'<div class="__kp_ag_hist_info">' +
|
|
1149
|
+
'<div class="__kp_ag_hist_label">' +
|
|
1150
|
+
escapeHtml(label) +
|
|
1151
|
+
'</div>' +
|
|
1152
|
+
'<div class="__kp_ag_hist_meta">' +
|
|
1153
|
+
escapeHtml(when) +
|
|
1154
|
+
' id:' +
|
|
1155
|
+
escapeHtml(cp.id) +
|
|
1156
|
+
'</div>' +
|
|
1157
|
+
'</div>' +
|
|
1158
|
+
'<button class="__kp_pl_btn" data-agentic-rollback="' +
|
|
1159
|
+
escapeHtml(cp.id) +
|
|
1160
|
+
'">rollback</button>' +
|
|
1161
|
+
'</div>';
|
|
1162
|
+
}
|
|
1163
|
+
html += '</div>';
|
|
1164
|
+
html += '</div>';
|
|
1165
|
+
return html;
|
|
1166
|
+
}
|
|
1167
|
+
export function renderAgenticSlashHint() {
|
|
1168
|
+
let html = '<div class="__kp_ag_slash" data-agentic="slash-hint">';
|
|
1169
|
+
html += '<span class="__kp_ag_slash_label">slash commands:</span> ';
|
|
1170
|
+
for (let i = 0; i < AGENTIC_SLASH_COMMANDS.length; i++) {
|
|
1171
|
+
const cmd = AGENTIC_SLASH_COMMANDS[i];
|
|
1172
|
+
html += '<code class="__kp_ag_slash_cmd">' + escapeHtml(cmd) + '</code>';
|
|
1173
|
+
if (i < AGENTIC_SLASH_COMMANDS.length - 1)
|
|
1174
|
+
html += ' ';
|
|
1175
|
+
}
|
|
1176
|
+
html += '<span class="__kp_ag_hint"> — type / to see hints</span>';
|
|
1177
|
+
html += '</div>';
|
|
1178
|
+
return html;
|
|
1179
|
+
}
|
|
1180
|
+
export function getAgenticSlashSuggestions(input, models, providers = AGENTIC_PROVIDERS, tools = []) {
|
|
1181
|
+
if (!input || !input.startsWith('/'))
|
|
1182
|
+
return [];
|
|
1183
|
+
const trimmed = input.trim();
|
|
1184
|
+
const spaceIdx = trimmed.indexOf(' ');
|
|
1185
|
+
const cmd = spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx);
|
|
1186
|
+
const arg = spaceIdx === -1 ? '' : trimmed.slice(spaceIdx + 1);
|
|
1187
|
+
const q = arg.toLowerCase();
|
|
1188
|
+
if (cmd === '/models' || cmd === '/model') {
|
|
1189
|
+
const filtered = filterAgenticModels(models, q);
|
|
1190
|
+
return filtered.slice(0, 20).map((m) => ({ label: m, value: cmd + ' ' + m, desc: 'model' }));
|
|
1191
|
+
}
|
|
1192
|
+
if (cmd === '/provider') {
|
|
1193
|
+
const filtered = providers.filter((p) => !q || p.toLowerCase().indexOf(q) !== -1);
|
|
1194
|
+
return filtered.map((p) => ({ label: p, value: '/provider ' + p, desc: agenticProviderLabel(p) }));
|
|
1195
|
+
}
|
|
1196
|
+
if (cmd === '/mode') {
|
|
1197
|
+
const modes = ['explore', 'debug', 'agent'];
|
|
1198
|
+
const filtered = modes.filter((m) => !q || m.indexOf(q) !== -1);
|
|
1199
|
+
return filtered.map((m) => ({ label: m, value: '/mode ' + m }));
|
|
1200
|
+
}
|
|
1201
|
+
if (cmd === '/tools' || cmd === '/tool') {
|
|
1202
|
+
const t = tools.length ? tools : ['vesk.inspectProject', 'filesystem.write', 'web.search', 'browser.open'];
|
|
1203
|
+
const filtered = t.filter((x) => !q || x.toLowerCase().indexOf(q) !== -1);
|
|
1204
|
+
return filtered.slice(0, 20).map((x) => ({ label: x, value: '/tool ' + x }));
|
|
1205
|
+
}
|
|
1206
|
+
if (trimmed === '/' || cmd === '/') {
|
|
1207
|
+
return AGENTIC_SLASH_COMMANDS.map((c) => ({ label: c, value: c }));
|
|
1208
|
+
}
|
|
1209
|
+
const filteredCmds = AGENTIC_SLASH_COMMANDS.filter((c) => c.toLowerCase().indexOf(cmd.toLowerCase()) !== -1);
|
|
1210
|
+
return filteredCmds.map((c) => ({ label: c, value: c }));
|
|
1211
|
+
}
|
|
1212
|
+
export function renderAgenticSlashPopup(input, models, providers = AGENTIC_PROVIDERS, tools = []) {
|
|
1213
|
+
const suggestions = getAgenticSlashSuggestions(input, models, providers, tools);
|
|
1214
|
+
if (suggestions.length === 0)
|
|
1215
|
+
return '';
|
|
1216
|
+
let html = '<div class="__kp_ag_popup" data-agentic-popup="1">';
|
|
1217
|
+
for (const s of suggestions) {
|
|
1218
|
+
html +=
|
|
1219
|
+
'<button class="__kp_ag_popup_item" data-agentic-pick="' +
|
|
1220
|
+
escapeHtml(s.value) +
|
|
1221
|
+
'">' +
|
|
1222
|
+
'<span class="__kp_ag_popup_label">' +
|
|
1223
|
+
escapeHtml(s.label) +
|
|
1224
|
+
'</span>' +
|
|
1225
|
+
(s.desc ? '<span class="__kp_ag_popup_desc">' + escapeHtml(s.desc) + '</span>' : '') +
|
|
1226
|
+
'</button>';
|
|
1227
|
+
}
|
|
1228
|
+
html += '</div>';
|
|
1229
|
+
return html;
|
|
1230
|
+
}
|
|
1231
|
+
export function renderAgenticChatInput(input, running, models = [], providers = AGENTIC_PROVIDERS, tools = []) {
|
|
1232
|
+
const popup = !running && input && input.startsWith('/') ? renderAgenticSlashPopup(input, models, providers, tools) : '';
|
|
1233
|
+
return ('<div class="__kp_ag_input_wrap">' +
|
|
1234
|
+
popup +
|
|
1235
|
+
'<div class="__kp_ag_input_row">' +
|
|
1236
|
+
'<input class="__kp_ag_input" data-agentic-input="1" placeholder="ask agentic... (try /help)" value="' +
|
|
1237
|
+
escapeHtml(input || '') +
|
|
1238
|
+
'"' +
|
|
1239
|
+
(running ? ' disabled' : '') +
|
|
1240
|
+
' autocomplete="off">' +
|
|
1241
|
+
'<button class="__kp_pl_btn" data-agentic-send="1"' +
|
|
1242
|
+
(running ? ' disabled' : '') +
|
|
1243
|
+
'>' +
|
|
1244
|
+
(running ? 'running...' : 'send') +
|
|
1245
|
+
'</button>' +
|
|
1246
|
+
'</div>' +
|
|
1247
|
+
'</div>');
|
|
1248
|
+
}
|
|
1249
|
+
export function renderAgenticPanel(state) {
|
|
1250
|
+
const provider = state.provider || 'openai';
|
|
1251
|
+
const model = typeof state.model === 'string' ? state.model : '';
|
|
1252
|
+
const models = Array.isArray(state.models) ? state.models : [];
|
|
1253
|
+
const mode = isAgenticMode(state.mode) ? state.mode : 'explore';
|
|
1254
|
+
const messages = Array.isArray(state.messages) ? state.messages : [];
|
|
1255
|
+
const history = Array.isArray(state.history) ? state.history : [];
|
|
1256
|
+
let html = '<div class="__kp_sec">> AGENTIC</div>';
|
|
1257
|
+
html += renderAgenticProviderSelect(provider);
|
|
1258
|
+
html += renderAgenticModelSelect(model, models, !!state.modelsLoading, state.modelsError || null);
|
|
1259
|
+
html += renderAgenticModeToggle(mode);
|
|
1260
|
+
if (state.error)
|
|
1261
|
+
html += '<div class="__kp_pl_err">' + escapeHtml(String(state.error)) + '</div>';
|
|
1262
|
+
html += '<div class="__kp_sec">> CHAT</div>';
|
|
1263
|
+
html += renderAgenticMessages(messages);
|
|
1264
|
+
html += renderAgenticChatInput(state.input || '', !!state.running);
|
|
1265
|
+
html += renderAgenticSlashHint();
|
|
1266
|
+
html += renderAgenticHistory(history, !!state.historyLoading);
|
|
1267
|
+
return html;
|
|
1268
|
+
}
|
|
1269
|
+
export function resolveUrls(opts) {
|
|
1270
|
+
return {
|
|
1271
|
+
wsUrl: (opts && opts.wsUrl) || '',
|
|
1272
|
+
stateUrl: (opts && opts.stateUrl) || '/__vesk/hmr/state',
|
|
1273
|
+
pluginsUrl: (opts && opts.pluginsUrl) || '/__vesk/plugins',
|
|
1274
|
+
diagnosticsUrl: (opts && opts.diagnosticsUrl) || '/__vesk/diagnostics',
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
export function registerGlobalHmr(api) {
|
|
1278
|
+
const g = globalThis;
|
|
1279
|
+
g.__vesk_hmr_show = api.show;
|
|
1280
|
+
g.__vesk_hmr_dismiss = api.dismiss;
|
|
1281
|
+
}
|
|
1282
|
+
const CSS = '@keyframes __v_pulse{0%,100%{opacity:1}50%{opacity:.3}}' +
|
|
1283
|
+
'@keyframes __v_in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}' +
|
|
1284
|
+
'@keyframes __v_tab{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}' +
|
|
1285
|
+
'@keyframes __v_vo{from{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}' +
|
|
1286
|
+
'#__vesk_dev,#__vesk_overlay{--vk-bg:#000;--vk-fg:#fff;--vk-border:#fff;--vk-line:#444;--vk-line-soft:#333;--vk-soft:#111;--vk-soft-hi:#222;--vk-muted:#bbb;--vk-dim:#666;--vk-codebg:#0a0a0a;--vk-inv-bg:#fff;--vk-inv-fg:#000;--vk-dot:#555;}' +
|
|
1287
|
+
'#__vesk_dev[data-theme="light"],#__vesk_overlay[data-theme="light"]{--vk-bg:#fff;--vk-fg:#111;--vk-border:#111;--vk-line:#ccc;--vk-line-soft:#ddd;--vk-soft:#f5f5f5;--vk-soft-hi:#ececec;--vk-muted:#444;--vk-dim:#888;--vk-codebg:#fafafa;--vk-inv-bg:#111;--vk-inv-fg:#fff;--vk-dot:#999;}' +
|
|
1288
|
+
'#__vesk_dev *,#__vesk_overlay *{transition:background .18s ease,color .18s ease,border-color .18s ease;}' +
|
|
1289
|
+
'#__vesk_dev,#__vesk_dev *,#__vesk_overlay,#__vesk_overlay *{scrollbar-width:none;}' +
|
|
1290
|
+
'#__vesk_dev *::-webkit-scrollbar,#__vesk_overlay *::-webkit-scrollbar{display:none;}' +
|
|
1291
|
+
'#__vesk_dev{all:initial;position:fixed;bottom:16px;right:16px;z-index:2147483647;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;color:var(--vk-fg);}' +
|
|
1292
|
+
'#__vesk_dev[data-pos="left"]{left:16px;right:auto;}' +
|
|
1293
|
+
'#__vesk_dev .__v_bar{display:flex;align-items:center;gap:8px;background:var(--vk-bg);border:1px solid var(--vk-border);border-radius:0;padding:6px 12px;cursor:pointer;animation:__v_in .3s cubic-bezier(.22,.61,.36,1);transition:background .15s ease,transform .15s ease,border-color .15s ease;}' +
|
|
1294
|
+
'#__vesk_dev .__v_bar:hover{background:var(--vk-soft);transform:translateY(-1px);border-color:var(--vk-dim);}' +
|
|
1295
|
+
'#__vesk_dev .__v_dot{width:8px;height:8px;flex-shrink:0;background:var(--vk-dot);transition:background .12s ease;}' +
|
|
1296
|
+
'#__vesk_dev .__v_dot.compiling{background:var(--vk-fg);animation:__v_pulse .8s infinite;}' +
|
|
1297
|
+
'#__vesk_dev .__v_dot.error{background:var(--vk-fg);animation:__v_pulse .4s infinite;}' +
|
|
1298
|
+
'#__vesk_overlay{all:initial;position:fixed;inset:0;z-index:2147483646;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;color:var(--vk-fg);display:flex;visibility:hidden;opacity:0;pointer-events:none;transition:opacity .16s ease,visibility 0s linear .16s;}' +
|
|
1299
|
+
'#__vesk_overlay.open{visibility:visible;opacity:1;pointer-events:auto;transition:opacity .16s ease;}' +
|
|
1300
|
+
'#__vesk_overlay .__vo_backdrop{position:absolute;inset:0;background:var(--vk-bg);opacity:.85;}' +
|
|
1301
|
+
'#__vesk_overlay .__vo_panel{position:relative;margin:32px auto;max-width:820px;width:90%;max-height:calc(100vh - 64px);background:var(--vk-bg);border:1px solid var(--vk-border);border-radius:0;display:flex;flex-direction:column;overflow:hidden;animation:__v_vo .22s cubic-bezier(.22,.61,.36,1);}' +
|
|
1302
|
+
'#__vesk_overlay .__vo_header{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--vk-line);flex-shrink:0;}' +
|
|
1303
|
+
'#__vesk_overlay .__vo_title{font-size:13px;font-weight:700;color:var(--vk-fg);flex:1;text-transform:uppercase;letter-spacing:.08em;}' +
|
|
1304
|
+
'#__vesk_overlay .__vo_close{all:unset;cursor:pointer;width:24px;height:24px;display:flex;align-items:center;justify-content:center;color:var(--vk-muted);font-size:16px;}' +
|
|
1305
|
+
'#__vesk_overlay .__vo_close:hover{color:var(--vk-fg);background:var(--vk-soft-hi);}' +
|
|
1306
|
+
'#__vesk_overlay .__vo_body{overflow-y:auto;padding:16px;flex:1;}' +
|
|
1307
|
+
'#__vesk_overlay .__vo_file{font-size:12px;color:var(--vk-muted);margin-bottom:8px;white-space:pre-wrap;word-break:break-all;}' +
|
|
1308
|
+
'#__vesk_overlay .__vo_file strong{color:var(--vk-fg);}' +
|
|
1309
|
+
'#__vesk_overlay .__vo_message{font-size:13px;color:var(--vk-inv-fg);background:var(--vk-inv-bg);padding:8px 12px;margin-bottom:12px;white-space:pre-wrap;word-break:break-all;font-weight:700;}' +
|
|
1310
|
+
'#__vesk_overlay .__vo_code{background:var(--vk-codebg);border:1px solid var(--vk-line-soft);border-radius:0;padding:12px;margin-bottom:12px;overflow-x:auto;font-size:12px;line-height:1.6;}' +
|
|
1311
|
+
'#__vesk_overlay .__vo_code .kf-ln{color:var(--vk-dim);user-select:none;margin-right:8px;}' +
|
|
1312
|
+
'#__vesk_overlay .__vo_code .kf-err{background:var(--vk-inv-bg);color:var(--vk-inv-fg);width:100%;}' +
|
|
1313
|
+
'#__vesk_overlay .__vo_code .kf-err .kf-ln{color:var(--vk-inv-fg);}' +
|
|
1314
|
+
'#__vesk_overlay .__vo_code .kf-caret{color:var(--vk-inv-fg);font-weight:700;}' +
|
|
1315
|
+
'#__vesk_overlay .__vo_tips{margin-top:8px;}' +
|
|
1316
|
+
'#__vesk_overlay .kf-sec{margin-top:10px;}' +
|
|
1317
|
+
'#__vesk_overlay .kf-sec-t{font-size:11px;font-weight:700;color:var(--vk-fg);margin-bottom:4px;text-transform:uppercase;letter-spacing:.08em;}' +
|
|
1318
|
+
'#__vesk_overlay .kf-item{padding:2px 0 2px 16px;font-size:12px;color:var(--vk-muted);position:relative;}' +
|
|
1319
|
+
'#__vesk_overlay .kf-item::before{content:"\\2192";position:absolute;left:0;color:var(--vk-fg);}' +
|
|
1320
|
+
'#__vesk_overlay .__vo_stack{border:1px solid var(--vk-line-soft);margin-top:12px;}' +
|
|
1321
|
+
'#__vesk_overlay .__vo_stack summary{cursor:pointer;font-size:11px;color:var(--vk-muted);padding:6px 10px;text-transform:uppercase;letter-spacing:.08em;}' +
|
|
1322
|
+
'#__vesk_overlay .__vo_stack pre{background:var(--vk-codebg);padding:8px 10px;margin:0;font-size:11px;color:var(--vk-muted);max-height:200px;overflow:auto;white-space:pre;border-top:1px solid var(--vk-line-soft);}' +
|
|
1323
|
+
'#__vesk_overlay .__vo_footer{padding:8px 16px;border-top:1px solid var(--vk-line);font-size:11px;color:var(--vk-dim);text-align:center;flex-shrink:0;}' +
|
|
1324
|
+
'#__vesk_dev .__kp{all:initial;position:fixed;inset:auto 16px 60px auto;width:520px;max-width:calc(100vw - 32px);max-height:calc(100vh - 24px);min-width:320px;min-height:200px;background:var(--vk-bg);border:1px solid var(--vk-border);border-radius:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.5;color:var(--vk-fg);display:flex;flex-direction:column;overflow:hidden;z-index:2147483647;visibility:visible;opacity:1;transform:translateY(0) scale(1);transition:opacity .18s ease,transform .18s cubic-bezier(.22,.61,.36,1),width .24s cubic-bezier(.22,.61,.36,1),height .24s cubic-bezier(.22,.61,.36,1),background .18s ease,color .18s ease,border-color .18s ease;}' +
|
|
1325
|
+
'#__vesk_dev[data-pos="left"] .__kp{left:16px;right:auto;}' +
|
|
1326
|
+
'#__vesk_dev .__kp.hidden{visibility:hidden;opacity:0;transform:translateY(10px) scale(.98);pointer-events:none;}' +
|
|
1327
|
+
'#__vesk_dev .__kp.resizing{transition:none;}' +
|
|
1328
|
+
'#__vesk_dev .__kp.maxed{width:min(560px,50vw);height:min(480px,50vh);max-width:none;max-height:none;}' +
|
|
1329
|
+
'#__vesk_dev .__kp_handle{position:absolute;right:0;bottom:0;width:20px;height:20px;cursor:nwse-resize;touch-action:none;z-index:2;}' +
|
|
1330
|
+
'#__vesk_dev .__kp_handle::before{content:"";position:absolute;right:4px;bottom:4px;width:10px;height:10px;border-right:2px solid var(--vk-dim);border-bottom:2px solid var(--vk-dim);}' +
|
|
1331
|
+
'#__vesk_dev .__kp_handle:hover::before,#__vesk_dev .__kp_handle.resizing::before{border-color:var(--vk-fg);}' +
|
|
1332
|
+
'#__vesk_dev .__kp.maxed .__kp_handle{display:none;}' +
|
|
1333
|
+
'#__vesk_dev .__kp_head{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--vk-line);flex-shrink:0;}' +
|
|
1334
|
+
'#__vesk_dev .__kp_title{font-weight:700;text-transform:uppercase;letter-spacing:.1em;flex:1;}' +
|
|
1335
|
+
'#__vesk_dev .__kp_tabs{display:flex;border-bottom:1px solid var(--vk-line);flex-shrink:0;padding:0 4px;overflow-x:auto;}' +
|
|
1336
|
+
'#__vesk_dev .__kp_tab{all:unset;cursor:pointer;font-family:inherit;font-size:11px;font-weight:700;color:var(--vk-muted);text-transform:uppercase;letter-spacing:.1em;padding:8px 12px;border-right:1px solid var(--vk-line-soft);white-space:nowrap;transition:color .12s ease,background .12s ease;}' +
|
|
1337
|
+
'#__vesk_dev .__kp_tab:hover{color:var(--vk-fg);background:var(--vk-soft);}' +
|
|
1338
|
+
'#__vesk_dev .__kp_tab.active{color:var(--vk-inv-fg);background:var(--vk-inv-bg);}' +
|
|
1339
|
+
'#__vesk_dev .__kp_body{overflow:hidden;flex:1;display:flex;flex-direction:column;min-height:0;}' +
|
|
1340
|
+
'#__vesk_dev .__kp_body #__kp_content{flex:1;display:flex;flex-direction:column;min-height:0;min-width:0;}' +
|
|
1341
|
+
'#__vesk_dev .__kp_pane{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;scroll-behavior:smooth;padding:12px;animation:__v_tab .18s cubic-bezier(.22,.61,.36,1);min-height:0;}' +
|
|
1342
|
+
'#__vesk_dev .__kp_pane::-webkit-scrollbar{display:none;}' +
|
|
1343
|
+
'#__vesk_dev .__kp_max{all:unset;cursor:pointer;color:var(--vk-muted);font-size:14px;padding:0 4px;transition:color .12s ease,background .12s ease;}' +
|
|
1344
|
+
'#__vesk_dev .__kp_max:hover{color:var(--vk-fg);background:var(--vk-soft-hi);}' +
|
|
1345
|
+
'#__vesk_dev .__kp_close{all:unset;cursor:pointer;color:var(--vk-muted);font-size:14px;transition:color .12s ease,background .12s ease;}' +
|
|
1346
|
+
'#__vesk_dev .__kp_close:hover{color:var(--vk-fg);background:var(--vk-soft-hi);}' +
|
|
1347
|
+
'#__vesk_dev .__kp_sec{font-size:11px;font-weight:700;color:var(--vk-fg);text-transform:uppercase;letter-spacing:.1em;border-bottom:1px solid var(--vk-line-soft);padding-bottom:4px;margin:14px 0 8px;}' +
|
|
1348
|
+
'#__vesk_dev .__kp_sec:first-child{margin-top:0;}' +
|
|
1349
|
+
'#__vesk_dev .__kp_setlabel{font-size:11px;font-weight:700;color:var(--vk-muted);text-transform:uppercase;letter-spacing:.1em;margin-top:6px;}' +
|
|
1350
|
+
'#__vesk_dev .__kp_optrow{display:flex;gap:6px;flex-wrap:wrap;margin:6px 0 14px;}' +
|
|
1351
|
+
'#__vesk_dev .__kp_ag_models{display:flex;gap:6px;flex-wrap:wrap;max-height:150px;overflow-y:auto;margin:6px 0 14px;padding:2px;}' +
|
|
1352
|
+
'#__vesk_dev .__kp_ag_model_row{display:flex;gap:8px;align-items:center;margin:6px 0 4px;}' +
|
|
1353
|
+
'#__vesk_dev .__kp_opt{all:unset;cursor:pointer;font-family:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--vk-muted);border:1px solid var(--vk-line-soft);padding:4px 10px;transition:color .12s ease,background .12s ease,border-color .12s ease;}' +
|
|
1354
|
+
'#__vesk_dev .__kp_opt:hover{color:var(--vk-fg);border-color:var(--vk-dim);}' +
|
|
1355
|
+
'#__vesk_dev .__kp_opt.active{color:var(--vk-inv-fg);background:var(--vk-inv-bg);border-color:var(--vk-border);}' +
|
|
1356
|
+
'#__vesk_dev .__kp_line{font-size:12px;color:var(--vk-muted);}' +
|
|
1357
|
+
'#__vesk_dev .__kp_line b{color:var(--vk-fg);}' +
|
|
1358
|
+
'#__vesk_dev .__kp_code{background:var(--vk-codebg);border:1px solid var(--vk-line-soft);padding:10px;font-size:12px;line-height:1.6;}' +
|
|
1359
|
+
'#__vesk_dev .__kp_code .kf-ln{color:var(--vk-dim);user-select:none;margin-right:8px;}' +
|
|
1360
|
+
'#__vesk_dev .__kp_code .kf-err{background:var(--vk-inv-bg);color:var(--vk-inv-fg);width:100%;}' +
|
|
1361
|
+
'#__vesk_dev .__kp_code .kf-err .kf-ln{color:var(--vk-inv-fg);}' +
|
|
1362
|
+
'#__vesk_dev .__kp_code .kf-caret{color:var(--vk-inv-fg);font-weight:700;}' +
|
|
1363
|
+
'#__vesk_dev .__kp_diag{border:1px solid var(--vk-line-soft);padding:8px 10px;margin:6px 0;}' +
|
|
1364
|
+
'#__vesk_dev .__kp_diag[data-severity="error"]{border-left:3px solid var(--vk-inv-bg);}' +
|
|
1365
|
+
'#__vesk_dev .__kp_diag[data-severity="warning"]{border-left:3px solid var(--vk-dim);}' +
|
|
1366
|
+
'#__vesk_dev .__kp_diag[data-severity="info"]{border-left:3px solid var(--vk-dot);}' +
|
|
1367
|
+
'#__vesk_dev .__kp_diag_head{display:flex;gap:8px;align-items:baseline;}' +
|
|
1368
|
+
'#__vesk_dev .__kp_diag_badge{font-size:10px;font-weight:700;letter-spacing:.05em;flex-shrink:0;}' +
|
|
1369
|
+
'#__vesk_dev .__kp_diag_badge.error{color:var(--vk-inv-fg);background:var(--vk-inv-bg);padding:0 5px;}' +
|
|
1370
|
+
'#__vesk_dev .__kp_diag_badge.warning{color:var(--vk-fg);background:var(--vk-soft-hi);padding:0 5px;}' +
|
|
1371
|
+
'#__vesk_dev .__kp_diag_badge.info{color:var(--vk-muted);padding:0 5px;}' +
|
|
1372
|
+
'#__vesk_dev .__kp_diag_code{font-size:10px;color:var(--vk-dim);font-weight:700;flex-shrink:0;}' +
|
|
1373
|
+
'#__vesk_dev .__kp_diag_msg{font-size:12px;color:var(--vk-fg);}' +
|
|
1374
|
+
'#__vesk_dev .__kp_diag_loc{font-size:11px;color:var(--vk-dim);margin-top:4px;word-break:break-all;}' +
|
|
1375
|
+
'#__vesk_dev .__kp_diag_hint{font-size:11px;color:var(--vk-muted);margin-top:4px;}' +
|
|
1376
|
+
'#__vesk_dev .__kp_item{padding:2px 0 2px 16px;color:var(--vk-muted);position:relative;font-size:12px;}' +
|
|
1377
|
+
'#__vesk_dev .__kp_item::before{content:"\\2192";position:absolute;left:0;color:var(--vk-fg);}' +
|
|
1378
|
+
'#__vesk_dev .__kp_stack summary{cursor:pointer;font-size:11px;color:var(--vk-muted);padding:4px 8px;border:1px solid var(--vk-line-soft);text-transform:uppercase;}' +
|
|
1379
|
+
'#__vesk_dev .__kp_stack pre{background:var(--vk-codebg);border:1px solid var(--vk-line-soft);border-top:none;padding:8px;margin:0;font-size:11px;color:var(--vk-muted);max-height:160px;overflow:auto;white-space:pre;}' +
|
|
1380
|
+
'#__vesk_dev .kp-row{font-size:12px;color:var(--vk-muted);}' +
|
|
1381
|
+
'#__vesk_dev .kp-err{font-size:12px;color:var(--vk-inv-fg);background:var(--vk-inv-bg);padding:6px 10px;font-weight:700;}' +
|
|
1382
|
+
'#__vesk_dev .kl-row{font-size:11px;color:var(--vk-muted);}' +
|
|
1383
|
+
'#__vesk_dev .__kp_tab .__kp_tab_label{display:none;}' +
|
|
1384
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp{display:grid;grid-template-columns:auto minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);grid-template-areas:"head head" "tabs body";gap:0;}' +
|
|
1385
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_head{grid-area:head;}' +
|
|
1386
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_tabs{grid-area:tabs;flex-direction:column;border-bottom:none;border-right:1px solid var(--vk-line);width:52px;flex-shrink:0;align-items:stretch;overflow-y:auto;overflow-x:hidden;}' +
|
|
1387
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_tabs:hover{width:104px;}' +
|
|
1388
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_tab{align-items:center;gap:10px;padding:10px 12px;border-right:none;border-bottom:1px solid var(--vk-line-soft);display:flex;}' +
|
|
1389
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_tab .__kp_tab_glyph{width:22px;height:22px;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:1px solid var(--vk-line-soft);}' +
|
|
1390
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_tab .__kp_tab_label{display:none;white-space:nowrap;}' +
|
|
1391
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_tabs:hover .__kp_tab .__kp_tab_label{display:inline;}' +
|
|
1392
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_body{grid-area:body;flex-direction:column;gap:0;min-height:0;}' +
|
|
1393
|
+
'#__vesk_dev[data-sidebar="rail"] .__kp_pane{flex:1;min-width:0;min-height:0;}' +
|
|
1394
|
+
'.__kp_pl_actions{display:flex;gap:8px;flex-wrap:wrap;margin:10px 0;}' +
|
|
1395
|
+
'#__vesk_dev .__kp_pl_view{all:unset;cursor:pointer;font-family:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--vk-muted);border:1px solid var(--vk-line-soft);padding:4px 10px;transition:color .12s ease,background .12s ease,border-color .12s ease;}' +
|
|
1396
|
+
'#__vesk_dev .__kp_pl_view:hover{color:var(--vk-fg);border-color:var(--vk-dim);}' +
|
|
1397
|
+
'#__vesk_dev .__kp_pl_view.active{color:var(--vk-inv-fg);background:var(--vk-inv-bg);border-color:var(--vk-border);}' +
|
|
1398
|
+
'#__vesk_dev .__kp_pl_grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px;margin-top:8px;}' +
|
|
1399
|
+
'#__vesk_dev .__kp_pl_card{background:var(--vk-soft);border:1px solid var(--vk-line-soft);padding:10px;cursor:pointer;transition:border-color .12s ease,background .12s ease;}' +
|
|
1400
|
+
'#__vesk_dev .__kp_pl_card:hover{border-color:var(--vk-dim);background:var(--vk-soft-hi);}' +
|
|
1401
|
+
'#__vesk_dev .__kp_pl_head{display:flex;align-items:flex-start;gap:8px;}' +
|
|
1402
|
+
'#__vesk_dev .__kp_pl_icon{width:28px;height:28px;flex-shrink:0;border:1px solid var(--vk-line-soft);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:var(--vk-fg);background:var(--vk-codebg);}' +
|
|
1403
|
+
'#__vesk_dev .__kp_pl_icon img{width:100%;height:100%;object-fit:contain;}' +
|
|
1404
|
+
'#__vesk_dev .__kp_pl_name{font-weight:700;color:var(--vk-fg);flex:1;overflow-wrap:anywhere;}' +
|
|
1405
|
+
'#__vesk_dev .__kp_pl_versions{font-size:11px;color:var(--vk-muted);}' +
|
|
1406
|
+
'#__vesk_dev .__kp_pl_desc{font-size:11px;color:var(--vk-muted);margin-top:6px;}' +
|
|
1407
|
+
'#__vesk_dev .__kp_pl_meta{font-size:11px;color:var(--vk-dim);margin-top:6px;word-break:break-all;}' +
|
|
1408
|
+
'#__vesk_dev .__kp_pl_keywords{margin-top:6px;display:flex;flex-wrap:wrap;gap:4px;}' +
|
|
1409
|
+
'#__vesk_dev .__kp_pl_kw{font-size:10px;color:var(--vk-muted);border:1px solid var(--vk-line-soft);padding:1px 6px;}' +
|
|
1410
|
+
'#__vesk_dev .__kp_pl_badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;padding:2px 6px;border:1px solid currentColor;flex-shrink:0;}' +
|
|
1411
|
+
'#__vesk_dev .__kp_pl_badge.ok{color:var(--vk-inv-fg);background:var(--vk-inv-bg);}' +
|
|
1412
|
+
'#__vesk_dev .__kp_pl_badge.off{color:var(--vk-dim);}' +
|
|
1413
|
+
'#__vesk_dev .__kp_pl_badge.on{color:#3fb950;}' +
|
|
1414
|
+
'#__vesk_dev .__kp_pl_err{font-size:11px;color:var(--vk-inv-fg);background:var(--vk-inv-bg);padding:4px 8px;margin-top:6px;font-weight:700;}' +
|
|
1415
|
+
'#__vesk_dev .__kp_pl_list{margin-top:8px;}' +
|
|
1416
|
+
'#__vesk_dev .__kp_pl_row{display:flex;align-items:center;gap:10px;padding:8px 6px;border-bottom:1px solid var(--vk-line-soft);cursor:pointer;}' +
|
|
1417
|
+
'#__vesk_dev .__kp_pl_row:hover{background:var(--vk-soft);}' +
|
|
1418
|
+
'#__vesk_dev .__kp_pl_row_info{flex:1;min-width:0;}' +
|
|
1419
|
+
'#__vesk_dev .__kp_pl_row_name{font-weight:700;color:var(--vk-fg);}' +
|
|
1420
|
+
'#__vesk_dev .__kp_pl_row_sub{font-size:11px;color:var(--vk-dim);}' +
|
|
1421
|
+
'#__vesk_dev .__kp_pl_detail{font-size:12px;color:var(--vk-muted);}' +
|
|
1422
|
+
'#__vesk_dev .__kp_pl_detail b{color:var(--vk-fg);}' +
|
|
1423
|
+
'#__vesk_dev .__kp_pl_btn{all:unset;cursor:pointer;font-family:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--vk-fg);border:1px solid var(--vk-line-soft);padding:4px 8px;transition:color .12s ease,background .12s ease,border-color .12s ease;}' +
|
|
1424
|
+
'#__vesk_dev .__kp_pl_btn:hover{background:var(--vk-soft-hi);border-color:var(--vk-dim);}' +
|
|
1425
|
+
'#__vesk_dev .__kp_pl_btn[disabled]{opacity:.4;cursor:not-allowed;}' +
|
|
1426
|
+
'#__vesk_dev .__kp_pl_btn.danger{color:var(--vk-inv-fg);background:var(--vk-inv-bg);}' +
|
|
1427
|
+
'#__vesk_dev .__kp_pl_pkg{font-size:11px;color:var(--vk-dim);word-break:break-all;margin:2px 0 8px;}' +
|
|
1428
|
+
'#__vesk_dev .__kp_pl_search{display:flex;gap:6px;margin:10px 0;}' +
|
|
1429
|
+
'#__vesk_dev .__kp_pl_q{flex:1;background:var(--vk-codebg);border:1px solid var(--vk-line-soft);color:var(--vk-fg);font-family:inherit;font-size:12px;padding:6px 8px;}' +
|
|
1430
|
+
'#__vesk_dev .__kp_pl_q::placeholder{color:var(--vk-dim);}' +
|
|
1431
|
+
'#__vesk_dev .__kp_pl_suggest{margin:4px 0 10px;}' +
|
|
1432
|
+
'#__vesk_dev .__kp_pl_export{background:var(--vk-codebg);border:1px solid var(--vk-line-soft);padding:10px;margin-top:4px;}' +
|
|
1433
|
+
'#__vesk_dev .__kp_pl_export dt{font-weight:700;color:var(--vk-fg);font-size:11px;text-transform:uppercase;letter-spacing:.08em;margin-top:8px;}' +
|
|
1434
|
+
'#__vesk_dev .__kp_pl_export dd{font-size:11px;color:var(--vk-muted);margin:2px 0 0;word-break:break-all;}' +
|
|
1435
|
+
'#__vesk_dev .__kp_pl_export .__kp_pl_ex_item{font-size:11px;color:var(--vk-muted);padding:1px 0;}' +
|
|
1436
|
+
'#__vesk_dev .__kp_pl_toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:6px;}' +
|
|
1437
|
+
'#__vesk_dev .__kp_ag_select{background:var(--vk-codebg);border:1px solid var(--vk-line-soft);color:var(--vk-fg);font-family:inherit;font-size:11px;padding:4px 8px;min-width:120px;}' +
|
|
1438
|
+
'#__vesk_dev .__kp_ag_select:focus{border-color:var(--vk-dim);outline:none;}' +
|
|
1439
|
+
'#__vesk_dev .__kp_ag_model_row{display:flex;gap:6px;align-items:center;margin:6px 0 8px;}' +
|
|
1440
|
+
'#__vesk_dev .__kp_ag_messages{max-height:240px;overflow-y:auto;border:1px solid var(--vk-line-soft);background:var(--vk-codebg);padding:6px;margin:6px 0;}' +
|
|
1441
|
+
'#__vesk_dev .__kp_ag_msg{border-bottom:1px solid var(--vk-line-soft);padding:6px 0;}' +
|
|
1442
|
+
'#__vesk_dev .__kp_ag_msg:last-child{border-bottom:none;}' +
|
|
1443
|
+
'#__vesk_dev .__kp_ag_msg_head{display:flex;gap:8px;align-items:baseline;font-size:10px;color:var(--vk-dim);text-transform:uppercase;letter-spacing:.06em;}' +
|
|
1444
|
+
'#__vesk_dev .__kp_ag_role{font-weight:700;color:var(--vk-fg);}' +
|
|
1445
|
+
'#__vesk_dev .__kp_ag_time{color:var(--vk-dim);}' +
|
|
1446
|
+
'#__vesk_dev .__kp_ag_msg_body{font-size:12px;color:var(--vk-fg);white-space:pre-wrap;word-break:break-word;margin-top:2px;}' +
|
|
1447
|
+
'#__vesk_dev .__kp_ag_msg[data-role="assistant"] .__kp_ag_msg_body{color:var(--vk-muted);}' +
|
|
1448
|
+
'#__vesk_dev .__kp_ag_msg[data-role="system"] .__kp_ag_msg_body{color:var(--vk-dim);font-style:italic;}' +
|
|
1449
|
+
'#__vesk_dev .__kp_ag_input_wrap{position:sticky;bottom:0;z-index:11;display:flex;flex-direction:column;background:var(--vk-bg);border-top:1px solid var(--vk-line-soft);margin:8px -12px -12px;padding:6px 12px 12px;}' +
|
|
1450
|
+
'#__vesk_dev .__kp_ag_popup{position:absolute;bottom:100%;left:0;right:0;max-height:180px;overflow-y:auto;background:var(--vk-bg);border:1px solid var(--vk-border);border-bottom:none;z-index:10;box-shadow:0 -4px 12px rgba(0,0,0,0.3);}' +
|
|
1451
|
+
'#__vesk_dev .__kp_ag_popup_item{all:unset;display:flex;justify-content:space-between;align-items:center;width:100%;padding:6px 8px;font-family:inherit;font-size:11px;color:var(--vk-fg);cursor:pointer;border-bottom:1px solid var(--vk-line-soft);}' +
|
|
1452
|
+
'#__vesk_dev .__kp_ag_popup_item:hover{background:var(--vk-soft);}' +
|
|
1453
|
+
'#__vesk_dev .__kp_ag_popup_item:active{background:var(--vk-soft-hi);}' +
|
|
1454
|
+
'#__vesk_dev .__kp_ag_popup_label{font-weight:700;}' +
|
|
1455
|
+
'#__vesk_dev .__kp_ag_popup_desc{font-size:10px;color:var(--vk-dim);margin-left:8px;}' +
|
|
1456
|
+
'#__vesk_dev .__kp_ag_input_row{display:flex;gap:6px;margin:8px 0;}' +
|
|
1457
|
+
'#__vesk_dev .__kp_ag_input{flex:1;background:var(--vk-codebg);border:1px solid var(--vk-line-soft);color:var(--vk-fg);font-family:inherit;font-size:12px;padding:6px 8px;}' +
|
|
1458
|
+
'#__vesk_dev .__kp_ag_input::placeholder{color:var(--vk-dim);}' +
|
|
1459
|
+
'#__vesk_dev .__kp_ag_input:focus{border-color:var(--vk-dim);outline:none;}' +
|
|
1460
|
+
'#__vesk_dev .__kp_ag_history{border:1px solid var(--vk-line-soft);margin-top:6px;max-height:200px;overflow-y:auto;}' +
|
|
1461
|
+
'#__vesk_dev .__kp_ag_hist_row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-bottom:1px solid var(--vk-line-soft);}' +
|
|
1462
|
+
'#__vesk_dev .__kp_ag_hist_row:hover{background:var(--vk-soft);}' +
|
|
1463
|
+
'#__vesk_dev .__kp_ag_hist_info{flex:1;min-width:0;}' +
|
|
1464
|
+
'#__vesk_dev .__kp_ag_hist_label{font-size:12px;color:var(--vk-fg);font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}' +
|
|
1465
|
+
'#__vesk_dev .__kp_ag_hist_meta{font-size:10px;color:var(--vk-dim);}' +
|
|
1466
|
+
'#__vesk_dev .__kp_ag_slash{font-size:11px;color:var(--vk-dim);margin:6px 0;padding:4px 6px;border:1px dashed var(--vk-line-soft);background:var(--vk-codebg);}' +
|
|
1467
|
+
'#__vesk_dev .__kp_ag_slash_cmd{font-size:10px;color:var(--vk-muted);background:var(--vk-soft);border:1px solid var(--vk-line-soft);padding:1px 4px;margin-right:2px;}' +
|
|
1468
|
+
'#__vesk_dev .__kp_ag_hint{color:var(--vk-dim);font-size:10px;}' +
|
|
1469
|
+
'#__vesk_dev .__kp_cfg_input{width:100%;background:var(--vk-codebg);border:1px solid var(--vk-line-soft);color:var(--vk-fg);font-family:inherit;font-size:12px;padding:6px 8px;margin:4px 0 8px;box-sizing:border-box;}' +
|
|
1470
|
+
'#__vesk_dev .__kp_cfg_input:focus{border-color:var(--vk-dim);outline:none;}' +
|
|
1471
|
+
'#__vesk_dev .__kp_cfg_input[disabled]{opacity:.4;}' +
|
|
1472
|
+
'#__vesk_dev .__kp_cfg_select{background:var(--vk-codebg);border:1px solid var(--vk-line-soft);color:var(--vk-fg);font-family:inherit;font-size:11px;padding:4px 8px;min-width:140px;margin:4px 0 8px;}' +
|
|
1473
|
+
'#__vesk_dev .__kp_cfg_select:focus{border-color:var(--vk-dim);outline:none;}' +
|
|
1474
|
+
'#__vesk_dev .__kp_cfg_textarea{width:100%;min-height:180px;background:var(--vk-codebg);border:1px solid var(--vk-line-soft);color:var(--vk-fg);font-family:ui-monospace,monospace;font-size:11px;padding:8px;resize:vertical;margin:6px 0 8px;box-sizing:border-box;}' +
|
|
1475
|
+
'#__vesk_dev .__kp_cfg_textarea:focus{border-color:var(--vk-dim);outline:none;}' +
|
|
1476
|
+
'#__vesk_dev .__kp_cfg_row{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin:4px 0 8px;}' +
|
|
1477
|
+
'#__vesk_dev .__kp_cfg_check{display:flex;gap:6px;align-items:center;font-size:11px;color:var(--vk-muted);margin:4px 0;cursor:pointer;}' +
|
|
1478
|
+
'#__vesk_dev .__kp_cfg_check input{accent-color:var(--vk-fg);}' +
|
|
1479
|
+
'#__vesk_dev .__kp_cfg_hint{font-size:10px;color:var(--vk-dim);margin:2px 0 6px;}' +
|
|
1480
|
+
'#__vesk_dev .__kp_cfg_meta{font-size:11px;color:var(--vk-dim);margin:6px 0;word-break:break-all;}' +
|
|
1481
|
+
'#__vesk_dev .__kp_cfg_meta b{color:var(--vk-fg);}' +
|
|
1482
|
+
'#__vesk_dev .__kp_settings_tabs{display:flex;gap:6px;border-bottom:1px solid var(--vk-line);margin:8px 0;padding-bottom:6px;flex-wrap:wrap;}' +
|
|
1483
|
+
'#__vesk_dev .__kp_settings_tab{all:unset;cursor:pointer;font-family:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--vk-muted);border:1px solid var(--vk-line-soft);padding:4px 10px;transition:color .12s ease,background .12s ease,border-color .12s ease;}' +
|
|
1484
|
+
'#__vesk_dev .__kp_settings_tab:hover{color:var(--vk-fg);border-color:var(--vk-dim);background:var(--vk-soft);}' +
|
|
1485
|
+
'#__vesk_dev .__kp_settings_tab.active{color:var(--vk-inv-fg);background:var(--vk-inv-bg);border-color:var(--vk-border);}' +
|
|
1486
|
+
'#__vesk_dev .__kp_settings_pane{animation:__v_tab .18s cubic-bezier(.22,.61,.36,1);}' +
|
|
1487
|
+
'#__vesk_dev .__kp_ag_key_row{border:1px solid var(--vk-line-soft);padding:8px;margin:6px 0;background:var(--vk-codebg);}' +
|
|
1488
|
+
'#__vesk_dev .__kp_ag_key_input_row{display:flex;gap:6px;align-items:center;margin:4px 0;}' +
|
|
1489
|
+
'#__vesk_dev .__kp_ag_key_input{flex:1;}' +
|
|
1490
|
+
'#__vesk_dev .__kp_key_has{color:#3fb950;font-size:10px;font-weight:700;}' +
|
|
1491
|
+
'#__vesk_dev .__kp_key_nohas{color:var(--vk-dim);font-size:10px;}' +
|
|
1492
|
+
'#__vesk_dev .__kp_key_preview{font-size:10px;color:var(--vk-muted);border:1px solid var(--vk-line-soft);padding:1px 4px;background:var(--vk-soft);}';
|
|
1493
|
+
export function createDevClient(opts) {
|
|
1494
|
+
if (typeof document === 'undefined')
|
|
1495
|
+
return { dispose() { } };
|
|
1496
|
+
const urls = resolveUrls(opts);
|
|
1497
|
+
const global = globalThis;
|
|
1498
|
+
const win = globalThis;
|
|
1499
|
+
const doc = document;
|
|
1500
|
+
let host = urls.wsUrl || '';
|
|
1501
|
+
if (!host) {
|
|
1502
|
+
const proto = win.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1503
|
+
host = proto + '//' + win.location.host + '/_vesk/hmr';
|
|
1504
|
+
}
|
|
1505
|
+
let ws = null;
|
|
1506
|
+
let status = 'idle';
|
|
1507
|
+
let lastError = null;
|
|
1508
|
+
let lastCompileMs = 0;
|
|
1509
|
+
let reconnectTimer = null;
|
|
1510
|
+
let disposed = false;
|
|
1511
|
+
const plugins = [];
|
|
1512
|
+
let pluginsError = null;
|
|
1513
|
+
let diagnostics = [];
|
|
1514
|
+
let diagnosticsError = null;
|
|
1515
|
+
const log = [];
|
|
1516
|
+
// Agentic state
|
|
1517
|
+
let agenticMessages = [];
|
|
1518
|
+
let agenticHistory = [];
|
|
1519
|
+
let agenticHistoryError = null;
|
|
1520
|
+
let agenticModelsCache = [];
|
|
1521
|
+
let agenticModelsLoading = false;
|
|
1522
|
+
let agenticModelsError = null;
|
|
1523
|
+
// The user's explicitly picked model — survives a later config-load that would
|
|
1524
|
+
// otherwise clobber ui.agenticModel back to the (possibly stale) persisted default.
|
|
1525
|
+
let agenticSelectedModel = '';
|
|
1526
|
+
let agenticBaseUrl = '';
|
|
1527
|
+
let agenticRunning = false;
|
|
1528
|
+
let agenticError = null;
|
|
1529
|
+
let agenticMaxSteps = 25;
|
|
1530
|
+
let agenticRunStep = 0;
|
|
1531
|
+
let agenticRunBudget = 0;
|
|
1532
|
+
let agenticInput = '';
|
|
1533
|
+
let agenticHistoryLoading = false;
|
|
1534
|
+
let pluginDetail = null;
|
|
1535
|
+
let pluginDetailMsg = null;
|
|
1536
|
+
let pluginView = 'list';
|
|
1537
|
+
let pluginSearchResults = [];
|
|
1538
|
+
let pluginSearchQuery = '';
|
|
1539
|
+
let pluginSearchError = null;
|
|
1540
|
+
let pluginSearchLoading = false;
|
|
1541
|
+
let pluginExports = null;
|
|
1542
|
+
let pluginExportsError = null;
|
|
1543
|
+
let pluginDetailSearch = null;
|
|
1544
|
+
let pluginInstalling = null;
|
|
1545
|
+
let dotEl = null;
|
|
1546
|
+
let panelEl = null;
|
|
1547
|
+
let panelOpen = false;
|
|
1548
|
+
let activeTab = 'overview';
|
|
1549
|
+
let overlayEl = null;
|
|
1550
|
+
let ui = loadDevtoolState(getStorage());
|
|
1551
|
+
let panelW = ui.w;
|
|
1552
|
+
let panelH = ui.h;
|
|
1553
|
+
// Settings subtab — persisted via ui.settingsSubTab
|
|
1554
|
+
let settingsSubtab = ui.settingsSubTab || 'devtools';
|
|
1555
|
+
// Vesk Config state
|
|
1556
|
+
let cfgState = null;
|
|
1557
|
+
let cfgLoading = false;
|
|
1558
|
+
let cfgError = null;
|
|
1559
|
+
let cfgSaving = false;
|
|
1560
|
+
let cfgSaveError = null;
|
|
1561
|
+
let cfgSaveOk = null;
|
|
1562
|
+
// Agentic Config state (settings agentic subtab)
|
|
1563
|
+
let agenticConfigState = null;
|
|
1564
|
+
let agenticConfigLoading = false;
|
|
1565
|
+
let agenticConfigError = null;
|
|
1566
|
+
let agenticConfigSaving = false;
|
|
1567
|
+
let agenticConfigSaveError = null;
|
|
1568
|
+
let agenticConfigSaveOk = null;
|
|
1569
|
+
function logEvent(type, ms) {
|
|
1570
|
+
log.push({ type, ms, time: ms, ts: Date.now() });
|
|
1571
|
+
if (log.length > 30)
|
|
1572
|
+
log.shift();
|
|
1573
|
+
}
|
|
1574
|
+
function setStatus(next) {
|
|
1575
|
+
status = next;
|
|
1576
|
+
updateDot();
|
|
1577
|
+
renderPanel();
|
|
1578
|
+
}
|
|
1579
|
+
function updateDot() {
|
|
1580
|
+
if (!doc.getElementById('__vesk_dev'))
|
|
1581
|
+
createDot();
|
|
1582
|
+
const d = doc.querySelector('#__vesk_dev .__v_dot');
|
|
1583
|
+
if (d)
|
|
1584
|
+
d.className = '__v_dot ' + status;
|
|
1585
|
+
}
|
|
1586
|
+
function effectiveTheme() {
|
|
1587
|
+
if (ui.theme !== 'system')
|
|
1588
|
+
return ui.theme;
|
|
1589
|
+
if (typeof matchMedia === 'function') {
|
|
1590
|
+
return matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
1591
|
+
}
|
|
1592
|
+
return 'dark';
|
|
1593
|
+
}
|
|
1594
|
+
function applyPrefs() {
|
|
1595
|
+
const theme = effectiveTheme();
|
|
1596
|
+
if (dotEl)
|
|
1597
|
+
dotEl.setAttribute('data-theme', theme);
|
|
1598
|
+
if (overlayEl)
|
|
1599
|
+
overlayEl.setAttribute('data-theme', theme);
|
|
1600
|
+
if (dotEl)
|
|
1601
|
+
dotEl.setAttribute('data-pos', ui.pos);
|
|
1602
|
+
if (dotEl)
|
|
1603
|
+
dotEl.setAttribute('data-sidebar', ui.sidebarMode);
|
|
1604
|
+
if (typeof matchMedia === 'function') {
|
|
1605
|
+
const mq = matchMedia('(prefers-color-scheme: light)');
|
|
1606
|
+
if (ui.theme === 'system') {
|
|
1607
|
+
mq.addEventListener?.('change', applyPrefs);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
renderPanel();
|
|
1611
|
+
}
|
|
1612
|
+
function changePref(key, val) {
|
|
1613
|
+
if (key === 'theme' && (val === 'system' || val === 'light' || val === 'dark')) {
|
|
1614
|
+
ui.theme = val;
|
|
1615
|
+
}
|
|
1616
|
+
else if (key === 'pos' && (val === 'left' || val === 'right')) {
|
|
1617
|
+
ui.pos = val;
|
|
1618
|
+
}
|
|
1619
|
+
else if (key === 'pluginsView' && (val === 'cards' || val === 'list')) {
|
|
1620
|
+
ui.pluginsView = val;
|
|
1621
|
+
}
|
|
1622
|
+
else if (key === 'sidebarMode' && (val === 'expanded' || val === 'rail')) {
|
|
1623
|
+
ui.sidebarMode = val;
|
|
1624
|
+
}
|
|
1625
|
+
else if (key === 'agenticProvider' && AGENTIC_PROVIDERS.indexOf(val) !== -1) {
|
|
1626
|
+
ui.agenticProvider = val;
|
|
1627
|
+
// a model pick is provider-scoped — clear it so the new provider re-resolves
|
|
1628
|
+
// to a valid model from its own freshly fetched list.
|
|
1629
|
+
agenticSelectedModel = '';
|
|
1630
|
+
agenticModelsCache = [];
|
|
1631
|
+
ui.agenticModels = [];
|
|
1632
|
+
if (agenticConfigState)
|
|
1633
|
+
agenticConfigState.models = [];
|
|
1634
|
+
refreshAgenticModels();
|
|
1635
|
+
// also refresh for settings sub-tab if open
|
|
1636
|
+
if (activeTab === 'settings' && settingsSubtab === 'agentic')
|
|
1637
|
+
refreshAgenticModels();
|
|
1638
|
+
}
|
|
1639
|
+
else if (key === 'agenticModel' && val.length > 0) {
|
|
1640
|
+
ui.agenticModel = val;
|
|
1641
|
+
agenticSelectedModel = val;
|
|
1642
|
+
}
|
|
1643
|
+
else if (key === 'agenticMode' && isAgenticMode(val)) {
|
|
1644
|
+
ui.agenticMode = val;
|
|
1645
|
+
}
|
|
1646
|
+
else {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
persistUi();
|
|
1650
|
+
applyPrefs();
|
|
1651
|
+
}
|
|
1652
|
+
function persistUi() {
|
|
1653
|
+
saveDevtoolState(getStorage(), ui);
|
|
1654
|
+
}
|
|
1655
|
+
function getStorage() {
|
|
1656
|
+
try {
|
|
1657
|
+
return win.localStorage;
|
|
1658
|
+
}
|
|
1659
|
+
catch {
|
|
1660
|
+
return null;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
9
1663
|
function connect() {
|
|
10
1664
|
try {
|
|
11
1665
|
ws = new WebSocket(host);
|
|
12
1666
|
ws.onopen = function () {
|
|
13
|
-
|
|
1667
|
+
if (disposed)
|
|
1668
|
+
return;
|
|
14
1669
|
clearError();
|
|
15
|
-
|
|
1670
|
+
setStatus('idle');
|
|
16
1671
|
};
|
|
17
1672
|
ws.onmessage = function (e) {
|
|
1673
|
+
if (disposed)
|
|
1674
|
+
return;
|
|
18
1675
|
try {
|
|
19
|
-
|
|
20
|
-
if (msg.nonce && !
|
|
21
|
-
|
|
1676
|
+
const msg = JSON.parse(e.data);
|
|
1677
|
+
if (msg.nonce && !global.__vesk_hmr_nonce) {
|
|
1678
|
+
global.__vesk_hmr_nonce = msg.nonce;
|
|
22
1679
|
}
|
|
23
|
-
|
|
1680
|
+
const type = msg.type;
|
|
1681
|
+
switch (type) {
|
|
24
1682
|
case 'update':
|
|
25
|
-
|
|
26
|
-
var _eval = globalThis.__vesk_hmr_eval || function (c) { try {
|
|
27
|
-
return eval(c);
|
|
28
|
-
}
|
|
29
|
-
catch (ex) {
|
|
30
|
-
console.error('HMR eval error:', ex);
|
|
31
|
-
} };
|
|
32
|
-
Object.values(msg.fnSources).forEach(function (fn) { _eval(fn, msg.nonce); });
|
|
33
|
-
}
|
|
34
|
-
globalThis.__updatedComponents = new Set(Object.keys((msg.components || {})));
|
|
35
|
-
var router = globalThis.__vesk_router;
|
|
36
|
-
if (router && typeof router.hmrUpdate === 'function') {
|
|
37
|
-
router.hmrUpdate();
|
|
38
|
-
}
|
|
39
|
-
lastCompileMs = msg.time || 0;
|
|
40
|
-
status = 'connected';
|
|
41
|
-
clearError();
|
|
42
|
-
updateDot();
|
|
1683
|
+
handleUpdate(msg);
|
|
43
1684
|
break;
|
|
44
1685
|
case 'reload':
|
|
45
|
-
|
|
1686
|
+
win.location.reload();
|
|
46
1687
|
break;
|
|
47
1688
|
case 'error':
|
|
48
|
-
|
|
49
|
-
lastError = msg;
|
|
50
|
-
showOverlay(msg);
|
|
51
|
-
updateDot();
|
|
1689
|
+
handleError(msg);
|
|
52
1690
|
break;
|
|
53
1691
|
case 'css-update':
|
|
54
|
-
|
|
55
|
-
var parent = el.parentNode;
|
|
56
|
-
if (!parent)
|
|
57
|
-
return;
|
|
58
|
-
var fresh = document.createElement('link');
|
|
59
|
-
fresh.rel = 'stylesheet';
|
|
60
|
-
fresh.href = el.href.split('?')[0] + '?t=' + Date.now();
|
|
61
|
-
fresh.onload = function () { el.remove(); };
|
|
62
|
-
fresh.onerror = function () { fresh.remove(); };
|
|
63
|
-
parent.insertBefore(fresh, el.nextSibling);
|
|
64
|
-
});
|
|
65
|
-
status = 'connected';
|
|
66
|
-
updateDot();
|
|
1692
|
+
handleCssUpdate();
|
|
67
1693
|
break;
|
|
68
1694
|
case 'compiling':
|
|
69
|
-
status = 'compiling';
|
|
70
1695
|
clearError();
|
|
71
|
-
|
|
1696
|
+
setStatus('compiling');
|
|
72
1697
|
break;
|
|
73
1698
|
}
|
|
74
1699
|
}
|
|
75
|
-
catch
|
|
1700
|
+
catch {
|
|
1701
|
+
/* ignore bad messages */
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1704
|
+
ws.onclose = function () {
|
|
1705
|
+
if (disposed)
|
|
1706
|
+
return;
|
|
1707
|
+
if (status !== 'error')
|
|
1708
|
+
setStatus('idle');
|
|
1709
|
+
scheduleReconnect();
|
|
1710
|
+
};
|
|
1711
|
+
ws.onerror = function () {
|
|
1712
|
+
if (disposed)
|
|
1713
|
+
return;
|
|
1714
|
+
if (status !== 'error')
|
|
1715
|
+
setStatus('idle');
|
|
1716
|
+
scheduleReconnect();
|
|
76
1717
|
};
|
|
77
|
-
ws.onclose = function () { if (status !== 'error') {
|
|
78
|
-
status = 'disconnected';
|
|
79
|
-
updateDot();
|
|
80
|
-
} scheduleReconnect(); };
|
|
81
|
-
ws.onerror = function () { if (status !== 'error') {
|
|
82
|
-
status = 'disconnected';
|
|
83
|
-
updateDot();
|
|
84
|
-
} scheduleReconnect(); };
|
|
85
1718
|
}
|
|
86
|
-
catch
|
|
1719
|
+
catch {
|
|
1720
|
+
/* WebSocket unavailable */
|
|
1721
|
+
}
|
|
87
1722
|
}
|
|
88
1723
|
function scheduleReconnect() {
|
|
89
1724
|
if (reconnectTimer)
|
|
90
1725
|
clearTimeout(reconnectTimer);
|
|
91
1726
|
reconnectTimer = setTimeout(connect, 3000);
|
|
92
1727
|
}
|
|
1728
|
+
function handleUpdate(msg) {
|
|
1729
|
+
if (msg.fnSources && typeof msg.fnSources === 'object') {
|
|
1730
|
+
const evaler = global.__vesk_hmr_eval ||
|
|
1731
|
+
function (c) {
|
|
1732
|
+
try {
|
|
1733
|
+
return eval(c);
|
|
1734
|
+
}
|
|
1735
|
+
catch (ex) {
|
|
1736
|
+
console.error('HMR eval error:', ex);
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
Object.values(msg.fnSources).forEach(function (fn) {
|
|
1740
|
+
evaler(fn, msg.nonce);
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
global.__updatedComponents = new Set(Object.keys((msg.components || {})));
|
|
1744
|
+
const router = global.__vesk_router;
|
|
1745
|
+
if (router && typeof router.hmrUpdate === 'function') {
|
|
1746
|
+
router.hmrUpdate();
|
|
1747
|
+
}
|
|
1748
|
+
lastCompileMs = msg.time || 0;
|
|
1749
|
+
status = 'connected';
|
|
1750
|
+
clearError();
|
|
1751
|
+
renderPanel();
|
|
1752
|
+
logEvent('update', lastCompileMs);
|
|
1753
|
+
}
|
|
1754
|
+
function handleCssUpdate() {
|
|
1755
|
+
doc.querySelectorAll('link[rel="stylesheet"]').forEach(function (el) {
|
|
1756
|
+
const parent = el.parentNode;
|
|
1757
|
+
if (!parent)
|
|
1758
|
+
return;
|
|
1759
|
+
const fresh = doc.createElement('link');
|
|
1760
|
+
fresh.rel = 'stylesheet';
|
|
1761
|
+
fresh.href = el.href.split('?')[0] + '?t=' + Date.now();
|
|
1762
|
+
fresh.onload = function () {
|
|
1763
|
+
el.remove();
|
|
1764
|
+
};
|
|
1765
|
+
fresh.onerror = function () {
|
|
1766
|
+
fresh.remove();
|
|
1767
|
+
};
|
|
1768
|
+
parent.insertBefore(fresh, el.nextSibling);
|
|
1769
|
+
});
|
|
1770
|
+
status = 'connected';
|
|
1771
|
+
renderPanel();
|
|
1772
|
+
logEvent('css-update', undefined);
|
|
1773
|
+
}
|
|
1774
|
+
function handleError(payload) {
|
|
1775
|
+
status = 'error';
|
|
1776
|
+
lastError = payload;
|
|
1777
|
+
showOverlay(payload);
|
|
1778
|
+
logEvent('error', undefined);
|
|
1779
|
+
renderPanel();
|
|
1780
|
+
}
|
|
93
1781
|
function clearError() {
|
|
94
1782
|
lastError = null;
|
|
95
|
-
|
|
1783
|
+
dismissOverlay();
|
|
96
1784
|
}
|
|
97
|
-
var overlayEl = null;
|
|
98
|
-
var overlayVisible = false;
|
|
99
1785
|
function createOverlay() {
|
|
100
|
-
if (
|
|
1786
|
+
if (doc.getElementById('__vesk_overlay'))
|
|
101
1787
|
return;
|
|
102
|
-
overlayEl =
|
|
1788
|
+
overlayEl = doc.createElement('div');
|
|
103
1789
|
overlayEl.id = '__vesk_overlay';
|
|
1790
|
+
overlayEl.setAttribute('data-theme', effectiveTheme());
|
|
104
1791
|
overlayEl.innerHTML =
|
|
105
1792
|
'<style>' +
|
|
106
|
-
|
|
107
|
-
'#__vesk_overlay.open{display:flex;}' +
|
|
108
|
-
'#__vesk_overlay .__vo_backdrop{position:absolute;inset:0;background:rgba(0,0,0,0.65);backdrop-filter:blur(2px);}' +
|
|
109
|
-
'#__vesk_overlay .__vo_panel{position:relative;margin:32px auto;max-width:800px;width:90%;max-height:calc(100vh - 64px);background:#1a1b26;border:1px solid #2a2b3e;border-radius:12px;box-shadow:0 8px 48px rgba(0,0,0,0.7);display:flex;flex-direction:column;overflow:hidden;}' +
|
|
110
|
-
'#__vesk_overlay .__vo_header{display:flex;align-items:center;gap:10px;padding:16px 20px;border-bottom:1px solid #2a2b3e;flex-shrink:0;}' +
|
|
111
|
-
'#__vesk_overlay .__vo_icon{width:20px;height:20px;border-radius:50%;background:#ef4444;flex-shrink:0;}' +
|
|
112
|
-
'#__vesk_overlay .__vo_title{font-size:14px;font-weight:700;color:#ef4444;flex:1;}' +
|
|
113
|
-
'#__vesk_overlay .__vo_close{all:unset;cursor:pointer;width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;color:#888;font-size:18px;}' +
|
|
114
|
-
'#__vesk_overlay .__vo_close:hover{background:#2a2b3e;color:#e0e0e0;}' +
|
|
115
|
-
'#__vesk_overlay .__vo_body{overflow-y:auto;padding:16px 20px;flex:1;}' +
|
|
116
|
-
'#__vesk_overlay .__vo_file{font-size:12px;color:#888;margin-bottom:8px;}' +
|
|
117
|
-
'#__vesk_overlay .__vo_file strong{color:#e0e0e0;}' +
|
|
118
|
-
'#__vesk_overlay .__vo_message{font-size:13px;color:#f87171;padding:8px 12px;background:#2a1b1b;border-radius:6px;margin-bottom:12px;white-space:pre-wrap;word-break:break-all;}' +
|
|
119
|
-
'#__vesk_overlay .__vo_code{background:#0f0f1a;border-radius:6px;padding:12px;margin-bottom:12px;overflow-x:auto;font-size:12px;line-height:1.6;}' +
|
|
120
|
-
'#__vesk_overlay .__vo_code pre{margin:0;white-space:pre;}' +
|
|
121
|
-
'#__vesk_overlay .__vo_code .hl{background:rgba(239,68,68,0.25);display:block;}' +
|
|
122
|
-
'#__vesk_overlay .__vo_code .hl::before{content:"> ";color:#ef4444;font-weight:700;}' +
|
|
123
|
-
'#__vesk_overlay .__vo_code .ln{color:#555;margin-right:12px;user-select:none;}' +
|
|
124
|
-
'#__vesk_overlay .__vo_tips{margin-top:8px;}' +
|
|
125
|
-
'#__vesk_overlay .__vo_tips_title{font-size:11px;font-weight:700;color:#eab308;margin-bottom:4px;}' +
|
|
126
|
-
'#__vesk_overlay .__vo_tip{padding:4px 0 4px 16px;font-size:12px;color:#bbb;position:relative;}' +
|
|
127
|
-
'#__vesk_overlay .__vo_tip::before{content:"\\2192";position:absolute;left:0;color:#eab308;}' +
|
|
128
|
-
'#__vesk_overlay .__vo_stack{margin-top:12px;}' +
|
|
129
|
-
'#__vesk_overlay .__vo_stack summary{cursor:pointer;font-size:11px;color:#888;padding:4px 0;}' +
|
|
130
|
-
'#__vesk_overlay .__vo_stack pre{background:#0f0f1a;border-radius:4px;padding:8px;margin-top:4px;font-size:11px;color:#888;max-height:200px;overflow:auto;white-space:pre;}' +
|
|
131
|
-
'#__vesk_overlay .__vo_footer{padding:8px 20px;border-top:1px solid #2a2b3e;font-size:11px;color:#555;text-align:center;flex-shrink:0;}' +
|
|
1793
|
+
CSS +
|
|
132
1794
|
'</style>' +
|
|
133
1795
|
'<div class="__vo_backdrop"></div>' +
|
|
134
1796
|
'<div class="__vo_panel">' +
|
|
135
1797
|
' <div class="__vo_header">' +
|
|
136
|
-
' <div class="
|
|
137
|
-
' <div class="__vo_title">Compilation Error</div>' +
|
|
1798
|
+
' <div class="__vo_title">> COMPILATION ERROR</div>' +
|
|
138
1799
|
' <button class="__vo_close" id="__vo_close">×</button>' +
|
|
139
1800
|
' </div>' +
|
|
140
1801
|
' <div class="__vo_body">' +
|
|
@@ -147,130 +1808,2141 @@
|
|
|
147
1808
|
' <pre id="__vo_stack"></pre>' +
|
|
148
1809
|
' </details>' +
|
|
149
1810
|
' </div>' +
|
|
150
|
-
' <div class="__vo_footer">
|
|
1811
|
+
' <div class="__vo_footer">VESK DEV - CLICK X TO DISMISS</div>' +
|
|
151
1812
|
'</div>';
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
overlayEl.querySelector('.__vo_backdrop').addEventListener('click',
|
|
155
|
-
|
|
156
|
-
|
|
1813
|
+
doc.body.appendChild(overlayEl);
|
|
1814
|
+
doc.getElementById('__vo_close').onclick = dismissOverlay;
|
|
1815
|
+
overlayEl.querySelector('.__vo_backdrop').addEventListener('click', dismissOverlay);
|
|
1816
|
+
doc.addEventListener('keydown', function (e) {
|
|
1817
|
+
if (e.key === 'Escape' && overlayEl && overlayEl.classList.contains('open'))
|
|
1818
|
+
dismissOverlay();
|
|
1819
|
+
});
|
|
157
1820
|
}
|
|
158
|
-
function showOverlay(
|
|
1821
|
+
function showOverlay(payload) {
|
|
159
1822
|
if (!overlayEl)
|
|
160
1823
|
createOverlay();
|
|
161
|
-
overlayEl =
|
|
1824
|
+
overlayEl = doc.getElementById('__vesk_overlay');
|
|
162
1825
|
if (!overlayEl)
|
|
163
1826
|
return;
|
|
164
|
-
|
|
1827
|
+
const nodes = buildErrorNodes(payload);
|
|
1828
|
+
doc.getElementById('__vo_file').innerHTML = nodes.file;
|
|
1829
|
+
doc.getElementById('__vo_message').textContent = payload.message || 'Unknown error';
|
|
1830
|
+
doc.getElementById('__vo_code').innerHTML = nodes.codeframe;
|
|
1831
|
+
doc.getElementById('__vo_tips').innerHTML = nodes.lists;
|
|
1832
|
+
doc.getElementById('__vo_stack').textContent = nodes.stack;
|
|
165
1833
|
overlayEl.classList.add('open');
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
fileInfo = 'File: <strong>' + escapeHtml(msg.file) + '</strong>';
|
|
169
|
-
if (msg.line)
|
|
170
|
-
fileInfo += ' at line ' + msg.line + (msg.column ? ', column ' + msg.column : '');
|
|
171
|
-
}
|
|
172
|
-
document.getElementById('__vo_file').innerHTML = fileInfo;
|
|
173
|
-
document.getElementById('__vo_message').textContent = msg.message || 'Unknown error';
|
|
174
|
-
var codeHtml = '';
|
|
175
|
-
if (msg.code) {
|
|
176
|
-
var codeLines = msg.code.split('\n');
|
|
177
|
-
codeHtml = '<pre>';
|
|
178
|
-
for (var i = 0; i < codeLines.length; i++) {
|
|
179
|
-
var ln = codeLines[i].match(/^(\d+):\s*/);
|
|
180
|
-
var lineNum = ln ? ln[1] : '';
|
|
181
|
-
var lineContent = ln ? codeLines[i].slice(ln[0].length) : codeLines[i];
|
|
182
|
-
var isErrorLine = msg.line && parseInt(lineNum) === msg.line;
|
|
183
|
-
codeHtml += '<span class="' + (isErrorLine ? 'hl' : '') + '"><span class="ln">' + padNum(lineNum, 4) + '</span>' + escapeHtml(lineContent) + '</span>';
|
|
184
|
-
}
|
|
185
|
-
codeHtml += '</pre>';
|
|
186
|
-
}
|
|
187
|
-
document.getElementById('__vo_code').innerHTML = codeHtml;
|
|
188
|
-
var tipsHtml = '';
|
|
189
|
-
if (msg.tips && msg.tips.length > 0) {
|
|
190
|
-
var shownHeader = false;
|
|
191
|
-
for (var t = 0; t < msg.tips.length; t++) {
|
|
192
|
-
if (!shownHeader) {
|
|
193
|
-
tipsHtml += '<div class="__vo_tips_title">Debug Tips</div>';
|
|
194
|
-
shownHeader = true;
|
|
195
|
-
}
|
|
196
|
-
tipsHtml += '<div class="__vo_tip">' + escapeHtml(msg.tips[t]) + '</div>';
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (msg.suggestions && msg.suggestions.length > 0) {
|
|
200
|
-
tipsHtml += '<div class="__vo_tips_title" style="margin-top:8px">Suggestions</div>';
|
|
201
|
-
for (var s = 0; s < Math.min(msg.suggestions.length, 4); s++) {
|
|
202
|
-
tipsHtml += '<div class="__vo_tip">-- ' + escapeHtml(msg.suggestions[s]) + '</div>';
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (msg.nextSteps && msg.nextSteps.length > 0) {
|
|
206
|
-
tipsHtml += '<div class="__vo_tips_title" style="margin-top:8px">Next Steps</div>';
|
|
207
|
-
for (var n = 0; n < Math.min(msg.nextSteps.length, 4); n++) {
|
|
208
|
-
tipsHtml += '<div class="__vo_tip">' + escapeHtml(msg.nextSteps[n]) + '</div>';
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
document.getElementById('__vo_tips').innerHTML = tipsHtml;
|
|
212
|
-
document.getElementById('__vo_stack').textContent = msg.stack || '(no stack trace)';
|
|
213
|
-
}
|
|
214
|
-
function hideOverlay() {
|
|
215
|
-
overlayVisible = false;
|
|
1834
|
+
}
|
|
1835
|
+
function dismissOverlay() {
|
|
216
1836
|
if (overlayEl)
|
|
217
1837
|
overlayEl.classList.remove('open');
|
|
218
1838
|
}
|
|
219
|
-
function escapeHtml(s) {
|
|
220
|
-
if (!s)
|
|
221
|
-
return '';
|
|
222
|
-
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
223
|
-
}
|
|
224
|
-
function padNum(n, len) {
|
|
225
|
-
n = String(n);
|
|
226
|
-
while (n.length < len)
|
|
227
|
-
n = ' ' + n;
|
|
228
|
-
return n;
|
|
229
|
-
}
|
|
230
|
-
var dot = null;
|
|
231
1839
|
function createDot() {
|
|
232
|
-
if (
|
|
1840
|
+
if (doc.getElementById('__vesk_dev'))
|
|
233
1841
|
return;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
1842
|
+
dotEl = doc.createElement('div');
|
|
1843
|
+
dotEl.id = '__vesk_dev';
|
|
1844
|
+
dotEl.setAttribute('data-pos', ui.pos);
|
|
1845
|
+
dotEl.setAttribute('data-theme', effectiveTheme());
|
|
1846
|
+
dotEl.innerHTML =
|
|
237
1847
|
'<style>' +
|
|
238
|
-
|
|
239
|
-
'#__vesk_dev .__v_bar{display:flex;align-items:center;gap:8px;background:#1a1b26;border:1px solid #2a2b3e;border-radius:10px;padding:6px 12px;box-shadow:0 4px 24px rgba(0,0,0,0.6);transition:all .2s;}' +
|
|
240
|
-
'#__vesk_dev .__v_bar:hover{border-color:#3a3b5e;}' +
|
|
241
|
-
'#__vesk_dev .__v_dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;transition:background .3s;}' +
|
|
242
|
-
'#__vesk_dev .__v_dot.connected{background:#22c55e;box-shadow:0 0 6px rgba(34,197,94,0.5);}' +
|
|
243
|
-
'#__vesk_dev .__v_dot.compiling{background:#eab308;box-shadow:0 0 6px rgba(234,179,8,0.5);animation:__v_pulse .8s infinite;}' +
|
|
244
|
-
'#__vesk_dev .__v_dot.error{background:#ef4444;box-shadow:0 0 6px rgba(239,68,68,0.5);}' +
|
|
245
|
-
'#__vesk_dev .__v_dot.disconnected{background:#6b7280;}' +
|
|
246
|
-
'#__vesk_dev .__v_dot.loading{background:#6b7280;animation:__v_pulse 1.2s infinite;}' +
|
|
247
|
-
'#__vesk_dev .__v_label{white-space:nowrap;}' +
|
|
248
|
-
'#__vesk_dev .__v_version{font-size:9px;color:#555;}' +
|
|
249
|
-
'@keyframes __v_pulse{0%,100%{opacity:1}50%{opacity:.4}}' +
|
|
1848
|
+
CSS +
|
|
250
1849
|
'</style>' +
|
|
251
1850
|
'<div class="__v_bar">' +
|
|
252
|
-
' <span class="__v_dot
|
|
1851
|
+
' <span class="__v_dot idle"></span>' +
|
|
253
1852
|
' <span class="__v_label">Vesk</span>' +
|
|
254
1853
|
' <span class="__v_version">dev</span>' +
|
|
255
1854
|
'</div>';
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
1855
|
+
dotEl.querySelector('.__v_bar').addEventListener('click', togglePanel);
|
|
1856
|
+
doc.body.appendChild(dotEl);
|
|
1857
|
+
createPanel();
|
|
1858
|
+
}
|
|
1859
|
+
function createPanel() {
|
|
1860
|
+
if (!dotEl)
|
|
1861
|
+
return;
|
|
1862
|
+
panelEl = doc.createElement('div');
|
|
1863
|
+
panelEl.className = '__kp hidden';
|
|
1864
|
+
panelEl.style.width = panelW + 'px';
|
|
1865
|
+
panelEl.style.height = panelH + 'px';
|
|
1866
|
+
panelEl.innerHTML =
|
|
1867
|
+
'<div class="__kp_head">' +
|
|
1868
|
+
' <div class="__kp_title">> VESK DEV</div>' +
|
|
1869
|
+
' <button class="__kp_max" id="__kp_max" title="expand/collapse">[+]</button>' +
|
|
1870
|
+
' <button class="__kp_close" id="__kp_close">[x]</button>' +
|
|
1871
|
+
'</div>' +
|
|
1872
|
+
renderTabBar(activeTab, ui.sidebarMode) +
|
|
1873
|
+
'<div class="__kp_body">' +
|
|
1874
|
+
' <div id="__kp_content"></div>' +
|
|
1875
|
+
'</div>' +
|
|
1876
|
+
'<div class="__kp_handle" id="__kp_handle" title="resize"></div>';
|
|
1877
|
+
dotEl.appendChild(panelEl);
|
|
1878
|
+
doc.getElementById('__kp_close').onclick = function () {
|
|
1879
|
+
panelOpen = false;
|
|
1880
|
+
ui.open = false;
|
|
1881
|
+
persistUi();
|
|
1882
|
+
if (panelEl)
|
|
1883
|
+
panelEl.classList.add('hidden');
|
|
1884
|
+
};
|
|
1885
|
+
doc.getElementById('__kp_max').onclick = function () {
|
|
1886
|
+
if (!panelEl)
|
|
1887
|
+
return;
|
|
1888
|
+
const maxed = panelEl.classList.toggle('maxed');
|
|
1889
|
+
ui.maxed = maxed;
|
|
1890
|
+
if (maxed) {
|
|
1891
|
+
panelEl.style.width = '';
|
|
1892
|
+
panelEl.style.height = '';
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
clampPanelSize();
|
|
1896
|
+
panelEl.style.width = panelW + 'px';
|
|
1897
|
+
panelEl.style.height = panelH + 'px';
|
|
1898
|
+
}
|
|
1899
|
+
persistUi();
|
|
1900
|
+
const mb = doc.getElementById('__kp_max');
|
|
1901
|
+
if (mb)
|
|
1902
|
+
mb.textContent = maxed ? '[-]' : '[+]';
|
|
1903
|
+
};
|
|
1904
|
+
const tabsEl = panelEl.querySelector('.__kp_tabs');
|
|
1905
|
+
if (tabsEl) {
|
|
1906
|
+
tabsEl.addEventListener('click', function (e) {
|
|
1907
|
+
const btn = e.target.closest('.__kp_tab');
|
|
1908
|
+
if (!btn)
|
|
1909
|
+
return;
|
|
1910
|
+
const tab = btn.getAttribute('data-tab') || 'overview';
|
|
1911
|
+
if (tab === 'plugins')
|
|
1912
|
+
refreshPlugins();
|
|
1913
|
+
if (tab === 'diagnostics')
|
|
1914
|
+
refreshDiagnostics();
|
|
1915
|
+
if (tab === 'agentic') {
|
|
1916
|
+
refreshAgenticModels();
|
|
1917
|
+
refreshAgenticHistory();
|
|
1918
|
+
}
|
|
1919
|
+
if (tab === 'settings') {
|
|
1920
|
+
if (settingsSubtab === 'vesk' && !cfgState && !cfgLoading)
|
|
1921
|
+
refreshVeskConfig();
|
|
1922
|
+
if (settingsSubtab === 'agentic' && !agenticConfigState && !agenticConfigLoading)
|
|
1923
|
+
refreshAgenticConfig();
|
|
1924
|
+
if (settingsSubtab === 'config' && !cfgState && !cfgLoading)
|
|
1925
|
+
refreshVeskConfig();
|
|
1926
|
+
}
|
|
1927
|
+
setTab(tab);
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
const contentEl = doc.getElementById('__kp_content');
|
|
1931
|
+
contentEl.addEventListener('click', function (e) {
|
|
1932
|
+
gpClickHandler(e);
|
|
1933
|
+
});
|
|
1934
|
+
contentEl.addEventListener('change', function (e) {
|
|
1935
|
+
const target = e.target;
|
|
1936
|
+
const providerSel = target.closest('[data-agentic-provider-select]');
|
|
1937
|
+
if (providerSel) {
|
|
1938
|
+
changePref('agenticProvider', providerSel.value);
|
|
1939
|
+
saveAgenticConfigPatch({ provider: providerSel.value });
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
const modelSel = target.closest('[data-agentic-model]');
|
|
1943
|
+
if (modelSel) {
|
|
1944
|
+
changePref('agenticModel', modelSel.value);
|
|
1945
|
+
saveAgenticConfigPatch({ model: modelSel.value });
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
// Agentic settings: baseUrl, maxSteps, mode, provider/model via settings subtab also use same keys
|
|
1949
|
+
const agBaseEl = target.closest('[data-agentic-baseurl]');
|
|
1950
|
+
if (agBaseEl) {
|
|
1951
|
+
const v = agBaseEl.value || '';
|
|
1952
|
+
saveAgenticConfigPatch({ baseUrl: v });
|
|
1953
|
+
if (agenticConfigState)
|
|
1954
|
+
agenticConfigState.baseUrl = v;
|
|
1955
|
+
agenticBaseUrl = v;
|
|
1956
|
+
// A base URL change can turn a stub list (custom) into a real one — refresh.
|
|
1957
|
+
refreshAgenticModels();
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
const agBaseKeyEl = target.closest('[data-agentic-key="baseUrl"]');
|
|
1961
|
+
if (agBaseKeyEl) {
|
|
1962
|
+
const v = agBaseKeyEl.value || '';
|
|
1963
|
+
saveAgenticConfigPatch({ baseUrl: v });
|
|
1964
|
+
if (agenticConfigState)
|
|
1965
|
+
agenticConfigState.baseUrl = v;
|
|
1966
|
+
agenticBaseUrl = v;
|
|
1967
|
+
refreshAgenticModels();
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const agMaxEl = target.closest('[data-agentic-maxsteps]');
|
|
1971
|
+
if (agMaxEl) {
|
|
1972
|
+
const n = parseInt(agMaxEl.value, 10);
|
|
1973
|
+
if (!isNaN(n) && n >= 1 && n <= 100) {
|
|
1974
|
+
saveAgenticConfigPatch({ maxSteps: n });
|
|
1975
|
+
if (agenticConfigState)
|
|
1976
|
+
agenticConfigState.maxSteps = n;
|
|
1977
|
+
}
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const agMaxKeyEl = target.closest('[data-agentic-key="maxSteps"]');
|
|
1981
|
+
if (agMaxKeyEl) {
|
|
1982
|
+
const n = parseInt(agMaxKeyEl.value, 10);
|
|
1983
|
+
if (!isNaN(n) && n >= 1 && n <= 100) {
|
|
1984
|
+
saveAgenticConfigPatch({ maxSteps: n });
|
|
1985
|
+
if (agenticConfigState)
|
|
1986
|
+
agenticConfigState.maxSteps = n;
|
|
1987
|
+
}
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
// Vesk Config: core fields (appDir, outDir, publicDir, routeDataCache)
|
|
1991
|
+
const cfgKeyEl = target.closest('[data-cfg-key]');
|
|
1992
|
+
if (cfgKeyEl) {
|
|
1993
|
+
const k = cfgKeyEl.getAttribute('data-cfg-key') || '';
|
|
1994
|
+
if (k === 'routeDataCache') {
|
|
1995
|
+
const n = parseInt(cfgKeyEl.value, 10);
|
|
1996
|
+
if (!isNaN(n) && n >= 0)
|
|
1997
|
+
postVeskConfigKey(k, n);
|
|
1998
|
+
else if (cfgKeyEl.value === '' || cfgKeyEl.value == null)
|
|
1999
|
+
postVeskConfigKey(k, 0);
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
if (k === 'appDir' || k === 'outDir' || k === 'publicDir') {
|
|
2003
|
+
postVeskConfigKey(k, cfgKeyEl.value);
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
const mdHtmlEl = target.closest('[data-cfg-md-html]');
|
|
2008
|
+
if (mdHtmlEl) {
|
|
2009
|
+
const htmlMode = mdHtmlEl.value;
|
|
2010
|
+
const curMd = (cfgState && cfgState.config && cfgState.config.md) ? cfgState.config.md : {};
|
|
2011
|
+
const curTags = Array.isArray(curMd.allowTags) ? curMd.allowTags : [];
|
|
2012
|
+
postVeskConfigKey('md', { html: htmlMode, allowTags: curTags });
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
const mdTagsEl = target.closest('[data-cfg-md-tags]');
|
|
2016
|
+
if (mdTagsEl) {
|
|
2017
|
+
const raw = mdTagsEl.value || '';
|
|
2018
|
+
const tags = raw.split(',').map(function (s) { return s.trim().toLowerCase().replace(/[^a-z0-9-]/g, ''); }).filter(Boolean);
|
|
2019
|
+
const curMd2 = (cfgState && cfgState.config && cfgState.config.md) ? cfgState.config.md : {};
|
|
2020
|
+
const htmlMode2 = typeof curMd2.html === 'string' ? curMd2.html : 'escape';
|
|
2021
|
+
postVeskConfigKey('md', { html: htmlMode2, allowTags: tags });
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
const secPresetEl = target.closest('[data-cfg-sec-preset]');
|
|
2025
|
+
if (secPresetEl) {
|
|
2026
|
+
const v = secPresetEl.value;
|
|
2027
|
+
if (v === 'custom') {
|
|
2028
|
+
postVeskConfigKey('security', {});
|
|
2029
|
+
}
|
|
2030
|
+
else if (v === 'off') {
|
|
2031
|
+
postVeskConfigKey('security', false);
|
|
2032
|
+
}
|
|
2033
|
+
else {
|
|
2034
|
+
postVeskConfigKey('security', v);
|
|
2035
|
+
}
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
const secKeyEl = target.closest('[data-sec-key]');
|
|
2039
|
+
if (secKeyEl) {
|
|
2040
|
+
const sk = secKeyEl.getAttribute('data-sec-key') || '';
|
|
2041
|
+
const rawVal = secKeyEl.value;
|
|
2042
|
+
// Build security patch
|
|
2043
|
+
const curSecRaw = cfgState && cfgState.config ? cfgState.config.security : {};
|
|
2044
|
+
let curSec = {};
|
|
2045
|
+
if (curSecRaw && typeof curSecRaw === 'object' && !Array.isArray(curSecRaw))
|
|
2046
|
+
curSec = Object.assign({}, curSecRaw);
|
|
2047
|
+
if (sk.indexOf('.') !== -1) {
|
|
2048
|
+
const parts = sk.split('.');
|
|
2049
|
+
const top = parts[0];
|
|
2050
|
+
const sub = parts[1];
|
|
2051
|
+
let nested = curSec[top];
|
|
2052
|
+
if (!nested || typeof nested !== 'object')
|
|
2053
|
+
nested = {};
|
|
2054
|
+
else
|
|
2055
|
+
nested = Object.assign({}, nested);
|
|
2056
|
+
if (sk === 'cors.origin') {
|
|
2057
|
+
const list = rawVal.split(',').map(function (s) { return s.trim(); }).filter(Boolean);
|
|
2058
|
+
nested[sub] = list.length === 1 ? list[0] : list.length ? list : undefined;
|
|
2059
|
+
}
|
|
2060
|
+
else if (sk === 'rateLimit.windowMs' || sk === 'rateLimit.max') {
|
|
2061
|
+
const num = rawVal === '' ? undefined : parseInt(rawVal, 10);
|
|
2062
|
+
if (num != null && !isNaN(num))
|
|
2063
|
+
nested[sub] = num;
|
|
2064
|
+
else
|
|
2065
|
+
delete nested[sub];
|
|
2066
|
+
}
|
|
2067
|
+
else {
|
|
2068
|
+
if (rawVal === '')
|
|
2069
|
+
delete nested[sub];
|
|
2070
|
+
else
|
|
2071
|
+
nested[sub] = rawVal;
|
|
2072
|
+
}
|
|
2073
|
+
// clean nested if empty and top is cors/rateLimit
|
|
2074
|
+
if (Object.keys(nested).length === 0)
|
|
2075
|
+
delete curSec[top];
|
|
2076
|
+
else
|
|
2077
|
+
curSec[top] = nested;
|
|
2078
|
+
}
|
|
2079
|
+
else {
|
|
2080
|
+
if (rawVal === '')
|
|
2081
|
+
delete curSec[sk];
|
|
2082
|
+
else
|
|
2083
|
+
curSec[sk] = rawVal;
|
|
2084
|
+
}
|
|
2085
|
+
postVeskConfigKey('security', curSec);
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
const secToggleEl = target.closest('[data-sec-toggle]');
|
|
2089
|
+
if (secToggleEl) {
|
|
2090
|
+
const tk = secToggleEl.getAttribute('data-sec-toggle') || '';
|
|
2091
|
+
const checked = !!secToggleEl.checked;
|
|
2092
|
+
const curSecRaw2 = cfgState && cfgState.config ? cfgState.config.security : {};
|
|
2093
|
+
let curSec2 = {};
|
|
2094
|
+
if (curSecRaw2 && typeof curSecRaw2 === 'object' && !Array.isArray(curSecRaw2))
|
|
2095
|
+
curSec2 = Object.assign({}, curSecRaw2);
|
|
2096
|
+
if (tk === 'xFrameOptions' || tk === 'hsts' || tk === 'referrerPolicy' || tk === 'contentSecurityPolicy') {
|
|
2097
|
+
if (checked)
|
|
2098
|
+
curSec2[tk] = false;
|
|
2099
|
+
else {
|
|
2100
|
+
if (tk === 'xFrameOptions')
|
|
2101
|
+
curSec2[tk] = 'DENY';
|
|
2102
|
+
else if (tk === 'hsts')
|
|
2103
|
+
curSec2[tk] = 'max-age=31536000; includeSubDomains';
|
|
2104
|
+
else if (tk === 'referrerPolicy')
|
|
2105
|
+
curSec2[tk] = 'strict-origin-when-cross-origin';
|
|
2106
|
+
else if (tk === 'contentSecurityPolicy')
|
|
2107
|
+
curSec2[tk] = "default-src 'self'";
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
else if (tk === 'autoEscape' || tk === 'csrf' || tk === 'redactLogs') {
|
|
2111
|
+
curSec2[tk] = checked;
|
|
2112
|
+
}
|
|
2113
|
+
else if (tk === 'trustProxy') {
|
|
2114
|
+
curSec2[tk] = checked ? true : false;
|
|
2115
|
+
}
|
|
2116
|
+
else if (tk === 'cors.credentials') {
|
|
2117
|
+
let corsObj = curSec2['cors'];
|
|
2118
|
+
if (!corsObj || typeof corsObj !== 'object')
|
|
2119
|
+
corsObj = {};
|
|
2120
|
+
else
|
|
2121
|
+
corsObj = Object.assign({}, corsObj);
|
|
2122
|
+
if (checked)
|
|
2123
|
+
corsObj['credentials'] = true;
|
|
2124
|
+
else
|
|
2125
|
+
delete corsObj['credentials'];
|
|
2126
|
+
if (Object.keys(corsObj).length === 0)
|
|
2127
|
+
delete curSec2['cors'];
|
|
2128
|
+
else
|
|
2129
|
+
curSec2['cors'] = corsObj;
|
|
2130
|
+
}
|
|
2131
|
+
postVeskConfigKey('security', curSec2);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
contentEl.addEventListener('input', function (e) {
|
|
2136
|
+
const target = e.target;
|
|
2137
|
+
if (target && target.getAttribute('data-agentic-input') === '1') {
|
|
2138
|
+
agenticInput = target.value;
|
|
2139
|
+
// Update only the slash popup, never re-render the whole panel:
|
|
2140
|
+
// recreating the textbox on every keystroke dismisses the mobile
|
|
2141
|
+
// keyboard and makes the UI jump.
|
|
2142
|
+
syncAgenticSlashPopup();
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const cfgSrc = target.closest('[data-cfg-source]');
|
|
2146
|
+
if (cfgSrc) {
|
|
2147
|
+
if (cfgState)
|
|
2148
|
+
cfgState.draftSource = cfgSrc.value;
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
contentEl.addEventListener('keydown', function (e) {
|
|
2153
|
+
const ke = e;
|
|
2154
|
+
const t = ke.target;
|
|
2155
|
+
if (t && t.getAttribute('data-agentic-input') === '1' && ke.key === 'Enter') {
|
|
2156
|
+
e.preventDefault();
|
|
2157
|
+
sendAgenticMessage();
|
|
2158
|
+
}
|
|
259
2159
|
});
|
|
260
|
-
|
|
2160
|
+
initResize();
|
|
2161
|
+
refreshPlugins();
|
|
2162
|
+
refreshDiagnostics();
|
|
261
2163
|
}
|
|
262
|
-
function
|
|
263
|
-
|
|
2164
|
+
function clampPanelSize() {
|
|
2165
|
+
const maxW = win.innerWidth - PANEL_EDGE - PANEL_MARGIN;
|
|
2166
|
+
const maxH = win.innerHeight - PANEL_EDGE - PANEL_MARGIN;
|
|
2167
|
+
panelW = Math.max(PANEL_MIN_W, Math.min(panelW, maxW));
|
|
2168
|
+
panelH = Math.max(PANEL_MIN_H, Math.min(panelH, maxH));
|
|
2169
|
+
}
|
|
2170
|
+
function initResize() {
|
|
2171
|
+
const handle = doc.getElementById('__kp_handle');
|
|
2172
|
+
if (!handle || !panelEl)
|
|
2173
|
+
return;
|
|
2174
|
+
handle.addEventListener('pointerdown', function (e) {
|
|
2175
|
+
if (!panelEl || panelEl.classList.contains('maxed'))
|
|
2176
|
+
return;
|
|
2177
|
+
e.preventDefault();
|
|
2178
|
+
const startX = e.clientX;
|
|
2179
|
+
const startY = e.clientY;
|
|
2180
|
+
const startW = panelEl.offsetWidth;
|
|
2181
|
+
const startH = panelEl.offsetHeight;
|
|
2182
|
+
const maxW = win.innerWidth - PANEL_EDGE - PANEL_MARGIN;
|
|
2183
|
+
const maxH = win.innerHeight - PANEL_EDGE - PANEL_MARGIN;
|
|
2184
|
+
panelEl.classList.add('resizing');
|
|
2185
|
+
handle.classList.add('resizing');
|
|
2186
|
+
function onMove(ev) {
|
|
2187
|
+
if (!panelEl)
|
|
2188
|
+
return;
|
|
2189
|
+
let w = startW + (ev.clientX - startX);
|
|
2190
|
+
let h = startH + (ev.clientY - startY);
|
|
2191
|
+
if (w >= maxW - 10)
|
|
2192
|
+
w = maxW;
|
|
2193
|
+
w = Math.max(PANEL_MIN_W, Math.min(w, maxW));
|
|
2194
|
+
h = Math.max(PANEL_MIN_H, Math.min(h, maxH));
|
|
2195
|
+
panelW = w;
|
|
2196
|
+
panelH = h;
|
|
2197
|
+
panelEl.style.width = w + 'px';
|
|
2198
|
+
panelEl.style.height = h + 'px';
|
|
2199
|
+
}
|
|
2200
|
+
function onUp() {
|
|
2201
|
+
if (panelEl)
|
|
2202
|
+
panelEl.classList.remove('resizing');
|
|
2203
|
+
handle?.classList.remove('resizing');
|
|
2204
|
+
doc.removeEventListener('pointermove', onMove);
|
|
2205
|
+
doc.removeEventListener('pointerup', onUp);
|
|
2206
|
+
doc.removeEventListener('pointercancel', onUp);
|
|
2207
|
+
ui.w = panelW;
|
|
2208
|
+
ui.h = panelH;
|
|
2209
|
+
persistUi();
|
|
2210
|
+
}
|
|
2211
|
+
doc.addEventListener('pointermove', onMove);
|
|
2212
|
+
doc.addEventListener('pointerup', onUp);
|
|
2213
|
+
doc.addEventListener('pointercancel', onUp);
|
|
2214
|
+
});
|
|
2215
|
+
win.addEventListener('resize', function () {
|
|
2216
|
+
if (!panelEl)
|
|
2217
|
+
return;
|
|
2218
|
+
if (!panelEl.classList.contains('maxed')) {
|
|
2219
|
+
clampPanelSize();
|
|
2220
|
+
panelEl.style.width = panelW + 'px';
|
|
2221
|
+
panelEl.style.height = panelH + 'px';
|
|
2222
|
+
}
|
|
2223
|
+
clampPanelSize();
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
function setSettingsSubTab(sub) {
|
|
2227
|
+
if (!isSettingsSubTab(sub))
|
|
2228
|
+
return;
|
|
2229
|
+
settingsSubtab = sub;
|
|
2230
|
+
ui.settingsSubTab = sub;
|
|
2231
|
+
persistUi();
|
|
2232
|
+
if (sub === 'vesk' && !cfgState && !cfgLoading)
|
|
2233
|
+
refreshVeskConfig();
|
|
2234
|
+
if (sub === 'agentic') {
|
|
2235
|
+
if (!agenticConfigState && !agenticConfigLoading)
|
|
2236
|
+
refreshAgenticConfig();
|
|
2237
|
+
// Always refresh live models via {PROVIDER}/models (through dev server proxy) when Agentic settings opened
|
|
2238
|
+
refreshAgenticModels();
|
|
2239
|
+
}
|
|
2240
|
+
renderPanel();
|
|
2241
|
+
}
|
|
2242
|
+
function setTab(tab) {
|
|
2243
|
+
activeTab = tab;
|
|
2244
|
+
ui.activeTab = tab;
|
|
2245
|
+
persistUi();
|
|
2246
|
+
const tabs = panelEl && panelEl.querySelectorAll('.__kp_tab');
|
|
2247
|
+
if (tabs) {
|
|
2248
|
+
Array.prototype.forEach.call(tabs, function (t) {
|
|
2249
|
+
t.classList.toggle('active', t.getAttribute('data-tab') === tab);
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
if (tab === 'settings') {
|
|
2253
|
+
if (settingsSubtab === 'vesk' && !cfgState && !cfgLoading)
|
|
2254
|
+
refreshVeskConfig();
|
|
2255
|
+
if (settingsSubtab === 'agentic' && !agenticConfigState && !agenticConfigLoading)
|
|
2256
|
+
refreshAgenticConfig();
|
|
2257
|
+
// legacy mapping: old prefs/config -> new ids already handled via isSettingsSubTab, but ensure legacy 'config' still triggers
|
|
2258
|
+
if ((settingsSubtab === 'config') && !cfgState && !cfgLoading)
|
|
2259
|
+
refreshVeskConfig();
|
|
2260
|
+
}
|
|
2261
|
+
renderPanel();
|
|
2262
|
+
}
|
|
2263
|
+
function togglePanel() {
|
|
2264
|
+
panelOpen = !panelOpen;
|
|
2265
|
+
ui.open = panelOpen;
|
|
2266
|
+
persistUi();
|
|
2267
|
+
if (!panelEl)
|
|
2268
|
+
createPanel();
|
|
2269
|
+
if (panelOpen) {
|
|
2270
|
+
if (panelEl)
|
|
2271
|
+
panelEl.classList.remove('hidden');
|
|
2272
|
+
renderPanel();
|
|
2273
|
+
}
|
|
2274
|
+
else {
|
|
2275
|
+
if (panelEl)
|
|
2276
|
+
panelEl.classList.add('hidden');
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
function syncAgenticSlashPopup() {
|
|
2280
|
+
if (!doc)
|
|
2281
|
+
return;
|
|
2282
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
2283
|
+
if (!inputEl)
|
|
2284
|
+
return;
|
|
2285
|
+
const wrap = inputEl.closest('.__kp_ag_input_wrap');
|
|
2286
|
+
if (!wrap)
|
|
2287
|
+
return;
|
|
2288
|
+
const cur = wrap.querySelector('[data-agentic-popup]');
|
|
2289
|
+
const nextHtml = !agenticRunning && agenticInput.charAt(0) === '/'
|
|
2290
|
+
? renderAgenticSlashPopup(agenticInput, [], AGENTIC_PROVIDERS, [])
|
|
2291
|
+
: '';
|
|
2292
|
+
if (nextHtml) {
|
|
2293
|
+
if (cur)
|
|
2294
|
+
cur.outerHTML = nextHtml;
|
|
2295
|
+
else
|
|
2296
|
+
wrap.insertAdjacentHTML('afterbegin', nextHtml);
|
|
2297
|
+
}
|
|
2298
|
+
else if (cur) {
|
|
2299
|
+
cur.remove();
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function renderPanel() {
|
|
2303
|
+
if (!doc.getElementById('__vesk_dev'))
|
|
264
2304
|
createDot();
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
2305
|
+
if (!panelEl)
|
|
2306
|
+
return;
|
|
2307
|
+
if (!panelOpen)
|
|
2308
|
+
return;
|
|
2309
|
+
const content = doc.getElementById('__kp_content');
|
|
2310
|
+
if (!content)
|
|
2311
|
+
return;
|
|
2312
|
+
// If the agentic textbox is focused (actively typing on mobile), carry
|
|
2313
|
+
// focus + caret across the redraw so the keyboard never dismisses.
|
|
2314
|
+
const activeEl = doc.activeElement;
|
|
2315
|
+
const wasAgenticInput = !!activeEl && activeEl.getAttribute('data-agentic-input') === '1';
|
|
2316
|
+
const caret = wasAgenticInput ? activeEl.selectionStart : -1;
|
|
2317
|
+
const renderers = {
|
|
2318
|
+
overview: () => renderOverviewPanel({
|
|
2319
|
+
host: win.location.host,
|
|
2320
|
+
lastCompileMs,
|
|
2321
|
+
lastError,
|
|
2322
|
+
}),
|
|
2323
|
+
agentic: () => renderAgenticPanel({
|
|
2324
|
+
provider: ui.agenticProvider,
|
|
2325
|
+
model: ui.agenticModel,
|
|
2326
|
+
models: agenticModelsCache.length ? agenticModelsCache : ui.agenticModels,
|
|
2327
|
+
modelsLoading: agenticModelsLoading,
|
|
2328
|
+
modelsError: agenticModelsError,
|
|
2329
|
+
mode: ui.agenticMode,
|
|
2330
|
+
messages: agenticMessages,
|
|
2331
|
+
history: agenticHistory,
|
|
2332
|
+
historyLoading: agenticHistoryLoading,
|
|
2333
|
+
input: agenticInput,
|
|
2334
|
+
running: agenticRunning,
|
|
2335
|
+
error: agenticError || agenticHistoryError,
|
|
2336
|
+
}),
|
|
2337
|
+
errors: () => renderErrorsPanel(lastError),
|
|
2338
|
+
diagnostics: () => diagnosticsError
|
|
2339
|
+
? '<div class="__kp_line">' + escapeHtml(diagnosticsError) + '</div>'
|
|
2340
|
+
: renderDiagnosticsPanel(diagnostics),
|
|
2341
|
+
plugins: () => renderPluginsTab(),
|
|
2342
|
+
log: () => renderLogPanel(log),
|
|
2343
|
+
settings: () => {
|
|
2344
|
+
// Build config render state for subtab
|
|
2345
|
+
let cfgRender = null;
|
|
2346
|
+
if (cfgLoading) {
|
|
2347
|
+
cfgRender = {
|
|
2348
|
+
path: cfgState ? cfgState.path : null,
|
|
2349
|
+
exists: cfgState ? cfgState.exists : false,
|
|
2350
|
+
source: cfgState ? cfgState.source : '',
|
|
2351
|
+
config: cfgState ? cfgState.config : {},
|
|
2352
|
+
loading: true,
|
|
2353
|
+
error: null,
|
|
2354
|
+
saving: cfgSaving,
|
|
2355
|
+
saveError: cfgSaveError,
|
|
2356
|
+
saveOk: cfgSaveOk,
|
|
2357
|
+
draftSource: cfgState ? cfgState.draftSource : '',
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
else if (cfgError) {
|
|
2361
|
+
cfgRender = {
|
|
2362
|
+
path: cfgState ? cfgState.path : null,
|
|
2363
|
+
exists: cfgState ? cfgState.exists : false,
|
|
2364
|
+
source: cfgState ? cfgState.source : '',
|
|
2365
|
+
config: cfgState ? cfgState.config : {},
|
|
2366
|
+
loading: false,
|
|
2367
|
+
error: cfgError,
|
|
2368
|
+
saving: cfgSaving,
|
|
2369
|
+
saveError: cfgSaveError,
|
|
2370
|
+
saveOk: cfgSaveOk,
|
|
2371
|
+
draftSource: cfgState ? cfgState.draftSource : '',
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
else if (cfgState) {
|
|
2375
|
+
cfgRender = {
|
|
2376
|
+
path: cfgState.path,
|
|
2377
|
+
exists: cfgState.exists,
|
|
2378
|
+
source: cfgState.source,
|
|
2379
|
+
config: cfgState.config,
|
|
2380
|
+
loading: false,
|
|
2381
|
+
error: null,
|
|
2382
|
+
saving: cfgSaving,
|
|
2383
|
+
saveError: cfgSaveError,
|
|
2384
|
+
saveOk: cfgSaveOk,
|
|
2385
|
+
draftSource: cfgState.draftSource,
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
// Build agentic render state
|
|
2389
|
+
let agenticRender = null;
|
|
2390
|
+
if (agenticConfigLoading) {
|
|
2391
|
+
agenticRender = agenticConfigState ? { ...agenticConfigState, loading: true } : { ...defaultAgenticConfigState(), loading: true, provider: ui.agenticProvider, model: ui.agenticModel, mode: ui.agenticMode, models: agenticModelsCache.slice() };
|
|
2392
|
+
}
|
|
2393
|
+
else if (agenticConfigError) {
|
|
2394
|
+
agenticRender = agenticConfigState ? { ...agenticConfigState, error: agenticConfigError } : { ...defaultAgenticConfigState(), error: agenticConfigError, provider: ui.agenticProvider, model: ui.agenticModel, mode: ui.agenticMode };
|
|
2395
|
+
}
|
|
2396
|
+
else if (agenticConfigState) {
|
|
2397
|
+
agenticRender = {
|
|
2398
|
+
...agenticConfigState,
|
|
2399
|
+
saving: agenticConfigSaving,
|
|
2400
|
+
saveError: agenticConfigSaveError,
|
|
2401
|
+
saveOk: agenticConfigSaveOk,
|
|
2402
|
+
// Live cache always wins over the possibly-stale config snapshot — the
|
|
2403
|
+
// subtab must show the models fetched for the CURRENT provider/baseUrl.
|
|
2404
|
+
models: agenticModelsCache.length ? agenticModelsCache.slice() : agenticConfigState.models,
|
|
2405
|
+
modelsLoading: agenticModelsLoading,
|
|
2406
|
+
modelsError: agenticModelsError,
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
else {
|
|
2410
|
+
agenticRender = {
|
|
2411
|
+
...defaultAgenticConfigState(),
|
|
2412
|
+
provider: ui.agenticProvider,
|
|
2413
|
+
model: ui.agenticModel,
|
|
2414
|
+
mode: ui.agenticMode,
|
|
2415
|
+
models: agenticModelsCache.slice(),
|
|
2416
|
+
modelsLoading: agenticModelsLoading,
|
|
2417
|
+
modelsError: agenticModelsError,
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
return renderSettingsPanel(ui, agenticRender, cfgRender, settingsSubtab);
|
|
2421
|
+
},
|
|
2422
|
+
};
|
|
2423
|
+
const renderTab = renderers[activeTab];
|
|
2424
|
+
const html = renderTab ? renderTab() : '<div class="__kp_line">unknown tab: ' + escapeHtml(activeTab) + '</div>';
|
|
2425
|
+
content.innerHTML = '<div class="__kp_pane" data-tab="' + escapeHtml(activeTab) + '">' + html + '</div>';
|
|
2426
|
+
const pane = content.firstElementChild;
|
|
2427
|
+
if (pane) {
|
|
2428
|
+
// Restore focus onto the redrawn textbox (skipping the scroll-to-top,
|
|
2429
|
+
// which would yank the pane out from under the keyboard).
|
|
2430
|
+
if (wasAgenticInput) {
|
|
2431
|
+
const nue = doc.querySelector('[data-agentic-input="1"]');
|
|
2432
|
+
if (nue) {
|
|
2433
|
+
nue.focus();
|
|
2434
|
+
try {
|
|
2435
|
+
nue.setSelectionRange(caret, caret);
|
|
2436
|
+
}
|
|
2437
|
+
catch (_e) {
|
|
2438
|
+
/* ignore selection restore failures */
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
else {
|
|
2443
|
+
pane.scrollTop = 0;
|
|
2444
|
+
}
|
|
2445
|
+
void pane.offsetWidth;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
function renderPluginsTab() {
|
|
2449
|
+
const installedPackages = [];
|
|
2450
|
+
for (const p of plugins) {
|
|
2451
|
+
const nm = p.package || p.name;
|
|
2452
|
+
if (nm && installedPackages.indexOf(nm) < 0)
|
|
2453
|
+
installedPackages.push(nm);
|
|
2454
|
+
}
|
|
2455
|
+
if (pluginDetailSearch) {
|
|
2456
|
+
return renderSearchPluginDetail(pluginDetailSearch, pluginInstalling, installedPackages);
|
|
2457
|
+
}
|
|
2458
|
+
if (pluginDetail != null) {
|
|
2459
|
+
const p = plugins.find((x) => x.name === pluginDetail);
|
|
2460
|
+
if (pluginExports) {
|
|
2461
|
+
const ex = pluginExports;
|
|
2462
|
+
return renderPluginExports(ex, pluginExportsError);
|
|
2463
|
+
}
|
|
2464
|
+
if (p) {
|
|
2465
|
+
return renderPluginDetail(p, pluginDetailMsg);
|
|
2466
|
+
}
|
|
2467
|
+
return '<div class="__kp_line">plugin not found</div>';
|
|
2468
|
+
}
|
|
2469
|
+
if (pluginView === 'search') {
|
|
2470
|
+
return renderPluginSearch(pluginSearchQuery, pluginSearchResults, pluginSearchError, pluginSearchLoading, pluginInstalling, installedPackages);
|
|
2471
|
+
}
|
|
2472
|
+
return renderPluginsPanel(plugins, pluginsError, ui.pluginsView);
|
|
2473
|
+
}
|
|
2474
|
+
function refreshPlugins() {
|
|
2475
|
+
if (typeof fetch === 'undefined')
|
|
2476
|
+
return;
|
|
2477
|
+
fetch(urls.pluginsUrl)
|
|
2478
|
+
.then(function (r) {
|
|
2479
|
+
if (!r.ok)
|
|
2480
|
+
throw new Error('HTTP ' + r.status);
|
|
2481
|
+
return r.json();
|
|
2482
|
+
})
|
|
2483
|
+
.then(function (data) {
|
|
2484
|
+
const arr = Array.isArray(data) ? data : data.plugins;
|
|
2485
|
+
plugins.length = 0;
|
|
2486
|
+
if (Array.isArray(arr)) {
|
|
2487
|
+
for (const raw of arr) {
|
|
2488
|
+
plugins.push(mapPluginRecord(raw));
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
pluginsError = null;
|
|
2492
|
+
renderPanel();
|
|
2493
|
+
})
|
|
2494
|
+
.catch(function (e) {
|
|
2495
|
+
pluginsError = 'plugins unavailable: ' + (e instanceof Error ? e.message : String(e));
|
|
2496
|
+
renderPanel();
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
function refreshDiagnostics() {
|
|
2500
|
+
if (typeof fetch === 'undefined')
|
|
2501
|
+
return;
|
|
2502
|
+
fetch(urls.diagnosticsUrl)
|
|
2503
|
+
.then(function (r) {
|
|
2504
|
+
if (!r.ok)
|
|
2505
|
+
throw new Error('HTTP ' + r.status);
|
|
2506
|
+
return r.json();
|
|
2507
|
+
})
|
|
2508
|
+
.then(function (data) {
|
|
2509
|
+
const list = data.diagnostics;
|
|
2510
|
+
diagnostics.length = 0;
|
|
2511
|
+
if (Array.isArray(list)) {
|
|
2512
|
+
for (const raw of list) {
|
|
2513
|
+
diagnostics.push(mapDiagnostic(raw));
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
diagnosticsError = null;
|
|
2517
|
+
renderPanel();
|
|
2518
|
+
})
|
|
2519
|
+
.catch(function (e) {
|
|
2520
|
+
diagnosticsError = 'diagnostics unavailable: ' + (e instanceof Error ? e.message : String(e));
|
|
2521
|
+
renderPanel();
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
function refreshVeskConfig() {
|
|
2525
|
+
if (typeof fetch === 'undefined')
|
|
2526
|
+
return;
|
|
2527
|
+
cfgLoading = true;
|
|
2528
|
+
cfgError = null;
|
|
2529
|
+
renderPanel();
|
|
2530
|
+
fetch('/__vesk/config')
|
|
2531
|
+
.then(function (r) {
|
|
2532
|
+
if (!r.ok)
|
|
2533
|
+
throw new Error('HTTP ' + r.status);
|
|
2534
|
+
return r.json();
|
|
2535
|
+
})
|
|
2536
|
+
.then(function (data) {
|
|
2537
|
+
const d = data;
|
|
2538
|
+
if (d && d.error)
|
|
2539
|
+
throw new Error(String(d.error));
|
|
2540
|
+
cfgState = {
|
|
2541
|
+
path: d.path != null ? String(d.path) : null,
|
|
2542
|
+
exists: !!d.exists,
|
|
2543
|
+
source: typeof d.source === 'string' ? d.source : '',
|
|
2544
|
+
config: (d.config && typeof d.config === 'object' ? d.config : {}),
|
|
2545
|
+
draftSource: typeof d.source === 'string' ? d.source : '',
|
|
2546
|
+
};
|
|
2547
|
+
cfgLoading = false;
|
|
2548
|
+
cfgError = null;
|
|
2549
|
+
cfgSaveError = null;
|
|
2550
|
+
cfgSaveOk = null;
|
|
2551
|
+
renderPanel();
|
|
2552
|
+
})
|
|
2553
|
+
.catch(function (e) {
|
|
2554
|
+
cfgLoading = false;
|
|
2555
|
+
cfgError = e instanceof Error ? e.message : String(e);
|
|
2556
|
+
renderPanel();
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
function postVeskConfigKey(key, value) {
|
|
2560
|
+
if (typeof fetch === 'undefined') {
|
|
2561
|
+
cfgSaveError = 'fetch unavailable';
|
|
2562
|
+
renderPanel();
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
cfgSaving = true;
|
|
2566
|
+
cfgSaveError = null;
|
|
2567
|
+
cfgSaveOk = null;
|
|
2568
|
+
renderPanel();
|
|
2569
|
+
fetch('/__vesk/config', {
|
|
2570
|
+
method: 'POST',
|
|
2571
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2572
|
+
body: JSON.stringify({ key: key, value: value }),
|
|
2573
|
+
})
|
|
2574
|
+
.then(function (r) {
|
|
2575
|
+
return r.json().then(function (body) {
|
|
2576
|
+
if (!r.ok)
|
|
2577
|
+
throw new Error(body.error || 'HTTP ' + r.status);
|
|
2578
|
+
return body;
|
|
2579
|
+
});
|
|
2580
|
+
})
|
|
2581
|
+
.then(function (body) {
|
|
2582
|
+
cfgSaving = false;
|
|
2583
|
+
if (body.config && typeof body.config === 'object') {
|
|
2584
|
+
if (!cfgState)
|
|
2585
|
+
cfgState = { path: body.path || null, exists: true, source: body.source || '', config: body.config, draftSource: body.source || '' };
|
|
2586
|
+
else {
|
|
2587
|
+
cfgState.config = body.config;
|
|
2588
|
+
cfgState.source = body.source || cfgState.source;
|
|
2589
|
+
cfgState.draftSource = body.source || cfgState.draftSource;
|
|
2590
|
+
cfgState.path = body.path || cfgState.path;
|
|
2591
|
+
cfgState.exists = true;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
cfgSaveOk = 'saved ' + key;
|
|
2595
|
+
setTimeout(function () { cfgSaveOk = null; renderPanel(); }, 2500);
|
|
2596
|
+
renderPanel();
|
|
2597
|
+
})
|
|
2598
|
+
.catch(function (e) {
|
|
2599
|
+
cfgSaving = false;
|
|
2600
|
+
cfgSaveError = e instanceof Error ? e.message : String(e);
|
|
2601
|
+
renderPanel();
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
function postVeskConfigSource(source) {
|
|
2605
|
+
if (typeof fetch === 'undefined') {
|
|
2606
|
+
cfgSaveError = 'fetch unavailable';
|
|
2607
|
+
renderPanel();
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
cfgSaving = true;
|
|
2611
|
+
cfgSaveError = null;
|
|
2612
|
+
cfgSaveOk = null;
|
|
2613
|
+
renderPanel();
|
|
2614
|
+
fetch('/__vesk/config', {
|
|
2615
|
+
method: 'POST',
|
|
2616
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2617
|
+
body: JSON.stringify({ source: source }),
|
|
2618
|
+
})
|
|
2619
|
+
.then(function (r) {
|
|
2620
|
+
return r.json().then(function (body) {
|
|
2621
|
+
if (!r.ok)
|
|
2622
|
+
throw new Error(body.error || 'HTTP ' + r.status);
|
|
2623
|
+
return body;
|
|
2624
|
+
});
|
|
2625
|
+
})
|
|
2626
|
+
.then(function (body) {
|
|
2627
|
+
cfgSaving = false;
|
|
2628
|
+
if (body.config && typeof body.config === 'object') {
|
|
2629
|
+
if (!cfgState)
|
|
2630
|
+
cfgState = { path: body.path || null, exists: true, source: body.source || source, config: body.config, draftSource: body.source || source };
|
|
2631
|
+
else {
|
|
2632
|
+
cfgState.config = body.config;
|
|
2633
|
+
cfgState.source = body.source || source;
|
|
2634
|
+
cfgState.draftSource = body.source || source;
|
|
2635
|
+
cfgState.path = body.path || cfgState.path;
|
|
2636
|
+
cfgState.exists = true;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
cfgSaveOk = 'config saved';
|
|
2640
|
+
setTimeout(function () { cfgSaveOk = null; renderPanel(); }, 2500);
|
|
2641
|
+
renderPanel();
|
|
2642
|
+
})
|
|
2643
|
+
.catch(function (e) {
|
|
2644
|
+
cfgSaving = false;
|
|
2645
|
+
cfgSaveError = e instanceof Error ? e.message : String(e);
|
|
2646
|
+
renderPanel();
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
function refreshAgenticConfig() {
|
|
2650
|
+
if (typeof fetch === 'undefined')
|
|
2651
|
+
return;
|
|
2652
|
+
agenticConfigLoading = true;
|
|
2653
|
+
agenticConfigError = null;
|
|
2654
|
+
renderPanel();
|
|
2655
|
+
fetch('/__vesk/agent/config')
|
|
2656
|
+
.then(function (r) {
|
|
2657
|
+
if (!r.ok)
|
|
2658
|
+
throw new Error('HTTP ' + r.status);
|
|
2659
|
+
return r.json();
|
|
2660
|
+
})
|
|
2661
|
+
.then(function (data) {
|
|
2662
|
+
const d = data;
|
|
2663
|
+
const provider = typeof d.provider === 'string' && AGENTIC_PROVIDERS.indexOf(d.provider) !== -1 ? String(d.provider) : ui.agenticProvider;
|
|
2664
|
+
const model = typeof d.model === 'string' ? String(d.model) : ui.agenticModel;
|
|
2665
|
+
const baseUrl = typeof d.baseUrl === 'string' ? String(d.baseUrl) : (typeof d.baseUrl === 'string' ? String(d.baseUrl) : '');
|
|
2666
|
+
agenticBaseUrl = baseUrl;
|
|
2667
|
+
const mode = typeof d.mode === 'string' && isAgenticMode(d.mode) ? d.mode : ui.agenticMode;
|
|
2668
|
+
const maxSteps = typeof d.maxSteps === 'number' ? d.maxSteps : 25;
|
|
2669
|
+
agenticMaxSteps = maxSteps;
|
|
2670
|
+
const hasKey = !!d.hasKey;
|
|
2671
|
+
const keyPreview = typeof d.keyPreview === 'string' ? String(d.keyPreview) : null;
|
|
2672
|
+
const previews = {};
|
|
2673
|
+
const hasKeys = {};
|
|
2674
|
+
for (const p of AGENTIC_PROVIDERS) {
|
|
2675
|
+
if (p === provider) {
|
|
2676
|
+
hasKeys[p] = hasKey;
|
|
2677
|
+
previews[p] = keyPreview;
|
|
2678
|
+
}
|
|
2679
|
+
else {
|
|
2680
|
+
hasKeys[p] = false;
|
|
2681
|
+
previews[p] = null;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
agenticConfigState = {
|
|
2685
|
+
provider: provider,
|
|
2686
|
+
model: model,
|
|
2687
|
+
models: Array.isArray(d.models) ? d.models : agenticModelsCache.slice(),
|
|
2688
|
+
baseUrl: baseUrl,
|
|
2689
|
+
mode: mode,
|
|
2690
|
+
maxSteps: maxSteps,
|
|
2691
|
+
apiKeys: {},
|
|
2692
|
+
hasKeys: hasKeys,
|
|
2693
|
+
keyPreviews: previews,
|
|
2694
|
+
loading: false,
|
|
2695
|
+
error: null,
|
|
2696
|
+
};
|
|
2697
|
+
agenticConfigLoading = false;
|
|
2698
|
+
agenticConfigError = null;
|
|
2699
|
+
// sync ui — but never clobber the user's explicitly selected model with a
|
|
2700
|
+
// (possibly stale persisted) config default. The fresh per-provider fetched
|
|
2701
|
+
// list + user pick win; server config is only the persisted default.
|
|
2702
|
+
ui.agenticProvider = provider;
|
|
2703
|
+
if (!agenticSelectedModel)
|
|
2704
|
+
ui.agenticModel = model;
|
|
2705
|
+
ui.agenticMode = mode;
|
|
2706
|
+
persistUi();
|
|
2707
|
+
renderPanel();
|
|
2708
|
+
})
|
|
2709
|
+
.catch(function (e) {
|
|
2710
|
+
agenticConfigLoading = false;
|
|
2711
|
+
agenticConfigError = e instanceof Error ? e.message : String(e);
|
|
2712
|
+
renderPanel();
|
|
2713
|
+
});
|
|
268
2714
|
}
|
|
269
|
-
|
|
2715
|
+
function saveAgenticConfigPatch(patch) {
|
|
2716
|
+
if (typeof fetch === 'undefined') {
|
|
2717
|
+
agenticConfigSaveError = 'fetch unavailable';
|
|
2718
|
+
renderPanel();
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
agenticConfigSaving = true;
|
|
2722
|
+
agenticConfigSaveError = null;
|
|
2723
|
+
agenticConfigSaveOk = null;
|
|
2724
|
+
renderPanel();
|
|
2725
|
+
fetch('/__vesk/agent/config', {
|
|
2726
|
+
method: 'POST',
|
|
2727
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2728
|
+
body: JSON.stringify(patch),
|
|
2729
|
+
})
|
|
2730
|
+
.then(function (r) {
|
|
2731
|
+
return r.json().then(function (body) {
|
|
2732
|
+
if (!r.ok)
|
|
2733
|
+
throw new Error(String(body.error || 'HTTP ' + r.status));
|
|
2734
|
+
return body;
|
|
2735
|
+
});
|
|
2736
|
+
})
|
|
2737
|
+
.then(function () {
|
|
2738
|
+
agenticConfigSaving = false;
|
|
2739
|
+
agenticConfigSaveOk = 'saved';
|
|
2740
|
+
setTimeout(function () { agenticConfigSaveOk = null; renderPanel(); }, 2000);
|
|
2741
|
+
// update local state
|
|
2742
|
+
if (agenticConfigState) {
|
|
2743
|
+
if (typeof patch.provider === 'string')
|
|
2744
|
+
agenticConfigState.provider = String(patch.provider);
|
|
2745
|
+
if (typeof patch.model === 'string')
|
|
2746
|
+
agenticConfigState.model = String(patch.model);
|
|
2747
|
+
if (typeof patch.baseUrl === 'string')
|
|
2748
|
+
agenticConfigState.baseUrl = String(patch.baseUrl);
|
|
2749
|
+
if (typeof patch.mode === 'string' && isAgenticMode(patch.mode))
|
|
2750
|
+
agenticConfigState.mode = patch.mode;
|
|
2751
|
+
if (typeof patch.maxSteps === 'number')
|
|
2752
|
+
agenticConfigState.maxSteps = patch.maxSteps;
|
|
2753
|
+
agenticConfigState.saveOk = agenticConfigSaveOk;
|
|
2754
|
+
}
|
|
2755
|
+
if (typeof patch.provider === 'string') {
|
|
2756
|
+
ui.agenticProvider = String(patch.provider);
|
|
2757
|
+
persistUi();
|
|
2758
|
+
}
|
|
2759
|
+
if (typeof patch.model === 'string') {
|
|
2760
|
+
ui.agenticModel = String(patch.model);
|
|
2761
|
+
persistUi();
|
|
2762
|
+
}
|
|
2763
|
+
if (typeof patch.mode === 'string') {
|
|
2764
|
+
ui.agenticMode = patch.mode;
|
|
2765
|
+
persistUi();
|
|
2766
|
+
}
|
|
2767
|
+
renderPanel();
|
|
2768
|
+
})
|
|
2769
|
+
.catch(function (e) {
|
|
2770
|
+
agenticConfigSaving = false;
|
|
2771
|
+
agenticConfigSaveError = e instanceof Error ? e.message : String(e);
|
|
2772
|
+
if (agenticConfigState)
|
|
2773
|
+
agenticConfigState.saveError = agenticConfigSaveError;
|
|
2774
|
+
renderPanel();
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
function saveAgenticApiKey(provider, key) {
|
|
2778
|
+
if (!key.trim()) {
|
|
2779
|
+
agenticConfigSaveError = 'api key empty';
|
|
2780
|
+
renderPanel();
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
saveAgenticConfigPatch({ provider: provider, apiKey: key });
|
|
2784
|
+
// optimistic hasKey update
|
|
2785
|
+
if (agenticConfigState) {
|
|
2786
|
+
agenticConfigState.hasKeys[provider] = true;
|
|
2787
|
+
agenticConfigState.keyPreviews[provider] = maskApiKey(key);
|
|
2788
|
+
}
|
|
2789
|
+
renderPanel();
|
|
2790
|
+
}
|
|
2791
|
+
function refreshAgenticModels() {
|
|
2792
|
+
if (typeof fetch === 'undefined')
|
|
2793
|
+
return;
|
|
2794
|
+
agenticModelsLoading = true;
|
|
2795
|
+
agenticModelsError = null;
|
|
2796
|
+
renderPanel();
|
|
2797
|
+
const url = buildAgenticModelsUrl(ui.agenticProvider, agenticBaseUrl);
|
|
2798
|
+
fetch(url)
|
|
2799
|
+
.then(function (r) {
|
|
2800
|
+
if (!r.ok)
|
|
2801
|
+
throw new Error('HTTP ' + r.status);
|
|
2802
|
+
return r.json();
|
|
2803
|
+
})
|
|
2804
|
+
.then(function (data) {
|
|
2805
|
+
const d = data;
|
|
2806
|
+
let list = [];
|
|
2807
|
+
if (Array.isArray(d.models)) {
|
|
2808
|
+
list = d.models.filter(function (v) { return typeof v === 'string' && v.length > 0; });
|
|
2809
|
+
}
|
|
2810
|
+
else if (Array.isArray(data)) {
|
|
2811
|
+
list = data.filter(function (v) { return typeof v === 'string'; });
|
|
2812
|
+
}
|
|
2813
|
+
// Live models always win — keep the settings subtab in sync with the cache.
|
|
2814
|
+
agenticModelsCache = list;
|
|
2815
|
+
ui.agenticModels = list.slice();
|
|
2816
|
+
if (agenticConfigState) {
|
|
2817
|
+
agenticConfigState.models = list.slice();
|
|
2818
|
+
agenticConfigState.modelsLoading = false;
|
|
2819
|
+
agenticConfigState.modelsError = null;
|
|
2820
|
+
// reflect the resolved provider/baseUrl so the subtab is self-consistent
|
|
2821
|
+
agenticConfigState.provider = ui.agenticProvider;
|
|
2822
|
+
agenticConfigState.baseUrl = agenticBaseUrl;
|
|
2823
|
+
// If the current model is not among the fetched models for this
|
|
2824
|
+
// provider, fall back to the first valid model (no hardcoding).
|
|
2825
|
+
if (list.length > 0 && list.indexOf(ui.agenticModel) === -1) {
|
|
2826
|
+
ui.agenticModel = list[0];
|
|
2827
|
+
agenticConfigState.model = ui.agenticModel;
|
|
2828
|
+
persistUi();
|
|
2829
|
+
saveAgenticConfigPatch({ model: ui.agenticModel });
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
persistUi();
|
|
2833
|
+
agenticModelsLoading = false;
|
|
2834
|
+
renderPanel();
|
|
2835
|
+
})
|
|
2836
|
+
.catch(function (e) {
|
|
2837
|
+
agenticModelsLoading = false;
|
|
2838
|
+
agenticModelsError = e instanceof Error ? e.message : String(e);
|
|
2839
|
+
renderPanel();
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
function patchAgenticHistorySection() {
|
|
2843
|
+
const pane = agenticPaneEl();
|
|
2844
|
+
if (!pane)
|
|
2845
|
+
return;
|
|
2846
|
+
const block = pane.querySelector('[data-agentic="history-block"]');
|
|
2847
|
+
if (!block)
|
|
2848
|
+
return;
|
|
2849
|
+
block.innerHTML = renderAgenticHistory(agenticHistory, agenticHistoryLoading) +
|
|
2850
|
+
(agenticHistoryError ? '<div class="__kp_pl_err">' + String(agenticHistoryError) + '</div>' : '');
|
|
2851
|
+
}
|
|
2852
|
+
function refreshAgenticHistory() {
|
|
2853
|
+
if (typeof fetch === 'undefined')
|
|
2854
|
+
return;
|
|
2855
|
+
agenticHistoryLoading = true;
|
|
2856
|
+
agenticHistoryError = null;
|
|
2857
|
+
patchAgenticHistorySection();
|
|
2858
|
+
fetch('/__vesk/agent/history')
|
|
2859
|
+
.then(function (r) {
|
|
2860
|
+
if (!r.ok)
|
|
2861
|
+
throw new Error('HTTP ' + r.status);
|
|
2862
|
+
return r.json();
|
|
2863
|
+
})
|
|
2864
|
+
.then(function (data) {
|
|
2865
|
+
const d = data;
|
|
2866
|
+
const raw = Array.isArray(d.history) ? d.history : Array.isArray(d.checkpoints) ? d.checkpoints : Array.isArray(data) ? data : [];
|
|
2867
|
+
const list = raw;
|
|
2868
|
+
agenticHistory = [];
|
|
2869
|
+
for (let i = 0; i < list.length; i++) {
|
|
2870
|
+
const c = list[i];
|
|
2871
|
+
agenticHistory.push({
|
|
2872
|
+
id: String(c.id || c.checkpointId || i),
|
|
2873
|
+
message: String(c.message || c.label || c.id || ''),
|
|
2874
|
+
timestamp: typeof c.timestamp === 'number' ? c.timestamp : typeof c.createdAt === 'string' ? Date.parse(c.createdAt) || Date.now() : Date.now(),
|
|
2875
|
+
label: typeof c.label === 'string' ? c.label : typeof c.message === 'string' ? c.message : undefined,
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
agenticHistoryLoading = false;
|
|
2879
|
+
patchAgenticHistorySection();
|
|
2880
|
+
})
|
|
2881
|
+
.catch(function (e) {
|
|
2882
|
+
agenticHistoryLoading = false;
|
|
2883
|
+
agenticHistoryError = e instanceof Error ? e.message : String(e);
|
|
2884
|
+
patchAgenticHistorySection();
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
// Resolve the model to send: the user's explicit pick wins when it's valid for
|
|
2888
|
+
// the active provider; otherwise fall back to the first valid model in the
|
|
2889
|
+
// fetched per-provider list (never a dead hardcoded default).
|
|
2890
|
+
function resolveAgenticModel() {
|
|
2891
|
+
// A user's explicit pick reliably wins at send time.
|
|
2892
|
+
if (agenticSelectedModel)
|
|
2893
|
+
return agenticSelectedModel;
|
|
2894
|
+
const list = agenticModelsCache.length ? agenticModelsCache : ui.agenticModels;
|
|
2895
|
+
const pick = (ui.agenticModel || '').trim();
|
|
2896
|
+
if (pick && (list.length === 0 || list.indexOf(pick) !== -1))
|
|
2897
|
+
return pick;
|
|
2898
|
+
if (list.length)
|
|
2899
|
+
return list[0];
|
|
2900
|
+
return pick;
|
|
2901
|
+
}
|
|
2902
|
+
function agenticPaneEl() {
|
|
2903
|
+
const pane = doc.querySelector('#__vesk_dev .__kp_pane[data-tab="agentic"]');
|
|
2904
|
+
return pane;
|
|
2905
|
+
}
|
|
2906
|
+
function agenticPaneNearBottom(pane) {
|
|
2907
|
+
return pane.scrollTop + pane.clientHeight >= pane.scrollHeight - 48;
|
|
2908
|
+
}
|
|
2909
|
+
function buildAgenticMessageEl(m, idx) {
|
|
2910
|
+
const role = m.role || 'user';
|
|
2911
|
+
const time = m.ts ? new Date(m.ts).toLocaleTimeString() : '';
|
|
2912
|
+
const wrap = doc.createElement('div');
|
|
2913
|
+
wrap.className = '__kp_ag_msg';
|
|
2914
|
+
wrap.setAttribute('data-role', role);
|
|
2915
|
+
wrap.setAttribute('data-idx', String(idx));
|
|
2916
|
+
const head = doc.createElement('div');
|
|
2917
|
+
head.className = '__kp_ag_msg_head';
|
|
2918
|
+
const roleSpan = doc.createElement('span');
|
|
2919
|
+
roleSpan.className = '__kp_ag_role';
|
|
2920
|
+
roleSpan.textContent = role;
|
|
2921
|
+
head.appendChild(roleSpan);
|
|
2922
|
+
if (time) {
|
|
2923
|
+
const t = doc.createElement('span');
|
|
2924
|
+
t.className = '__kp_ag_time';
|
|
2925
|
+
t.textContent = time;
|
|
2926
|
+
head.appendChild(t);
|
|
2927
|
+
}
|
|
2928
|
+
const body = doc.createElement('div');
|
|
2929
|
+
body.className = '__kp_ag_msg_body';
|
|
2930
|
+
body.textContent = m.content;
|
|
2931
|
+
wrap.appendChild(head);
|
|
2932
|
+
wrap.appendChild(body);
|
|
2933
|
+
return wrap;
|
|
2934
|
+
}
|
|
2935
|
+
function updateAgenticChatState() {
|
|
2936
|
+
// Send button + running indicator only for the live chat pane. The input
|
|
2937
|
+
// stays enabled while running (disabling a focused input blurs it and
|
|
2938
|
+
// dismisses the mobile keyboard); sends are guarded by agenticRunning.
|
|
2939
|
+
const pane = agenticPaneEl();
|
|
2940
|
+
if (!pane)
|
|
2941
|
+
return;
|
|
2942
|
+
const sendBtn = pane.querySelector('button[data-agentic-send]');
|
|
2943
|
+
if (sendBtn) {
|
|
2944
|
+
sendBtn.disabled = !!agenticRunning;
|
|
2945
|
+
const label = agenticRunning
|
|
2946
|
+
? 'running' + (agenticRunStep > 0 ? ' step ' + agenticRunStep + (agenticRunBudget > 0 ? '/' + agenticRunBudget : '') : '') + '…'
|
|
2947
|
+
: 'send';
|
|
2948
|
+
if (sendBtn.textContent !== label)
|
|
2949
|
+
sendBtn.textContent = label;
|
|
2950
|
+
}
|
|
2951
|
+
const errHost = pane.querySelector('.__kp_pl_err');
|
|
2952
|
+
if (agenticError) {
|
|
2953
|
+
if (errHost) {
|
|
2954
|
+
errHost.textContent = agenticError;
|
|
2955
|
+
}
|
|
2956
|
+
else {
|
|
2957
|
+
const errDiv = doc.createElement('div');
|
|
2958
|
+
errDiv.className = '__kp_pl_err';
|
|
2959
|
+
const host = pane.querySelector('.__kp_ag_messages');
|
|
2960
|
+
if (host && host.parentNode) {
|
|
2961
|
+
if (host.nextSibling)
|
|
2962
|
+
host.parentNode.insertBefore(errDiv, host.nextSibling);
|
|
2963
|
+
else
|
|
2964
|
+
host.parentNode.appendChild(errDiv);
|
|
2965
|
+
}
|
|
2966
|
+
else {
|
|
2967
|
+
pane.appendChild(errDiv);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
else if (errHost) {
|
|
2972
|
+
errHost.remove();
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
// Incremental chat DOM update: grows/shrinks the message list in place and
|
|
2976
|
+
// patches the last message body as stream deltas arrive. The pane element is
|
|
2977
|
+
// never recreated, so scroll position and the textbox stay put during
|
|
2978
|
+
// send/receive — no UI jumping.
|
|
2979
|
+
function patchAgenticChat() {
|
|
2980
|
+
const pane = agenticPaneEl();
|
|
2981
|
+
if (!pane)
|
|
2982
|
+
return;
|
|
2983
|
+
let host = pane.querySelector('.__kp_ag_messages');
|
|
2984
|
+
if (!host) {
|
|
2985
|
+
// The empty chat renders only a placeholder line (no host node), so
|
|
2986
|
+
// create the host in its place the first time a message exists. We
|
|
2987
|
+
// NEVER re-render the whole pane here — that is what makes the UI jump.
|
|
2988
|
+
const wrap = doc.createElement('div');
|
|
2989
|
+
wrap.className = '__kp_ag_messages';
|
|
2990
|
+
wrap.setAttribute('data-agentic', 'messages');
|
|
2991
|
+
const placeholder = pane.querySelector('.__kp_line');
|
|
2992
|
+
if (placeholder && placeholder.parentNode === pane) {
|
|
2993
|
+
placeholder.parentNode.replaceChild(wrap, placeholder);
|
|
2994
|
+
}
|
|
2995
|
+
else {
|
|
2996
|
+
const inputWrap = pane.querySelector('.__kp_ag_input_wrap');
|
|
2997
|
+
if (inputWrap)
|
|
2998
|
+
pane.insertBefore(wrap, inputWrap);
|
|
2999
|
+
else
|
|
3000
|
+
pane.appendChild(wrap);
|
|
3001
|
+
}
|
|
3002
|
+
host = wrap;
|
|
3003
|
+
}
|
|
3004
|
+
if (pane && host) {
|
|
3005
|
+
let count = host.children.length;
|
|
3006
|
+
while (count < agenticMessages.length) {
|
|
3007
|
+
host.appendChild(buildAgenticMessageEl(agenticMessages[count], count));
|
|
3008
|
+
count++;
|
|
3009
|
+
}
|
|
3010
|
+
while (count > agenticMessages.length && host.lastChild) {
|
|
3011
|
+
host.removeChild(host.lastChild);
|
|
3012
|
+
count--;
|
|
3013
|
+
}
|
|
3014
|
+
for (let i = 0; i < agenticMessages.length; i++) {
|
|
3015
|
+
const row = host.children[i];
|
|
3016
|
+
if (!row)
|
|
3017
|
+
continue;
|
|
3018
|
+
const m = agenticMessages[i];
|
|
3019
|
+
const body = row.querySelector('.__kp_ag_msg_body');
|
|
3020
|
+
if (body && body.textContent !== m.content)
|
|
3021
|
+
body.textContent = m.content;
|
|
3022
|
+
if (m.ts) {
|
|
3023
|
+
const t = new Date(m.ts).toLocaleTimeString();
|
|
3024
|
+
const timeEl = row.querySelector('.__kp_ag_time');
|
|
3025
|
+
if (timeEl && timeEl.textContent !== t)
|
|
3026
|
+
timeEl.textContent = t;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
if (agenticRunning || agenticPaneNearBottom(pane)) {
|
|
3030
|
+
pane.scrollTop = pane.scrollHeight;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
updateAgenticChatState();
|
|
3034
|
+
}
|
|
3035
|
+
function consumeAgenticStream(reader) {
|
|
3036
|
+
const decoder = new TextDecoder();
|
|
3037
|
+
let buffer = '';
|
|
3038
|
+
let finished = false;
|
|
3039
|
+
function dispatch(rawFrame) {
|
|
3040
|
+
if (finished)
|
|
3041
|
+
return;
|
|
3042
|
+
let type = 'message';
|
|
3043
|
+
let data = '';
|
|
3044
|
+
const lines = rawFrame.split('\n');
|
|
3045
|
+
for (const line of lines) {
|
|
3046
|
+
if (line.indexOf('event:') === 0)
|
|
3047
|
+
type = line.slice(6).trim();
|
|
3048
|
+
else if (line.indexOf('data:') === 0)
|
|
3049
|
+
data = line.slice(5).trim();
|
|
3050
|
+
}
|
|
3051
|
+
if (!data)
|
|
3052
|
+
return;
|
|
3053
|
+
let ev;
|
|
3054
|
+
try {
|
|
3055
|
+
ev = JSON.parse(data);
|
|
3056
|
+
}
|
|
3057
|
+
catch {
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
const evType = type;
|
|
3061
|
+
if (evType === 'step') {
|
|
3062
|
+
if (typeof ev.step === 'number')
|
|
3063
|
+
agenticRunStep = ev.step;
|
|
3064
|
+
if (typeof ev.budget === 'number' && ev.budget > 0)
|
|
3065
|
+
agenticRunBudget = ev.budget;
|
|
3066
|
+
updateAgenticChatState();
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
if (evType === 'text_delta') {
|
|
3070
|
+
const content = typeof ev.content === 'string' ? ev.content : '';
|
|
3071
|
+
if (!content)
|
|
3072
|
+
return;
|
|
3073
|
+
const cur = agenticMessages[agenticMessages.length - 1];
|
|
3074
|
+
if (cur && cur.role === 'assistant' && typeof cur.content === 'string') {
|
|
3075
|
+
cur.content += content;
|
|
3076
|
+
}
|
|
3077
|
+
else {
|
|
3078
|
+
agenticMessages.push({ role: 'assistant', content: content, ts: Date.now() });
|
|
3079
|
+
}
|
|
3080
|
+
patchAgenticChat();
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
if (evType === 'tool_call') {
|
|
3084
|
+
const call = ev.call;
|
|
3085
|
+
const name = call && typeof call.name === 'string' ? String(call.name) : '';
|
|
3086
|
+
if (name) {
|
|
3087
|
+
agenticMessages.push({ role: 'system', content: '→ tool: ' + name + (typeof ev.step === 'number' ? ' (step ' + String(ev.step) + ')' : ''), ts: Date.now() });
|
|
3088
|
+
patchAgenticChat();
|
|
3089
|
+
}
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
if (evType === 'error') {
|
|
3093
|
+
finished = true;
|
|
3094
|
+
agenticError = typeof ev.message === 'string' ? String(ev.message) : typeof ev.error === 'string' ? String(ev.error) : 'agent error';
|
|
3095
|
+
agenticRunning = false;
|
|
3096
|
+
agenticRunStep = 0;
|
|
3097
|
+
patchAgenticChat();
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
if (evType === 'done') {
|
|
3101
|
+
finished = true;
|
|
3102
|
+
const res = ev.result;
|
|
3103
|
+
const text = res && typeof res.text === 'string' ? String(res.text) : '';
|
|
3104
|
+
const cur = agenticMessages[agenticMessages.length - 1];
|
|
3105
|
+
if (cur && cur.role === 'assistant' && cur.content) {
|
|
3106
|
+
if (text && cur.content !== text)
|
|
3107
|
+
cur.content = text;
|
|
3108
|
+
}
|
|
3109
|
+
else if (text) {
|
|
3110
|
+
agenticMessages.push({ role: 'assistant', content: text, ts: Date.now() });
|
|
3111
|
+
}
|
|
3112
|
+
agenticRunning = false;
|
|
3113
|
+
agenticRunStep = 0;
|
|
3114
|
+
patchAgenticChat();
|
|
3115
|
+
refreshAgenticHistory();
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
function pump(step) {
|
|
3119
|
+
if (step.done) {
|
|
3120
|
+
try {
|
|
3121
|
+
reader.releaseLock();
|
|
3122
|
+
}
|
|
3123
|
+
catch {
|
|
3124
|
+
/* already released */
|
|
3125
|
+
}
|
|
3126
|
+
agenticRunning = false;
|
|
3127
|
+
agenticRunStep = 0;
|
|
3128
|
+
patchAgenticChat();
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
buffer += decoder.decode(step.value, { stream: true });
|
|
3132
|
+
let idx = buffer.indexOf('\n\n');
|
|
3133
|
+
while (idx !== -1) {
|
|
3134
|
+
const rawFrame = buffer.slice(0, idx);
|
|
3135
|
+
buffer = buffer.slice(idx + 2);
|
|
3136
|
+
dispatch(rawFrame);
|
|
3137
|
+
idx = buffer.indexOf('\n\n');
|
|
3138
|
+
}
|
|
3139
|
+
return reader.read().then(pump);
|
|
3140
|
+
}
|
|
3141
|
+
return reader.read().then(pump);
|
|
3142
|
+
}
|
|
3143
|
+
function sendAgenticMessage() {
|
|
3144
|
+
if (agenticRunning)
|
|
3145
|
+
return;
|
|
3146
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3147
|
+
const text = inputEl ? inputEl.value : agenticInput;
|
|
3148
|
+
const prompt = (text || '').trim();
|
|
3149
|
+
if (!prompt)
|
|
3150
|
+
return;
|
|
3151
|
+
if (prompt.charAt(0) === '/') {
|
|
3152
|
+
const cmd = prompt.split(/\s+/)[0];
|
|
3153
|
+
if (AGENTIC_SLASH_COMMANDS.indexOf(cmd) !== -1) {
|
|
3154
|
+
handleAgenticSlash(cmd, prompt);
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
agenticRunning = true;
|
|
3159
|
+
agenticError = null;
|
|
3160
|
+
agenticRunStep = 0;
|
|
3161
|
+
agenticRunBudget = agenticMaxSteps > 0 ? agenticMaxSteps : 0;
|
|
3162
|
+
agenticMessages.push({ role: 'user', content: prompt, ts: Date.now() });
|
|
3163
|
+
if (inputEl)
|
|
3164
|
+
inputEl.value = '';
|
|
3165
|
+
agenticInput = '';
|
|
3166
|
+
patchAgenticChat();
|
|
3167
|
+
if (typeof fetch === 'undefined') {
|
|
3168
|
+
agenticRunning = false;
|
|
3169
|
+
agenticError = 'fetch unavailable';
|
|
3170
|
+
patchAgenticChat();
|
|
3171
|
+
return;
|
|
3172
|
+
}
|
|
3173
|
+
const sendModel = resolveAgenticModel();
|
|
3174
|
+
if (!sendModel) {
|
|
3175
|
+
agenticRunning = false;
|
|
3176
|
+
agenticError = 'no model selected — refresh the model list for ' + ui.agenticProvider;
|
|
3177
|
+
patchAgenticChat();
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
const maxSteps = agenticMaxSteps > 0 ? agenticMaxSteps : undefined;
|
|
3181
|
+
fetch('/__vesk/agent/run', {
|
|
3182
|
+
method: 'POST',
|
|
3183
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3184
|
+
body: JSON.stringify({ prompt: prompt, provider: ui.agenticProvider, model: sendModel, mode: ui.agenticMode, maxSteps: maxSteps, stream: true, providerConfig: { provider: ui.agenticProvider, model: sendModel } }),
|
|
3185
|
+
})
|
|
3186
|
+
.then(function (r) {
|
|
3187
|
+
const ctype = r.headers.get('content-type') || '';
|
|
3188
|
+
if (!r.ok) {
|
|
3189
|
+
return r.json().then(function (b) {
|
|
3190
|
+
throw new Error(b.error || 'HTTP ' + r.status);
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
if (ctype.indexOf('text/event-stream') !== -1 && r.body && typeof r.body.getReader === 'function') {
|
|
3194
|
+
return consumeAgenticStream(r.body.getReader());
|
|
3195
|
+
}
|
|
3196
|
+
// Older server / non-streaming fallback: whole reply at once.
|
|
3197
|
+
return r.json().then(function (body) {
|
|
3198
|
+
const result = body.result;
|
|
3199
|
+
const reply = result && typeof result.text === 'string' ? result.text : 'done';
|
|
3200
|
+
agenticMessages.push({ role: 'assistant', content: reply, ts: Date.now() });
|
|
3201
|
+
agenticRunning = false;
|
|
3202
|
+
agenticRunStep = 0;
|
|
3203
|
+
patchAgenticChat();
|
|
3204
|
+
refreshAgenticHistory();
|
|
3205
|
+
});
|
|
3206
|
+
})
|
|
3207
|
+
.catch(function (e) {
|
|
3208
|
+
agenticRunning = false;
|
|
3209
|
+
agenticRunStep = 0;
|
|
3210
|
+
agenticError = e instanceof Error ? e.message : String(e);
|
|
3211
|
+
patchAgenticChat();
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
function handleAgenticSlash(cmd, full) {
|
|
3215
|
+
// normalize /models -> /model
|
|
3216
|
+
const normalizedCmd = cmd === '/models' ? '/model' : cmd;
|
|
3217
|
+
if (normalizedCmd === '/help' || normalizedCmd === '/commands') {
|
|
3218
|
+
let help = 'slash commands: ' + AGENTIC_SLASH_COMMANDS.join(', ') + ' — provider: ' + ui.agenticProvider + ' model: ' + ui.agenticModel + ' mode: ' + ui.agenticMode + '\n';
|
|
3219
|
+
help += '/provider <openai|anthropic|google|ollama> — switch provider\n';
|
|
3220
|
+
help += '/model [name] — switch model or list models (no arg = list)\n';
|
|
3221
|
+
help += '/mode <explore|debug|agent> — switch mode\n';
|
|
3222
|
+
help += '/tools — list available vesk tools\n';
|
|
3223
|
+
help += '/commands — list this help\n';
|
|
3224
|
+
help += '/config — show current agentic config\n';
|
|
3225
|
+
help += '/history — refresh checkpoints\n';
|
|
3226
|
+
agenticMessages.push({ role: 'system', content: help, ts: Date.now() });
|
|
3227
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3228
|
+
if (inputEl)
|
|
3229
|
+
inputEl.value = '';
|
|
3230
|
+
agenticInput = '';
|
|
3231
|
+
renderPanel();
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
if (normalizedCmd === '/clear') {
|
|
3235
|
+
agenticMessages = [];
|
|
3236
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3237
|
+
if (inputEl)
|
|
3238
|
+
inputEl.value = '';
|
|
3239
|
+
agenticInput = '';
|
|
3240
|
+
renderPanel();
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
if (normalizedCmd === '/provider') {
|
|
3244
|
+
const arg = full.slice(cmd.length).trim().split(/\s+/)[0] || '';
|
|
3245
|
+
if (!arg || AGENTIC_PROVIDERS.indexOf(arg) === -1) {
|
|
3246
|
+
agenticMessages.push({ role: 'system', content: 'usage: /provider <' + AGENTIC_PROVIDERS.join('|') + '> — current: ' + ui.agenticProvider, ts: Date.now() });
|
|
3247
|
+
renderPanel();
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
changePref('agenticProvider', arg);
|
|
3251
|
+
// also persist server-side via POST /__vesk/agent/config
|
|
3252
|
+
if (typeof fetch !== 'undefined')
|
|
3253
|
+
fetch('/__vesk/agent/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider: arg }) }).catch(function () { });
|
|
3254
|
+
agenticMessages.push({ role: 'system', content: 'provider → ' + arg, ts: Date.now() });
|
|
3255
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3256
|
+
if (inputEl)
|
|
3257
|
+
inputEl.value = '';
|
|
3258
|
+
agenticInput = '';
|
|
3259
|
+
renderPanel();
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
if (normalizedCmd === '/model') {
|
|
3263
|
+
const arg = full.slice(cmd.length).trim();
|
|
3264
|
+
if (!arg) {
|
|
3265
|
+
// list models for current provider
|
|
3266
|
+
agenticMessages.push({ role: 'system', content: 'fetching models for ' + ui.agenticProvider + '...', ts: Date.now() });
|
|
3267
|
+
renderPanel();
|
|
3268
|
+
if (typeof fetch !== 'undefined') {
|
|
3269
|
+
fetch(buildAgenticModelsUrl(ui.agenticProvider, agenticBaseUrl)).then(function (r) { return r.json(); }).then(function (data) {
|
|
3270
|
+
const list = Array.isArray(data.models) ? data.models : [];
|
|
3271
|
+
agenticModelsCache = list;
|
|
3272
|
+
ui.agenticModels = list.slice();
|
|
3273
|
+
persistUi();
|
|
3274
|
+
agenticMessages.push({ role: 'system', content: 'models (' + ui.agenticProvider + '): ' + (list.length ? list.join(', ') : 'none (fallback)'), ts: Date.now() });
|
|
3275
|
+
renderPanel();
|
|
3276
|
+
}).catch(function (e) {
|
|
3277
|
+
agenticMessages.push({ role: 'system', content: 'model list error: ' + (e instanceof Error ? e.message : String(e)), ts: Date.now() });
|
|
3278
|
+
renderPanel();
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3282
|
+
if (inputEl)
|
|
3283
|
+
inputEl.value = '';
|
|
3284
|
+
agenticInput = '';
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
// set model
|
|
3288
|
+
const modelName = arg.split(/\s+/)[0];
|
|
3289
|
+
changePref('agenticModel', modelName);
|
|
3290
|
+
if (typeof fetch !== 'undefined')
|
|
3291
|
+
fetch('/__vesk/agent/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: modelName }) }).catch(function () { });
|
|
3292
|
+
agenticMessages.push({ role: 'system', content: 'model → ' + modelName, ts: Date.now() });
|
|
3293
|
+
const inputEl2 = doc.querySelector('[data-agentic-input]');
|
|
3294
|
+
if (inputEl2)
|
|
3295
|
+
inputEl2.value = '';
|
|
3296
|
+
agenticInput = '';
|
|
3297
|
+
renderPanel();
|
|
3298
|
+
return;
|
|
3299
|
+
}
|
|
3300
|
+
if (normalizedCmd === '/mode') {
|
|
3301
|
+
const arg = full.slice(cmd.length).trim().split(/\s+/)[0] || '';
|
|
3302
|
+
if (!arg || AGENTIC_MODES.indexOf(arg) === -1) {
|
|
3303
|
+
agenticMessages.push({ role: 'system', content: 'usage: /mode <' + AGENTIC_MODES.join('|') + '> — current: ' + ui.agenticMode, ts: Date.now() });
|
|
3304
|
+
renderPanel();
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
changePref('agenticMode', arg);
|
|
3308
|
+
if (typeof fetch !== 'undefined')
|
|
3309
|
+
fetch('/__vesk/agent/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: arg }) }).catch(function () { });
|
|
3310
|
+
agenticMessages.push({ role: 'system', content: 'mode → ' + arg, ts: Date.now() });
|
|
3311
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3312
|
+
if (inputEl)
|
|
3313
|
+
inputEl.value = '';
|
|
3314
|
+
agenticInput = '';
|
|
3315
|
+
renderPanel();
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
if (normalizedCmd === '/tools') {
|
|
3319
|
+
agenticMessages.push({ role: 'system', content: 'fetching tools...', ts: Date.now() });
|
|
3320
|
+
renderPanel();
|
|
3321
|
+
if (typeof fetch !== 'undefined') {
|
|
3322
|
+
fetch('/__vesk/agent/tools').then(function (r) { return r.json(); }).then(function (data) {
|
|
3323
|
+
const tools = Array.isArray(data.tools) ? data.tools : [];
|
|
3324
|
+
const lines = tools.map(function (t) { return t.name + ' — ' + t.description + ' [' + t.capability + ']'; });
|
|
3325
|
+
agenticMessages.push({ role: 'system', content: 'tools (' + tools.length + '):\n' + lines.join('\n'), ts: Date.now() });
|
|
3326
|
+
renderPanel();
|
|
3327
|
+
}).catch(function (e) {
|
|
3328
|
+
agenticMessages.push({ role: 'system', content: 'tools error: ' + (e instanceof Error ? e.message : String(e)), ts: Date.now() });
|
|
3329
|
+
renderPanel();
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3333
|
+
if (inputEl)
|
|
3334
|
+
inputEl.value = '';
|
|
3335
|
+
agenticInput = '';
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
if (normalizedCmd === '/config') {
|
|
3339
|
+
if (typeof fetch !== 'undefined') {
|
|
3340
|
+
fetch('/__vesk/agent/config').then(function (r) { return r.json(); }).then(function (data) {
|
|
3341
|
+
agenticMessages.push({ role: 'system', content: 'config: ' + JSON.stringify(data, null, 2), ts: Date.now() });
|
|
3342
|
+
renderPanel();
|
|
3343
|
+
}).catch(function (e) {
|
|
3344
|
+
agenticMessages.push({ role: 'system', content: 'config error: ' + (e instanceof Error ? e.message : String(e)), ts: Date.now() });
|
|
3345
|
+
renderPanel();
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
const inputElc = doc.querySelector('[data-agentic-input]');
|
|
3349
|
+
if (inputElc)
|
|
3350
|
+
inputElc.value = '';
|
|
3351
|
+
agenticInput = '';
|
|
3352
|
+
renderPanel();
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
3355
|
+
if (normalizedCmd === '/history') {
|
|
3356
|
+
refreshAgenticHistory();
|
|
3357
|
+
agenticMessages.push({ role: 'system', content: 'refreshing history...', ts: Date.now() });
|
|
3358
|
+
const inputElh = doc.querySelector('[data-agentic-input]');
|
|
3359
|
+
if (inputElh)
|
|
3360
|
+
inputElh.value = '';
|
|
3361
|
+
agenticInput = '';
|
|
3362
|
+
renderPanel();
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
if (normalizedCmd === '/tool') {
|
|
3366
|
+
const name = full.slice(cmd.length).trim().split(/\s+/)[0] || '';
|
|
3367
|
+
if (!name) {
|
|
3368
|
+
agenticMessages.push({ role: 'system', content: 'usage: /tool <name> — try /tools to list', ts: Date.now() });
|
|
3369
|
+
renderPanel();
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
if (typeof fetch !== 'undefined') {
|
|
3373
|
+
fetch('/__vesk/agent/tools').then(function (r) { return r.json(); }).then(function (data) {
|
|
3374
|
+
const hit = (data.tools || []).find(function (t) { return t.name === name; });
|
|
3375
|
+
if (hit)
|
|
3376
|
+
agenticMessages.push({ role: 'system', content: hit.name + ': ' + hit.description, ts: Date.now() });
|
|
3377
|
+
else
|
|
3378
|
+
agenticMessages.push({ role: 'system', content: 'tool not found: ' + name, ts: Date.now() });
|
|
3379
|
+
renderPanel();
|
|
3380
|
+
}).catch(function (e) { agenticMessages.push({ role: 'system', content: 'error: ' + (e instanceof Error ? e.message : String(e)), ts: Date.now() }); renderPanel(); });
|
|
3381
|
+
}
|
|
3382
|
+
const inputElt = doc.querySelector('[data-agentic-input]');
|
|
3383
|
+
if (inputElt)
|
|
3384
|
+
inputElt.value = '';
|
|
3385
|
+
agenticInput = '';
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
if (normalizedCmd === '/checkpoint') {
|
|
3389
|
+
agenticRunning = true;
|
|
3390
|
+
renderPanel();
|
|
3391
|
+
fetch('/__vesk/agent/checkpoint', {
|
|
3392
|
+
method: 'POST',
|
|
3393
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3394
|
+
body: JSON.stringify({ message: full.slice('/checkpoint'.length).trim() || 'checkpoint' }),
|
|
3395
|
+
}).then(function (r) { return r.json().then(function (b) { if (!r.ok)
|
|
3396
|
+
throw new Error(b.error || 'HTTP ' + r.status); return b; }); }).then(function () {
|
|
3397
|
+
agenticRunning = false;
|
|
3398
|
+
agenticMessages.push({ role: 'system', content: 'checkpoint created', ts: Date.now() });
|
|
3399
|
+
renderPanel();
|
|
3400
|
+
refreshAgenticHistory();
|
|
3401
|
+
}).catch(function (e) {
|
|
3402
|
+
agenticRunning = false;
|
|
3403
|
+
agenticError = e instanceof Error ? e.message : String(e);
|
|
3404
|
+
renderPanel();
|
|
3405
|
+
});
|
|
3406
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3407
|
+
if (inputEl)
|
|
3408
|
+
inputEl.value = '';
|
|
3409
|
+
agenticInput = '';
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
if (normalizedCmd === '/rollback') {
|
|
3413
|
+
const id = full.slice('/rollback'.length).trim();
|
|
3414
|
+
if (!id) {
|
|
3415
|
+
agenticMessages.push({ role: 'system', content: 'usage: /rollback <checkpointId>', ts: Date.now() });
|
|
3416
|
+
renderPanel();
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
rollbackAgenticCheckpoint(id);
|
|
3420
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3421
|
+
if (inputEl)
|
|
3422
|
+
inputEl.value = '';
|
|
3423
|
+
agenticInput = '';
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
agenticMessages.push({ role: 'system', content: 'unknown slash command: ' + cmd + ' — try /help', ts: Date.now() });
|
|
3427
|
+
renderPanel();
|
|
3428
|
+
}
|
|
3429
|
+
function rollbackAgenticCheckpoint(id) {
|
|
3430
|
+
if (typeof fetch === 'undefined') {
|
|
3431
|
+
agenticHistoryError = 'fetch unavailable';
|
|
3432
|
+
renderPanel();
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
agenticHistoryError = null;
|
|
3436
|
+
renderPanel();
|
|
3437
|
+
fetch('/__vesk/agent/rollback', {
|
|
3438
|
+
method: 'POST',
|
|
3439
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3440
|
+
body: JSON.stringify({ checkpointId: id }),
|
|
3441
|
+
})
|
|
3442
|
+
.then(function (r) {
|
|
3443
|
+
return r.json().then(function (body) {
|
|
3444
|
+
if (!r.ok)
|
|
3445
|
+
throw new Error(body.error || 'HTTP ' + r.status);
|
|
3446
|
+
return body;
|
|
3447
|
+
});
|
|
3448
|
+
})
|
|
3449
|
+
.then(function () {
|
|
3450
|
+
agenticMessages.push({ role: 'system', content: 'rolled back to ' + id, ts: Date.now() });
|
|
3451
|
+
renderPanel();
|
|
3452
|
+
refreshAgenticHistory();
|
|
3453
|
+
})
|
|
3454
|
+
.catch(function (e) {
|
|
3455
|
+
agenticHistoryError = e instanceof Error ? e.message : String(e);
|
|
3456
|
+
renderPanel();
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
function mapDiagnostic(raw) {
|
|
3460
|
+
const d = (raw || {});
|
|
3461
|
+
const sev = d.severity === 'error' || d.severity === 'warning' || d.severity === 'info' ? d.severity : 'info';
|
|
3462
|
+
return {
|
|
3463
|
+
severity: sev,
|
|
3464
|
+
code: String(d.code || ''),
|
|
3465
|
+
file: d.file != null ? String(d.file) : null,
|
|
3466
|
+
line: typeof d.line === 'number' ? d.line : null,
|
|
3467
|
+
column: typeof d.column === 'number' ? d.column : null,
|
|
3468
|
+
message: String(d.message || ''),
|
|
3469
|
+
hint: d.hint != null ? String(d.hint) : null,
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
function mapPluginRecord(raw) {
|
|
3473
|
+
const p = (raw || {});
|
|
3474
|
+
return {
|
|
3475
|
+
name: String(p.name || ''),
|
|
3476
|
+
package: String(p.package || p.name || ''),
|
|
3477
|
+
path: p.path != null ? String(p.path) : null,
|
|
3478
|
+
active: !!p.active,
|
|
3479
|
+
installed: !!p.installed,
|
|
3480
|
+
version: p.version != null && p.version !== '' ? String(p.version) : null,
|
|
3481
|
+
latest: p.latest != null && p.latest !== '' ? String(p.latest) : null,
|
|
3482
|
+
description: p.description != null ? String(p.description) : null,
|
|
3483
|
+
author: p.author != null ? String(p.author) : null,
|
|
3484
|
+
license: p.license != null ? String(p.license) : null,
|
|
3485
|
+
homepage: p.homepage != null ? String(p.homepage) : null,
|
|
3486
|
+
repository: p.repository != null ? String(p.repository) : null,
|
|
3487
|
+
updatedAt: p.updatedAt != null ? String(p.updatedAt) : null,
|
|
3488
|
+
keywords: Array.isArray(p.keywords) ? p.keywords.map((k) => String(k)) : [],
|
|
3489
|
+
iconUrl: p.iconUrl != null ? String(p.iconUrl) : null,
|
|
3490
|
+
metaSource: p.metaSource === 'vesk.meta.json' || p.metaSource === 'package.json' ? p.metaSource : 'none',
|
|
3491
|
+
source: p.source === 'config' ? 'config' : 'state',
|
|
3492
|
+
error: p.error != null ? String(p.error) : null,
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
function openPlugin(name) {
|
|
3496
|
+
pluginDetail = name;
|
|
3497
|
+
pluginDetailMsg = null;
|
|
3498
|
+
pluginDetailSearch = null;
|
|
3499
|
+
pluginExports = null;
|
|
3500
|
+
pluginExportsError = null;
|
|
3501
|
+
pluginInstalling = null;
|
|
3502
|
+
renderPanel();
|
|
3503
|
+
}
|
|
3504
|
+
function openSearchResult(r) {
|
|
3505
|
+
pluginDetailSearch = r;
|
|
3506
|
+
pluginDetail = null;
|
|
3507
|
+
pluginExports = null;
|
|
3508
|
+
pluginExportsError = null;
|
|
3509
|
+
pluginDetailMsg = null;
|
|
3510
|
+
pluginView = 'search';
|
|
3511
|
+
renderPanel();
|
|
3512
|
+
}
|
|
3513
|
+
function backFromPlugin() {
|
|
3514
|
+
pluginDetail = null;
|
|
3515
|
+
pluginDetailSearch = null;
|
|
3516
|
+
pluginExports = null;
|
|
3517
|
+
pluginExportsError = null;
|
|
3518
|
+
pluginDetailMsg = null;
|
|
3519
|
+
if (pluginView === 'search') {
|
|
3520
|
+
pluginView = 'list';
|
|
3521
|
+
pluginSearchResults = [];
|
|
3522
|
+
pluginSearchError = null;
|
|
3523
|
+
}
|
|
3524
|
+
refreshPlugins();
|
|
3525
|
+
renderPanel();
|
|
3526
|
+
}
|
|
3527
|
+
function pluginMutation(path, body) {
|
|
3528
|
+
if (typeof fetch === 'undefined') {
|
|
3529
|
+
pluginDetailMsg = 'fetch unavailable';
|
|
3530
|
+
renderPanel();
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
pluginDetailMsg = 'applying...';
|
|
3534
|
+
renderPanel();
|
|
3535
|
+
fetch(path, {
|
|
3536
|
+
method: 'POST',
|
|
3537
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3538
|
+
body: JSON.stringify(body),
|
|
3539
|
+
})
|
|
3540
|
+
.then(function (r) {
|
|
3541
|
+
if (!r.ok)
|
|
3542
|
+
return r.json().catch(function () {
|
|
3543
|
+
return { error: 'HTTP ' + r.status };
|
|
3544
|
+
}).then(function (body2) {
|
|
3545
|
+
throw new Error(body2.error || 'HTTP ' + r.status);
|
|
3546
|
+
});
|
|
3547
|
+
return r.json();
|
|
3548
|
+
})
|
|
3549
|
+
.then(function () {
|
|
3550
|
+
runPluginAction(path, body);
|
|
3551
|
+
})
|
|
3552
|
+
.catch(function (e) {
|
|
3553
|
+
pluginDetailMsg = (e instanceof Error ? e.message : String(e));
|
|
3554
|
+
refreshPlugins();
|
|
3555
|
+
renderPanel();
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
function runPluginAction(path, body) {
|
|
3559
|
+
pluginDetailMsg = 'done \u2014 reloading';
|
|
3560
|
+
renderPanel();
|
|
3561
|
+
}
|
|
3562
|
+
function loadPluginExports(name) {
|
|
3563
|
+
if (typeof fetch === 'undefined') {
|
|
3564
|
+
pluginExportsError = 'fetch unavailable';
|
|
3565
|
+
renderPanel();
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
pluginExports = null;
|
|
3569
|
+
pluginExportsError = null;
|
|
3570
|
+
renderPanel();
|
|
3571
|
+
fetch(urls.pluginsUrl + '/' + encodeURIComponent(name) + '/exports')
|
|
3572
|
+
.then(function (r) {
|
|
3573
|
+
return r.json().then(function (body) {
|
|
3574
|
+
if (!r.ok)
|
|
3575
|
+
throw new Error((body && body.error) || 'HTTP ' + r.status);
|
|
3576
|
+
return body;
|
|
3577
|
+
});
|
|
3578
|
+
})
|
|
3579
|
+
.then(function (body) {
|
|
3580
|
+
pluginExports = body;
|
|
3581
|
+
renderPanel();
|
|
3582
|
+
})
|
|
3583
|
+
.catch(function (e) {
|
|
3584
|
+
pluginExports = null;
|
|
3585
|
+
pluginExportsError = e instanceof Error ? e.message : String(e);
|
|
3586
|
+
renderPanel();
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
function searchPlugins(q) {
|
|
3590
|
+
if (typeof fetch === 'undefined') {
|
|
3591
|
+
pluginSearchError = 'fetch unavailable';
|
|
3592
|
+
renderPanel();
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
pluginSearchQuery = q;
|
|
3596
|
+
pluginSearchLoading = true;
|
|
3597
|
+
pluginSearchError = null;
|
|
3598
|
+
renderPanel();
|
|
3599
|
+
fetch(urls.pluginsUrl + '/search?q=' + encodeURIComponent(q))
|
|
3600
|
+
.then(function (r) {
|
|
3601
|
+
return r.json().then(function (body) {
|
|
3602
|
+
if (!r.ok)
|
|
3603
|
+
throw new Error(body.error || 'HTTP ' + r.status);
|
|
3604
|
+
return body;
|
|
3605
|
+
});
|
|
3606
|
+
})
|
|
3607
|
+
.then(function (body) {
|
|
3608
|
+
pluginSearchLoading = false;
|
|
3609
|
+
pluginSearchResults = (Array.isArray(body.results) ? body.results : []).map(function (rr) {
|
|
3610
|
+
const r = (rr || {});
|
|
3611
|
+
return {
|
|
3612
|
+
name: String(r.name || ''),
|
|
3613
|
+
version: r.version != null ? String(r.version) : '',
|
|
3614
|
+
description: r.description != null ? String(r.description) : '',
|
|
3615
|
+
author: r.author != null ? String(r.author) : '',
|
|
3616
|
+
date: r.date != null ? String(r.date) : '',
|
|
3617
|
+
keywords: Array.isArray(r.keywords) ? r.keywords.map((k) => String(k)) : [],
|
|
3618
|
+
};
|
|
3619
|
+
});
|
|
3620
|
+
renderPanel();
|
|
3621
|
+
})
|
|
3622
|
+
.catch(function (e) {
|
|
3623
|
+
pluginSearchLoading = false;
|
|
3624
|
+
pluginSearchResults = [];
|
|
3625
|
+
pluginSearchError = e instanceof Error ? e.message : String(e);
|
|
3626
|
+
renderPanel();
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
function gpClickHandler(e) {
|
|
3630
|
+
const target = e.target;
|
|
3631
|
+
const opt = target.closest('.__kp_opt');
|
|
3632
|
+
if (opt) {
|
|
3633
|
+
const key = opt.getAttribute('data-key');
|
|
3634
|
+
const val = opt.getAttribute('data-val');
|
|
3635
|
+
if (key && val) {
|
|
3636
|
+
changePref(key, val);
|
|
3637
|
+
if (key === 'agenticProvider')
|
|
3638
|
+
saveAgenticConfigPatch({ provider: val });
|
|
3639
|
+
if (key === 'agenticModel')
|
|
3640
|
+
saveAgenticConfigPatch({ model: val });
|
|
3641
|
+
if (key === 'agenticMode' && isAgenticMode(val))
|
|
3642
|
+
saveAgenticConfigPatch({ mode: val });
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
const viewBtn = target.closest('[data-pl-view]');
|
|
3647
|
+
if (viewBtn) {
|
|
3648
|
+
const v = viewBtn.getAttribute('data-pl-view');
|
|
3649
|
+
if (v === 'cards' || v === 'list') {
|
|
3650
|
+
ui.pluginsView = v;
|
|
3651
|
+
persistUi();
|
|
3652
|
+
renderPanel();
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
const searchEntry = target.closest('[data-pl-search]');
|
|
3657
|
+
if (searchEntry) {
|
|
3658
|
+
pluginView = 'search';
|
|
3659
|
+
pluginSearchResults = [];
|
|
3660
|
+
pluginSearchError = null;
|
|
3661
|
+
renderPanel();
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
if (target.closest('[data-pl-back]')) {
|
|
3665
|
+
backFromPlugin();
|
|
3666
|
+
return;
|
|
3667
|
+
}
|
|
3668
|
+
const suggest = target.closest('[data-pl-suggest]');
|
|
3669
|
+
if (suggest) {
|
|
3670
|
+
const q = suggest.getAttribute('data-pl-suggest') || '';
|
|
3671
|
+
searchPlugins(q);
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
if (target.closest('[data-pl-search-go]')) {
|
|
3675
|
+
const q = doc.getElementById('__kp_pl_q')?.value || '';
|
|
3676
|
+
searchPlugins(q);
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
const installBtn = target.closest('[data-pl-install]');
|
|
3680
|
+
if (installBtn) {
|
|
3681
|
+
const pkg = installBtn.getAttribute('data-pl-install') || '';
|
|
3682
|
+
pluginInstall(pkg);
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
const detailOpen = target.closest('[data-search-pkg-open]');
|
|
3686
|
+
if (detailOpen) {
|
|
3687
|
+
const name = detailOpen.getAttribute('data-search-pkg-open') || '';
|
|
3688
|
+
const hit = pluginSearchResults.find((rr) => rr.name === name);
|
|
3689
|
+
if (hit)
|
|
3690
|
+
openSearchResult(hit);
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
const searchRow = target.closest('[data-search-pkg]');
|
|
3694
|
+
if (searchRow && !target.closest('[data-pl-install],[data-search-pkg-open]')) {
|
|
3695
|
+
const name = searchRow.getAttribute('data-search-pkg') || '';
|
|
3696
|
+
const hit = pluginSearchResults.find((rr) => rr.name === name);
|
|
3697
|
+
if (hit)
|
|
3698
|
+
openSearchResult(hit);
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
const exportsBtn = target.closest('[data-pl-exports]');
|
|
3702
|
+
if (exportsBtn) {
|
|
3703
|
+
const name = exportsBtn.getAttribute('data-name') || '';
|
|
3704
|
+
loadPluginExports(name);
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
const agenticSend = target.closest('[data-agentic-send]');
|
|
3708
|
+
if (agenticSend) {
|
|
3709
|
+
sendAgenticMessage();
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
const agenticRefresh = target.closest('[data-agentic-refresh-models]');
|
|
3713
|
+
if (agenticRefresh) {
|
|
3714
|
+
refreshAgenticModels();
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
const agenticRollback = target.closest('[data-agentic-rollback]');
|
|
3718
|
+
if (agenticRollback) {
|
|
3719
|
+
const id = agenticRollback.getAttribute('data-agentic-rollback') || '';
|
|
3720
|
+
if (id)
|
|
3721
|
+
rollbackAgenticCheckpoint(id);
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
const agenticProvider = target.closest('[data-agentic-provider]');
|
|
3725
|
+
if (agenticProvider) {
|
|
3726
|
+
const p = agenticProvider.getAttribute('data-agentic-provider') || '';
|
|
3727
|
+
if (p) {
|
|
3728
|
+
changePref('agenticProvider', p);
|
|
3729
|
+
saveAgenticConfigPatch({ provider: p });
|
|
3730
|
+
if (agenticConfigState)
|
|
3731
|
+
agenticConfigState.provider = p;
|
|
3732
|
+
}
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
const agenticModeBtn = target.closest('[data-agentic-mode]');
|
|
3736
|
+
if (agenticModeBtn) {
|
|
3737
|
+
const m = agenticModeBtn.getAttribute('data-agentic-mode') || '';
|
|
3738
|
+
if (m) {
|
|
3739
|
+
changePref('agenticMode', m);
|
|
3740
|
+
saveAgenticConfigPatch({ mode: m });
|
|
3741
|
+
if (agenticConfigState)
|
|
3742
|
+
agenticConfigState.mode = m;
|
|
3743
|
+
}
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
const agenticPick = target.closest('[data-agentic-pick]');
|
|
3747
|
+
if (agenticPick) {
|
|
3748
|
+
const val = agenticPick.getAttribute('data-agentic-pick') || '';
|
|
3749
|
+
const inputEl = doc.querySelector('[data-agentic-input]');
|
|
3750
|
+
if (inputEl) {
|
|
3751
|
+
inputEl.value = val + ' ';
|
|
3752
|
+
agenticInput = val + ' ';
|
|
3753
|
+
inputEl.focus();
|
|
3754
|
+
}
|
|
3755
|
+
else {
|
|
3756
|
+
agenticInput = val + ' ';
|
|
3757
|
+
}
|
|
3758
|
+
renderPanel();
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
const actBtn = target.closest('[data-pl-act]');
|
|
3762
|
+
if (actBtn) {
|
|
3763
|
+
const act = actBtn.getAttribute('data-pl-act');
|
|
3764
|
+
const name = actBtn.getAttribute('data-name');
|
|
3765
|
+
const pkg = actBtn.getAttribute('data-package');
|
|
3766
|
+
if (act === 'activate' && name)
|
|
3767
|
+
pluginMutation('/__vesk/plugins/activate', { name });
|
|
3768
|
+
else if (act === 'deactivate' && name)
|
|
3769
|
+
pluginMutation('/__vesk/plugins/deactivate', { name });
|
|
3770
|
+
else if (act === 'update' && pkg)
|
|
3771
|
+
pluginMutation('/__vesk/plugins/update', { package: pkg });
|
|
3772
|
+
else if (act === 'uninstall' && pkg)
|
|
3773
|
+
confirmUninstall(pkg);
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
const card = target.closest('[data-plugin]');
|
|
3777
|
+
if (card) {
|
|
3778
|
+
const name = card.getAttribute('data-plugin') || '';
|
|
3779
|
+
openPlugin(name);
|
|
3780
|
+
return;
|
|
3781
|
+
}
|
|
3782
|
+
const agenticSaveKey = target.closest('[data-agentic-save-key]');
|
|
3783
|
+
if (agenticSaveKey) {
|
|
3784
|
+
const prov = agenticSaveKey.getAttribute('data-agentic-save-key') || agenticSaveKey.getAttribute('data-provider') || '';
|
|
3785
|
+
const inp = doc.querySelector('[data-agentic-key-input="' + prov + '"]');
|
|
3786
|
+
const keyVal = inp ? inp.value : '';
|
|
3787
|
+
if (prov)
|
|
3788
|
+
saveAgenticApiKey(prov, keyVal);
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
const settingsSub = target.closest('[data-settings-subtab]');
|
|
3792
|
+
if (settingsSub) {
|
|
3793
|
+
const subRaw = settingsSub.getAttribute('data-settings-subtab') || settingsSub.getAttribute('data-settings-tab') || 'devtools';
|
|
3794
|
+
// support legacy ids
|
|
3795
|
+
let sub = subRaw;
|
|
3796
|
+
if (sub === 'prefs')
|
|
3797
|
+
sub = 'devtools';
|
|
3798
|
+
if (sub === 'config')
|
|
3799
|
+
sub = 'vesk';
|
|
3800
|
+
if (isSettingsSubTab(sub)) {
|
|
3801
|
+
setSettingsSubTab(sub);
|
|
3802
|
+
}
|
|
3803
|
+
else if (sub === 'config' || sub === 'prefs') {
|
|
3804
|
+
// fallback legacy
|
|
3805
|
+
setSettingsSubTab(sub === 'config' ? 'vesk' : 'devtools');
|
|
3806
|
+
}
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
if (target.closest('[data-cfg-save]')) {
|
|
3810
|
+
const ta = doc.querySelector('[data-cfg-source]');
|
|
3811
|
+
const src = ta ? ta.value : (cfgState ? cfgState.source : '');
|
|
3812
|
+
postVeskConfigSource(src);
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
if (target.closest('[data-cfg-reload]')) {
|
|
3816
|
+
refreshVeskConfig();
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
function pluginInstall(pkg) {
|
|
3821
|
+
if (typeof fetch === 'undefined') {
|
|
3822
|
+
pluginSearchError = 'fetch unavailable';
|
|
3823
|
+
renderPanel();
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
if (pluginInstalling === pkg)
|
|
3827
|
+
return;
|
|
3828
|
+
pluginInstalling = pkg;
|
|
3829
|
+
pluginSearchError = null;
|
|
3830
|
+
renderPanel();
|
|
3831
|
+
fetch('/__vesk/plugins/install', {
|
|
3832
|
+
method: 'POST',
|
|
3833
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3834
|
+
body: JSON.stringify({ package: pkg }),
|
|
3835
|
+
})
|
|
3836
|
+
.then(function (r) {
|
|
3837
|
+
return r.json().catch(function () {
|
|
3838
|
+
return {};
|
|
3839
|
+
}).then(function (body) {
|
|
3840
|
+
if (!r.ok)
|
|
3841
|
+
throw new Error(body.error || 'HTTP ' + r.status);
|
|
3842
|
+
});
|
|
3843
|
+
})
|
|
3844
|
+
.then(function () {
|
|
3845
|
+
pluginInstalling = null;
|
|
3846
|
+
pluginSearchError = 'installed \u2014 reloading';
|
|
3847
|
+
renderPanel();
|
|
3848
|
+
})
|
|
3849
|
+
.catch(function (e) {
|
|
3850
|
+
pluginInstalling = null;
|
|
3851
|
+
pluginSearchError = e instanceof Error ? e.message : String(e);
|
|
3852
|
+
renderPanel();
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
function confirmUninstall(pkg) {
|
|
3856
|
+
if (pluginDetailMsg === 'confirm uninstall?') {
|
|
3857
|
+
pluginDetailMsg = null;
|
|
3858
|
+
pluginMutation('/__vesk/plugins/uninstall', { package: pkg });
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
pluginDetailMsg = 'confirm uninstall?';
|
|
3862
|
+
renderPanel();
|
|
3863
|
+
}
|
|
3864
|
+
function loadPersistedState() {
|
|
3865
|
+
if (typeof fetch === 'undefined')
|
|
3866
|
+
return;
|
|
3867
|
+
fetch(urls.stateUrl)
|
|
3868
|
+
.then(function (r) {
|
|
3869
|
+
if (!r.ok)
|
|
3870
|
+
throw new Error('HTTP ' + r.status);
|
|
3871
|
+
return r.json();
|
|
3872
|
+
})
|
|
3873
|
+
.then(function (data) {
|
|
3874
|
+
const payload = data;
|
|
3875
|
+
if (payload && payload.error) {
|
|
3876
|
+
lastError = payload.error;
|
|
3877
|
+
status = 'error';
|
|
3878
|
+
showOverlay(payload.error);
|
|
3879
|
+
renderPanel();
|
|
3880
|
+
}
|
|
3881
|
+
})
|
|
3882
|
+
.catch(function () {
|
|
3883
|
+
/* state endpoint optional */
|
|
3884
|
+
});
|
|
3885
|
+
}
|
|
3886
|
+
registerGlobalHmr({
|
|
3887
|
+
show: handleError,
|
|
3888
|
+
dismiss: dismissOverlay,
|
|
3889
|
+
});
|
|
3890
|
+
connect();
|
|
3891
|
+
loadPersistedState();
|
|
3892
|
+
function boot() {
|
|
3893
|
+
if (disposed)
|
|
3894
|
+
return;
|
|
270
3895
|
createDot();
|
|
271
|
-
|
|
3896
|
+
applyPrefs();
|
|
3897
|
+
if (panelEl) {
|
|
3898
|
+
clampPanelSize();
|
|
3899
|
+
ui.w = panelW;
|
|
3900
|
+
ui.h = panelH;
|
|
3901
|
+
persistUi();
|
|
3902
|
+
if (ui.maxed) {
|
|
3903
|
+
panelEl.classList.add('maxed');
|
|
3904
|
+
panelEl.style.width = '';
|
|
3905
|
+
panelEl.style.height = '';
|
|
3906
|
+
}
|
|
3907
|
+
else {
|
|
3908
|
+
panelEl.style.width = panelW + 'px';
|
|
3909
|
+
panelEl.style.height = panelH + 'px';
|
|
3910
|
+
}
|
|
3911
|
+
if (ui.open) {
|
|
3912
|
+
panelOpen = true;
|
|
3913
|
+
panelEl.classList.remove('hidden');
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
setTab(ui.activeTab);
|
|
3917
|
+
}
|
|
3918
|
+
if (doc.body) {
|
|
3919
|
+
boot();
|
|
272
3920
|
}
|
|
273
|
-
else
|
|
274
|
-
|
|
3921
|
+
else {
|
|
3922
|
+
doc.addEventListener('DOMContentLoaded', function () {
|
|
3923
|
+
boot();
|
|
3924
|
+
});
|
|
275
3925
|
}
|
|
276
|
-
|
|
3926
|
+
return {
|
|
3927
|
+
dispose() {
|
|
3928
|
+
disposed = true;
|
|
3929
|
+
if (reconnectTimer)
|
|
3930
|
+
clearTimeout(reconnectTimer);
|
|
3931
|
+
if (ws) {
|
|
3932
|
+
try {
|
|
3933
|
+
ws.close();
|
|
3934
|
+
}
|
|
3935
|
+
catch {
|
|
3936
|
+
/* ignore */
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
if (dotEl && dotEl.parentNode)
|
|
3940
|
+
dotEl.parentNode.removeChild(dotEl);
|
|
3941
|
+
if (overlayEl && overlayEl.parentNode)
|
|
3942
|
+
overlayEl.parentNode.removeChild(overlayEl);
|
|
3943
|
+
},
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
if (typeof document !== 'undefined') {
|
|
3947
|
+
createDevClient();
|
|
3948
|
+
}
|