@kolbo/mcp 1.28.1 → 1.30.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 +14 -1
- package/package.json +2 -1
- package/skill/GENERATED.md +1 -1
- package/skill/SKILL.md +23 -6
- package/skill/VERSION +1 -1
- package/skill/references/models/seedance.md +7 -7
- package/skill/references/workflows/marketing-studio.md +62 -0
- package/src/apps/bridge.js +124 -0
- package/src/apps/html.js +73 -0
- package/src/apps/index.js +142 -0
- package/src/apps/theme.js +247 -0
- package/src/apps/widgets/catalog.js +73 -0
- package/src/apps/widgets/generation.js +374 -0
- package/src/apps/widgets/mediaGrid.js +129 -0
- package/src/apps/widgets/transcript.js +108 -0
- package/src/index.js +28 -14
- package/src/tools/_shared.js +75 -0
- package/src/tools/generate.js +123 -13
- package/src/tools/media.js +26 -5
- package/src/tools/models.js +71 -13
- package/src/tools/moodboards.js +26 -10
- package/src/tools/music_library.js +44 -3
- package/src/tools/presets.js +30 -11
- package/src/tools/shorts_creator.js +405 -0
- package/src/tools/stock_library.js +60 -3
- package/src/tools/visual_dna.js +27 -10
- package/src/tools/voices.js +23 -7
- package/skill/references/workflows/music-library.md +0 -32
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { widgetPage } = require('../html');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Universal generation widget — used by every generate_ / edit_ tool.
|
|
7
|
+
*
|
|
8
|
+
* structuredContent contract (set by src/tools/*):
|
|
9
|
+
* {
|
|
10
|
+
* phase: 'generating' | 'completed' | 'failed',
|
|
11
|
+
* kind: 'image' | 'video' | 'audio' | '3d' | 'scenes',
|
|
12
|
+
* tool: 'generate_image', // originating MCP tool name
|
|
13
|
+
* generation_id, poll_tool, // when phase === 'generating'
|
|
14
|
+
* status_args, // extra args for the poll tool (optional)
|
|
15
|
+
* estimated_seconds, // optional ETA hint
|
|
16
|
+
* model, model_icon, prompt, count,
|
|
17
|
+
* settings: { duration, resolution, aspect_ratio, audio, voice, mode },
|
|
18
|
+
* reference_image, // thumbnail URL (optional)
|
|
19
|
+
* urls, thumbnail_url, title, duration, credits_used,
|
|
20
|
+
* scenes: [{ scene_number, title, image_urls, video_urls }],
|
|
21
|
+
* error,
|
|
22
|
+
* open_url // "Open in Kolbo" target (optional)
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const BODY = `
|
|
27
|
+
<div class="k-card" id="card">
|
|
28
|
+
<div class="k-head">
|
|
29
|
+
<span class="k-logo" id="logo"></span>
|
|
30
|
+
<span class="k-title" id="tool-title"></span>
|
|
31
|
+
<span class="k-spacer"></span>
|
|
32
|
+
<span class="k-chip" id="phase-chip" style="display:none"></span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="k-body">
|
|
35
|
+
<div class="k-prompt" id="prompt"></div>
|
|
36
|
+
<div class="k-chips" id="chips"></div>
|
|
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
|
+
<div class="k-prompt-row" id="prompt-row">
|
|
43
|
+
<input class="k-input" id="action-input" placeholder="">
|
|
44
|
+
<button class="k-btn primary" id="action-send">Send</button>
|
|
45
|
+
<button class="k-btn ghost" id="action-cancel">✕</button>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="k-actions" id="actions"></div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="k-footer">
|
|
50
|
+
<span>Powered by <a href="#" id="kolbo-link">Kolbo.AI</a></span>
|
|
51
|
+
<span class="k-credits" id="credits"></span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const SCRIPT = `
|
|
57
|
+
var state = null; // current structuredContent
|
|
58
|
+
var selected = 0; // selected result index
|
|
59
|
+
var pollTimer = null;
|
|
60
|
+
var progressTimer = null;
|
|
61
|
+
var startedAt = Date.now();
|
|
62
|
+
|
|
63
|
+
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
64
|
+
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
65
|
+
|
|
66
|
+
var TOOL_TITLES = {
|
|
67
|
+
generate_image: 'Image Generation', generate_image_edit: 'Image Edit',
|
|
68
|
+
generate_video: 'Video Generation', generate_video_from_image: 'Image to Video',
|
|
69
|
+
generate_video_from_video: 'Video to Video', generate_elements: 'Elements Video',
|
|
70
|
+
generate_first_last_frame: 'First–Last Frame', generate_lipsync: 'Lipsync',
|
|
71
|
+
generate_music: 'Music Generation', generate_speech: 'Text to Speech',
|
|
72
|
+
generate_sound: 'Sound Effect', generate_3d: '3D Generation',
|
|
73
|
+
generate_creative_director: 'Creative Director', edit_image: 'Image Edit', edit_video: 'Video Edit'
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function boot(sc) {
|
|
77
|
+
if (!sc) return;
|
|
78
|
+
state = sc;
|
|
79
|
+
el('tool-title').textContent = TOOL_TITLES[sc.tool] || 'Generation';
|
|
80
|
+
el('prompt').textContent = sc.prompt || '';
|
|
81
|
+
el('prompt').style.display = sc.prompt ? '' : 'none';
|
|
82
|
+
renderChips(sc);
|
|
83
|
+
el('credits').textContent = sc.credits_used != null ? fmtCredits(sc.credits_used) : '';
|
|
84
|
+
if (sc.phase === 'generating') renderGenerating(sc);
|
|
85
|
+
else if (sc.phase === 'failed') renderError(sc.error || 'Generation failed');
|
|
86
|
+
else renderResult(sc);
|
|
87
|
+
window.kolbo.notifySize();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderChips(sc) {
|
|
91
|
+
var h = modelChipHTML(sc.model, sc.model_icon);
|
|
92
|
+
var s = sc.settings || {};
|
|
93
|
+
if (sc.kind) h += chip(iconFor(sc.kind) + ' ' + sc.kind);
|
|
94
|
+
if (s.duration) h += chip('⏱ ' + fmtDur(s.duration));
|
|
95
|
+
if (s.resolution) h += chip(esc(s.resolution));
|
|
96
|
+
if (s.aspect_ratio) h += chip(esc(s.aspect_ratio));
|
|
97
|
+
if (s.audio) h += chip('🔊 audio');
|
|
98
|
+
if (s.voice) h += chip('🎤 ' + esc(s.voice));
|
|
99
|
+
if (s.mode) h += chip(esc(s.mode));
|
|
100
|
+
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="ref" title="Reference image">';
|
|
102
|
+
el('chips').innerHTML = h;
|
|
103
|
+
}
|
|
104
|
+
function chip(inner) { return '<span class="k-chip">' + inner + '</span>'; }
|
|
105
|
+
function iconFor(kind) {
|
|
106
|
+
return { image: '🖼', video: '🎬', audio: '🎵', '3d': '🧊', scenes: '🎞' }[kind] || '✨';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ---------- generating ---------- */
|
|
110
|
+
function renderGenerating(sc) {
|
|
111
|
+
setPhaseChip('Generating', true);
|
|
112
|
+
var n = Math.min(sc.count || 1, 4);
|
|
113
|
+
var shape = sc.kind === 'video' || sc.kind === 'scenes' ? 'video' : (sc.kind === 'audio' ? 'video' : 'square');
|
|
114
|
+
var cells = '';
|
|
115
|
+
for (var i = 0; i < n; i++) {
|
|
116
|
+
cells += '<div class="k-skel ' + shape + '">' +
|
|
117
|
+
(i === 0 ? '<span class="k-gen-badge"><span class="k-spin"></span>Generating</span>' : '') + '</div>';
|
|
118
|
+
}
|
|
119
|
+
el('stage').innerHTML = '<div class="k-gen-grid n' + n + '">' + cells + '</div>';
|
|
120
|
+
el('progress').style.display = '';
|
|
121
|
+
el('status-line').style.display = '';
|
|
122
|
+
el('actions').innerHTML = '';
|
|
123
|
+
startProgress(sc.estimated_seconds || defaultEta(sc.kind));
|
|
124
|
+
schedulePoll(sc);
|
|
125
|
+
}
|
|
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
|
+
|
|
139
|
+
function schedulePoll(sc) {
|
|
140
|
+
clearTimeout(pollTimer);
|
|
141
|
+
pollTimer = setTimeout(function () { poll(sc); }, 4000);
|
|
142
|
+
}
|
|
143
|
+
function poll(sc) {
|
|
144
|
+
var args = sc.status_args || { generation_id: sc.generation_id };
|
|
145
|
+
window.kolbo.callTool(sc.poll_tool || 'get_generation_status', args).then(function (res) {
|
|
146
|
+
var st = structured(res) || {};
|
|
147
|
+
var stateName = st.state || st.phase || st.status;
|
|
148
|
+
if (stateName === 'completed') {
|
|
149
|
+
var r = st.result || st;
|
|
150
|
+
finishProgress();
|
|
151
|
+
var done = Object.assign({}, sc, r, {
|
|
152
|
+
phase: 'completed',
|
|
153
|
+
urls: r.urls || st.urls || [],
|
|
154
|
+
credits_used: st.credits_used != null ? st.credits_used : sc.credits_used
|
|
155
|
+
});
|
|
156
|
+
state = done;
|
|
157
|
+
el('credits').textContent = done.credits_used != null ? fmtCredits(done.credits_used) : '';
|
|
158
|
+
renderResult(done);
|
|
159
|
+
// Let the model know the outcome without it having to poll.
|
|
160
|
+
try {
|
|
161
|
+
window.kolbo.updateModelContext(
|
|
162
|
+
'Generation ' + (sc.generation_id || '') + ' completed (' + (sc.tool || '') + ').' +
|
|
163
|
+
'\\nOutput URLs:\\n' + (done.urls || []).join('\\n') +
|
|
164
|
+
(done.credits_used != null ? '\\nCredits used: ' + done.credits_used : ''));
|
|
165
|
+
} catch (e) {}
|
|
166
|
+
} else if (stateName === 'failed' || stateName === 'error' || stateName === 'cancelled') {
|
|
167
|
+
renderError(st.error || 'Generation ' + stateName);
|
|
168
|
+
} else {
|
|
169
|
+
if (st.progress != null) el('status-text').textContent = 'Generating… ' + Math.round(st.progress) + '%';
|
|
170
|
+
schedulePoll(sc);
|
|
171
|
+
}
|
|
172
|
+
}).catch(function () { schedulePoll(sc); });
|
|
173
|
+
}
|
|
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
|
+
|
|
180
|
+
/* ---------- results ---------- */
|
|
181
|
+
function renderResult(sc) {
|
|
182
|
+
clearTimeout(pollTimer); clearInterval(progressTimer);
|
|
183
|
+
el('progress').style.display = 'none'; el('status-line').style.display = 'none';
|
|
184
|
+
setPhaseChip('', false);
|
|
185
|
+
if (sc.kind === 'scenes' && sc.scenes && sc.scenes.length) return renderScenes(sc);
|
|
186
|
+
var urls = sc.urls || [];
|
|
187
|
+
if (!urls.length) return renderError('No output received');
|
|
188
|
+
if (sc.kind === 'image') renderImages(sc, urls);
|
|
189
|
+
else if (sc.kind === 'video') renderVideo(sc, urls);
|
|
190
|
+
else if (sc.kind === 'audio') renderAudio(sc, urls);
|
|
191
|
+
else if (sc.kind === '3d') render3d(sc, urls);
|
|
192
|
+
else renderLinks(urls);
|
|
193
|
+
renderActions(sc);
|
|
194
|
+
window.kolbo.notifySize();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function renderImages(sc, urls) {
|
|
198
|
+
selected = Math.min(selected, urls.length - 1);
|
|
199
|
+
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt=""></div>';
|
|
200
|
+
var thumbs = '';
|
|
201
|
+
if (urls.length > 1) {
|
|
202
|
+
thumbs = '<div class="k-thumbs">' + urls.map(function (u, i) {
|
|
203
|
+
return '<div class="k-thumb' + (i === selected ? ' active' : '') + '" data-i="' + i + '"><img src="' + esc(u) + '" alt=""></div>';
|
|
204
|
+
}).join('') + '</div>';
|
|
205
|
+
}
|
|
206
|
+
el('stage').innerHTML = viewer + thumbs;
|
|
207
|
+
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-thumb'), function (t) {
|
|
208
|
+
t.onclick = function () {
|
|
209
|
+
selected = +t.getAttribute('data-i');
|
|
210
|
+
el('main-img').src = state.urls[selected];
|
|
211
|
+
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-thumb'), function (x) { x.classList.remove('active'); });
|
|
212
|
+
t.classList.add('active');
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function renderVideo(sc, urls) {
|
|
218
|
+
el('stage').innerHTML = '<div class="k-viewer"><video id="main-video" src="' + esc(urls[0]) + '"' +
|
|
219
|
+
(sc.thumbnail_url ? ' poster="' + esc(sc.thumbnail_url) + '"' : '') + ' controls playsinline></video></div>';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function renderAudio(sc, urls) {
|
|
223
|
+
el('stage').innerHTML = urls.map(function (u, i) {
|
|
224
|
+
var title = sc.title || ((TOOL_TITLES[sc.tool] || 'Audio') + (urls.length > 1 ? ' ' + (i + 1) : ''));
|
|
225
|
+
return '<div class="k-audio-row">' +
|
|
226
|
+
(sc.thumbnail_url ? '<img class="k-audio-art" src="' + esc(sc.thumbnail_url) + '">' : '<div class="k-audio-art"></div>') +
|
|
227
|
+
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(title) + '</div>' +
|
|
228
|
+
'<div class="k-audio-sub">' + esc(sc.model || '') + (sc.duration ? ' · ' + fmtDur(sc.duration) : '') + '</div></div>' +
|
|
229
|
+
'<audio src="' + esc(u) + '" controls style="height:32px;max-width:260px"></audio></div>';
|
|
230
|
+
}).join('');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function render3d(sc, urls) {
|
|
234
|
+
el('stage').innerHTML = (sc.thumbnail_url
|
|
235
|
+
? '<div class="k-viewer"><img src="' + esc(sc.thumbnail_url) + '" alt=""></div>' : '') +
|
|
236
|
+
urls.map(function (u) {
|
|
237
|
+
var extMatch = u.split('?')[0].match(/\\.(\\w+)$/);
|
|
238
|
+
var ext = extMatch ? extMatch[1].toUpperCase() : 'FILE';
|
|
239
|
+
return '<div class="k-audio-row"><div class="k-audio-art" style="display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700">' + esc(ext) + '</div>' +
|
|
240
|
+
'<div class="k-audio-meta"><div class="k-audio-title">3D Model (' + esc(ext) + ')</div></div>' +
|
|
241
|
+
'<button class="k-btn" data-url="' + esc(u) + '">Download</button></div>';
|
|
242
|
+
}).join('');
|
|
243
|
+
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-btn[data-url]'), function (b) {
|
|
244
|
+
b.onclick = function () { window.kolbo.openLink(b.getAttribute('data-url')); };
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function renderLinks(urls) {
|
|
249
|
+
el('stage').innerHTML = urls.map(function (u) {
|
|
250
|
+
return '<div class="k-audio-row"><div class="k-audio-meta"><div class="k-audio-title" style="word-break:break-all">' + esc(u) + '</div></div>' +
|
|
251
|
+
'<button class="k-btn" data-url="' + esc(u) + '">Open</button></div>';
|
|
252
|
+
}).join('');
|
|
253
|
+
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-btn[data-url]'), function (b) {
|
|
254
|
+
b.onclick = function () { window.kolbo.openLink(b.getAttribute('data-url')); };
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function renderScenes(sc) {
|
|
259
|
+
el('stage').innerHTML = sc.scenes.map(function (scene) {
|
|
260
|
+
var media = (scene.video_urls || []).map(function (u) {
|
|
261
|
+
return '<div class="k-media"><video src="' + esc(u) + '" controls playsinline></video></div>';
|
|
262
|
+
}).join('') + (scene.image_urls || []).map(function (u) {
|
|
263
|
+
return '<div class="k-media"><img src="' + esc(u) + '" alt=""></div>';
|
|
264
|
+
}).join('');
|
|
265
|
+
return '<div style="margin-bottom:14px"><div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:8px">Scene ' +
|
|
266
|
+
esc(scene.scene_number) + (scene.title ? ' — ' + esc(scene.title) : '') + '</div>' +
|
|
267
|
+
'<div class="k-gen-grid n2">' + media + '</div></div>';
|
|
268
|
+
}).join('');
|
|
269
|
+
renderActions(sc);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function renderError(msg) {
|
|
273
|
+
clearTimeout(pollTimer); clearInterval(progressTimer);
|
|
274
|
+
el('progress').style.display = 'none'; el('status-line').style.display = 'none';
|
|
275
|
+
setPhaseChip('Failed', false);
|
|
276
|
+
el('stage').innerHTML = '<div class="k-error">⚠ ' + esc(msg) + '</div>';
|
|
277
|
+
el('actions').innerHTML = '<button class="k-btn" id="retry-btn">↻ Try Again</button>';
|
|
278
|
+
el('retry-btn').onclick = function () {
|
|
279
|
+
window.kolbo.sendMessage('Please retry that ' + (TOOL_TITLES[state.tool] || 'generation').toLowerCase() + ' — it failed with: ' + msg);
|
|
280
|
+
};
|
|
281
|
+
window.kolbo.notifySize();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function setPhaseChip(text, spinning) {
|
|
285
|
+
var c = el('phase-chip');
|
|
286
|
+
if (!text) { c.style.display = 'none'; return; }
|
|
287
|
+
c.style.display = '';
|
|
288
|
+
c.innerHTML = (spinning ? '<span class="k-spin"></span>' : '') + esc(text);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* ---------- actions ---------- */
|
|
292
|
+
function currentUrl() {
|
|
293
|
+
return (state.urls && state.urls[state.kind === 'image' ? selected : 0]) || '';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function renderActions(sc) {
|
|
297
|
+
var a = [];
|
|
298
|
+
if (sc.kind === 'image') {
|
|
299
|
+
a.push('<button class="k-btn primary" id="btn-animate">🎬 Animate</button>');
|
|
300
|
+
a.push('<button class="k-btn" id="btn-edit">✏️ Edit</button>');
|
|
301
|
+
}
|
|
302
|
+
if (sc.kind === 'video') {
|
|
303
|
+
a.push('<button class="k-btn primary" id="btn-download">⬇ Download</button>');
|
|
304
|
+
a.push('<button class="k-btn" id="btn-analyze">📊 Analyze</button>');
|
|
305
|
+
} else {
|
|
306
|
+
a.push('<button class="k-btn" id="btn-download">⬇ Download</button>');
|
|
307
|
+
}
|
|
308
|
+
a.push('<button class="k-btn" id="btn-recreate">↻ Recreate</button>');
|
|
309
|
+
a.push('<button class="k-btn ghost" id="btn-open">Open in Kolbo ↗</button>');
|
|
310
|
+
el('actions').innerHTML = a.join('');
|
|
311
|
+
|
|
312
|
+
bind('btn-download', function () { window.kolbo.openLink(currentUrl()); });
|
|
313
|
+
bind('btn-open', function () { window.kolbo.openLink(state.open_url || 'https://app.kolbo.ai'); });
|
|
314
|
+
bind('btn-recreate', function () {
|
|
315
|
+
window.kolbo.sendMessage('Recreate this with the same settings' +
|
|
316
|
+
(state.model ? '\\nModel: ' + state.model : '') +
|
|
317
|
+
(state.prompt ? '\\nPrompt: ' + state.prompt : '') +
|
|
318
|
+
'\\n(from the ' + (TOOL_TITLES[state.tool] || 'generation') + ' widget)');
|
|
319
|
+
});
|
|
320
|
+
bind('btn-animate', function () {
|
|
321
|
+
openPromptRow('Describe the motion (optional — Smart Select picks the best video model)…', function (text) {
|
|
322
|
+
window.kolbo.sendMessage('Animate this image into a short video' +
|
|
323
|
+
'\\n🎬 Reference image: ' + currentUrl() +
|
|
324
|
+
'\\nModel: smart select — pick the best image-to-video model' +
|
|
325
|
+
(text ? '\\nMotion prompt: ' + text : '\\nMotion prompt: subtle cinematic motion, slow push-in'));
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
bind('btn-edit', function () {
|
|
329
|
+
openPromptRow('Describe the edit — e.g. "make the background a beach at sunset"…', function (text) {
|
|
330
|
+
if (!text) return;
|
|
331
|
+
window.kolbo.sendMessage('Edit this image' +
|
|
332
|
+
'\\n🖼 Reference image: ' + currentUrl() +
|
|
333
|
+
'\\nEdit instruction: ' + text);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
bind('btn-analyze', function () {
|
|
337
|
+
window.kolbo.sendMessage('Analyze this video and give me an engagement/virality read — hook strength, pacing, retention risks, and concrete improvement tips:\\n' + currentUrl());
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function bind(id, fn) { var b = el(id); if (b) b.onclick = fn; }
|
|
341
|
+
|
|
342
|
+
function openPromptRow(placeholder, onSend) {
|
|
343
|
+
var row = el('prompt-row');
|
|
344
|
+
row.classList.add('open');
|
|
345
|
+
var input = el('action-input');
|
|
346
|
+
input.placeholder = placeholder;
|
|
347
|
+
input.value = '';
|
|
348
|
+
input.focus();
|
|
349
|
+
el('action-send').onclick = function () { row.classList.remove('open'); onSend(input.value.trim()); };
|
|
350
|
+
input.onkeydown = function (e) { if (e.key === 'Enter') el('action-send').onclick(); };
|
|
351
|
+
el('action-cancel').onclick = function () { row.classList.remove('open'); };
|
|
352
|
+
window.kolbo.notifySize();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* ---------- wire host events ---------- */
|
|
356
|
+
window.kolbo.onToolResult(function (result) {
|
|
357
|
+
var sc = result.structuredContent || structured(result);
|
|
358
|
+
if (sc) boot(sc);
|
|
359
|
+
});
|
|
360
|
+
window.kolbo.ready(function (ctx) {
|
|
361
|
+
// Some hosts deliver the initial result via hostContext.toolInfo; the
|
|
362
|
+
// tool-result notification is the primary path.
|
|
363
|
+
if (!state && ctx && ctx.toolInfo && ctx.toolInfo.result) {
|
|
364
|
+
var sc = ctx.toolInfo.result.structuredContent;
|
|
365
|
+
if (sc) boot(sc);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
`;
|
|
369
|
+
|
|
370
|
+
function generationWidgetHtml() {
|
|
371
|
+
return widgetPage({ title: 'Kolbo Generation', body: BODY, script: SCRIPT });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
module.exports = { generationWidgetHtml };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { widgetPage } = require('../html');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Media grid widget — media library, stock search, presets, voices, moodboards,
|
|
7
|
+
* visual DNAs, music library.
|
|
8
|
+
*
|
|
9
|
+
* structuredContent contract:
|
|
10
|
+
* {
|
|
11
|
+
* widget: 'media-grid',
|
|
12
|
+
* title: 'Stock Search — "rain on window"',
|
|
13
|
+
* items: [{
|
|
14
|
+
* id, title, subtitle, thumbnail, media_type: 'image'|'video'|'audio'|'3d',
|
|
15
|
+
* url, // full asset / playback URL
|
|
16
|
+
* preview_audio, // audio preview URL (voices, music)
|
|
17
|
+
* use_hint // message template sent when "Use" clicked, {URL}/{ID}/{TITLE} substituted
|
|
18
|
+
* }],
|
|
19
|
+
* total, has_more,
|
|
20
|
+
* import_tool_hint // e.g. 'import via import_stock_asset' — shown on Use
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const BODY = `
|
|
25
|
+
<div class="k-card">
|
|
26
|
+
<div class="k-head">
|
|
27
|
+
<span class="k-logo" id="logo"></span>
|
|
28
|
+
<span class="k-title" id="title"></span>
|
|
29
|
+
<span class="k-spacer"></span>
|
|
30
|
+
<span class="k-chip" id="count-chip" style="display:none"></span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="k-body"><div id="stage" class="k-empty">Loading…</div></div>
|
|
33
|
+
<div class="k-footer"><span>Powered by <a href="#" id="kolbo-link">Kolbo.AI</a></span></div>
|
|
34
|
+
</div>
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const SCRIPT = `
|
|
38
|
+
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
39
|
+
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
40
|
+
var state = null;
|
|
41
|
+
var playing = null;
|
|
42
|
+
|
|
43
|
+
function boot(sc) {
|
|
44
|
+
if (!sc || !sc.items) return;
|
|
45
|
+
state = sc;
|
|
46
|
+
el('title').textContent = sc.title || 'Library';
|
|
47
|
+
if (sc.total != null) { el('count-chip').style.display = ''; el('count-chip').textContent = sc.total + ' results'; }
|
|
48
|
+
if (!sc.items.length) { el('stage').innerHTML = '<div class="k-empty">No results</div>'; return; }
|
|
49
|
+
var audioItems = sc.items.filter(function (i) { return i.media_type === 'audio'; });
|
|
50
|
+
var visualItems = sc.items.filter(function (i) { return i.media_type !== 'audio'; });
|
|
51
|
+
var h = '';
|
|
52
|
+
if (visualItems.length) {
|
|
53
|
+
h += '<div class="k-grid">' + visualItems.slice(0, 24).map(cellHTML).join('') + '</div>';
|
|
54
|
+
}
|
|
55
|
+
if (audioItems.length) {
|
|
56
|
+
h += audioItems.slice(0, 12).map(audioRowHTML).join('');
|
|
57
|
+
}
|
|
58
|
+
el('stage').innerHTML = h;
|
|
59
|
+
el('stage').classList.remove('k-empty');
|
|
60
|
+
wire();
|
|
61
|
+
window.kolbo.notifySize();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cellHTML(item, i) {
|
|
65
|
+
var idx = state.items.indexOf(item);
|
|
66
|
+
var media = item.thumbnail
|
|
67
|
+
? '<img src="' + esc(item.thumbnail) + '" loading="lazy" alt="">'
|
|
68
|
+
: '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-faint);font-size:20px">' +
|
|
69
|
+
({ video: '🎬', '3d': '🧊' }[item.media_type] || '🖼') + '</div>';
|
|
70
|
+
return '<div class="k-cell" data-i="' + idx + '">' +
|
|
71
|
+
'<div class="k-cell-media">' + media + '</div>' +
|
|
72
|
+
'<div class="k-cell-label">' + esc(item.title || '') + '</div>' +
|
|
73
|
+
(item.subtitle ? '<div class="k-cell-sub">' + esc(item.subtitle) + '</div>' : '') +
|
|
74
|
+
'</div>';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function audioRowHTML(item) {
|
|
78
|
+
var idx = state.items.indexOf(item);
|
|
79
|
+
return '<div class="k-audio-row" data-i="' + idx + '">' +
|
|
80
|
+
(item.thumbnail ? '<img class="k-audio-art" src="' + esc(item.thumbnail) + '">' : '<div class="k-audio-art"></div>') +
|
|
81
|
+
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(item.title || '') + '</div>' +
|
|
82
|
+
'<div class="k-audio-sub">' + esc(item.subtitle || '') + '</div></div>' +
|
|
83
|
+
(item.preview_audio || item.url
|
|
84
|
+
? '<button class="k-play" data-play="' + esc(item.preview_audio || item.url) + '">▶</button>' : '') +
|
|
85
|
+
'<button class="k-btn" data-use="' + idx + '">Use</button></div>';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function wire() {
|
|
89
|
+
Array.prototype.forEach.call(document.querySelectorAll('.k-cell'), function (c) {
|
|
90
|
+
c.onclick = function () { useItem(+c.getAttribute('data-i')); };
|
|
91
|
+
});
|
|
92
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-use]'), function (b) {
|
|
93
|
+
b.onclick = function (e) { e.stopPropagation(); useItem(+b.getAttribute('data-use')); };
|
|
94
|
+
});
|
|
95
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-play]'), function (b) {
|
|
96
|
+
b.onclick = function (e) {
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
var url = b.getAttribute('data-play');
|
|
99
|
+
if (playing && playing.src === url && !playing.paused) { playing.pause(); b.textContent = '▶'; return; }
|
|
100
|
+
if (playing) playing.pause();
|
|
101
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-play]'), function (x) { x.textContent = '▶'; });
|
|
102
|
+
playing = new Audio(url);
|
|
103
|
+
playing.play();
|
|
104
|
+
b.textContent = '⏸';
|
|
105
|
+
playing.onended = function () { b.textContent = '▶'; };
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function useItem(i) {
|
|
111
|
+
var item = state.items[i];
|
|
112
|
+
if (!item) return;
|
|
113
|
+
var msg = item.use_hint
|
|
114
|
+
? item.use_hint.replace('{URL}', item.url || '').replace('{ID}', item.id || '').replace('{TITLE}', item.title || '')
|
|
115
|
+
: 'Use this asset: "' + (item.title || item.id) + '"\\nURL: ' + (item.url || '') + (item.id ? '\\nID: ' + item.id : '');
|
|
116
|
+
window.kolbo.sendMessage(msg);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
window.kolbo.onToolResult(function (result) {
|
|
120
|
+
var sc = result.structuredContent || structured(result);
|
|
121
|
+
if (sc) boot(sc);
|
|
122
|
+
});
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
function mediaGridWidgetHtml() {
|
|
126
|
+
return widgetPage({ title: 'Kolbo Library', body: BODY, script: SCRIPT });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { mediaGridWidgetHtml };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { widgetPage } = require('../html');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Transcript widget — transcribe_audio results.
|
|
7
|
+
*
|
|
8
|
+
* structuredContent: {
|
|
9
|
+
* widget: 'transcript', phase: 'completed'|'generating'|'failed',
|
|
10
|
+
* generation_id, poll_tool, text, duration, audio_url, srt_url,
|
|
11
|
+
* word_by_word_srt_url, txt_url, credits_used, error
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const BODY = `
|
|
16
|
+
<div class="k-card">
|
|
17
|
+
<div class="k-head">
|
|
18
|
+
<span class="k-logo" id="logo"></span>
|
|
19
|
+
<span class="k-title">Transcription</span>
|
|
20
|
+
<span class="k-spacer"></span>
|
|
21
|
+
<span class="k-chip" id="phase-chip" style="display:none"></span>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="k-body">
|
|
24
|
+
<div id="player"></div>
|
|
25
|
+
<div id="stage" class="k-empty">Loading…</div>
|
|
26
|
+
<div class="k-actions" id="actions"></div>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="k-footer">
|
|
29
|
+
<span>ElevenLabs Scribe v2 · <a href="#" id="kolbo-link">Kolbo.AI</a></span>
|
|
30
|
+
<span class="k-credits" id="credits"></span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const SCRIPT = `
|
|
36
|
+
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
37
|
+
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
38
|
+
var state = null, pollTimer = null;
|
|
39
|
+
|
|
40
|
+
function boot(sc) {
|
|
41
|
+
if (!sc) return;
|
|
42
|
+
state = sc;
|
|
43
|
+
el('credits').textContent = sc.credits_used != null ? fmtCredits(sc.credits_used) : '';
|
|
44
|
+
if (sc.phase === 'generating') {
|
|
45
|
+
el('phase-chip').style.display = '';
|
|
46
|
+
el('phase-chip').innerHTML = '<span class="k-spin"></span>Transcribing';
|
|
47
|
+
el('stage').innerHTML = '<div class="k-skel video" style="min-height:80px"></div>';
|
|
48
|
+
el('stage').classList.remove('k-empty');
|
|
49
|
+
clearTimeout(pollTimer);
|
|
50
|
+
pollTimer = setTimeout(poll, 5000);
|
|
51
|
+
window.kolbo.notifySize();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (sc.phase === 'failed') {
|
|
55
|
+
el('phase-chip').style.display = 'none';
|
|
56
|
+
el('stage').innerHTML = '<div class="k-error">⚠ ' + esc(sc.error || 'Transcription failed') + '</div>';
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
el('phase-chip').style.display = '';
|
|
60
|
+
el('phase-chip').textContent = sc.duration ? fmtDur(sc.duration) : 'Done';
|
|
61
|
+
if (sc.audio_url) {
|
|
62
|
+
el('player').innerHTML = '<audio src="' + esc(sc.audio_url) + '" controls style="width:100%;height:36px;margin-bottom:10px"></audio>';
|
|
63
|
+
}
|
|
64
|
+
el('stage').classList.remove('k-empty');
|
|
65
|
+
el('stage').innerHTML = '<div style="max-height:280px;overflow-y:auto;padding:10px 12px;border-radius:10px;' +
|
|
66
|
+
'background:var(--surface);border:1px solid var(--border);font-size:12.5px;color:var(--text-muted);white-space:pre-wrap">' +
|
|
67
|
+
esc(sc.text || '(empty transcript)') + '</div>';
|
|
68
|
+
var a = [];
|
|
69
|
+
if (sc.srt_url) a.push('<button class="k-btn primary" data-url="' + esc(sc.srt_url) + '">⬇ SRT</button>');
|
|
70
|
+
if (sc.word_by_word_srt_url) a.push('<button class="k-btn" data-url="' + esc(sc.word_by_word_srt_url) + '">⬇ Word-by-word SRT</button>');
|
|
71
|
+
if (sc.txt_url) a.push('<button class="k-btn" data-url="' + esc(sc.txt_url) + '">⬇ TXT</button>');
|
|
72
|
+
a.push('<button class="k-btn ghost" id="btn-copy">Copy text</button>');
|
|
73
|
+
el('actions').innerHTML = a.join('');
|
|
74
|
+
Array.prototype.forEach.call(el('actions').querySelectorAll('[data-url]'), function (b) {
|
|
75
|
+
b.onclick = function () { window.kolbo.openLink(b.getAttribute('data-url')); };
|
|
76
|
+
});
|
|
77
|
+
var copyBtn = el('btn-copy');
|
|
78
|
+
if (copyBtn) copyBtn.onclick = function () {
|
|
79
|
+
try { navigator.clipboard.writeText(state.text || ''); copyBtn.textContent = 'Copied ✓'; } catch (e) {}
|
|
80
|
+
};
|
|
81
|
+
window.kolbo.notifySize();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function poll() {
|
|
85
|
+
window.kolbo.callTool(state.poll_tool || 'get_generation_status', { generation_id: state.generation_id })
|
|
86
|
+
.then(function (res) {
|
|
87
|
+
var st = structured(res) || {};
|
|
88
|
+
var s = st.state || st.phase;
|
|
89
|
+
if (s === 'completed') {
|
|
90
|
+
var r = st.result || st;
|
|
91
|
+
boot(Object.assign({}, state, r, { phase: 'completed', credits_used: st.credits_used }));
|
|
92
|
+
} else if (s === 'failed' || s === 'cancelled') {
|
|
93
|
+
boot(Object.assign({}, state, { phase: 'failed', error: st.error }));
|
|
94
|
+
} else { pollTimer = setTimeout(poll, 5000); }
|
|
95
|
+
}).catch(function () { pollTimer = setTimeout(poll, 5000); });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
window.kolbo.onToolResult(function (result) {
|
|
99
|
+
var sc = result.structuredContent || structured(result);
|
|
100
|
+
if (sc) boot(sc);
|
|
101
|
+
});
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
function transcriptWidgetHtml() {
|
|
105
|
+
return widgetPage({ title: 'Kolbo Transcription', body: BODY, script: SCRIPT });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { transcriptWidgetHtml };
|