@kolbo/mcp 1.31.2 → 1.31.3
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/package.json
CHANGED
package/src/apps/theme.js
CHANGED
|
@@ -159,6 +159,18 @@ body {
|
|
|
159
159
|
.k-media img, .k-media video { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
160
160
|
.k-media.selected { outline: 2px solid var(--brand); outline-offset: 1px; }
|
|
161
161
|
|
|
162
|
+
/* ---- Per-item hover download button (multi-image grids, CD scenes, viewer) ---- */
|
|
163
|
+
.k-dl {
|
|
164
|
+
position: absolute; top: 8px; right: 8px; z-index: 3;
|
|
165
|
+
width: 30px; height: 30px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.18);
|
|
166
|
+
background: rgba(0, 0, 0, 0.62); color: #fff; font-size: 14px; line-height: 1;
|
|
167
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
168
|
+
cursor: pointer; opacity: 0; transition: opacity 150ms var(--smooth), background 150ms var(--smooth);
|
|
169
|
+
}
|
|
170
|
+
.k-media:hover .k-dl, .k-viewer:hover .k-dl { opacity: 1; }
|
|
171
|
+
.k-dl:hover { background: var(--brand); border-color: var(--brand); }
|
|
172
|
+
.k-viewer { position: relative; }
|
|
173
|
+
|
|
162
174
|
/* Keep the whole completed card under the host's iframe height cap (~800px):
|
|
163
175
|
header + prompt + chips + viewer + thumbs + actions + footer must all fit,
|
|
164
176
|
or claude.ai adds an inner scrollbar. Click the image to expand in-Claude. */
|
|
@@ -171,7 +171,7 @@ function renderImages(sc, urls) {
|
|
|
171
171
|
selected = Math.min(selected, urls.length - 1);
|
|
172
172
|
// If the host CSP still blocks the image, degrade to open-in-browser rows
|
|
173
173
|
// instead of a broken empty viewer.
|
|
174
|
-
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt="" onerror="window.__imgFail && window.__imgFail()"
|
|
174
|
+
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt="" onerror="window.__imgFail && window.__imgFail()">' + dlBtnHTML(urls[selected]) + '</div>';
|
|
175
175
|
window.__imgFail = function () { renderLinks(urls); window.kolbo.notifySize(); };
|
|
176
176
|
// Click → expand into an in-Claude fullscreen viewer (all actions stay
|
|
177
177
|
// available); click again (or Exit) collapses back. Hosts that refuse
|
|
@@ -187,10 +187,14 @@ function renderImages(sc, urls) {
|
|
|
187
187
|
}).join('') + '</div>';
|
|
188
188
|
}
|
|
189
189
|
el('stage').innerHTML = viewer + thumbs;
|
|
190
|
+
wireDlButtons(el('stage'));
|
|
190
191
|
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-thumb'), function (t) {
|
|
191
192
|
t.onclick = function () {
|
|
192
193
|
selected = +t.getAttribute('data-i');
|
|
193
194
|
el('main-img').src = state.urls[selected];
|
|
195
|
+
// Keep the viewer's hover download pointing at the newly selected image.
|
|
196
|
+
var dl = el('stage').querySelector('.k-viewer .k-dl');
|
|
197
|
+
if (dl) dl.setAttribute('data-dl', state.urls[selected]);
|
|
194
198
|
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-thumb'), function (x) { x.classList.remove('active'); });
|
|
195
199
|
t.classList.add('active');
|
|
196
200
|
};
|
|
@@ -199,7 +203,9 @@ function renderImages(sc, urls) {
|
|
|
199
203
|
|
|
200
204
|
function renderVideo(sc, urls) {
|
|
201
205
|
el('stage').innerHTML = '<div class="k-viewer"><video id="main-video" src="' + esc(urls[0]) + '"' +
|
|
202
|
-
(sc.thumbnail_url ? ' poster="' + esc(sc.thumbnail_url) + '"' : '') + ' controls playsinline></video
|
|
206
|
+
(sc.thumbnail_url ? ' poster="' + esc(sc.thumbnail_url) + '"' : '') + ' controls playsinline></video>' +
|
|
207
|
+
dlBtnHTML(urls[0]) + '</div>';
|
|
208
|
+
wireDlButtons(el('stage'));
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
function renderAudio(sc, urls) {
|
|
@@ -238,17 +244,32 @@ function renderLinks(urls) {
|
|
|
238
244
|
});
|
|
239
245
|
}
|
|
240
246
|
|
|
247
|
+
// Small hover download button attached to a media cell (per-item downloads —
|
|
248
|
+
// batch grids and CD scenes have no single "current" url for the action row).
|
|
249
|
+
function dlBtnHTML(u) {
|
|
250
|
+
return '<button class="k-dl" data-dl="' + esc(u) + '" title="Download" aria-label="Download">⬇</button>';
|
|
251
|
+
}
|
|
252
|
+
function wireDlButtons(root) {
|
|
253
|
+
Array.prototype.forEach.call((root || document).querySelectorAll('.k-dl[data-dl]'), function (b) {
|
|
254
|
+
b.onclick = function (e) {
|
|
255
|
+
e.stopPropagation();
|
|
256
|
+
window.kolbo.openLink(downloadUrl(b.getAttribute('data-dl')));
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
241
261
|
function renderScenes(sc) {
|
|
242
262
|
el('stage').innerHTML = sc.scenes.map(function (scene) {
|
|
243
263
|
var media = (scene.video_urls || []).map(function (u) {
|
|
244
|
-
return '<div class="k-media"><video src="' + esc(u) + '" controls playsinline></video
|
|
264
|
+
return '<div class="k-media"><video src="' + esc(u) + '" controls playsinline></video>' + dlBtnHTML(u) + '</div>';
|
|
245
265
|
}).join('') + (scene.image_urls || []).map(function (u) {
|
|
246
|
-
return '<div class="k-media"><img src="' + esc(u) + '" alt=""
|
|
266
|
+
return '<div class="k-media"><img src="' + esc(u) + '" alt="">' + dlBtnHTML(u) + '</div>';
|
|
247
267
|
}).join('');
|
|
248
268
|
return '<div style="margin-bottom:14px"><div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:8px">Scene ' +
|
|
249
269
|
esc(scene.scene_number) + (scene.title ? ' — ' + esc(scene.title) : '') + '</div>' +
|
|
250
270
|
'<div class="k-gen-grid n2">' + media + '</div></div>';
|
|
251
271
|
}).join('');
|
|
272
|
+
wireDlButtons(el('stage'));
|
|
252
273
|
renderActions(sc);
|
|
253
274
|
}
|
|
254
275
|
|
|
@@ -311,6 +332,7 @@ function currentUrl() {
|
|
|
311
332
|
|
|
312
333
|
function renderActions(sc) {
|
|
313
334
|
var a = [];
|
|
335
|
+
var hasSingleUrl = !!(sc.urls && sc.urls.length);
|
|
314
336
|
if (sc.kind === 'image') {
|
|
315
337
|
a.push('<button class="k-btn primary" id="btn-animate">🎬 Animate</button>');
|
|
316
338
|
a.push('<button class="k-btn" id="btn-edit">✏️ Edit</button>');
|
|
@@ -318,7 +340,9 @@ function renderActions(sc) {
|
|
|
318
340
|
if (sc.kind === 'video') {
|
|
319
341
|
a.push('<button class="k-btn primary" id="btn-download">⬇ Download</button>');
|
|
320
342
|
a.push('<button class="k-btn" id="btn-analyze">📊 Analyze</button>');
|
|
321
|
-
} else {
|
|
343
|
+
} else if (hasSingleUrl) {
|
|
344
|
+
// Scenes (Creative Director) have no single "current" url — per-item hover
|
|
345
|
+
// download buttons cover them instead.
|
|
322
346
|
a.push('<button class="k-btn" id="btn-download">⬇ Download</button>');
|
|
323
347
|
}
|
|
324
348
|
a.push('<button class="k-btn" id="btn-recreate">↻ Recreate</button>');
|
|
@@ -118,7 +118,12 @@ function useItem(i) {
|
|
|
118
118
|
|
|
119
119
|
window.kolbo.onToolResult(function (result) {
|
|
120
120
|
var sc = result.structuredContent || structured(result);
|
|
121
|
-
if (sc) boot(sc);
|
|
121
|
+
if (sc && sc.items) return boot(sc);
|
|
122
|
+
// No grid data (empty result set, error, or timeout path returned plain
|
|
123
|
+
// text) — collapse instead of showing a dead "Loading…" card forever.
|
|
124
|
+
var card = document.querySelector('.k-card');
|
|
125
|
+
if (card) card.style.display = 'none';
|
|
126
|
+
window.kolbo.notifySize();
|
|
122
127
|
});
|
|
123
128
|
`;
|
|
124
129
|
|
|
@@ -97,7 +97,23 @@ function poll() {
|
|
|
97
97
|
|
|
98
98
|
window.kolbo.onToolResult(function (result) {
|
|
99
99
|
var sc = result.structuredContent || structured(result);
|
|
100
|
-
|
|
100
|
+
// Only boot on a real widget contract (phase present). A text-JSON
|
|
101
|
+
// fallback like { status: 'submitted' } has no phase and would render a
|
|
102
|
+
// bogus "(empty transcript)" completed view.
|
|
103
|
+
if (sc && (sc.phase || sc.widget === 'transcript')) return boot(sc);
|
|
104
|
+
var txt = '';
|
|
105
|
+
try { txt = (result.content || []).filter(function (c) { return c.type === 'text'; }).map(function (c) { return c.text; }).join(' '); } catch (e) {}
|
|
106
|
+
if (result.isError || /error|failed/i.test(txt)) {
|
|
107
|
+
el('phase-chip').style.display = 'none';
|
|
108
|
+
el('stage').classList.remove('k-empty');
|
|
109
|
+
el('stage').innerHTML = '<div class="k-error">⚠ ' + esc((txt || 'Transcription failed').slice(0, 300)) + '</div>';
|
|
110
|
+
window.kolbo.notifySize();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// No usable data — collapse instead of a dead "Loading…" card.
|
|
114
|
+
var card = document.querySelector('.k-card');
|
|
115
|
+
if (card) card.style.display = 'none';
|
|
116
|
+
window.kolbo.notifySize();
|
|
101
117
|
});
|
|
102
118
|
`;
|
|
103
119
|
|
package/src/tools/_shared.js
CHANGED
|
@@ -184,9 +184,23 @@ async function resolveToBuffer(source, kind, opts = {}) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
if (!path.isAbsolute(source)) {
|
|
187
|
-
throw new Error(
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Local file paths must be absolute: ${source}. ` +
|
|
189
|
+
`If you are using Kolbo over a remote connector (e.g. claude.ai), local files are not reachable — ` +
|
|
190
|
+
`pass a public https:// URL instead (upload the file somewhere first, or use a URL from list_media).`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
let stat;
|
|
194
|
+
try {
|
|
195
|
+
stat = fs.statSync(source);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Local file not found or unreadable: ${source}. ` +
|
|
199
|
+
`If you are using Kolbo over a remote connector (e.g. claude.ai), local file paths are not reachable — ` +
|
|
200
|
+
`pass a public https:// URL instead (upload the file somewhere first, or use a URL from list_media).` +
|
|
201
|
+
(err && err.code ? ` [${err.code}]` : '')
|
|
202
|
+
);
|
|
188
203
|
}
|
|
189
|
-
const stat = fs.statSync(source);
|
|
190
204
|
if (stat.size > maxBytes) {
|
|
191
205
|
throw new Error(`File ${source} (${stat.size} bytes) exceeds ${maxBytes}-byte limit`);
|
|
192
206
|
}
|