@kolbo/mcp 1.30.2 → 1.30.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 +1 -1
- package/src/apps/index.js +28 -16
- package/src/apps/widgets/generation.js +6 -32
- package/src/tools/_shared.js +1 -1
- package/src/tools/generate.js +12 -14
- package/src/tools/shorts_creator.js +0 -1
package/package.json
CHANGED
package/src/apps/index.js
CHANGED
|
@@ -123,38 +123,49 @@ function uiResult(uri, text, structured) {
|
|
|
123
123
|
/* ------------------------------------------------------------------ */
|
|
124
124
|
|
|
125
125
|
const ICON_TTL_MS = 10 * 60 * 1000;
|
|
126
|
-
const
|
|
126
|
+
const infoCache = new Map(); // apiBase → { at, byKey: Map<lowername, {icon, eta}> }
|
|
127
127
|
|
|
128
|
-
async function
|
|
128
|
+
async function modelInfoMap(client) {
|
|
129
129
|
const cacheKey = client.apiBase || 'default';
|
|
130
|
-
const hit =
|
|
130
|
+
const hit = infoCache.get(cacheKey);
|
|
131
131
|
if (hit && Date.now() - hit.at < ICON_TTL_MS) return hit.byKey;
|
|
132
132
|
const byKey = new Map();
|
|
133
133
|
try {
|
|
134
134
|
const res = await client.request('GET', '/v1/models');
|
|
135
135
|
const models = res?.models || res?.data?.models || [];
|
|
136
136
|
for (const m of models) {
|
|
137
|
-
if (!m
|
|
137
|
+
if (!m) continue;
|
|
138
138
|
// The API usually resolves avatars to absolute URLs; bare filenames (older
|
|
139
139
|
// deployments / internal calls) resolve against the app's public icon dir.
|
|
140
|
-
const
|
|
141
|
-
? m.avatar
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
const icon = m.avatar
|
|
141
|
+
? (/^https?:\/\//i.test(m.avatar)
|
|
142
|
+
? m.avatar
|
|
143
|
+
: `https://app.kolbo.ai/models_icons/${encodeURIComponent(m.avatar)}`)
|
|
144
|
+
: null;
|
|
145
|
+
// Real p75 wall-clock estimate mined from production creditUsages —
|
|
146
|
+
// the same source the in-app countdowns use. No estimate → no ETA shown.
|
|
147
|
+
const eta = Number(m.estimatedDurationSeconds || m.estimated_duration_seconds) || null;
|
|
148
|
+
const info = { icon, eta };
|
|
149
|
+
if (m.name) byKey.set(String(m.name).toLowerCase(), info);
|
|
150
|
+
if (m.identifier) byKey.set(String(m.identifier).toLowerCase(), info);
|
|
145
151
|
}
|
|
146
152
|
} catch (_) {
|
|
147
|
-
/* fail open — widgets fall back to monogram chips */
|
|
153
|
+
/* fail open — widgets fall back to monogram chips, no ETA */
|
|
148
154
|
}
|
|
149
|
-
|
|
155
|
+
infoCache.set(cacheKey, { at: Date.now(), byKey });
|
|
150
156
|
return byKey;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
/** Resolve one model's icon
|
|
159
|
+
/** Resolve one model's { icon, eta }; missing → { icon: null, eta: null }. */
|
|
160
|
+
async function modelInfo(client, modelName) {
|
|
161
|
+
if (!modelName) return { icon: null, eta: null };
|
|
162
|
+
const map = await modelInfoMap(client);
|
|
163
|
+
return map.get(String(modelName).toLowerCase()) || { icon: null, eta: null };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Back-compat shim (used by uiCompleted and older call sites). */
|
|
154
167
|
async function modelIcon(client, modelName) {
|
|
155
|
-
|
|
156
|
-
const map = await modelIconMap(client);
|
|
157
|
-
return map.get(String(modelName).toLowerCase()) || null;
|
|
168
|
+
return (await modelInfo(client, modelName)).icon;
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
/* ------------------------------------------------------------------ */
|
|
@@ -219,6 +230,7 @@ module.exports = {
|
|
|
219
230
|
uiResult,
|
|
220
231
|
appsEnabled,
|
|
221
232
|
modelIcon,
|
|
222
|
-
|
|
233
|
+
modelInfo,
|
|
234
|
+
modelInfoMap,
|
|
223
235
|
widgetHtml, // exported for smoke tests
|
|
224
236
|
};
|
|
@@ -12,7 +12,7 @@ const { widgetPage } = require('../html');
|
|
|
12
12
|
* tool: 'generate_image', // originating MCP tool name
|
|
13
13
|
* generation_id, poll_tool, // when phase === 'generating'
|
|
14
14
|
* status_args, // extra args for the poll tool (optional)
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
* model, model_icon, prompt, count,
|
|
17
17
|
* settings: { duration, resolution, aspect_ratio, audio, voice, mode },
|
|
18
18
|
* reference_image, // thumbnail URL (optional)
|
|
@@ -35,10 +35,6 @@ const BODY = `
|
|
|
35
35
|
<div class="k-prompt" id="prompt"></div>
|
|
36
36
|
<div class="k-chips" id="chips"></div>
|
|
37
37
|
<div id="stage"></div>
|
|
38
|
-
<div class="k-progress" id="progress" style="display:none"><i id="progress-fill"></i></div>
|
|
39
|
-
<div class="k-status-line" id="status-line" style="display:none">
|
|
40
|
-
<span id="status-text">Generating…</span><span id="eta"></span>
|
|
41
|
-
</div>
|
|
42
38
|
<div class="k-prompt-row" id="prompt-row">
|
|
43
39
|
<input class="k-input" id="action-input" placeholder="">
|
|
44
40
|
<button class="k-btn primary" id="action-send">Send</button>
|
|
@@ -57,8 +53,6 @@ const SCRIPT = `
|
|
|
57
53
|
var state = null; // current structuredContent
|
|
58
54
|
var selected = 0; // selected result index
|
|
59
55
|
var pollTimer = null;
|
|
60
|
-
var progressTimer = null;
|
|
61
|
-
var startedAt = Date.now();
|
|
62
56
|
|
|
63
57
|
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
64
58
|
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
@@ -117,24 +111,9 @@ function renderGenerating(sc) {
|
|
|
117
111
|
(i === 0 ? '<span class="k-gen-badge"><span class="k-spin"></span>Generating</span>' : '') + '</div>';
|
|
118
112
|
}
|
|
119
113
|
el('stage').innerHTML = '<div class="k-gen-grid n' + n + '">' + cells + '</div>';
|
|
120
|
-
el('progress').style.display = '';
|
|
121
|
-
el('status-line').style.display = '';
|
|
122
114
|
el('actions').innerHTML = '';
|
|
123
|
-
startProgress(sc.estimated_seconds || defaultEta(sc.kind));
|
|
124
115
|
schedulePoll(sc);
|
|
125
116
|
}
|
|
126
|
-
function defaultEta(kind) { return { image: 25, video: 120, audio: 45, '3d': 300, scenes: 240 }[kind] || 60; }
|
|
127
|
-
|
|
128
|
-
function startProgress(etaSec) {
|
|
129
|
-
clearInterval(progressTimer);
|
|
130
|
-
progressTimer = setInterval(function () {
|
|
131
|
-
var t = (Date.now() - startedAt) / 1000;
|
|
132
|
-
var pct = Math.min(92, 100 * (1 - Math.exp(-t / (etaSec * 0.55))));
|
|
133
|
-
el('progress-fill').style.width = pct.toFixed(1) + '%';
|
|
134
|
-
var remain = Math.max(0, etaSec - t);
|
|
135
|
-
el('eta').textContent = remain > 1 ? '~' + fmtDur(remain) + ' left' : 'finishing…';
|
|
136
|
-
}, 500);
|
|
137
|
-
}
|
|
138
117
|
|
|
139
118
|
function schedulePoll(sc) {
|
|
140
119
|
clearTimeout(pollTimer);
|
|
@@ -147,7 +126,7 @@ function poll(sc) {
|
|
|
147
126
|
var stateName = st.state || st.phase || st.status;
|
|
148
127
|
if (stateName === 'completed') {
|
|
149
128
|
var r = st.result || st;
|
|
150
|
-
|
|
129
|
+
|
|
151
130
|
var done = Object.assign({}, sc, r, {
|
|
152
131
|
phase: 'completed',
|
|
153
132
|
urls: r.urls || st.urls || [],
|
|
@@ -170,16 +149,11 @@ function poll(sc) {
|
|
|
170
149
|
}
|
|
171
150
|
}).catch(function () { schedulePoll(sc); });
|
|
172
151
|
}
|
|
173
|
-
function finishProgress() {
|
|
174
|
-
clearInterval(progressTimer);
|
|
175
|
-
el('progress-fill').style.width = '100%';
|
|
176
|
-
setTimeout(function () { el('progress').style.display = 'none'; el('status-line').style.display = 'none'; }, 450);
|
|
177
|
-
}
|
|
178
152
|
|
|
179
153
|
/* ---------- results ---------- */
|
|
180
154
|
function renderResult(sc) {
|
|
181
|
-
clearTimeout(pollTimer);
|
|
182
|
-
|
|
155
|
+
clearTimeout(pollTimer);
|
|
156
|
+
|
|
183
157
|
setPhaseChip('', false);
|
|
184
158
|
if (sc.kind === 'scenes' && sc.scenes && sc.scenes.length) return renderScenes(sc);
|
|
185
159
|
var urls = sc.urls || [];
|
|
@@ -272,8 +246,8 @@ function renderScenes(sc) {
|
|
|
272
246
|
}
|
|
273
247
|
|
|
274
248
|
function renderError(msg) {
|
|
275
|
-
clearTimeout(pollTimer);
|
|
276
|
-
|
|
249
|
+
clearTimeout(pollTimer);
|
|
250
|
+
|
|
277
251
|
setPhaseChip('Failed', false);
|
|
278
252
|
el('stage').innerHTML = '<div class="k-error">⚠ ' + esc(msg) + '</div>';
|
|
279
253
|
el('actions').innerHTML = '<button class="k-btn" id="retry-btn">↻ Try Again</button>';
|
package/src/tools/_shared.js
CHANGED
|
@@ -311,6 +311,7 @@ const { UI, uiResult, appsEnabled, modelIcon } = require('../apps');
|
|
|
311
311
|
* status_args args for poll_tool (default { generation_id })
|
|
312
312
|
*/
|
|
313
313
|
async function uiGenerating(p) {
|
|
314
|
+
// No ETAs anywhere — just a spinner until the poll flips to completed.
|
|
314
315
|
const icon = await modelIcon(p.client, p.model).catch(() => null);
|
|
315
316
|
const structured = {
|
|
316
317
|
phase: 'generating',
|
|
@@ -326,7 +327,6 @@ async function uiGenerating(p) {
|
|
|
326
327
|
count: p.count || 1,
|
|
327
328
|
settings: p.settings || {},
|
|
328
329
|
reference_image: p.reference_image,
|
|
329
|
-
estimated_seconds: p.estimated_seconds,
|
|
330
330
|
};
|
|
331
331
|
const text = JSON.stringify({
|
|
332
332
|
status: 'submitted',
|
package/src/tools/generate.js
CHANGED
|
@@ -45,7 +45,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
45
45
|
if (ui()) return uiGenerating({
|
|
46
46
|
tool: 'generate_image', kind: 'image', gen, client, model, prompt,
|
|
47
47
|
count: num_images, settings: { resolution, aspect_ratio },
|
|
48
|
-
reference_image: reference_images?.[0]
|
|
48
|
+
reference_image: reference_images?.[0]
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -95,7 +95,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
95
95
|
if (ui()) return uiGenerating({
|
|
96
96
|
tool: 'generate_image_edit', kind: 'image', gen, client, model, prompt,
|
|
97
97
|
count: num_images, settings: { resolution, aspect_ratio },
|
|
98
|
-
reference_image: source_images?.[0]
|
|
98
|
+
reference_image: source_images?.[0]
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
// Multi-source compositing or DNA-anchored edits routinely exceed 120s
|
|
@@ -211,7 +211,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
211
211
|
if (ui()) return uiGenerating({
|
|
212
212
|
tool: 'generate_video', kind: 'video', gen, client, model, prompt,
|
|
213
213
|
settings: { duration, resolution, aspect_ratio },
|
|
214
|
-
reference_image: reference_images?.[0]
|
|
214
|
+
reference_image: reference_images?.[0]
|
|
215
215
|
});
|
|
216
216
|
|
|
217
217
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -259,7 +259,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
259
259
|
if (ui()) return uiGenerating({
|
|
260
260
|
tool: 'generate_video_from_image', kind: 'video', gen, client, model, prompt,
|
|
261
261
|
settings: { duration, resolution, aspect_ratio },
|
|
262
|
-
reference_image: image_url
|
|
262
|
+
reference_image: image_url
|
|
263
263
|
});
|
|
264
264
|
|
|
265
265
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -306,7 +306,6 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
306
306
|
if (ui()) return uiGenerating({
|
|
307
307
|
tool: 'generate_music', kind: 'audio', gen, client, model: model || 'Suno', prompt,
|
|
308
308
|
settings: { mode: instrumental ? 'instrumental' : (style || undefined) },
|
|
309
|
-
estimated_seconds: 90
|
|
310
309
|
});
|
|
311
310
|
|
|
312
311
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -347,7 +346,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
347
346
|
|
|
348
347
|
if (ui()) return uiGenerating({
|
|
349
348
|
tool: 'generate_speech', kind: 'audio', gen, client, model, prompt: text,
|
|
350
|
-
settings: { voice: voice || 'Rachel' }
|
|
349
|
+
settings: { voice: voice || 'Rachel' }
|
|
351
350
|
});
|
|
352
351
|
|
|
353
352
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -387,7 +386,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
387
386
|
|
|
388
387
|
if (ui()) return uiGenerating({
|
|
389
388
|
tool: 'generate_sound', kind: 'audio', gen, client, model, prompt,
|
|
390
|
-
settings: { duration }
|
|
389
|
+
settings: { duration }
|
|
391
390
|
});
|
|
392
391
|
|
|
393
392
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -486,7 +485,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
486
485
|
if (ui()) return uiGenerating({
|
|
487
486
|
tool: 'generate_elements', kind: 'video', gen: startResponse, client, model, prompt,
|
|
488
487
|
settings: { duration, resolution, aspect_ratio },
|
|
489
|
-
reference_image: reference_images?.[0]
|
|
488
|
+
reference_image: reference_images?.[0]
|
|
490
489
|
});
|
|
491
490
|
|
|
492
491
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
@@ -564,7 +563,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
564
563
|
if (ui()) return uiGenerating({
|
|
565
564
|
tool: 'generate_first_last_frame', kind: 'video', gen: startResponse, client, model, prompt,
|
|
566
565
|
settings: { duration, resolution, aspect_ratio },
|
|
567
|
-
reference_image: first_frame_url || undefined
|
|
566
|
+
reference_image: first_frame_url || undefined
|
|
568
567
|
});
|
|
569
568
|
|
|
570
569
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
@@ -674,7 +673,6 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
674
673
|
tool: 'generate_lipsync', kind: 'video', gen: startResponse, client, model,
|
|
675
674
|
prompt: text_prompt, settings: { mode: 'lipsync' },
|
|
676
675
|
reference_image: sourceIsUrl && !/\.(mp4|mov|webm|mkv|avi|m4v)(\?|$)/i.test(source) ? source : undefined,
|
|
677
|
-
estimated_seconds: 180
|
|
678
676
|
});
|
|
679
677
|
|
|
680
678
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
@@ -773,7 +771,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
773
771
|
tool: 'generate_video_from_video', kind: 'video', gen: startResponse, client, model,
|
|
774
772
|
prompt: prompt || (preset ? `Subtitles preset: ${preset}` : undefined),
|
|
775
773
|
settings: { duration, resolution, aspect_ratio, mode: preset ? 'subtitles' : 'restyle' },
|
|
776
|
-
reference_image: reference_images?.[0]
|
|
774
|
+
reference_image: reference_images?.[0]
|
|
777
775
|
});
|
|
778
776
|
|
|
779
777
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
@@ -890,7 +888,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
890
888
|
if (ui()) return uiGenerating({
|
|
891
889
|
tool: 'generate_3d', kind: '3d', gen: startResponse, client, model, prompt,
|
|
892
890
|
settings: { mode: mode || (reference_images?.length > 1 ? 'multi' : reference_images?.length === 1 ? 'single' : 'text') },
|
|
893
|
-
reference_image: reference_images?.[0]
|
|
891
|
+
reference_image: reference_images?.[0]
|
|
894
892
|
});
|
|
895
893
|
|
|
896
894
|
const result = await pollUntilDone(client, startResponse.generation_id, {
|
|
@@ -940,7 +938,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
940
938
|
tool: 'edit_image', kind: 'image', gen, client, model,
|
|
941
939
|
prompt: prompt || operation,
|
|
942
940
|
settings: { mode: operation, aspect_ratio },
|
|
943
|
-
reference_image: image_url
|
|
941
|
+
reference_image: image_url
|
|
944
942
|
});
|
|
945
943
|
|
|
946
944
|
const result = await pollUntilDone(client, gen.generation_id, {
|
|
@@ -997,7 +995,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
997
995
|
tool: 'edit_video', kind: 'video', gen, client, model,
|
|
998
996
|
prompt: prompt || operation,
|
|
999
997
|
settings: { mode: operation, duration, aspect_ratio },
|
|
1000
|
-
reference_image: image_url
|
|
998
|
+
reference_image: image_url
|
|
1001
999
|
});
|
|
1002
1000
|
|
|
1003
1001
|
const result = await pollUntilDone(client, gen.generation_id, {
|