agent-discover 1.3.9 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +31 -7
- package/agent-desk-plugin.json +10 -3
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +15 -0
- package/dist/context.js.map +1 -1
- package/dist/domain/log.d.ts +7 -3
- package/dist/domain/log.d.ts.map +1 -1
- package/dist/domain/log.js +15 -5
- package/dist/domain/log.js.map +1 -1
- package/dist/domain/presets.d.ts +30 -0
- package/dist/domain/presets.d.ts.map +1 -0
- package/dist/domain/presets.js +76 -0
- package/dist/domain/presets.js.map +1 -0
- package/dist/domain/proxy.d.ts +153 -0
- package/dist/domain/proxy.d.ts.map +1 -1
- package/dist/domain/proxy.js +396 -3
- package/dist/domain/proxy.js.map +1 -1
- package/dist/domain/sampling.d.ts +9 -0
- package/dist/domain/sampling.d.ts.map +1 -0
- package/dist/domain/sampling.js +68 -0
- package/dist/domain/sampling.js.map +1 -0
- package/dist/storage/database.js +21 -0
- package/dist/storage/database.js.map +1 -1
- package/dist/transport/rest.d.ts.map +1 -1
- package/dist/transport/rest.js +349 -0
- package/dist/transport/rest.js.map +1 -1
- package/dist/transport/ws.d.ts.map +1 -1
- package/dist/transport/ws.js +32 -0
- package/dist/transport/ws.js.map +1 -1
- package/dist/ui/app.js +16 -0
- package/dist/ui/index.html +3 -0
- package/dist/ui/markdown.js +102 -0
- package/dist/ui/schema-form.js +393 -0
- package/dist/ui/styles.css +724 -0
- package/dist/ui/tester-window.html +116 -0
- package/dist/ui/tester-window.js +153 -0
- package/dist/ui/tester.js +1412 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1412 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
(function () {
|
|
3
|
+
'use strict';
|
|
4
|
+
var AD = (window.AD = window.AD || {});
|
|
5
|
+
|
|
6
|
+
var state = {};
|
|
7
|
+
var eventBuffers = {};
|
|
8
|
+
var presetsCache = {};
|
|
9
|
+
var elicitationModalOpen = null;
|
|
10
|
+
var PRESET_KEY = 'agent-discover-tester-presets-v1';
|
|
11
|
+
var PRESET_MIGRATED_KEY = 'agent-discover-presets-migrated-v2';
|
|
12
|
+
|
|
13
|
+
function esc(s) {
|
|
14
|
+
return String(s == null ? '' : s)
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/"/g, '"');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getState(serverId) {
|
|
22
|
+
if (!state[serverId]) {
|
|
23
|
+
state[serverId] = {
|
|
24
|
+
subtab: 'tools',
|
|
25
|
+
tools: null,
|
|
26
|
+
resources: null,
|
|
27
|
+
resourceCursor: null,
|
|
28
|
+
prompts: null,
|
|
29
|
+
info: null,
|
|
30
|
+
selectedTool: null,
|
|
31
|
+
selectedPrompt: null,
|
|
32
|
+
selectedResource: null,
|
|
33
|
+
result: null,
|
|
34
|
+
resultMode: 'pretty',
|
|
35
|
+
pingRtt: null,
|
|
36
|
+
loggingLevel: 'info',
|
|
37
|
+
loading: false,
|
|
38
|
+
error: null,
|
|
39
|
+
floating: false,
|
|
40
|
+
handle: null,
|
|
41
|
+
serverName: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return state[serverId];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function baseUrl(serverId, handle) {
|
|
48
|
+
return handle ? '/api/transient/' + encodeURIComponent(handle) : '/api/servers/' + serverId;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function doFetch(url, opts) {
|
|
52
|
+
return AD._fetch(url, opts).then(function (r) {
|
|
53
|
+
return r.json().then(function (body) {
|
|
54
|
+
if (!r.ok) throw new Error(body && body.error ? body.error : 'HTTP ' + r.status);
|
|
55
|
+
return body;
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
AD.renderTesterShell = function (server) {
|
|
61
|
+
var s = getState(server.id);
|
|
62
|
+
s.serverName = server.name;
|
|
63
|
+
var capsKnown = s.info && s.info !== 'error' && s.info.capabilities;
|
|
64
|
+
var caps = capsKnown ? s.info.capabilities : null;
|
|
65
|
+
// When capabilities are not yet known, show all tabs optimistically.
|
|
66
|
+
// The server will 400 on unsupported methods and the UI will surface it.
|
|
67
|
+
var hasResources = caps ? !!caps.resources : true;
|
|
68
|
+
var hasPrompts = caps ? !!caps.prompts : true;
|
|
69
|
+
return (
|
|
70
|
+
'<div class="tester-shell" data-tester-id="' +
|
|
71
|
+
server.id +
|
|
72
|
+
'">' +
|
|
73
|
+
'<div class="tester-tabs">' +
|
|
74
|
+
tabBtn(server.id, 'tools', 'Tools', true) +
|
|
75
|
+
tabBtn(server.id, 'info', 'Info', true) +
|
|
76
|
+
tabBtn(
|
|
77
|
+
server.id,
|
|
78
|
+
'resources',
|
|
79
|
+
'Resources',
|
|
80
|
+
hasResources,
|
|
81
|
+
!hasResources ? 'Server does not advertise resources' : '',
|
|
82
|
+
) +
|
|
83
|
+
tabBtn(
|
|
84
|
+
server.id,
|
|
85
|
+
'prompts',
|
|
86
|
+
'Prompts',
|
|
87
|
+
hasPrompts,
|
|
88
|
+
!hasPrompts ? 'Server does not advertise prompts' : '',
|
|
89
|
+
) +
|
|
90
|
+
tabBtn(server.id, 'events', 'Events', true) +
|
|
91
|
+
tabBtn(server.id, 'export', 'Export', true) +
|
|
92
|
+
tabBtn(server.id, 'diagnostics', 'Diagnostics', true) +
|
|
93
|
+
'<button class="tester-popout" data-action="tester-popout" data-id="' +
|
|
94
|
+
server.id +
|
|
95
|
+
'" title="Open in floating panel"><span class="material-symbols-outlined" style="font-size:14px">open_in_new</span></button>' +
|
|
96
|
+
'</div>' +
|
|
97
|
+
'<div class="tester-body" data-tester-body="' +
|
|
98
|
+
server.id +
|
|
99
|
+
'">' +
|
|
100
|
+
'<div class="hint">Expand to load...</div>' +
|
|
101
|
+
'</div>' +
|
|
102
|
+
'</div>'
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function tabBtn(serverId, name, label, enabled, title) {
|
|
107
|
+
var st = getState(serverId);
|
|
108
|
+
return (
|
|
109
|
+
'<button class="tester-tab' +
|
|
110
|
+
(st.subtab === name ? ' active' : '') +
|
|
111
|
+
(enabled ? '' : ' disabled') +
|
|
112
|
+
'"' +
|
|
113
|
+
(enabled
|
|
114
|
+
? ' data-action="tester-subtab" data-id="' + serverId + '" data-subtab="' + name + '"'
|
|
115
|
+
: ' disabled') +
|
|
116
|
+
(title ? ' title="' + esc(title) + '"' : '') +
|
|
117
|
+
'>' +
|
|
118
|
+
esc(label) +
|
|
119
|
+
'</button>'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
AD.openTesterFor = function (serverId) {
|
|
124
|
+
var s = getState(serverId);
|
|
125
|
+
renderBody(serverId);
|
|
126
|
+
if (!s.info) loadInfo(serverId);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
function renderBody(serverId) {
|
|
130
|
+
var bodies = document.querySelectorAll('[data-tester-body="' + serverId + '"]');
|
|
131
|
+
if (!bodies.length) return;
|
|
132
|
+
var s = getState(serverId);
|
|
133
|
+
var html;
|
|
134
|
+
switch (s.subtab) {
|
|
135
|
+
case 'tools':
|
|
136
|
+
html = renderToolsTab(serverId);
|
|
137
|
+
break;
|
|
138
|
+
case 'info':
|
|
139
|
+
html = renderInfoTab(serverId);
|
|
140
|
+
break;
|
|
141
|
+
case 'resources':
|
|
142
|
+
html = renderResourcesTab(serverId);
|
|
143
|
+
break;
|
|
144
|
+
case 'prompts':
|
|
145
|
+
html = renderPromptsTab(serverId);
|
|
146
|
+
break;
|
|
147
|
+
case 'events':
|
|
148
|
+
html = renderEventsTab(serverId);
|
|
149
|
+
break;
|
|
150
|
+
case 'export':
|
|
151
|
+
html = renderExportTab(serverId);
|
|
152
|
+
break;
|
|
153
|
+
case 'diagnostics':
|
|
154
|
+
html = renderDiagnosticsTab(serverId);
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
html = '<div class="hint">Unknown tab</div>';
|
|
158
|
+
}
|
|
159
|
+
for (var i = 0; i < bodies.length; i++) {
|
|
160
|
+
bodies[i].innerHTML = html;
|
|
161
|
+
}
|
|
162
|
+
syncTabHighlight(serverId);
|
|
163
|
+
bindBody(serverId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function syncTabHighlight(serverId) {
|
|
167
|
+
var s = getState(serverId);
|
|
168
|
+
var shells = document.querySelectorAll('[data-tester-id="' + serverId + '"]');
|
|
169
|
+
shells.forEach(function (shell) {
|
|
170
|
+
shell.querySelectorAll('.tester-tab').forEach(function (b) {
|
|
171
|
+
if (b.getAttribute('data-subtab') === s.subtab) b.classList.add('active');
|
|
172
|
+
else b.classList.remove('active');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Tools tab
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
function renderToolsTab(serverId) {
|
|
182
|
+
var s = getState(serverId);
|
|
183
|
+
if (s.tools == null) {
|
|
184
|
+
loadTools(serverId);
|
|
185
|
+
return '<div class="hint">Loading tools...</div>';
|
|
186
|
+
}
|
|
187
|
+
if (s.tools.length === 0) return '<div class="hint">This server exposes no tools.</div>';
|
|
188
|
+
var list = s.tools
|
|
189
|
+
.map(function (t) {
|
|
190
|
+
return (
|
|
191
|
+
'<button class="tester-listitem' +
|
|
192
|
+
(s.selectedTool === t.name ? ' active' : '') +
|
|
193
|
+
'" data-action="tester-select-tool" data-id="' +
|
|
194
|
+
serverId +
|
|
195
|
+
'" data-name="' +
|
|
196
|
+
esc(t.name) +
|
|
197
|
+
'">' +
|
|
198
|
+
'<div class="tester-listitem-name">' +
|
|
199
|
+
esc(t.name) +
|
|
200
|
+
'</div>' +
|
|
201
|
+
(t.description
|
|
202
|
+
? '<div class="tester-listitem-desc">' + esc(t.description) + '</div>'
|
|
203
|
+
: '') +
|
|
204
|
+
'</button>'
|
|
205
|
+
);
|
|
206
|
+
})
|
|
207
|
+
.join('');
|
|
208
|
+
var panel = s.selectedTool
|
|
209
|
+
? renderToolForm(serverId)
|
|
210
|
+
: '<div class="hint">Select a tool to call.</div>';
|
|
211
|
+
return (
|
|
212
|
+
'<div class="tester-split">' +
|
|
213
|
+
'<div class="tester-list">' +
|
|
214
|
+
list +
|
|
215
|
+
'</div>' +
|
|
216
|
+
'<div class="tester-detail">' +
|
|
217
|
+
panel +
|
|
218
|
+
'</div>' +
|
|
219
|
+
'</div>'
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function renderToolForm(serverId) {
|
|
224
|
+
var s = getState(serverId);
|
|
225
|
+
var tool = s.tools.find(function (t) {
|
|
226
|
+
return t.name === s.selectedTool;
|
|
227
|
+
});
|
|
228
|
+
if (!tool) return '<div class="hint">Tool disappeared.</div>';
|
|
229
|
+
var presets = loadPresets(s.serverName, 'tool', tool.name);
|
|
230
|
+
var presetList = Object.keys(presets);
|
|
231
|
+
var presetOpts =
|
|
232
|
+
'<option value="">Preset…</option>' +
|
|
233
|
+
presetList
|
|
234
|
+
.map(function (k) {
|
|
235
|
+
return '<option value="' + esc(k) + '">' + esc(k) + '</option>';
|
|
236
|
+
})
|
|
237
|
+
.join('');
|
|
238
|
+
return (
|
|
239
|
+
'<div class="tester-form">' +
|
|
240
|
+
'<div class="tester-form-head"><strong>' +
|
|
241
|
+
esc(tool.name) +
|
|
242
|
+
'</strong>' +
|
|
243
|
+
(tool.description ? '<p class="tester-desc">' + esc(tool.description) + '</p>' : '') +
|
|
244
|
+
'</div>' +
|
|
245
|
+
'<div class="tester-form-fields" data-form-for="' +
|
|
246
|
+
esc(tool.name) +
|
|
247
|
+
'"></div>' +
|
|
248
|
+
'<div class="tester-form-actions">' +
|
|
249
|
+
'<button class="btn-activate" data-action="tester-call" data-id="' +
|
|
250
|
+
serverId +
|
|
251
|
+
'">Call tool</button>' +
|
|
252
|
+
'<select class="tester-preset-sel" data-action="tester-preset-load" data-id="' +
|
|
253
|
+
serverId +
|
|
254
|
+
'">' +
|
|
255
|
+
presetOpts +
|
|
256
|
+
'</select>' +
|
|
257
|
+
'<button class="btn-health" data-action="tester-preset-save" data-id="' +
|
|
258
|
+
serverId +
|
|
259
|
+
'">Save as preset</button>' +
|
|
260
|
+
'</div>' +
|
|
261
|
+
renderResult(serverId) +
|
|
262
|
+
'</div>'
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// Info tab
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
function renderInfoTab(serverId) {
|
|
271
|
+
var s = getState(serverId);
|
|
272
|
+
if (s.info == null) {
|
|
273
|
+
loadInfo(serverId);
|
|
274
|
+
return '<div class="hint">Loading server info...</div>';
|
|
275
|
+
}
|
|
276
|
+
if (s.info === 'error')
|
|
277
|
+
return '<div class="hint error">' + esc(s.error || 'Failed to load info') + '</div>';
|
|
278
|
+
var caps = s.info.capabilities || {};
|
|
279
|
+
var capRows = Object.keys(caps)
|
|
280
|
+
.map(function (k) {
|
|
281
|
+
return (
|
|
282
|
+
'<div class="tester-cap-row"><span class="tester-cap-name">' +
|
|
283
|
+
esc(k) +
|
|
284
|
+
'</span><span class="tester-cap-val">' +
|
|
285
|
+
esc(JSON.stringify(caps[k])) +
|
|
286
|
+
'</span></div>'
|
|
287
|
+
);
|
|
288
|
+
})
|
|
289
|
+
.join('');
|
|
290
|
+
return (
|
|
291
|
+
'<div class="tester-info">' +
|
|
292
|
+
'<div class="tester-info-grid">' +
|
|
293
|
+
'<div><span class="tester-info-k">Name</span><span class="tester-info-v">' +
|
|
294
|
+
esc(s.info.name) +
|
|
295
|
+
'</span></div>' +
|
|
296
|
+
'<div><span class="tester-info-k">Version</span><span class="tester-info-v">' +
|
|
297
|
+
esc(s.info.version) +
|
|
298
|
+
'</span></div>' +
|
|
299
|
+
'</div>' +
|
|
300
|
+
(s.info.instructions
|
|
301
|
+
? '<div class="tester-info-instr"><h4>Instructions</h4><div class="md">' +
|
|
302
|
+
(AD.renderMarkdown ? AD.renderMarkdown(s.info.instructions) : esc(s.info.instructions)) +
|
|
303
|
+
'</div></div>'
|
|
304
|
+
: '') +
|
|
305
|
+
'<h4>Capabilities</h4>' +
|
|
306
|
+
'<div class="tester-caps">' +
|
|
307
|
+
(capRows || '<div class="hint">(none)</div>') +
|
|
308
|
+
'</div>' +
|
|
309
|
+
'</div>'
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// Resources tab
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
function renderResourcesTab(serverId) {
|
|
318
|
+
var s = getState(serverId);
|
|
319
|
+
if (s.resources == null) {
|
|
320
|
+
loadResources(serverId);
|
|
321
|
+
return '<div class="hint">Loading resources...</div>';
|
|
322
|
+
}
|
|
323
|
+
var list = s.resources
|
|
324
|
+
.map(function (r) {
|
|
325
|
+
return (
|
|
326
|
+
'<button class="tester-listitem' +
|
|
327
|
+
(s.selectedResource === r.uri ? ' active' : '') +
|
|
328
|
+
'" data-action="tester-select-resource" data-id="' +
|
|
329
|
+
serverId +
|
|
330
|
+
'" data-uri="' +
|
|
331
|
+
esc(r.uri) +
|
|
332
|
+
'">' +
|
|
333
|
+
'<div class="tester-listitem-name">' +
|
|
334
|
+
esc(r.name || r.uri) +
|
|
335
|
+
'</div>' +
|
|
336
|
+
'<div class="tester-listitem-desc">' +
|
|
337
|
+
esc(r.uri) +
|
|
338
|
+
(r.mimeType ? ' — ' + esc(r.mimeType) : '') +
|
|
339
|
+
'</div>' +
|
|
340
|
+
'</button>'
|
|
341
|
+
);
|
|
342
|
+
})
|
|
343
|
+
.join('');
|
|
344
|
+
var more = s.resourceCursor
|
|
345
|
+
? '<button class="btn-health" data-action="tester-resources-more" data-id="' +
|
|
346
|
+
serverId +
|
|
347
|
+
'">Load more</button>'
|
|
348
|
+
: '';
|
|
349
|
+
var detail = s.selectedResource
|
|
350
|
+
? renderResourceDetail(serverId)
|
|
351
|
+
: '<div class="hint">Pick a resource to read. You can also subscribe for live updates.</div>';
|
|
352
|
+
return (
|
|
353
|
+
'<div class="tester-split">' +
|
|
354
|
+
'<div class="tester-list">' +
|
|
355
|
+
(list || '<div class="hint">No resources.</div>') +
|
|
356
|
+
more +
|
|
357
|
+
'</div>' +
|
|
358
|
+
'<div class="tester-detail">' +
|
|
359
|
+
detail +
|
|
360
|
+
'</div>' +
|
|
361
|
+
'</div>'
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function renderResourceDetail(serverId) {
|
|
366
|
+
var s = getState(serverId);
|
|
367
|
+
var contents = s.resourceContents;
|
|
368
|
+
var body;
|
|
369
|
+
if (!contents) body = '<div class="hint">Click Read to load.</div>';
|
|
370
|
+
else {
|
|
371
|
+
body =
|
|
372
|
+
'<div class="tester-result-raw"><pre>' +
|
|
373
|
+
esc(JSON.stringify(contents, null, 2)) +
|
|
374
|
+
'</pre></div>';
|
|
375
|
+
}
|
|
376
|
+
return (
|
|
377
|
+
'<div class="tester-form">' +
|
|
378
|
+
'<div class="tester-form-head"><strong>' +
|
|
379
|
+
esc(s.selectedResource) +
|
|
380
|
+
'</strong></div>' +
|
|
381
|
+
'<div class="tester-form-actions">' +
|
|
382
|
+
'<button class="btn-activate" data-action="tester-resource-read" data-id="' +
|
|
383
|
+
serverId +
|
|
384
|
+
'">Read</button>' +
|
|
385
|
+
'<button class="btn-health" data-action="tester-resource-subscribe" data-id="' +
|
|
386
|
+
serverId +
|
|
387
|
+
'">Subscribe</button>' +
|
|
388
|
+
'<button class="btn-delete" data-action="tester-resource-unsubscribe" data-id="' +
|
|
389
|
+
serverId +
|
|
390
|
+
'">Unsubscribe</button>' +
|
|
391
|
+
'</div>' +
|
|
392
|
+
body +
|
|
393
|
+
'</div>'
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Prompts tab
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
function renderPromptsTab(serverId) {
|
|
402
|
+
var s = getState(serverId);
|
|
403
|
+
if (s.prompts == null) {
|
|
404
|
+
loadPrompts(serverId);
|
|
405
|
+
return '<div class="hint">Loading prompts...</div>';
|
|
406
|
+
}
|
|
407
|
+
var list = s.prompts
|
|
408
|
+
.map(function (p) {
|
|
409
|
+
return (
|
|
410
|
+
'<button class="tester-listitem' +
|
|
411
|
+
(s.selectedPrompt === p.name ? ' active' : '') +
|
|
412
|
+
'" data-action="tester-select-prompt" data-id="' +
|
|
413
|
+
serverId +
|
|
414
|
+
'" data-name="' +
|
|
415
|
+
esc(p.name) +
|
|
416
|
+
'">' +
|
|
417
|
+
'<div class="tester-listitem-name">' +
|
|
418
|
+
esc(p.name) +
|
|
419
|
+
'</div>' +
|
|
420
|
+
(p.description
|
|
421
|
+
? '<div class="tester-listitem-desc">' + esc(p.description) + '</div>'
|
|
422
|
+
: '') +
|
|
423
|
+
'</button>'
|
|
424
|
+
);
|
|
425
|
+
})
|
|
426
|
+
.join('');
|
|
427
|
+
var detail = s.selectedPrompt
|
|
428
|
+
? renderPromptForm(serverId)
|
|
429
|
+
: '<div class="hint">Pick a prompt.</div>';
|
|
430
|
+
return (
|
|
431
|
+
'<div class="tester-split">' +
|
|
432
|
+
'<div class="tester-list">' +
|
|
433
|
+
(list || '<div class="hint">No prompts.</div>') +
|
|
434
|
+
'</div>' +
|
|
435
|
+
'<div class="tester-detail">' +
|
|
436
|
+
detail +
|
|
437
|
+
'</div>' +
|
|
438
|
+
'</div>'
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function renderPromptForm(serverId) {
|
|
443
|
+
var s = getState(serverId);
|
|
444
|
+
var prompt = s.prompts.find(function (p) {
|
|
445
|
+
return p.name === s.selectedPrompt;
|
|
446
|
+
});
|
|
447
|
+
if (!prompt) return '<div class="hint">Prompt missing.</div>';
|
|
448
|
+
var argsRows = (prompt.arguments || [])
|
|
449
|
+
.map(function (a) {
|
|
450
|
+
return (
|
|
451
|
+
'<div class="sf-field" data-type="string" data-path="' +
|
|
452
|
+
esc(a.name) +
|
|
453
|
+
'">' +
|
|
454
|
+
'<label>' +
|
|
455
|
+
esc(a.name) +
|
|
456
|
+
(a.required ? ' <span class="sf-req">*</span>' : '') +
|
|
457
|
+
'</label>' +
|
|
458
|
+
'<input type="text" class="sf-input" data-path="' +
|
|
459
|
+
esc(a.name) +
|
|
460
|
+
'" />' +
|
|
461
|
+
(a.description ? '<div class="sf-desc">' + esc(a.description) + '</div>' : '') +
|
|
462
|
+
'</div>'
|
|
463
|
+
);
|
|
464
|
+
})
|
|
465
|
+
.join('');
|
|
466
|
+
var msgs = s.promptMessages;
|
|
467
|
+
var msgRender = '';
|
|
468
|
+
if (msgs) {
|
|
469
|
+
msgRender =
|
|
470
|
+
'<div class="tester-messages">' +
|
|
471
|
+
msgs
|
|
472
|
+
.map(function (m) {
|
|
473
|
+
var body = '';
|
|
474
|
+
if (m.content && m.content.type === 'text')
|
|
475
|
+
body = AD.renderMarkdown ? AD.renderMarkdown(m.content.text) : esc(m.content.text);
|
|
476
|
+
else body = '<pre>' + esc(JSON.stringify(m.content, null, 2)) + '</pre>';
|
|
477
|
+
return (
|
|
478
|
+
'<div class="tester-message tester-message-' +
|
|
479
|
+
esc(m.role) +
|
|
480
|
+
'"><div class="tester-msg-role">' +
|
|
481
|
+
esc(m.role) +
|
|
482
|
+
'</div><div class="tester-msg-body">' +
|
|
483
|
+
body +
|
|
484
|
+
'</div></div>'
|
|
485
|
+
);
|
|
486
|
+
})
|
|
487
|
+
.join('') +
|
|
488
|
+
'</div>';
|
|
489
|
+
}
|
|
490
|
+
return (
|
|
491
|
+
'<div class="tester-form">' +
|
|
492
|
+
'<div class="tester-form-head"><strong>' +
|
|
493
|
+
esc(prompt.name) +
|
|
494
|
+
'</strong>' +
|
|
495
|
+
(prompt.description ? '<p class="tester-desc">' + esc(prompt.description) + '</p>' : '') +
|
|
496
|
+
'</div>' +
|
|
497
|
+
'<div class="tester-prompt-fields">' +
|
|
498
|
+
(argsRows || '<div class="hint">No arguments.</div>') +
|
|
499
|
+
'</div>' +
|
|
500
|
+
'<div class="tester-form-actions">' +
|
|
501
|
+
'<button class="btn-activate" data-action="tester-prompt-get" data-id="' +
|
|
502
|
+
serverId +
|
|
503
|
+
'">Get prompt</button>' +
|
|
504
|
+
'</div>' +
|
|
505
|
+
msgRender +
|
|
506
|
+
'</div>'
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
// Events / Export / Diagnostics
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
|
|
514
|
+
function renderEventsTab(serverId) {
|
|
515
|
+
var s = getState(serverId);
|
|
516
|
+
var buf = eventBuffers[s.serverName] || [];
|
|
517
|
+
if (buf.length === 0)
|
|
518
|
+
return '<div class="hint">No events yet. Server notifications and progress updates will appear here.</div>';
|
|
519
|
+
var rows = buf
|
|
520
|
+
.slice(0, 100)
|
|
521
|
+
.map(function (e) {
|
|
522
|
+
return (
|
|
523
|
+
'<div class="tester-event tester-event-' +
|
|
524
|
+
esc(e.type) +
|
|
525
|
+
'">' +
|
|
526
|
+
'<span class="tester-event-ts">' +
|
|
527
|
+
esc((e.ts || '').substr(11, 8)) +
|
|
528
|
+
'</span>' +
|
|
529
|
+
'<span class="tester-event-kind">' +
|
|
530
|
+
esc(e.type) +
|
|
531
|
+
'</span>' +
|
|
532
|
+
'<span class="tester-event-method">' +
|
|
533
|
+
esc(e.method || (e.payload && e.payload.token) || '') +
|
|
534
|
+
'</span>' +
|
|
535
|
+
'<pre class="tester-event-payload">' +
|
|
536
|
+
esc(JSON.stringify(e.params || e.payload || {}, null, 2)) +
|
|
537
|
+
'</pre>' +
|
|
538
|
+
'</div>'
|
|
539
|
+
);
|
|
540
|
+
})
|
|
541
|
+
.join('');
|
|
542
|
+
return '<div class="tester-events">' + rows + '</div>';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function renderExportTab(serverId) {
|
|
546
|
+
var s = getState(serverId);
|
|
547
|
+
if (!s.exportFormat) s.exportFormat = 'mcp-json';
|
|
548
|
+
var fmt = s.exportFormat;
|
|
549
|
+
if (!s.exportBody || s.exportBody.format !== fmt) {
|
|
550
|
+
exportConfig(serverId, fmt);
|
|
551
|
+
}
|
|
552
|
+
var formats = [
|
|
553
|
+
{
|
|
554
|
+
id: 'mcp-json',
|
|
555
|
+
label: 'mcp.json',
|
|
556
|
+
hint: 'Generic MCP client config — Claude Desktop, Claude Code, Cursor, Windsurf.',
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
id: 'agent-discover',
|
|
560
|
+
label: 'agent-discover',
|
|
561
|
+
hint: 'Declarative setup file (AGENT_DISCOVER_SETUP_FILE).',
|
|
562
|
+
},
|
|
563
|
+
];
|
|
564
|
+
var seg = formats
|
|
565
|
+
.map(function (f) {
|
|
566
|
+
return (
|
|
567
|
+
'<button class="tester-seg' +
|
|
568
|
+
(fmt === f.id ? ' active' : '') +
|
|
569
|
+
'" data-action="tester-export" data-id="' +
|
|
570
|
+
serverId +
|
|
571
|
+
'" data-format="' +
|
|
572
|
+
f.id +
|
|
573
|
+
'">' +
|
|
574
|
+
esc(f.label) +
|
|
575
|
+
'</button>'
|
|
576
|
+
);
|
|
577
|
+
})
|
|
578
|
+
.join('');
|
|
579
|
+
var hint = formats.find(function (f) {
|
|
580
|
+
return f.id === fmt;
|
|
581
|
+
});
|
|
582
|
+
var body =
|
|
583
|
+
s.exportBody && s.exportBody.format === fmt
|
|
584
|
+
? '<div class="tester-result-raw"><pre>' +
|
|
585
|
+
(AD.highlightJson
|
|
586
|
+
? AD.highlightJson(s.exportBody.config)
|
|
587
|
+
: esc(JSON.stringify(s.exportBody.config, null, 2))) +
|
|
588
|
+
'</pre></div>'
|
|
589
|
+
: '<div class="hint">Loading…</div>';
|
|
590
|
+
return (
|
|
591
|
+
'<div class="tester-export">' +
|
|
592
|
+
'<div class="tester-export-head">' +
|
|
593
|
+
'<div class="tester-segmented">' +
|
|
594
|
+
seg +
|
|
595
|
+
'</div>' +
|
|
596
|
+
'<button class="tester-copy-btn" data-action="tester-export-copy" data-id="' +
|
|
597
|
+
serverId +
|
|
598
|
+
'" title="Copy to clipboard">' +
|
|
599
|
+
'<span class="material-symbols-outlined" style="font-size:14px">content_copy</span>' +
|
|
600
|
+
' Copy' +
|
|
601
|
+
'</button>' +
|
|
602
|
+
'</div>' +
|
|
603
|
+
(hint ? '<div class="tester-export-hint">' + esc(hint.hint) + '</div>' : '') +
|
|
604
|
+
body +
|
|
605
|
+
'</div>'
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function renderDiagnosticsTab(serverId) {
|
|
610
|
+
var s = getState(serverId);
|
|
611
|
+
var rtt =
|
|
612
|
+
s.pingRtt != null
|
|
613
|
+
? '<strong>' + esc(s.pingRtt) + ' ms</strong>'
|
|
614
|
+
: '<span class="hint">not pinged yet</span>';
|
|
615
|
+
return (
|
|
616
|
+
'<div class="tester-diagnostics">' +
|
|
617
|
+
'<div class="tester-form-row"><label>Last ping</label>' +
|
|
618
|
+
rtt +
|
|
619
|
+
' <button class="btn-activate" data-action="tester-ping" data-id="' +
|
|
620
|
+
serverId +
|
|
621
|
+
'">Ping</button></div>' +
|
|
622
|
+
'<div class="tester-form-row"><label>Logging level</label>' +
|
|
623
|
+
'<select data-action="noop" data-tester-logging="' +
|
|
624
|
+
serverId +
|
|
625
|
+
'">' +
|
|
626
|
+
['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency']
|
|
627
|
+
.map(function (l) {
|
|
628
|
+
return (
|
|
629
|
+
'<option value="' +
|
|
630
|
+
l +
|
|
631
|
+
'"' +
|
|
632
|
+
(l === s.loggingLevel ? ' selected' : '') +
|
|
633
|
+
'>' +
|
|
634
|
+
l +
|
|
635
|
+
'</option>'
|
|
636
|
+
);
|
|
637
|
+
})
|
|
638
|
+
.join('') +
|
|
639
|
+
'</select> <button class="btn-activate" data-action="tester-set-logging" data-id="' +
|
|
640
|
+
serverId +
|
|
641
|
+
'">Apply</button></div>' +
|
|
642
|
+
'</div>'
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
// Result pane
|
|
648
|
+
// ---------------------------------------------------------------------------
|
|
649
|
+
|
|
650
|
+
function renderResult(serverId) {
|
|
651
|
+
var s = getState(serverId);
|
|
652
|
+
if (s.loading) return '<div class="tester-result loading">Calling…</div>';
|
|
653
|
+
if (s.error) return '<div class="tester-result error">' + esc(s.error) + '</div>';
|
|
654
|
+
if (!s.result) return '';
|
|
655
|
+
var mode = s.resultMode || 'pretty';
|
|
656
|
+
var pretty = renderPrettyResult(s.result);
|
|
657
|
+
var raw =
|
|
658
|
+
'<pre class="tester-result-raw">' +
|
|
659
|
+
(AD.highlightJson ? AD.highlightJson(s.result) : esc(JSON.stringify(s.result, null, 2))) +
|
|
660
|
+
'</pre>';
|
|
661
|
+
var curl = renderCurl(serverId, s.selectedTool, s.lastArgs);
|
|
662
|
+
var body = mode === 'raw' ? raw : mode === 'curl' ? curl : pretty;
|
|
663
|
+
var pill = s.result.isError
|
|
664
|
+
? '<span class="tester-pill fail">error</span>'
|
|
665
|
+
: '<span class="tester-pill ok">ok</span>';
|
|
666
|
+
var latency =
|
|
667
|
+
s.lastLatency != null
|
|
668
|
+
? '<span class="tester-latency">' + esc(s.lastLatency) + ' ms</span>'
|
|
669
|
+
: '';
|
|
670
|
+
return (
|
|
671
|
+
'<div class="tester-result">' +
|
|
672
|
+
'<div class="tester-result-tabs">' +
|
|
673
|
+
'<button class="' +
|
|
674
|
+
(mode === 'pretty' ? 'active' : '') +
|
|
675
|
+
'" data-action="tester-result-mode" data-id="' +
|
|
676
|
+
serverId +
|
|
677
|
+
'" data-mode="pretty">Pretty</button>' +
|
|
678
|
+
'<button class="' +
|
|
679
|
+
(mode === 'raw' ? 'active' : '') +
|
|
680
|
+
'" data-action="tester-result-mode" data-id="' +
|
|
681
|
+
serverId +
|
|
682
|
+
'" data-mode="raw">Raw</button>' +
|
|
683
|
+
'<button class="' +
|
|
684
|
+
(mode === 'curl' ? 'active' : '') +
|
|
685
|
+
'" data-action="tester-result-mode" data-id="' +
|
|
686
|
+
serverId +
|
|
687
|
+
'" data-mode="curl">cURL</button>' +
|
|
688
|
+
pill +
|
|
689
|
+
latency +
|
|
690
|
+
'</div>' +
|
|
691
|
+
'<div class="tester-result-body">' +
|
|
692
|
+
body +
|
|
693
|
+
'</div>' +
|
|
694
|
+
'</div>'
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function renderPrettyResult(result) {
|
|
699
|
+
if (!result.content || !Array.isArray(result.content)) {
|
|
700
|
+
return '<pre>' + esc(JSON.stringify(result, null, 2)) + '</pre>';
|
|
701
|
+
}
|
|
702
|
+
return result.content
|
|
703
|
+
.map(function (c) {
|
|
704
|
+
if (c.type === 'text')
|
|
705
|
+
return (
|
|
706
|
+
'<div class="tester-content-text">' +
|
|
707
|
+
(AD.renderMarkdown ? AD.renderMarkdown(c.text) : esc(c.text)) +
|
|
708
|
+
'</div>'
|
|
709
|
+
);
|
|
710
|
+
if (c.type === 'image')
|
|
711
|
+
return (
|
|
712
|
+
'<img class="tester-content-img" src="data:' +
|
|
713
|
+
esc(c.mimeType) +
|
|
714
|
+
';base64,' +
|
|
715
|
+
esc(c.data) +
|
|
716
|
+
'"/>'
|
|
717
|
+
);
|
|
718
|
+
if (c.type === 'audio')
|
|
719
|
+
return (
|
|
720
|
+
'<audio class="tester-content-audio" controls src="data:' +
|
|
721
|
+
esc(c.mimeType) +
|
|
722
|
+
';base64,' +
|
|
723
|
+
esc(c.data) +
|
|
724
|
+
'"></audio>'
|
|
725
|
+
);
|
|
726
|
+
if (c.type === 'resource')
|
|
727
|
+
return (
|
|
728
|
+
'<div class="tester-content-resource">Resource: <code>' +
|
|
729
|
+
esc(c.resource && c.resource.uri) +
|
|
730
|
+
'</code></div>'
|
|
731
|
+
);
|
|
732
|
+
if (c.type === 'resource_link')
|
|
733
|
+
return (
|
|
734
|
+
'<div class="tester-content-resource">Link: <a href="' +
|
|
735
|
+
esc(c.uri) +
|
|
736
|
+
'" target="_blank">' +
|
|
737
|
+
esc(c.name || c.uri) +
|
|
738
|
+
'</a></div>'
|
|
739
|
+
);
|
|
740
|
+
return '<pre>' + esc(JSON.stringify(c, null, 2)) + '</pre>';
|
|
741
|
+
})
|
|
742
|
+
.join('');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function renderCurl(serverId, tool, args) {
|
|
746
|
+
var url = (location.origin || '') + '/api/servers/' + serverId + '/call';
|
|
747
|
+
var body = JSON.stringify({ tool: tool, args: args || {} });
|
|
748
|
+
return (
|
|
749
|
+
'<pre class="tester-curl">curl -X POST ' +
|
|
750
|
+
esc(url) +
|
|
751
|
+
" \\\n -H 'Content-Type: application/json' \\\n -d '" +
|
|
752
|
+
esc(body) +
|
|
753
|
+
"'</pre>"
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ---------------------------------------------------------------------------
|
|
758
|
+
// Data loads
|
|
759
|
+
// ---------------------------------------------------------------------------
|
|
760
|
+
|
|
761
|
+
function loadInfo(serverId) {
|
|
762
|
+
var s = getState(serverId);
|
|
763
|
+
doFetch(baseUrl(serverId, s.handle) + '/info')
|
|
764
|
+
.then(function (body) {
|
|
765
|
+
s.info =
|
|
766
|
+
body && body.capabilities ? body : { name: s.serverName, version: '', capabilities: {} };
|
|
767
|
+
renderBody(serverId);
|
|
768
|
+
})
|
|
769
|
+
.catch(function (err) {
|
|
770
|
+
s.info = 'error';
|
|
771
|
+
s.error = err.message;
|
|
772
|
+
renderBody(serverId);
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function loadTools(serverId) {
|
|
777
|
+
var s = getState(serverId);
|
|
778
|
+
doFetch(baseUrl(serverId, s.handle) + '/tools')
|
|
779
|
+
.then(function (body) {
|
|
780
|
+
s.tools = body.tools || [];
|
|
781
|
+
renderBody(serverId);
|
|
782
|
+
})
|
|
783
|
+
.catch(function (err) {
|
|
784
|
+
s.tools = [];
|
|
785
|
+
s.error = err.message;
|
|
786
|
+
renderBody(serverId);
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function loadResources(serverId, append) {
|
|
791
|
+
var s = getState(serverId);
|
|
792
|
+
var url =
|
|
793
|
+
baseUrl(serverId, s.handle) +
|
|
794
|
+
'/resources' +
|
|
795
|
+
(s.resourceCursor && append ? '?cursor=' + encodeURIComponent(s.resourceCursor) : '');
|
|
796
|
+
doFetch(url)
|
|
797
|
+
.then(function (body) {
|
|
798
|
+
var incoming = body.resources || [];
|
|
799
|
+
s.resources = append ? (s.resources || []).concat(incoming) : incoming;
|
|
800
|
+
s.resourceCursor = body.nextCursor || null;
|
|
801
|
+
renderBody(serverId);
|
|
802
|
+
})
|
|
803
|
+
.catch(function (err) {
|
|
804
|
+
s.resources = [];
|
|
805
|
+
s.error = err.message;
|
|
806
|
+
renderBody(serverId);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function loadPrompts(serverId) {
|
|
811
|
+
var s = getState(serverId);
|
|
812
|
+
doFetch(baseUrl(serverId, s.handle) + '/prompts')
|
|
813
|
+
.then(function (body) {
|
|
814
|
+
s.prompts = body.prompts || [];
|
|
815
|
+
renderBody(serverId);
|
|
816
|
+
})
|
|
817
|
+
.catch(function (err) {
|
|
818
|
+
s.prompts = [];
|
|
819
|
+
s.error = err.message;
|
|
820
|
+
renderBody(serverId);
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// ---------------------------------------------------------------------------
|
|
825
|
+
// Presets (localStorage)
|
|
826
|
+
// ---------------------------------------------------------------------------
|
|
827
|
+
|
|
828
|
+
function presetCacheKey(serverName, kind, targetName) {
|
|
829
|
+
return (serverName || '') + '::' + kind + '::' + (targetName || '');
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function loadPresets(serverName, kind, targetName) {
|
|
833
|
+
var key = presetCacheKey(serverName, kind, targetName);
|
|
834
|
+
if (presetsCache[key]) return presetsCache[key];
|
|
835
|
+
// Kick an async fetch; return empty until it resolves.
|
|
836
|
+
fetchPresets(serverName, kind, targetName);
|
|
837
|
+
return {};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function fetchPresets(serverName, kind, targetName) {
|
|
841
|
+
var key = presetCacheKey(serverName, kind, targetName);
|
|
842
|
+
var url =
|
|
843
|
+
'/api/presets?server=' +
|
|
844
|
+
encodeURIComponent(serverName || '') +
|
|
845
|
+
'&kind=' +
|
|
846
|
+
encodeURIComponent(kind) +
|
|
847
|
+
'&target=' +
|
|
848
|
+
encodeURIComponent(targetName || '');
|
|
849
|
+
AD._fetch(url)
|
|
850
|
+
.then(function (r) {
|
|
851
|
+
return r.json();
|
|
852
|
+
})
|
|
853
|
+
.then(function (body) {
|
|
854
|
+
var map = {};
|
|
855
|
+
(body.entries || []).forEach(function (e) {
|
|
856
|
+
map[e.preset_name] = { id: e.id, payload: e.payload };
|
|
857
|
+
});
|
|
858
|
+
presetsCache[key] = map;
|
|
859
|
+
// Re-render any tester whose tool form shows this server.
|
|
860
|
+
Object.keys(state).forEach(function (serverId) {
|
|
861
|
+
var s = state[serverId];
|
|
862
|
+
if (s.serverName === serverName && s.selectedTool === targetName && s.subtab === 'tools')
|
|
863
|
+
renderBody(serverId);
|
|
864
|
+
});
|
|
865
|
+
})
|
|
866
|
+
.catch(function () {
|
|
867
|
+
presetsCache[key] = {};
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function savePresetRemote(serverName, kind, targetName, presetName, payload) {
|
|
872
|
+
return AD._fetch('/api/presets', {
|
|
873
|
+
method: 'POST',
|
|
874
|
+
headers: { 'Content-Type': 'application/json' },
|
|
875
|
+
body: JSON.stringify({
|
|
876
|
+
server: serverName,
|
|
877
|
+
kind: kind,
|
|
878
|
+
target: targetName,
|
|
879
|
+
preset: presetName,
|
|
880
|
+
payload: payload,
|
|
881
|
+
}),
|
|
882
|
+
})
|
|
883
|
+
.then(function (r) {
|
|
884
|
+
return r.json();
|
|
885
|
+
})
|
|
886
|
+
.then(function (body) {
|
|
887
|
+
if (body && body.id != null) {
|
|
888
|
+
var key = presetCacheKey(serverName, kind, targetName);
|
|
889
|
+
if (!presetsCache[key]) presetsCache[key] = {};
|
|
890
|
+
presetsCache[key][presetName] = { id: body.id, payload: payload };
|
|
891
|
+
}
|
|
892
|
+
return body;
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function migrateLocalStoragePresets() {
|
|
897
|
+
try {
|
|
898
|
+
if (localStorage.getItem(PRESET_MIGRATED_KEY)) return;
|
|
899
|
+
var raw = localStorage.getItem(PRESET_KEY);
|
|
900
|
+
if (!raw) {
|
|
901
|
+
localStorage.setItem(PRESET_MIGRATED_KEY, '1');
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
var parsed = JSON.parse(raw);
|
|
905
|
+
var uploads = [];
|
|
906
|
+
Object.keys(parsed || {}).forEach(function (groupKey) {
|
|
907
|
+
var parts = groupKey.split('::');
|
|
908
|
+
if (parts.length < 3) return;
|
|
909
|
+
var serverName = parts[0];
|
|
910
|
+
var kind = parts[1];
|
|
911
|
+
var targetName = parts[2];
|
|
912
|
+
if (kind !== 'tool' && kind !== 'prompt') return;
|
|
913
|
+
var group = parsed[groupKey] || {};
|
|
914
|
+
Object.keys(group).forEach(function (presetName) {
|
|
915
|
+
uploads.push({
|
|
916
|
+
server: serverName,
|
|
917
|
+
kind: kind,
|
|
918
|
+
target: targetName,
|
|
919
|
+
preset: presetName,
|
|
920
|
+
payload: group[presetName],
|
|
921
|
+
});
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
if (uploads.length === 0) {
|
|
925
|
+
localStorage.setItem(PRESET_MIGRATED_KEY, '1');
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
Promise.all(
|
|
929
|
+
uploads.map(function (u) {
|
|
930
|
+
return AD._fetch('/api/presets', {
|
|
931
|
+
method: 'POST',
|
|
932
|
+
headers: { 'Content-Type': 'application/json' },
|
|
933
|
+
body: JSON.stringify(u),
|
|
934
|
+
}).catch(function () {
|
|
935
|
+
/* best-effort */
|
|
936
|
+
});
|
|
937
|
+
}),
|
|
938
|
+
).then(function () {
|
|
939
|
+
localStorage.setItem(PRESET_MIGRATED_KEY, '1');
|
|
940
|
+
localStorage.removeItem(PRESET_KEY);
|
|
941
|
+
});
|
|
942
|
+
} catch (e) {
|
|
943
|
+
/* ignore */
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
migrateLocalStoragePresets();
|
|
948
|
+
|
|
949
|
+
// ---------------------------------------------------------------------------
|
|
950
|
+
// Bindings
|
|
951
|
+
// ---------------------------------------------------------------------------
|
|
952
|
+
|
|
953
|
+
function bindBody(serverId) {
|
|
954
|
+
var s = getState(serverId);
|
|
955
|
+
if (s.subtab === 'tools' && s.selectedTool) {
|
|
956
|
+
var tool = s.tools.find(function (t) {
|
|
957
|
+
return t.name === s.selectedTool;
|
|
958
|
+
});
|
|
959
|
+
if (tool) {
|
|
960
|
+
var containers = document.querySelectorAll('[data-form-for="' + cssEsc(tool.name) + '"]');
|
|
961
|
+
containers.forEach(function (container) {
|
|
962
|
+
if (AD.renderSchemaForm) {
|
|
963
|
+
AD.renderSchemaForm(
|
|
964
|
+
tool.inputSchema || { type: 'object', properties: {} },
|
|
965
|
+
container,
|
|
966
|
+
s.lastArgs || null,
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function scopeFor(btn) {
|
|
975
|
+
return (btn && btn.closest && btn.closest('.tester-shell')) || document;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function cssEsc(n) {
|
|
979
|
+
return String(n).replace(/"/g, '\\"');
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Delegated handler
|
|
983
|
+
document.addEventListener('click', function (e) {
|
|
984
|
+
var btn = e.target.closest && e.target.closest('[data-action]');
|
|
985
|
+
if (!btn) return;
|
|
986
|
+
var action = btn.getAttribute('data-action');
|
|
987
|
+
if (!action || action.indexOf('tester-') !== 0) return;
|
|
988
|
+
var rawId = btn.getAttribute('data-id');
|
|
989
|
+
var id = /^-?\d+$/.test(rawId || '') ? parseInt(rawId, 10) : rawId;
|
|
990
|
+
if (id == null || id === '') return;
|
|
991
|
+
e.preventDefault();
|
|
992
|
+
handleAction(action, id, btn);
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
function handleAction(action, id, btn) {
|
|
996
|
+
var s = getState(id);
|
|
997
|
+
switch (action) {
|
|
998
|
+
case 'tester-subtab':
|
|
999
|
+
s.subtab = btn.getAttribute('data-subtab');
|
|
1000
|
+
renderBody(id);
|
|
1001
|
+
break;
|
|
1002
|
+
case 'tester-select-tool':
|
|
1003
|
+
s.selectedTool = btn.getAttribute('data-name');
|
|
1004
|
+
s.result = null;
|
|
1005
|
+
s.error = null;
|
|
1006
|
+
renderBody(id);
|
|
1007
|
+
break;
|
|
1008
|
+
case 'tester-call':
|
|
1009
|
+
callTool(id, btn);
|
|
1010
|
+
break;
|
|
1011
|
+
case 'tester-result-mode':
|
|
1012
|
+
s.resultMode = btn.getAttribute('data-mode');
|
|
1013
|
+
renderBody(id);
|
|
1014
|
+
break;
|
|
1015
|
+
case 'tester-select-resource':
|
|
1016
|
+
s.selectedResource = btn.getAttribute('data-uri');
|
|
1017
|
+
s.resourceContents = null;
|
|
1018
|
+
renderBody(id);
|
|
1019
|
+
break;
|
|
1020
|
+
case 'tester-resource-read':
|
|
1021
|
+
resourceRead(id);
|
|
1022
|
+
break;
|
|
1023
|
+
case 'tester-resource-subscribe':
|
|
1024
|
+
resourceSubscribe(id, 'subscribe');
|
|
1025
|
+
break;
|
|
1026
|
+
case 'tester-resource-unsubscribe':
|
|
1027
|
+
resourceSubscribe(id, 'unsubscribe');
|
|
1028
|
+
break;
|
|
1029
|
+
case 'tester-resources-more':
|
|
1030
|
+
loadResources(id, true);
|
|
1031
|
+
break;
|
|
1032
|
+
case 'tester-select-prompt':
|
|
1033
|
+
s.selectedPrompt = btn.getAttribute('data-name');
|
|
1034
|
+
s.promptMessages = null;
|
|
1035
|
+
renderBody(id);
|
|
1036
|
+
break;
|
|
1037
|
+
case 'tester-prompt-get':
|
|
1038
|
+
promptGet(id, btn);
|
|
1039
|
+
break;
|
|
1040
|
+
case 'tester-preset-save':
|
|
1041
|
+
presetSave(id);
|
|
1042
|
+
break;
|
|
1043
|
+
case 'tester-preset-load':
|
|
1044
|
+
/* handled via change, but button also triggers no-op */
|
|
1045
|
+
break;
|
|
1046
|
+
case 'tester-export':
|
|
1047
|
+
exportConfig(id, btn.getAttribute('data-format'));
|
|
1048
|
+
break;
|
|
1049
|
+
case 'tester-export-copy':
|
|
1050
|
+
copyExport(id);
|
|
1051
|
+
break;
|
|
1052
|
+
case 'tester-ping':
|
|
1053
|
+
ping(id);
|
|
1054
|
+
break;
|
|
1055
|
+
case 'tester-set-logging':
|
|
1056
|
+
setLogging(id, btn);
|
|
1057
|
+
break;
|
|
1058
|
+
case 'tester-popout':
|
|
1059
|
+
popOut(id);
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// preset-load is a <select> change, not a click
|
|
1065
|
+
document.addEventListener('change', function (e) {
|
|
1066
|
+
var el = e.target;
|
|
1067
|
+
if (!el.getAttribute) return;
|
|
1068
|
+
var action = el.getAttribute('data-action');
|
|
1069
|
+
var id = parseInt(el.getAttribute('data-id'), 10);
|
|
1070
|
+
if (action === 'tester-preset-load' && !isNaN(id)) {
|
|
1071
|
+
var name = el.value;
|
|
1072
|
+
if (!name) return;
|
|
1073
|
+
var s = getState(id);
|
|
1074
|
+
var presets = loadPresets(s.serverName, 'tool', s.selectedTool);
|
|
1075
|
+
var entry = presets[name];
|
|
1076
|
+
s.lastArgs = entry && entry.payload ? entry.payload : {};
|
|
1077
|
+
renderBody(id);
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
function callTool(serverId, btn) {
|
|
1082
|
+
var s = getState(serverId);
|
|
1083
|
+
if (!s.selectedTool) return;
|
|
1084
|
+
var tool = s.tools.find(function (t) {
|
|
1085
|
+
return t.name === s.selectedTool;
|
|
1086
|
+
});
|
|
1087
|
+
if (!tool) return;
|
|
1088
|
+
var scope = scopeFor(btn);
|
|
1089
|
+
var container = scope.querySelector('[data-form-for="' + cssEsc(tool.name) + '"]');
|
|
1090
|
+
var args = {};
|
|
1091
|
+
try {
|
|
1092
|
+
args = AD.collectSchemaForm(container) || {};
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
s.error = err.message;
|
|
1095
|
+
renderBody(serverId);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
s.loading = true;
|
|
1099
|
+
s.error = null;
|
|
1100
|
+
s.lastArgs = args;
|
|
1101
|
+
renderBody(serverId);
|
|
1102
|
+
var start = Date.now();
|
|
1103
|
+
doFetch(baseUrl(serverId, s.handle) + '/call', {
|
|
1104
|
+
method: 'POST',
|
|
1105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1106
|
+
body: JSON.stringify({ tool: tool.name, args: args }),
|
|
1107
|
+
})
|
|
1108
|
+
.then(function (body) {
|
|
1109
|
+
s.loading = false;
|
|
1110
|
+
s.result = body;
|
|
1111
|
+
s.lastLatency = Date.now() - start;
|
|
1112
|
+
renderBody(serverId);
|
|
1113
|
+
})
|
|
1114
|
+
.catch(function (err) {
|
|
1115
|
+
s.loading = false;
|
|
1116
|
+
s.error = err.message;
|
|
1117
|
+
renderBody(serverId);
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function resourceRead(serverId) {
|
|
1122
|
+
var s = getState(serverId);
|
|
1123
|
+
if (!s.selectedResource) return;
|
|
1124
|
+
doFetch(baseUrl(serverId, s.handle) + '/resource/read', {
|
|
1125
|
+
method: 'POST',
|
|
1126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1127
|
+
body: JSON.stringify({ uri: s.selectedResource }),
|
|
1128
|
+
})
|
|
1129
|
+
.then(function (body) {
|
|
1130
|
+
s.resourceContents = body.contents;
|
|
1131
|
+
renderBody(serverId);
|
|
1132
|
+
})
|
|
1133
|
+
.catch(function (err) {
|
|
1134
|
+
s.error = err.message;
|
|
1135
|
+
renderBody(serverId);
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function resourceSubscribe(serverId, op) {
|
|
1140
|
+
var s = getState(serverId);
|
|
1141
|
+
if (!s.selectedResource) return;
|
|
1142
|
+
doFetch(baseUrl(serverId, s.handle) + '/resource/' + op, {
|
|
1143
|
+
method: 'POST',
|
|
1144
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1145
|
+
body: JSON.stringify({ uri: s.selectedResource }),
|
|
1146
|
+
}).catch(function (err) {
|
|
1147
|
+
s.error = err.message;
|
|
1148
|
+
renderBody(serverId);
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function promptGet(serverId, btn) {
|
|
1153
|
+
var s = getState(serverId);
|
|
1154
|
+
if (!s.selectedPrompt) return;
|
|
1155
|
+
var scope = scopeFor(btn);
|
|
1156
|
+
var container = scope.querySelector('.tester-prompt-fields');
|
|
1157
|
+
var args = {};
|
|
1158
|
+
if (container) {
|
|
1159
|
+
try {
|
|
1160
|
+
var inputs = container.querySelectorAll('[data-path]');
|
|
1161
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
1162
|
+
var p = inputs[i].getAttribute('data-path');
|
|
1163
|
+
if (!p) continue;
|
|
1164
|
+
var v = inputs[i].value;
|
|
1165
|
+
if (v) args[p] = v;
|
|
1166
|
+
}
|
|
1167
|
+
} catch (err) {
|
|
1168
|
+
s.error = err.message;
|
|
1169
|
+
renderBody(serverId);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
doFetch(baseUrl(serverId, s.handle) + '/prompt/get', {
|
|
1174
|
+
method: 'POST',
|
|
1175
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1176
|
+
body: JSON.stringify({ name: s.selectedPrompt, arguments: args }),
|
|
1177
|
+
})
|
|
1178
|
+
.then(function (body) {
|
|
1179
|
+
s.promptMessages = body.messages || [];
|
|
1180
|
+
renderBody(serverId);
|
|
1181
|
+
})
|
|
1182
|
+
.catch(function (err) {
|
|
1183
|
+
s.error = err.message;
|
|
1184
|
+
renderBody(serverId);
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function ping(serverId) {
|
|
1189
|
+
var s = getState(serverId);
|
|
1190
|
+
doFetch(baseUrl(serverId, s.handle) + '/ping', {
|
|
1191
|
+
method: 'POST',
|
|
1192
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1193
|
+
body: '{}',
|
|
1194
|
+
})
|
|
1195
|
+
.then(function (body) {
|
|
1196
|
+
s.pingRtt = body.rtt_ms;
|
|
1197
|
+
renderBody(serverId);
|
|
1198
|
+
})
|
|
1199
|
+
.catch(function (err) {
|
|
1200
|
+
s.error = err.message;
|
|
1201
|
+
renderBody(serverId);
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function setLogging(serverId, btn) {
|
|
1206
|
+
var s = getState(serverId);
|
|
1207
|
+
var scope = scopeFor(btn);
|
|
1208
|
+
var sel = scope.querySelector('[data-tester-logging="' + serverId + '"]');
|
|
1209
|
+
if (!sel) return;
|
|
1210
|
+
var level = sel.value;
|
|
1211
|
+
doFetch(baseUrl(serverId, s.handle) + '/logging-level', {
|
|
1212
|
+
method: 'POST',
|
|
1213
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1214
|
+
body: JSON.stringify({ level: level }),
|
|
1215
|
+
})
|
|
1216
|
+
.then(function () {
|
|
1217
|
+
s.loggingLevel = level;
|
|
1218
|
+
renderBody(serverId);
|
|
1219
|
+
})
|
|
1220
|
+
.catch(function (err) {
|
|
1221
|
+
s.error = err.message;
|
|
1222
|
+
renderBody(serverId);
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function exportConfig(serverId, format) {
|
|
1227
|
+
var s = getState(serverId);
|
|
1228
|
+
s.exportFormat = format;
|
|
1229
|
+
// Mark loading so renderExportTab shows the hint, not stale JSON
|
|
1230
|
+
s.exportBody = null;
|
|
1231
|
+
doFetch(baseUrl(serverId, s.handle) + '/export?format=' + encodeURIComponent(format))
|
|
1232
|
+
.then(function (body) {
|
|
1233
|
+
s.exportBody = body;
|
|
1234
|
+
renderBody(serverId);
|
|
1235
|
+
})
|
|
1236
|
+
.catch(function (err) {
|
|
1237
|
+
s.error = err.message;
|
|
1238
|
+
renderBody(serverId);
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function copyExport(serverId) {
|
|
1243
|
+
var s = getState(serverId);
|
|
1244
|
+
if (!s.exportBody) return;
|
|
1245
|
+
var txt = JSON.stringify(s.exportBody.config, null, 2);
|
|
1246
|
+
if (navigator.clipboard) {
|
|
1247
|
+
navigator.clipboard.writeText(txt).then(function () {
|
|
1248
|
+
flashCopyButton(serverId);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function flashCopyButton(serverId) {
|
|
1254
|
+
var btn = document.querySelector(
|
|
1255
|
+
'.tester-copy-btn[data-action="tester-export-copy"][data-id="' + serverId + '"]',
|
|
1256
|
+
);
|
|
1257
|
+
if (!btn) return;
|
|
1258
|
+
btn.classList.add('copied');
|
|
1259
|
+
var orig = btn.innerHTML;
|
|
1260
|
+
btn.innerHTML =
|
|
1261
|
+
'<span class="material-symbols-outlined" style="font-size:14px">check</span> Copied';
|
|
1262
|
+
setTimeout(function () {
|
|
1263
|
+
btn.classList.remove('copied');
|
|
1264
|
+
btn.innerHTML = orig;
|
|
1265
|
+
}, 1200);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function presetSave(serverId) {
|
|
1269
|
+
var s = getState(serverId);
|
|
1270
|
+
if (!s.selectedTool) return;
|
|
1271
|
+
var name = prompt('Preset name:');
|
|
1272
|
+
if (!name) return;
|
|
1273
|
+
savePresetRemote(s.serverName, 'tool', s.selectedTool, name, s.lastArgs || {}).then(
|
|
1274
|
+
function () {
|
|
1275
|
+
renderBody(serverId);
|
|
1276
|
+
},
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function popOut(serverId) {
|
|
1281
|
+
var url = '/tester/' + encodeURIComponent(serverId);
|
|
1282
|
+
var win = window.open(
|
|
1283
|
+
url,
|
|
1284
|
+
'mcp-tester-' + serverId,
|
|
1285
|
+
'width=1000,height=720,resizable=yes,scrollbars=yes,menubar=no,toolbar=no,location=no',
|
|
1286
|
+
);
|
|
1287
|
+
if (!win) {
|
|
1288
|
+
alert(
|
|
1289
|
+
'Popup blocked. Allow popups for this site and try again (the tester opens in its own window).',
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// Exposed for tester-window.js bootstrap to associate a non-numeric render
|
|
1295
|
+
// key with a transient handle so baseUrl resolution still works.
|
|
1296
|
+
AD._setTesterHandle = function (serverId, handle) {
|
|
1297
|
+
var s = getState(serverId);
|
|
1298
|
+
s.handle = handle;
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
// ---------------------------------------------------------------------------
|
|
1302
|
+
// WS event ingestion
|
|
1303
|
+
// ---------------------------------------------------------------------------
|
|
1304
|
+
|
|
1305
|
+
AD.onTesterEvent = function (msg) {
|
|
1306
|
+
var name = msg.serverName || '';
|
|
1307
|
+
if (!eventBuffers[name]) eventBuffers[name] = [];
|
|
1308
|
+
eventBuffers[name].unshift(msg);
|
|
1309
|
+
if (eventBuffers[name].length > 500) eventBuffers[name].length = 500;
|
|
1310
|
+
// Refresh any tester whose events tab is open for this server
|
|
1311
|
+
Object.keys(state).forEach(function (serverId) {
|
|
1312
|
+
var s = state[serverId];
|
|
1313
|
+
if (s.serverName === name && s.subtab === 'events') renderBody(serverId);
|
|
1314
|
+
});
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
AD.onTesterLogEntry = function (entry) {
|
|
1318
|
+
if (!entry || entry.kind !== 'resource-read') return;
|
|
1319
|
+
/* placeholder hook for future per-resource refresh */
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
// ---------------------------------------------------------------------------
|
|
1323
|
+
// Auto-load on section expand
|
|
1324
|
+
// ---------------------------------------------------------------------------
|
|
1325
|
+
|
|
1326
|
+
document.addEventListener('click', function (e) {
|
|
1327
|
+
var btn = e.target.closest && e.target.closest('[data-action="toggle-section"]');
|
|
1328
|
+
if (!btn) return;
|
|
1329
|
+
if (btn.getAttribute('data-section') !== 'tester') return;
|
|
1330
|
+
var id = parseInt(btn.getAttribute('data-id'), 10);
|
|
1331
|
+
setTimeout(function () {
|
|
1332
|
+
AD.openTesterFor(id);
|
|
1333
|
+
}, 20);
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
// ---------------------------------------------------------------------------
|
|
1337
|
+
// Elicitation modal — server → human round-trip (transient connections only)
|
|
1338
|
+
// ---------------------------------------------------------------------------
|
|
1339
|
+
|
|
1340
|
+
AD.onElicitationRequest = function (msg) {
|
|
1341
|
+
showElicitationModal({
|
|
1342
|
+
id: msg.id,
|
|
1343
|
+
serverName: msg.serverName,
|
|
1344
|
+
message: msg.message,
|
|
1345
|
+
requestedSchema: msg.requestedSchema || { type: 'object', properties: {} },
|
|
1346
|
+
});
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
function showElicitationModal(req) {
|
|
1350
|
+
hideElicitationModal();
|
|
1351
|
+
var backdrop = document.createElement('div');
|
|
1352
|
+
backdrop.className = 'tester-elicit-backdrop';
|
|
1353
|
+
backdrop.innerHTML =
|
|
1354
|
+
'<div class="tester-elicit-modal">' +
|
|
1355
|
+
'<div class="tester-elicit-head">' +
|
|
1356
|
+
'<span class="material-symbols-outlined">chat</span>' +
|
|
1357
|
+
'<strong>Server request</strong>' +
|
|
1358
|
+
'<span class="tester-pill">' +
|
|
1359
|
+
esc(req.serverName) +
|
|
1360
|
+
'</span>' +
|
|
1361
|
+
'</div>' +
|
|
1362
|
+
'<div class="tester-elicit-message">' +
|
|
1363
|
+
(AD.renderMarkdown ? AD.renderMarkdown(req.message) : esc(req.message)) +
|
|
1364
|
+
'</div>' +
|
|
1365
|
+
'<div class="tester-elicit-form"></div>' +
|
|
1366
|
+
'<div class="tester-elicit-actions">' +
|
|
1367
|
+
'<button class="btn-activate" data-elicit="accept">Accept</button>' +
|
|
1368
|
+
'<button class="btn-health" data-elicit="decline">Decline</button>' +
|
|
1369
|
+
'<button class="btn-delete" data-elicit="cancel">Cancel</button>' +
|
|
1370
|
+
'</div></div>';
|
|
1371
|
+
document.body.appendChild(backdrop);
|
|
1372
|
+
var formEl = backdrop.querySelector('.tester-elicit-form');
|
|
1373
|
+
if (formEl && AD.renderSchemaForm) {
|
|
1374
|
+
AD.renderSchemaForm(req.requestedSchema, formEl, null);
|
|
1375
|
+
}
|
|
1376
|
+
var handleClick = function (e) {
|
|
1377
|
+
var btn = e.target.closest && e.target.closest('[data-elicit]');
|
|
1378
|
+
if (!btn) return;
|
|
1379
|
+
e.preventDefault();
|
|
1380
|
+
var action = btn.getAttribute('data-elicit');
|
|
1381
|
+
var payload = { action: action };
|
|
1382
|
+
if (action === 'accept' && formEl && AD.collectSchemaForm) {
|
|
1383
|
+
try {
|
|
1384
|
+
payload.content = AD.collectSchemaForm(formEl);
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
alert('Form invalid: ' + err.message);
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
AD._fetch('/api/elicitations/' + encodeURIComponent(req.id) + '/respond', {
|
|
1391
|
+
method: 'POST',
|
|
1392
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1393
|
+
body: JSON.stringify(payload),
|
|
1394
|
+
})
|
|
1395
|
+
.then(function () {
|
|
1396
|
+
hideElicitationModal();
|
|
1397
|
+
})
|
|
1398
|
+
.catch(function (err) {
|
|
1399
|
+
alert('Elicitation reply failed: ' + err.message);
|
|
1400
|
+
});
|
|
1401
|
+
};
|
|
1402
|
+
backdrop.addEventListener('click', handleClick);
|
|
1403
|
+
elicitationModalOpen = { backdrop: backdrop, handler: handleClick, req: req };
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function hideElicitationModal() {
|
|
1407
|
+
if (!elicitationModalOpen) return;
|
|
1408
|
+
elicitationModalOpen.backdrop.removeEventListener('click', elicitationModalOpen.handler);
|
|
1409
|
+
elicitationModalOpen.backdrop.remove();
|
|
1410
|
+
elicitationModalOpen = null;
|
|
1411
|
+
}
|
|
1412
|
+
})();
|