@kolbo/mcp 1.30.1 → 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/bridge.js +6 -2
- package/src/apps/index.js +58 -19
- package/src/apps/widgets/catalog.js +11 -9
- package/src/apps/widgets/generation.js +11 -35
- package/src/tools/_shared.js +1 -1
- package/src/tools/generate.js +12 -14
- package/src/tools/models.js +25 -3
- package/src/tools/shorts_creator.js +0 -1
package/package.json
CHANGED
package/src/apps/bridge.js
CHANGED
|
@@ -91,9 +91,13 @@ const BRIDGE_JS = `
|
|
|
91
91
|
}).catch(function () { /* host without apps support — widget stays static */ });
|
|
92
92
|
|
|
93
93
|
function notifySize() {
|
|
94
|
-
|
|
94
|
+
// Measure the widget card itself — documentElement.scrollHeight over-reports
|
|
95
|
+
// in some hosts and leaves a huge empty iframe below the card.
|
|
96
|
+
var card = document.querySelector('.k-card');
|
|
97
|
+
var rect = card ? card.getBoundingClientRect() : null;
|
|
98
|
+
var height = rect ? Math.ceil(rect.bottom + 8) : document.documentElement.scrollHeight;
|
|
95
99
|
notify('ui/notifications/size-changed', {
|
|
96
|
-
width:
|
|
100
|
+
width: document.documentElement.scrollWidth, height: height
|
|
97
101
|
});
|
|
98
102
|
}
|
|
99
103
|
|
package/src/apps/index.js
CHANGED
|
@@ -42,6 +42,26 @@ function widgetHtml(uri) {
|
|
|
42
42
|
return htmlCache.get(uri);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Hosts apply a deny-by-default CSP to widget iframes — without this
|
|
46
|
+
// declaration EVERY external asset (generated images/videos on the CDN, model
|
|
47
|
+
// icons, Google Fonts) is silently blocked. resourceDomains maps to
|
|
48
|
+
// img/script/style/font/media-src; connectDomains to connect-src.
|
|
49
|
+
const WIDGET_CSP = {
|
|
50
|
+
resourceDomains: [
|
|
51
|
+
'https://*.kolbo.ai', // media.kolbo.ai CDN + app.kolbo.ai model icons
|
|
52
|
+
'https://*.digitaloceanspaces.com', // DO Spaces buckets (all envs)
|
|
53
|
+
'https://*.cdn.digitaloceanspaces.com', // DO Spaces CDN endpoints
|
|
54
|
+
'https://fonts.googleapis.com', // Inter / JetBrains Mono stylesheet
|
|
55
|
+
'https://fonts.gstatic.com', // font files
|
|
56
|
+
'https://images.pexels.com', // stock thumbnails
|
|
57
|
+
'https://*.pexels.com',
|
|
58
|
+
'https://*.pixabay.com',
|
|
59
|
+
'https://*.sketchfab.com',
|
|
60
|
+
'https://*.cloudfront.net', // provider-hosted previews
|
|
61
|
+
],
|
|
62
|
+
connectDomains: [],
|
|
63
|
+
};
|
|
64
|
+
|
|
45
65
|
/** Register all Kolbo widget resources on an McpServer. */
|
|
46
66
|
function registerApps(server) {
|
|
47
67
|
for (const [uri, name] of [
|
|
@@ -50,9 +70,16 @@ function registerApps(server) {
|
|
|
50
70
|
[UI.catalog, 'Kolbo Model Catalog Widget'],
|
|
51
71
|
[UI.transcript, 'Kolbo Transcription Widget'],
|
|
52
72
|
]) {
|
|
53
|
-
registerAppResource(
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
registerAppResource(
|
|
74
|
+
server, name, uri,
|
|
75
|
+
{ mimeType: RESOURCE_MIME_TYPE, _meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } } },
|
|
76
|
+
async () => ({
|
|
77
|
+
contents: [{
|
|
78
|
+
uri, mimeType: RESOURCE_MIME_TYPE, text: widgetHtml(uri),
|
|
79
|
+
_meta: { csp: WIDGET_CSP, ui: { csp: WIDGET_CSP } },
|
|
80
|
+
}],
|
|
81
|
+
})
|
|
82
|
+
);
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
85
|
|
|
@@ -96,38 +123,49 @@ function uiResult(uri, text, structured) {
|
|
|
96
123
|
/* ------------------------------------------------------------------ */
|
|
97
124
|
|
|
98
125
|
const ICON_TTL_MS = 10 * 60 * 1000;
|
|
99
|
-
const
|
|
126
|
+
const infoCache = new Map(); // apiBase → { at, byKey: Map<lowername, {icon, eta}> }
|
|
100
127
|
|
|
101
|
-
async function
|
|
128
|
+
async function modelInfoMap(client) {
|
|
102
129
|
const cacheKey = client.apiBase || 'default';
|
|
103
|
-
const hit =
|
|
130
|
+
const hit = infoCache.get(cacheKey);
|
|
104
131
|
if (hit && Date.now() - hit.at < ICON_TTL_MS) return hit.byKey;
|
|
105
132
|
const byKey = new Map();
|
|
106
133
|
try {
|
|
107
134
|
const res = await client.request('GET', '/v1/models');
|
|
108
135
|
const models = res?.models || res?.data?.models || [];
|
|
109
136
|
for (const m of models) {
|
|
110
|
-
if (!m
|
|
137
|
+
if (!m) continue;
|
|
111
138
|
// The API usually resolves avatars to absolute URLs; bare filenames (older
|
|
112
139
|
// deployments / internal calls) resolve against the app's public icon dir.
|
|
113
|
-
const
|
|
114
|
-
? m.avatar
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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);
|
|
118
151
|
}
|
|
119
152
|
} catch (_) {
|
|
120
|
-
/* fail open — widgets fall back to monogram chips */
|
|
153
|
+
/* fail open — widgets fall back to monogram chips, no ETA */
|
|
121
154
|
}
|
|
122
|
-
|
|
155
|
+
infoCache.set(cacheKey, { at: Date.now(), byKey });
|
|
123
156
|
return byKey;
|
|
124
157
|
}
|
|
125
158
|
|
|
126
|
-
/** 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). */
|
|
127
167
|
async function modelIcon(client, modelName) {
|
|
128
|
-
|
|
129
|
-
const map = await modelIconMap(client);
|
|
130
|
-
return map.get(String(modelName).toLowerCase()) || null;
|
|
168
|
+
return (await modelInfo(client, modelName)).icon;
|
|
131
169
|
}
|
|
132
170
|
|
|
133
171
|
/* ------------------------------------------------------------------ */
|
|
@@ -192,6 +230,7 @@ module.exports = {
|
|
|
192
230
|
uiResult,
|
|
193
231
|
appsEnabled,
|
|
194
232
|
modelIcon,
|
|
195
|
-
|
|
233
|
+
modelInfo,
|
|
234
|
+
modelInfoMap,
|
|
196
235
|
widgetHtml, // exported for smoke tests
|
|
197
236
|
};
|
|
@@ -34,20 +34,22 @@ function boot(sc) {
|
|
|
34
34
|
if (!sc || !sc.groups) return;
|
|
35
35
|
state = sc;
|
|
36
36
|
el('title').textContent = sc.title || 'AI Models';
|
|
37
|
-
var
|
|
37
|
+
var shown = sc.groups.reduce(function (n, g) { return n + g.models.length; }, 0);
|
|
38
38
|
el('count-chip').style.display = '';
|
|
39
|
-
el('count-chip').textContent =
|
|
39
|
+
el('count-chip').textContent = sc.total_available && sc.total_available > shown
|
|
40
|
+
? 'top picks · ' + sc.total_available + ' total'
|
|
41
|
+
: shown + ' models';
|
|
40
42
|
el('stage').innerHTML = sc.groups.map(function (g, gi) {
|
|
41
|
-
return '<div style="margin-bottom:
|
|
42
|
-
'<div style="font-size:
|
|
43
|
+
return '<div style="margin-bottom:10px">' +
|
|
44
|
+
'<div style="font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--text-faint);margin-bottom:6px">' + esc(g.name) + '</div>' +
|
|
43
45
|
g.models.map(function (m, mi) {
|
|
44
|
-
return '<div class="k-audio-row" data-g="' + gi + '" data-m="' + mi + '" style="cursor:pointer">' +
|
|
45
|
-
(m.icon ? '<img class="k-audio-art" style="width:
|
|
46
|
+
return '<div class="k-audio-row" data-g="' + gi + '" data-m="' + mi + '" style="cursor:pointer;padding:6px 8px;margin-bottom:4px">' +
|
|
47
|
+
(m.icon ? '<img class="k-audio-art" style="width:24px;height:24px;border-radius:6px" src="' + esc(m.icon) + '" onerror="this.outerHTML=monogram(\\'' + esc(m.name).replace(/'/g, '') + '\\')">'
|
|
46
48
|
: '<span style="flex:none">' + monogram(m.name) + '</span>') +
|
|
47
|
-
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(m.name) + '</div>' +
|
|
48
|
-
(m.description ? '<div class="k-audio-sub">' + esc(m.description) + '</div>' : '') + '</div>' +
|
|
49
|
+
'<div class="k-audio-meta"><div class="k-audio-title" style="font-size:12px">' + esc(m.name) + '</div>' +
|
|
50
|
+
(m.description ? '<div class="k-audio-sub" style="font-size:10.5px">' + esc(m.description) + '</div>' : '') + '</div>' +
|
|
49
51
|
'<div style="display:flex;gap:4px;flex:none">' + (m.chips || []).slice(0, 3).map(function (c) {
|
|
50
|
-
return '<span class="k-chip">' + esc(c) + '</span>';
|
|
52
|
+
return '<span class="k-chip" style="padding:2px 7px;font-size:10px">' + esc(c) + '</span>';
|
|
51
53
|
}).join('') + '</div></div>';
|
|
52
54
|
}).join('') + '</div>';
|
|
53
55
|
}).join('');
|
|
@@ -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'); };
|
|
@@ -98,7 +92,7 @@ function renderChips(sc) {
|
|
|
98
92
|
if (s.voice) h += chip('🎤 ' + esc(s.voice));
|
|
99
93
|
if (s.mode) h += chip(esc(s.mode));
|
|
100
94
|
if (sc.count > 1) h += chip('×' + sc.count);
|
|
101
|
-
if (sc.reference_image) h += '<img class="k-ref-thumb" src="' + esc(sc.reference_image) + '" alt="
|
|
95
|
+
if (sc.reference_image) h += '<img class="k-ref-thumb" src="' + esc(sc.reference_image) + '" alt="" title="Reference image" onerror="this.style.display=\'none\'">';
|
|
102
96
|
el('chips').innerHTML = h;
|
|
103
97
|
}
|
|
104
98
|
function chip(inner) { return '<span class="k-chip">' + inner + '</span>'; }
|
|
@@ -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 || [],
|
|
@@ -166,21 +145,15 @@ function poll(sc) {
|
|
|
166
145
|
} else if (stateName === 'failed' || stateName === 'error' || stateName === 'cancelled') {
|
|
167
146
|
renderError(st.error || 'Generation ' + stateName);
|
|
168
147
|
} else {
|
|
169
|
-
if (st.progress != null) el('status-text').textContent = 'Generating… ' + Math.round(st.progress) + '%';
|
|
170
148
|
schedulePoll(sc);
|
|
171
149
|
}
|
|
172
150
|
}).catch(function () { schedulePoll(sc); });
|
|
173
151
|
}
|
|
174
|
-
function finishProgress() {
|
|
175
|
-
clearInterval(progressTimer);
|
|
176
|
-
el('progress-fill').style.width = '100%';
|
|
177
|
-
setTimeout(function () { el('progress').style.display = 'none'; el('status-line').style.display = 'none'; }, 450);
|
|
178
|
-
}
|
|
179
152
|
|
|
180
153
|
/* ---------- results ---------- */
|
|
181
154
|
function renderResult(sc) {
|
|
182
|
-
clearTimeout(pollTimer);
|
|
183
|
-
|
|
155
|
+
clearTimeout(pollTimer);
|
|
156
|
+
|
|
184
157
|
setPhaseChip('', false);
|
|
185
158
|
if (sc.kind === 'scenes' && sc.scenes && sc.scenes.length) return renderScenes(sc);
|
|
186
159
|
var urls = sc.urls || [];
|
|
@@ -196,7 +169,10 @@ function renderResult(sc) {
|
|
|
196
169
|
|
|
197
170
|
function renderImages(sc, urls) {
|
|
198
171
|
selected = Math.min(selected, urls.length - 1);
|
|
199
|
-
|
|
172
|
+
// If the host CSP still blocks the image, degrade to open-in-browser rows
|
|
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()"></div>';
|
|
175
|
+
window.__imgFail = function () { renderLinks(urls); window.kolbo.notifySize(); };
|
|
200
176
|
var thumbs = '';
|
|
201
177
|
if (urls.length > 1) {
|
|
202
178
|
thumbs = '<div class="k-thumbs">' + urls.map(function (u, i) {
|
|
@@ -270,8 +246,8 @@ function renderScenes(sc) {
|
|
|
270
246
|
}
|
|
271
247
|
|
|
272
248
|
function renderError(msg) {
|
|
273
|
-
clearTimeout(pollTimer);
|
|
274
|
-
|
|
249
|
+
clearTimeout(pollTimer);
|
|
250
|
+
|
|
275
251
|
setPhaseChip('Failed', false);
|
|
276
252
|
el('stage').innerHTML = '<div class="k-error">⚠ ' + esc(msg) + '</div>';
|
|
277
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, {
|
package/src/tools/models.js
CHANGED
|
@@ -43,14 +43,34 @@ function modelChips(m) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// structuredContent for ui://kolbo/catalog.html — see src/apps/widgets/catalog.js
|
|
46
|
+
// Deliberately CURATED, not exhaustive: the widget is a picker, not a database.
|
|
47
|
+
// Smart Select is pinned first, each group shows the recommended/new models
|
|
48
|
+
// (max 6), and the total count chip tells the user how many exist overall.
|
|
46
49
|
function buildCatalogStructured(models, type) {
|
|
47
50
|
const groups = [];
|
|
48
51
|
const byName = new Map();
|
|
49
|
-
|
|
52
|
+
const isAuto = (m) => /^auto$|smart.select/i.test(String(m.name || '')) || /smart-select|k_auto/i.test(String(m.identifier || ''));
|
|
53
|
+
|
|
54
|
+
// Pinned Smart Select entry (replaces the confusing "Other / Auto" row).
|
|
55
|
+
const smartSelect = {
|
|
56
|
+
name: 'Smart Select',
|
|
57
|
+
icon: null,
|
|
58
|
+
description: 'Recommended — automatically routes to the best model for your prompt, quality and cost.',
|
|
59
|
+
chips: ['AUTO'],
|
|
60
|
+
use_hint: 'Generate with Smart Select (omit the model) — ask me what I want to create first.',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Recommended + new models float to the top of each group.
|
|
64
|
+
const ranked = [...models].filter((m) => !isAuto(m)).sort((a, b) => {
|
|
65
|
+
const score = (m) => (m.recommended ? 2 : 0) + (m.new_model || m.newModel ? 1 : 0);
|
|
66
|
+
return score(b) - score(a);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
for (const m of ranked) {
|
|
50
70
|
const name = groupNameFor(m);
|
|
51
71
|
let g = byName.get(name);
|
|
52
72
|
if (!g) { g = { name, models: [] }; byName.set(name, g); groups.push(g); }
|
|
53
|
-
if (g.models.length >=
|
|
73
|
+
if (g.models.length >= 6) continue; // curated cap — full list lives in the text payload
|
|
54
74
|
g.models.push({
|
|
55
75
|
name: m.name,
|
|
56
76
|
icon: m.avatar
|
|
@@ -61,10 +81,12 @@ function buildCatalogStructured(models, type) {
|
|
|
61
81
|
use_hint: `Generate with the "${m.name}" model — ask me what I want to create first.`,
|
|
62
82
|
});
|
|
63
83
|
}
|
|
84
|
+
groups.sort((a, b) => (a.name === 'Other' ? 1 : b.name === 'Other' ? -1 : 0));
|
|
64
85
|
return {
|
|
65
86
|
widget: 'catalog',
|
|
66
87
|
title: 'Kolbo AI Models' + (type ? ' — ' + type : ''),
|
|
67
|
-
|
|
88
|
+
total_available: models.length,
|
|
89
|
+
groups: [{ name: 'Recommended', models: [smartSelect] }, ...groups],
|
|
68
90
|
};
|
|
69
91
|
}
|
|
70
92
|
|