@kolbo/mcp
Version:
Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code
95 lines (86 loc) • 4.48 kB
JavaScript
'use strict';
const { widgetPage } = require('../html');
/**
* Model catalog widget — list_models.
*
* structuredContent: {
* widget: 'catalog', title,
* groups: [{ name: 'Video Generation', models: [{ name, icon, description,
* chips: ['5–12s', '4K', 'audio'], use_hint }] }]
* }
*/
const BODY = `
<div class="k-card">
<div class="k-head">
<span class="k-logo" id="logo"></span>
<span class="k-title" id="title"></span>
<span class="k-spacer"></span>
<span class="k-chip" id="count-chip" style="display:none"></span>
</div>
<div class="k-body" id="stage"><div class="k-empty">Loading…</div></div>
<div class="k-footer"><span>Powered by <a href="#" id="kolbo-link">Kolbo.AI</a></span></div>
</div>
`;
const SCRIPT = `
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
var state = null;
function boot(sc) {
if (!sc || !sc.groups || !sc.groups.length) return false;
state = sc;
el('title').textContent = sc.title || 'AI Models';
// Internal lookup (Claude checking caps mid-task): a full catalog card would
// be chat spam, an empty card would look broken. One expandable row instead.
if (sc.compact) {
el('stage').innerHTML = '<div class="k-audio-row" id="expand" style="cursor:pointer;padding:8px">' +
'<div class="k-audio-meta"><div class="k-audio-title" style="font-size:12px">Kolbo AI models</div>' +
'<div class="k-audio-sub" style="font-size:10.5px">Tap to browse what you can generate</div></div>' +
'<span class="k-chip" style="flex:none;padding:2px 7px;font-size:10px">Browse</span></div>';
el('expand').onclick = function () { sc.compact = false; boot(sc); };
window.kolbo.notifySize();
return true;
}
var shown = sc.groups.reduce(function (n, g) { return n + g.models.length; }, 0);
el('count-chip').style.display = '';
el('count-chip').textContent = sc.total_available && sc.total_available > shown
? 'top picks · ' + sc.total_available + ' total'
: shown + ' models';
el('stage').innerHTML = sc.groups.map(function (g, gi) {
return '<div style="margin-bottom:10px">' +
'<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>' +
g.models.map(function (m, mi) {
return '<div class="k-audio-row" data-g="' + gi + '" data-m="' + mi + '" style="cursor:pointer;padding:6px 8px;margin-bottom:4px">' +
(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, '') + '\\')">'
: '<span style="flex:none">' + monogram(m.name) + '</span>') +
'<div class="k-audio-meta"><div class="k-audio-title" style="font-size:12px">' + esc(m.name) + '</div>' +
(m.description ? '<div class="k-audio-sub" style="font-size:10.5px">' + esc(m.description) + '</div>' : '') + '</div>' +
'<div style="display:flex;gap:4px;flex:none">' + (m.chips || []).slice(0, 3).map(function (c) {
return '<span class="k-chip" style="padding:2px 7px;font-size:10px">' + esc(c) + '</span>';
}).join('') + '</div></div>';
}).join('') + '</div>';
}).join('');
Array.prototype.forEach.call(document.querySelectorAll('[data-g]'), function (row) {
row.onclick = function () {
var m = state.groups[+row.getAttribute('data-g')].models[+row.getAttribute('data-m')];
window.kolbo.insertText(m.identifier || m.name || '');
};
});
window.kolbo.notifySize();
return true;
}
window.kolbo.onToolResult(function (result) {
// NB: no text fallback here. The old second operand called a structured()
// that was never defined, so on any host sending no structuredContent this
// threw a ReferenceError and the card sat on Loading instead of collapsing.
if (boot(result.structuredContent)) return;
// No catalog data reached the iframe (host/version mismatch, or a filter
// that matched nothing) — collapse instead of showing a dead card.
var card = document.querySelector('.k-card');
if (card) card.style.display = 'none';
window.kolbo.notifySize();
});
`;
function catalogWidgetHtml() {
return widgetPage({ title: 'Kolbo Models', body: BODY, script: SCRIPT });
}
module.exports = { catalogWidgetHtml };