@kolbo/mcp 1.79.7 → 1.81.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/README.md +6 -2
- package/package.json +1 -1
- package/src/apps/bridge.js +2 -0
- package/src/apps/html.js +90 -1
- package/src/apps/index.js +153 -5
- package/src/apps/theme.js +42 -4
- package/src/apps/widgets/generation.js +198 -84
- package/src/apps/widgets/list.js +4 -1
- package/src/apps/widgets/mediaGrid.js +10 -3
- package/src/apps/widgets/transcript.js +4 -1
- package/src/client.js +14 -6
- package/src/toolAnnotations.js +5 -2
- package/src/tools/_shared.js +159 -24
- package/src/tools/agents.js +1 -1
- package/src/tools/docs.js +1 -1
- package/src/tools/generate.js +45 -21
- package/src/tools/moodboards.js +3 -2
- package/src/tools/presets.js +37 -36
- package/src/tools/projects.js +148 -5
- package/src/tools/visual_dna.js +126 -9
|
@@ -15,7 +15,9 @@ const { widgetPage } = require('../html');
|
|
|
15
15
|
|
|
16
16
|
* model, model_icon, prompt, count,
|
|
17
17
|
* settings: { duration, resolution, aspect_ratio, quality, audio, voice, mode,
|
|
18
|
-
* enhance_prompt, web_search, visual_dna, moodboard, preset,
|
|
18
|
+
* enhance_prompt, web_search, visual_dna, moodboard, preset,
|
|
19
|
+
* preset_id, preset_name, cinematic },
|
|
20
|
+
* visual_dnas: [{ id, name, thumbnail }],
|
|
19
21
|
* reference_images, // all reference thumbnail URLs (optional)
|
|
20
22
|
* reference_image, // legacy single-thumbnail fallback
|
|
21
23
|
* urls, thumbnail_url, title, duration, credits_used,
|
|
@@ -74,28 +76,67 @@ var TOOL_TITLES = {
|
|
|
74
76
|
get_generation_status: 'Generations'
|
|
75
77
|
};
|
|
76
78
|
|
|
77
|
-
// Long text is clamped by CSS (.k-prompt 2 lines / .k-caption 1 line).
|
|
78
|
-
//
|
|
79
|
-
function
|
|
79
|
+
// Long text is clamped by CSS (.k-prompt 2 lines / .k-caption 1 line). Expand
|
|
80
|
+
// lives on a separate button so the text itself stays selectable.
|
|
81
|
+
function stripTools(node) {
|
|
82
|
+
var prev = node && node.nextSibling;
|
|
83
|
+
if (prev && prev.classList && prev.classList.contains('k-text-tools') && prev.parentNode) {
|
|
84
|
+
prev.parentNode.removeChild(prev);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function setPrompt(html, raw) {
|
|
88
|
+
var node = el('prompt');
|
|
89
|
+
if (!html) {
|
|
90
|
+
node.innerHTML = '';
|
|
91
|
+
node.style.display = 'none';
|
|
92
|
+
stripTools(node);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
node.innerHTML = html;
|
|
96
|
+
node.style.display = '';
|
|
97
|
+
makeExpandable(node, raw);
|
|
98
|
+
}
|
|
99
|
+
function makeExpandable(node, raw) {
|
|
80
100
|
if (!node) return;
|
|
81
|
-
node.classList.remove('k-clamped');
|
|
101
|
+
node.classList.remove('k-clamped', 'expanded');
|
|
102
|
+
node.onclick = null;
|
|
103
|
+
node.removeAttribute('title');
|
|
104
|
+
stripTools(node);
|
|
105
|
+
var text = raw != null ? String(raw) : (node.innerText || node.textContent || '');
|
|
106
|
+
if (!text || !node.parentNode) return;
|
|
82
107
|
// Synchronous layout read — rAF would never fire in a hidden/backgrounded
|
|
83
108
|
// iframe, leaving long prompts stuck without the expand affordance.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
var overflow = node.scrollHeight > node.clientHeight + 2 || node.scrollWidth > node.clientWidth + 2;
|
|
110
|
+
if (overflow) node.classList.add('k-clamped');
|
|
111
|
+
var tools = document.createElement('div');
|
|
112
|
+
tools.className = 'k-text-tools';
|
|
113
|
+
tools.innerHTML =
|
|
114
|
+
'<button type="button" class="k-text-btn" data-act="copy">' + ICONS.copy + ' Copy</button>' +
|
|
115
|
+
(overflow ? '<button type="button" class="k-text-btn" data-act="expand">' + ICONS.chevronDown + ' Expand</button>' : '');
|
|
116
|
+
node.parentNode.insertBefore(tools, node.nextSibling);
|
|
117
|
+
var copyBtn = tools.querySelector('[data-act="copy"]');
|
|
118
|
+
if (copyBtn) copyBtn.onclick = function (e) {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
writeClipboard(text).then(function (ok) {
|
|
122
|
+
copyBtn.innerHTML = ok ? (ICONS.check + ' Copied') : 'Could not copy';
|
|
123
|
+
setTimeout(function () { copyBtn.innerHTML = ICONS.copy + ' Copy'; }, 1600);
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
var exp = tools.querySelector('[data-act="expand"]');
|
|
127
|
+
if (exp) exp.onclick = function (e) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
e.stopPropagation();
|
|
130
|
+
var on = node.classList.toggle('expanded');
|
|
131
|
+
exp.innerHTML = on ? (ICONS.chevronUp + ' Collapse') : (ICONS.chevronDown + ' Expand');
|
|
132
|
+
if (window.kolbo && window.kolbo.notifySize) window.kolbo.notifySize();
|
|
133
|
+
};
|
|
93
134
|
}
|
|
94
135
|
|
|
95
136
|
function renderList(sc) {
|
|
96
137
|
state = sc;
|
|
97
138
|
el('tool-title').textContent = sc.title || 'List';
|
|
98
|
-
|
|
139
|
+
setPrompt('');
|
|
99
140
|
el('chips').innerHTML = '';
|
|
100
141
|
el('credits').textContent = '';
|
|
101
142
|
var items = sc.items || [];
|
|
@@ -128,9 +169,7 @@ function boot(sc) {
|
|
|
128
169
|
if (isListPayload(sc, sc.tool)) return renderList(sc);
|
|
129
170
|
state = sc;
|
|
130
171
|
el('tool-title').textContent = TOOL_TITLES[sc.tool] || 'Generation';
|
|
131
|
-
|
|
132
|
-
el('prompt').style.display = sc.prompt ? '' : 'none';
|
|
133
|
-
makeExpandable(el('prompt'));
|
|
172
|
+
setPrompt(sc.prompt ? promptHTML(sc.prompt) : '', sc.prompt);
|
|
134
173
|
renderChips(sc);
|
|
135
174
|
el('credits').textContent = sc.credits_used != null ? fmtCredits(sc.credits_used) : '';
|
|
136
175
|
// Every legitimate completed payload sets phase:'completed' explicitly
|
|
@@ -182,10 +221,37 @@ function promptHTML(text) {
|
|
|
182
221
|
});
|
|
183
222
|
}
|
|
184
223
|
|
|
224
|
+
function ownedHost(url) {
|
|
225
|
+
try { return /(?:^|\\.)kolbo\\.ai$|digitaloceanspaces\\.com$/i.test(new URL(url).hostname); }
|
|
226
|
+
catch (e) { return false; }
|
|
227
|
+
}
|
|
228
|
+
function preferKolbo(urls) {
|
|
229
|
+
var list = (urls || []).filter(function (u) { return typeof u === 'string' && u; });
|
|
230
|
+
var ours = list.filter(ownedHost);
|
|
231
|
+
return ours.length ? ours : list;
|
|
232
|
+
}
|
|
233
|
+
function displayKind(sc) {
|
|
234
|
+
var urls = preferKolbo(sc.urls || []);
|
|
235
|
+
var first = (urls[0] || '').split('?')[0].toLowerCase();
|
|
236
|
+
if (/\\.(mp4|mov|webm|mkv)$/.test(first) || /video-elements-results|generated-videos/.test(first)) return 'video';
|
|
237
|
+
if (/\\.(mp3|wav|m4a|aac|ogg|flac)$/.test(first)) return 'audio';
|
|
238
|
+
var kind = sc.kind;
|
|
239
|
+
if (kind === 'video' || kind === 'scenes' || kind === 'audio' || kind === '3d' || kind === 'model3d') {
|
|
240
|
+
return kind === 'scenes' ? 'video' : kind;
|
|
241
|
+
}
|
|
242
|
+
var tool = sc.tool || '';
|
|
243
|
+
if (/video|elements|lipsync|first_last_frame/.test(tool)) return 'video';
|
|
244
|
+
if (/music|speech|sound/.test(tool)) return 'audio';
|
|
245
|
+
if (/3d/.test(tool)) return '3d';
|
|
246
|
+
if (kind === 'status') return '';
|
|
247
|
+
return kind || 'image';
|
|
248
|
+
}
|
|
249
|
+
|
|
185
250
|
function renderChips(sc) {
|
|
186
251
|
var h = modelChipHTML(modelLabel(sc), sc.model_icon);
|
|
187
252
|
var s = sc.settings || {};
|
|
188
|
-
|
|
253
|
+
var kind = displayKind(sc);
|
|
254
|
+
if (kind) h += chip(iconFor(kind) + ' ' + kind);
|
|
189
255
|
if (s.duration) h += chip(ICONS.clock + ' ' + fmtDur(s.duration) + (s.shots > 1 ? ' · ' + s.shots + ' shots' : ''));
|
|
190
256
|
else if (s.shots > 1) h += chip(s.shots + ' shots');
|
|
191
257
|
if (s.resolution) h += chip(esc(s.resolution));
|
|
@@ -197,11 +263,14 @@ function renderChips(sc) {
|
|
|
197
263
|
// falling back to the old count/boolean shape for payloads generated before
|
|
198
264
|
// the ids were carried.
|
|
199
265
|
h += dnaChipsHTML(sc, s);
|
|
200
|
-
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
266
|
+
h += moodboardChipsHTML(sc, s);
|
|
267
|
+
if (s.preset_id || s.preset_name || s.preset) {
|
|
268
|
+
var presetName = s.preset_name || (typeof s.preset === 'string' && s.preset !== 'true' ? s.preset : '');
|
|
269
|
+
var face = s.preset_thumbnail
|
|
270
|
+
? '<img src="' + esc(s.preset_thumbnail) + '" alt="" loading="lazy" onerror="this.style.display=\\'none\\'">'
|
|
271
|
+
: '';
|
|
272
|
+
h += chipT(face + esc(presetName || 'preset'), s.preset_id || presetName);
|
|
273
|
+
}
|
|
205
274
|
if (s.cinematic) h += chip('cinematic');
|
|
206
275
|
if (s.audio) h += chip(ICONS.sound + ' audio');
|
|
207
276
|
var voice = voiceLabel(sc);
|
|
@@ -215,6 +284,7 @@ function renderChips(sc) {
|
|
|
215
284
|
if (sc.count > 1) h += chip('×' + sc.count);
|
|
216
285
|
h += referenceHTML(sc);
|
|
217
286
|
el('chips').innerHTML = h;
|
|
287
|
+
bindPeekHits(el('chips'));
|
|
218
288
|
}
|
|
219
289
|
|
|
220
290
|
var REF_VIDEO_RE = /\\.(mp4|mov|webm|mkv|avi|m4v)(\\?|#|$)/i;
|
|
@@ -251,15 +321,16 @@ function referenceHTML(sc) {
|
|
|
251
321
|
for (var i = 0; i < refs.length; i++) {
|
|
252
322
|
var url = esc(refs[i].url);
|
|
253
323
|
var title = 'Reference ' + refs[i].kind + ' ' + (i + 1) + ' of ' + refs.length;
|
|
324
|
+
var peek = ' data-peek="' + url + '" data-peek-kind="' + refs[i].kind + '" data-peek-cap="' + title + '"';
|
|
254
325
|
if (refs[i].kind === 'video') {
|
|
255
326
|
// #t=0.1 so the poster is a real frame, not a black canvas.
|
|
256
|
-
h += '<video class="k-ref-thumb" src="' + url + '#t=0.1" muted playsinline preload="metadata" title="'
|
|
257
|
-
+ title + '" onerror="this.style.display=\\'none\\'"></video>';
|
|
327
|
+
h += '<video class="k-ref-thumb k-peek-hit" src="' + url + '#t=0.1" muted playsinline preload="metadata" title="'
|
|
328
|
+
+ title + '"' + peek + ' onerror="this.style.display=\\'none\\'"></video>';
|
|
258
329
|
} else if (refs[i].kind === 'audio') {
|
|
259
330
|
h += '<span class="k-chip" title="' + title + '">' + ICONS.sound + ' audio ref</span>';
|
|
260
331
|
} else {
|
|
261
|
-
h += '<img class="k-ref-thumb" src="' + url + '" alt="" loading="lazy" title="' + title
|
|
262
|
-
+ '
|
|
332
|
+
h += '<img class="k-ref-thumb k-peek-hit" src="' + url + '" alt="" loading="lazy" title="' + title + '"'
|
|
333
|
+
+ peek + ' onerror="this.style.display=\\'none\\'">';
|
|
263
334
|
}
|
|
264
335
|
}
|
|
265
336
|
return h;
|
|
@@ -288,7 +359,8 @@ function dnaChipsHTML(sc, s) {
|
|
|
288
359
|
var h = '';
|
|
289
360
|
if (dnas.length <= DNA_CHIP_MAX) {
|
|
290
361
|
for (var i = 0; i < dnas.length; i++) {
|
|
291
|
-
h += '<span class="k-chip" title="' + esc(dnas[i].id) + '"
|
|
362
|
+
h += '<span class="k-chip' + (dnas[i].thumbnail ? ' k-peek-hit' : '') + '" title="' + esc(dnas[i].id) + '"'
|
|
363
|
+
+ peekAttrs(dnas[i].thumbnail, 'image', dnas[i].name) + '>'
|
|
292
364
|
+ dnaFaceHTML(dnas[i], 'k-dna-face') + esc(dnas[i].name) + '</span>';
|
|
293
365
|
}
|
|
294
366
|
return h;
|
|
@@ -297,11 +369,31 @@ function dnaChipsHTML(sc, s) {
|
|
|
297
369
|
var stack = '';
|
|
298
370
|
for (var j = 0; j < dnas.length; j++) {
|
|
299
371
|
names.push(dnas[j].name);
|
|
300
|
-
if (j < 4)
|
|
372
|
+
if (j < 4) {
|
|
373
|
+
stack += '<span class="k-dna-stack-item' + (dnas[j].thumbnail ? ' k-peek-hit' : '') + '"'
|
|
374
|
+
+ peekAttrs(dnas[j].thumbnail, 'image', dnas[j].name) + '>'
|
|
375
|
+
+ dnaFaceHTML(dnas[j], 'k-dna-face') + '</span>';
|
|
376
|
+
}
|
|
301
377
|
}
|
|
302
378
|
return '<span class="k-chip k-dna-stack" title="' + esc(names.join('\\n')) + '">'
|
|
303
379
|
+ stack + dnas.length + ' Visual DNA</span>';
|
|
304
380
|
}
|
|
381
|
+
function moodboardChipsHTML(sc, s) {
|
|
382
|
+
var boards = Array.isArray(sc.moodboards) ? sc.moodboards : [];
|
|
383
|
+
if (!boards.length) {
|
|
384
|
+
var ids = s.moodboard_ids || (s.moodboard_id ? [s.moodboard_id] : []);
|
|
385
|
+
if (ids.length) return chipT(ids.length > 1 ? ids.length + ' moodboards' : 'moodboard', ids.join('\\n'));
|
|
386
|
+
if (s.moodboard) return chip('moodboard');
|
|
387
|
+
return '';
|
|
388
|
+
}
|
|
389
|
+
var h = '';
|
|
390
|
+
for (var i = 0; i < boards.length; i++) {
|
|
391
|
+
h += '<span class="k-chip' + (boards[i].thumbnail ? ' k-peek-hit' : '') + '" title="' + esc(boards[i].id) + '"'
|
|
392
|
+
+ peekAttrs(boards[i].thumbnail, 'image', boards[i].name) + '>'
|
|
393
|
+
+ dnaFaceHTML(boards[i], 'k-dna-face') + esc(boards[i].name) + '</span>';
|
|
394
|
+
}
|
|
395
|
+
return h;
|
|
396
|
+
}
|
|
305
397
|
function chip(inner) { return '<span class="k-chip">' + inner + '</span>'; }
|
|
306
398
|
// Same chip with a hover title — used to surface the asset id behind a
|
|
307
399
|
// "2 Visual DNA" / "preset" label without spending chip width on it.
|
|
@@ -324,13 +416,13 @@ function isBatch(sc) { return !!(sc && sc.generation_ids && sc.generation_ids.le
|
|
|
324
416
|
function renderGenerating(sc) {
|
|
325
417
|
setPhaseChip('Generating', true);
|
|
326
418
|
var n = Math.min(sc.count || 1, isBatch(sc) ? 8 : 4);
|
|
327
|
-
var
|
|
419
|
+
var kind = displayKind(sc);
|
|
420
|
+
var shape = kind === 'video' || kind === 'audio' ? 'video' : 'square';
|
|
328
421
|
var cells = '';
|
|
329
422
|
for (var i = 0; i < n; i++) {
|
|
330
423
|
var cap = (sc.prompts && sc.prompts[i])
|
|
331
424
|
? '<span class="k-skel-cap" title="' + esc(sc.prompts[i]) + '">' + esc(sc.prompts[i]) + '</span>' : '';
|
|
332
|
-
cells += '<div class="k-skel ' + shape + '" data-cell="' + i + '">' +
|
|
333
|
-
(i === 0 ? '<span class="k-gen-badge"><span class="k-spin"></span>Generating</span>' : '') + cap + '</div>';
|
|
425
|
+
cells += '<div class="k-skel ' + shape + '" data-cell="' + i + '">' + cap + '</div>';
|
|
334
426
|
}
|
|
335
427
|
// Grid class caps at n4 — the auto-fill rule handles any larger batch count.
|
|
336
428
|
el('stage').innerHTML = '<div class="k-gen-grid n' + Math.min(n, 4) + '">' + cells + '</div>';
|
|
@@ -355,48 +447,66 @@ function cancelSpec(sc) {
|
|
|
355
447
|
function renderStopButton(sc) {
|
|
356
448
|
var spec = cancelSpec(sc);
|
|
357
449
|
if (!spec) { el('actions').innerHTML = ''; return; }
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
450
|
+
var armTimer = null;
|
|
451
|
+
function idle() {
|
|
452
|
+
clearTimeout(armTimer);
|
|
453
|
+
el('actions').innerHTML = '<button class="k-btn ghost" id="stop-btn">' + ICONS.x + ' Stop</button>';
|
|
454
|
+
el('stop-btn').onclick = function () {
|
|
455
|
+
el('actions').innerHTML =
|
|
456
|
+
'<span class="k-stop-ask">Stop this generation?</span>' +
|
|
457
|
+
'<button class="k-btn ghost" id="stop-keep">Keep</button>' +
|
|
458
|
+
'<button class="k-btn danger" id="stop-btn">' + ICONS.x + ' Stop</button>';
|
|
459
|
+
el('stop-keep').onclick = idle;
|
|
460
|
+
el('stop-btn').onclick = function () { stopNow(sc, spec); };
|
|
461
|
+
if (window.kolbo && window.kolbo.notifySize) window.kolbo.notifySize();
|
|
462
|
+
armTimer = setTimeout(idle, 5000);
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
idle();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function stopNow(sc, spec) {
|
|
469
|
+
var btn = el('stop-btn');
|
|
470
|
+
if (btn) {
|
|
361
471
|
btn.disabled = true;
|
|
362
472
|
btn.innerHTML = '<span class="k-spin"></span> Stopping';
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return schedulePoll(sc);
|
|
391
|
-
}
|
|
392
|
-
renderCancelled(st.credits_refunded);
|
|
393
|
-
}).catch(function (e) {
|
|
473
|
+
}
|
|
474
|
+
var keep = el('stop-keep');
|
|
475
|
+
if (keep) keep.disabled = true;
|
|
476
|
+
// Stop polling immediately so a long-wait status call that lands mid-cancel
|
|
477
|
+
// cannot repaint the card back into "Generating".
|
|
478
|
+
cancelRequested = true;
|
|
479
|
+
clearTimeout(pollTimer);
|
|
480
|
+
// Batch: cancel every id; report combined refund. Entries that already
|
|
481
|
+
// finished return cancelled:false — only resume polling if ALL did.
|
|
482
|
+
var call = spec.batch
|
|
483
|
+
? Promise.all(spec.batch.map(function (id) {
|
|
484
|
+
return window.kolbo.callTool('cancel_generation', { generation_id: id })
|
|
485
|
+
.then(function (r) { return structured(r) || {}; })
|
|
486
|
+
.catch(function () { return {}; });
|
|
487
|
+
})).then(function (sts) {
|
|
488
|
+
var refund = 0;
|
|
489
|
+
sts.forEach(function (s) { if (s.credits_refunded) refund += s.credits_refunded; });
|
|
490
|
+
return {
|
|
491
|
+
cancelled: sts.some(function (s) { return s.cancelled !== false; }),
|
|
492
|
+
credits_refunded: refund || undefined
|
|
493
|
+
};
|
|
494
|
+
})
|
|
495
|
+
: window.kolbo.callTool(spec.tool, spec.args).then(function (r) { return structured(r) || {}; });
|
|
496
|
+
call.then(function (st) {
|
|
497
|
+
if (st.cancelled === false) {
|
|
498
|
+
// Already terminal — let the normal poll path report the real outcome
|
|
499
|
+
// instead of claiming a cancel that did not happen.
|
|
394
500
|
cancelRequested = false;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
501
|
+
renderStopButton(sc);
|
|
502
|
+
return schedulePoll(sc);
|
|
503
|
+
}
|
|
504
|
+
renderCancelled(st.credits_refunded);
|
|
505
|
+
}).catch(function () {
|
|
506
|
+
cancelRequested = false;
|
|
507
|
+
renderStopButton(sc);
|
|
508
|
+
schedulePoll(sc);
|
|
509
|
+
});
|
|
400
510
|
}
|
|
401
511
|
|
|
402
512
|
function renderCancelled(creditsRefunded) {
|
|
@@ -608,12 +718,13 @@ function renderResult(sc) {
|
|
|
608
718
|
if (sc.kind === 'status' && Array.isArray(sc.items)) return renderStatusGrid(sc);
|
|
609
719
|
if (sc.batch && sc.scenes && sc.scenes.length) return renderBatchGrid(sc);
|
|
610
720
|
if (sc.kind === 'scenes' && sc.scenes && sc.scenes.length) return renderScenes(sc);
|
|
611
|
-
var urls = sc.urls || [];
|
|
721
|
+
var urls = preferKolbo(sc.urls || []);
|
|
722
|
+
var kind = displayKind(Object.assign({}, sc, { urls: urls }));
|
|
612
723
|
if (!urls.length) return renderError('No output received');
|
|
613
|
-
if (
|
|
614
|
-
else if (
|
|
615
|
-
else if (
|
|
616
|
-
else if (
|
|
724
|
+
if (kind === 'image') renderImages(sc, urls);
|
|
725
|
+
else if (kind === 'video') renderVideo(sc, urls);
|
|
726
|
+
else if (kind === 'audio') renderAudio(sc, urls);
|
|
727
|
+
else if (kind === '3d') render3d(sc, urls);
|
|
617
728
|
else renderLinks(urls);
|
|
618
729
|
renderActions(sc);
|
|
619
730
|
window.kolbo.notifySize();
|
|
@@ -624,7 +735,12 @@ function renderImages(sc, urls) {
|
|
|
624
735
|
// If the host CSP still blocks the image, degrade to open-in-browser rows
|
|
625
736
|
// instead of a broken empty viewer.
|
|
626
737
|
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt="" onerror="window.__imgFail && window.__imgFail()">' + dlBtnHTML(urls[selected]) + '</div>';
|
|
627
|
-
window.__imgFail = function () {
|
|
738
|
+
window.__imgFail = function () {
|
|
739
|
+
var look = displayKind({ urls: urls, tool: state && state.tool, kind: 'image' });
|
|
740
|
+
if (look === 'video') renderVideo(state || { urls: urls }, urls);
|
|
741
|
+
else renderLinks(urls);
|
|
742
|
+
window.kolbo.notifySize();
|
|
743
|
+
};
|
|
628
744
|
// Click → expand into an in-Claude fullscreen viewer (all actions stay
|
|
629
745
|
// available); click again (or Exit) collapses back. Hosts that refuse
|
|
630
746
|
// fullscreen fall back to opening the original in a new tab.
|
|
@@ -857,7 +973,7 @@ function renderScenes(sc) {
|
|
|
857
973
|
'<div class="k-viewer">' + mediaHtml + dlBtnHTML(it.url) + '</div>' +
|
|
858
974
|
'<div class="k-caption" id="scene-cap">' + esc(it.label) + '</div>' +
|
|
859
975
|
thumbs;
|
|
860
|
-
makeExpandable(el('scene-cap'));
|
|
976
|
+
makeExpandable(el('scene-cap'), it.label);
|
|
861
977
|
wireDlButtons(el('stage'));
|
|
862
978
|
if (it.type === 'image') {
|
|
863
979
|
var main = el('scene-main');
|
|
@@ -1035,15 +1151,13 @@ function bootPre(toolName, args) {
|
|
|
1035
1151
|
el('tool-title').textContent = toolName === 'list_sessions' ? 'Sessions' : 'List';
|
|
1036
1152
|
setPhaseChip('Loading', true);
|
|
1037
1153
|
el('stage').innerHTML = '';
|
|
1038
|
-
|
|
1154
|
+
setPrompt('');
|
|
1039
1155
|
return;
|
|
1040
1156
|
}
|
|
1041
1157
|
el('tool-title').textContent = TOOL_TITLES[toolName] || 'Generation';
|
|
1042
1158
|
if (args && (args.prompt || args.text || (Array.isArray(args.prompts) && args.prompts.length))) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
el('prompt').style.display = '';
|
|
1046
|
-
makeExpandable(el('prompt'));
|
|
1159
|
+
var raw = args.prompt || args.text || (args.prompts.length + ' prompts — ' + args.prompts.join(' · '));
|
|
1160
|
+
setPrompt(promptHTML(raw), raw);
|
|
1047
1161
|
}
|
|
1048
1162
|
setPhaseChip('Preparing', true);
|
|
1049
1163
|
if (!el('stage').innerHTML) {
|
package/src/apps/widgets/list.js
CHANGED
|
@@ -74,7 +74,9 @@ function apply(result) {
|
|
|
74
74
|
function itemHTML(item, i) {
|
|
75
75
|
var clickable = !!item.use_hint;
|
|
76
76
|
var avatar = item.thumbnail
|
|
77
|
-
? '<img class="k-audio-art" src="' + esc(item.thumbnail) + '" alt="" loading="lazy"
|
|
77
|
+
? '<img class="k-audio-art k-peek-hit" src="' + esc(item.thumbnail) + '" alt="" loading="lazy"'
|
|
78
|
+
+ peekAttrs(item.thumbnail, 'image', item.title)
|
|
79
|
+
+ ' onerror="this.outerHTML=monogram(\\'' + esc(item.title || '?').replace(/'/g, '') + '\\')">'
|
|
78
80
|
: monogram(item.title || '?');
|
|
79
81
|
return '<div class="k-audio-row" data-i="' + i + '"' + (clickable ? ' style="cursor:pointer"' : '') + '>' +
|
|
80
82
|
avatar +
|
|
@@ -87,6 +89,7 @@ function itemHTML(item, i) {
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
function wire() {
|
|
92
|
+
bindPeekHits(el('stage'));
|
|
90
93
|
Array.prototype.forEach.call(document.querySelectorAll('[data-open]'), function (b) {
|
|
91
94
|
b.onclick = function (e) {
|
|
92
95
|
e.stopPropagation();
|
|
@@ -79,8 +79,11 @@ function cellHTML(item, i) {
|
|
|
79
79
|
// thumbnail-less item gets, so a dead URL degrades instead of looking broken.
|
|
80
80
|
var fallbackCell = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-faint);font-size:22px">' +
|
|
81
81
|
kindIcon(item.media_type) + '</div>';
|
|
82
|
+
var peekUrl = item.url || item.thumbnail;
|
|
82
83
|
var media = item.thumbnail
|
|
83
|
-
? '<img src="' + esc(item.thumbnail) + '" loading="lazy" alt=""
|
|
84
|
+
? '<img class="k-peek-hit" src="' + esc(item.thumbnail) + '" loading="lazy" alt=""'
|
|
85
|
+
+ peekAttrs(peekUrl, item.media_type === 'video' ? 'video' : 'image', item.title)
|
|
86
|
+
+ ' onerror="this.parentNode.innerHTML=this.getAttribute(\\'data-fb\\')" data-fb="' + esc(fallbackCell) + '">'
|
|
84
87
|
: fallbackCell;
|
|
85
88
|
return '<div class="k-cell" data-i="' + idx + '">' +
|
|
86
89
|
'<div class="k-cell-media">' + media +
|
|
@@ -100,7 +103,7 @@ function audioRowHTML(item) {
|
|
|
100
103
|
var idx = state.items.indexOf(item);
|
|
101
104
|
var src = item.preview_audio || item.url;
|
|
102
105
|
return '<div class="k-audio-row k-generated-audio" data-i="' + idx + '">' +
|
|
103
|
-
(item.thumbnail ? '<img class="k-audio-art" src="' + esc(item.thumbnail) + '">' : '<div class="k-audio-art"></div>') +
|
|
106
|
+
(item.thumbnail ? '<img class="k-audio-art k-peek-hit" src="' + esc(item.thumbnail) + '"' + peekAttrs(item.thumbnail, 'image', item.title) + '>' : '<div class="k-audio-art"></div>') +
|
|
104
107
|
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(item.title || '') + '</div>' +
|
|
105
108
|
'<div class="k-audio-sub">' + esc(item.subtitle || '') + '</div></div>' +
|
|
106
109
|
'<button class="k-btn" data-use="' + idx + '">Use</button>' +
|
|
@@ -109,8 +112,12 @@ function audioRowHTML(item) {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
function wire() {
|
|
115
|
+
bindPeekHits(el('stage'));
|
|
112
116
|
Array.prototype.forEach.call(document.querySelectorAll('.k-cell'), function (c) {
|
|
113
|
-
c.onclick = function () {
|
|
117
|
+
c.onclick = function (e) {
|
|
118
|
+
if (e.target && e.target.closest && e.target.closest('[data-peek]')) return;
|
|
119
|
+
useItem(+c.getAttribute('data-i'));
|
|
120
|
+
};
|
|
114
121
|
});
|
|
115
122
|
Array.prototype.forEach.call(document.querySelectorAll('[data-use]'), function (b) {
|
|
116
123
|
b.onclick = function (e) { e.stopPropagation(); useItem(+b.getAttribute('data-use')); };
|
|
@@ -77,7 +77,10 @@ function boot(sc) {
|
|
|
77
77
|
});
|
|
78
78
|
var copyBtn = el('btn-copy');
|
|
79
79
|
if (copyBtn) copyBtn.onclick = function () {
|
|
80
|
-
|
|
80
|
+
writeClipboard(state.text || '').then(function (ok) {
|
|
81
|
+
copyBtn.innerHTML = ok ? ('Copied ' + ICONS.check) : 'Could not copy';
|
|
82
|
+
setTimeout(function () { copyBtn.innerHTML = 'Copy text'; }, 1600);
|
|
83
|
+
});
|
|
81
84
|
};
|
|
82
85
|
window.kolbo.notifySize();
|
|
83
86
|
}
|
package/src/client.js
CHANGED
|
@@ -451,17 +451,25 @@ class KolboClient {
|
|
|
451
451
|
// poll loop, which does its own capped backoff in polling.js) still surfaces
|
|
452
452
|
// the 429 straight to the caller, now with the wait spelled out in the message.
|
|
453
453
|
async postMultipart(reqPath, formData) {
|
|
454
|
+
return this._multipart('POST', reqPath, formData);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async putMultipart(reqPath, formData) {
|
|
458
|
+
return this._multipart('PUT', reqPath, formData);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async _multipart(method, reqPath, formData) {
|
|
454
462
|
if (!this.apiKey) await this._ensureLogin();
|
|
455
463
|
return retryOnce429(async () => {
|
|
456
|
-
const result = await this._doMultipart(reqPath, formData);
|
|
464
|
+
const result = await this._doMultipart(method, reqPath, formData);
|
|
457
465
|
if (result._status === 401 && this._tryRefreshKey()) {
|
|
458
|
-
return this._doMultipart(reqPath, formData);
|
|
466
|
+
return this._doMultipart(method, reqPath, formData);
|
|
459
467
|
}
|
|
460
468
|
return result;
|
|
461
469
|
});
|
|
462
470
|
}
|
|
463
471
|
|
|
464
|
-
async _doMultipart(reqPath, formData) {
|
|
472
|
+
async _doMultipart(method, reqPath, formData) {
|
|
465
473
|
const url = `${this.baseUrl}${reqPath}`;
|
|
466
474
|
const headers = {
|
|
467
475
|
'X-API-Key': this.apiKey,
|
|
@@ -484,7 +492,7 @@ class KolboClient {
|
|
|
484
492
|
let response;
|
|
485
493
|
try {
|
|
486
494
|
response = await fetch(url, {
|
|
487
|
-
method
|
|
495
|
+
method,
|
|
488
496
|
headers,
|
|
489
497
|
body,
|
|
490
498
|
signal: composed.signal
|
|
@@ -493,12 +501,12 @@ class KolboClient {
|
|
|
493
501
|
if (isAbortError(err)) {
|
|
494
502
|
if (callerSignal?.aborted) {
|
|
495
503
|
throw new KolboApiError(
|
|
496
|
-
`Upload cancelled by caller:
|
|
504
|
+
`Upload cancelled by caller: ${method} ${reqPath}`,
|
|
497
505
|
{ code: 'REQUEST_CANCELLED', status: 499 }
|
|
498
506
|
);
|
|
499
507
|
}
|
|
500
508
|
throw new KolboApiError(
|
|
501
|
-
`Upload timed out after ${UPLOAD_TIMEOUT_MS / 1000}s:
|
|
509
|
+
`Upload timed out after ${UPLOAD_TIMEOUT_MS / 1000}s: ${method} ${reqPath} ` +
|
|
502
510
|
`(${Math.round(body.length / 1024)}KB). Raise KOLBO_UPLOAD_TIMEOUT_MS for slow links.`,
|
|
503
511
|
{ code: 'UPLOAD_TIMEOUT', status: 504 }
|
|
504
512
|
);
|
package/src/toolAnnotations.js
CHANGED
|
@@ -17,7 +17,8 @@ const READ_ONLY = [
|
|
|
17
17
|
'list_color_palettes', 'analyze_color_palette',
|
|
18
18
|
'list_media', 'list_media_folders', 'get_media', 'get_media_stats',
|
|
19
19
|
'list_presets', 'list_cinematic_presets',
|
|
20
|
-
'list_projects', 'list_sessions', 'list_project_context', 'get_project_profile',
|
|
20
|
+
'list_projects', 'get_project', 'list_sessions', 'list_project_context', 'get_project_profile',
|
|
21
|
+
'list_project_assets',
|
|
21
22
|
'list_session_generations',
|
|
22
23
|
'list_agents', 'list_docs', 'get_doc',
|
|
23
24
|
'get_review_storage_usage', 'list_review_assets', 'get_review_asset',
|
|
@@ -47,6 +48,7 @@ const PRIVATE_WRITE = [
|
|
|
47
48
|
'rename_session', 'restore_session',
|
|
48
49
|
'create_project', 'update_project',
|
|
49
50
|
'archive_project', 'unarchive_project', 'add_project_context',
|
|
51
|
+
'link_project_asset', 'unlink_project_asset',
|
|
50
52
|
'create_agent',
|
|
51
53
|
'create_doc',
|
|
52
54
|
'create_review_asset', 'update_review_asset', 'add_review_version',
|
|
@@ -69,7 +71,7 @@ const DESTRUCTIVE_WRITE = [
|
|
|
69
71
|
'separate_audio_stems', 'clean_dialogue_leftovers', 'separate_ambience',
|
|
70
72
|
|
|
71
73
|
// Deletes and whole-value replacement updates are conservatively destructive.
|
|
72
|
-
'delete_voice', 'delete_visual_dna', 'delete_visual_dna_folder',
|
|
74
|
+
'delete_voice', 'update_visual_dna', 'delete_visual_dna', 'delete_visual_dna_folder',
|
|
73
75
|
'update_moodboard', 'delete_moodboard',
|
|
74
76
|
'update_color_palette', 'delete_color_palette',
|
|
75
77
|
'delete_media_folder', 'delete_media', 'permanently_delete_media',
|
|
@@ -77,6 +79,7 @@ const DESTRUCTIVE_WRITE = [
|
|
|
77
79
|
'unshare_media_folder',
|
|
78
80
|
'delete_session',
|
|
79
81
|
'delete_project_context', 'regenerate_project_profile',
|
|
82
|
+
'update_project_asset',
|
|
80
83
|
'update_agent', 'delete_agent', 'update_doc', 'delete_doc',
|
|
81
84
|
'delete_review_asset', 'delete_review_collection',
|
|
82
85
|
'edit_review_comment', 'delete_review_comment',
|