@kolbo/mcp 1.88.4 → 1.89.1
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 +8 -2
- package/package.json +1 -1
- package/src/apps/bridge.js +53 -19
- package/src/apps/html.js +2 -0
- package/src/apps/index.js +8 -8
- package/src/apps/theme.js +58 -10
- package/src/apps/widgets/generation.js +13 -4
- package/src/apps/widgets/list.js +67 -25
- package/src/apps/widgets/mediaGrid.js +148 -79
- package/src/index.js +269 -267
- package/src/toolAnnotations.js +11 -9
- package/src/tools/_shared.js +10 -5
- package/src/tools/agents.js +51 -25
- package/src/tools/chat.js +3 -1
- package/src/tools/editor.js +22 -0
- package/src/tools/generate.js +19 -7
- package/src/tools/media.js +10 -10
- package/src/tools/models.js +11 -5
- package/src/tools/visual_dna.js +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@ Use `list_fonts` / `get_font` to discover My Fonts. Local stdio clients upload o
|
|
|
8
8
|
|
|
9
9
|
Only models reporting `supports_custom_fonts: true` accept these selections. Fonts and backend-generated specimens never go through media upload. See [Personal Fonts](https://docs.kolbo.ai/developer-api/personal-fonts). Availability requires a published client and deployed backend containing these tools; source changes alone do not update installed clients.
|
|
10
10
|
|
|
11
|
+
## No-background images
|
|
12
|
+
|
|
13
|
+
`generate_image` and `generate_image_edit` accept `background: "transparent"` for models whose `list_models` record reports `supports_transparent_background: true`. Kolbo also appends the exact phrase `no background` once to the effective prompt, while the structured setting requests the native alpha channel through the configured provider route. Use `output_format: "png"` or `"webp"` (PNG is selected automatically when omitted). Prompt wording alone is not sufficient, and unsupported models reject the request instead of silently returning an opaque image.
|
|
14
|
+
|
|
11
15
|
Use [Kolbo AI](https://kolbo.ai) as native tools in Claude Code and Claude Desktop via MCP (Model Context Protocol).
|
|
12
16
|
|
|
13
17
|
Generate images, videos, music, speech, sound effects, multi-scene campaigns, and conversational chat — all from natural language in your coding environment. 100+ AI models behind Smart Select routing, with reusable Visual DNA profiles for character/style consistency.
|
|
@@ -278,7 +282,9 @@ Every generation tool also accepts an optional `project_id` arg that routes the
|
|
|
278
282
|
| `generate_character_sheet` | Generate a multi-angle character sheet from reference images (credits) → pass URL to create_visual_dna or update_visual_dna |
|
|
279
283
|
| `list_visual_dna_folders` / `create_visual_dna_folder` / `update_visual_dna_folder` / `delete_visual_dna_folder` / `move_visual_dna_to_folder` | Organize Visual DNA characters into user folders (create/rename/recolor/delete + move DNAs in/out) |
|
|
280
284
|
| `create_project` / `update_project` / `archive_project` / `unarchive_project` | Project lifecycle (create/rename/describe/archive; deletion stays in-app) |
|
|
281
|
-
| `
|
|
285
|
+
| `list_skills` / `create_skill` / `update_skill` / `delete_skill` | Skills — reusable named personas for the chat tool; `description` is the system instruction |
|
|
286
|
+
| `list_agents` / `create_agent` / `update_agent` / `delete_agent` | Same four tools under their original names. Still supported and unchanged; new integrations should use the `*_skill` names |
|
|
287
|
+
| `create_video_editor_session` / `export_video_editor_session` | Build an editable timeline from Kolbo-hosted clips, audio and text, then export it to MP4. No new media is generated; identical snapshots reuse the same export job |
|
|
282
288
|
| `get_creative_director_status` | Re-check a Creative Director batch by generation_id until all parallel scenes finish (use after a `_timed_out` Director run) |
|
|
283
289
|
| `list_sessions` | Enumerate sessions across all types, filterable by project, `type`, and `types[]` |
|
|
284
290
|
| `rename_session` / `delete_session` / `restore_session` | Rename a session; soft-delete leftovers after a move; restore from trash |
|
|
@@ -307,4 +313,4 @@ Both are optional — the local install logs in via the browser on first use.
|
|
|
307
313
|
|
|
308
314
|
### Chat thinking level
|
|
309
315
|
|
|
310
|
-
`chat_send_message` accepts optional `thinking_level`, using an ID from `list_models` with `type: "text"`. The server validates it against the resolved model; omitted or invalid values use `thinkingDefault`. Existing safeguards and legacy `deep_think` take precedence. Discover allowed levels through `thinkingLevels`; no package update is required when the server changes a model capability.
|
|
316
|
+
`chat_send_message` accepts optional `thinking_level`, using an ID from `list_models` with `type: "text"`. The server validates it against the resolved model; omitted or invalid values use `thinkingDefault`. Existing safeguards and legacy `deep_think` take precedence. Discover allowed levels through `thinkingLevels`; no package update is required when the server changes a model capability. For the Auto model, optional `routing_mode` accepts `fast`, `balanced`, or `smart`; omitted uses the server default of `balanced`.
|
package/package.json
CHANGED
package/src/apps/bridge.js
CHANGED
|
@@ -36,6 +36,22 @@ const BRIDGE_JS = `
|
|
|
36
36
|
var queue = []; // deferred host-bound sends until initialized
|
|
37
37
|
var hostContext = null;
|
|
38
38
|
var readyFns = [], toolResultFns = [], themeFns = [], toolInputFns = [];
|
|
39
|
+
var latestResult, latestInput;
|
|
40
|
+
function deliver(f, args) {
|
|
41
|
+
try { f.apply(null, args); }
|
|
42
|
+
catch (e) {
|
|
43
|
+
console.error('Kolbo widget render failed', e);
|
|
44
|
+
var card = document.querySelector('.k-card');
|
|
45
|
+
var notice = document.getElementById('kolbo-render-error');
|
|
46
|
+
if (card && !notice) {
|
|
47
|
+
notice = document.createElement('div');
|
|
48
|
+
notice.id = 'kolbo-render-error';
|
|
49
|
+
notice.setAttribute('role', 'alert');
|
|
50
|
+
notice.textContent = 'This preview could not update. Your generation may still be running. Reopen this card or check your Kolbo library.';
|
|
51
|
+
card.appendChild(notice);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
39
55
|
|
|
40
56
|
function post(msg) { window.parent.postMessage(msg, '*'); }
|
|
41
57
|
|
|
@@ -67,11 +83,13 @@ const BRIDGE_JS = `
|
|
|
67
83
|
return;
|
|
68
84
|
}
|
|
69
85
|
if (m.method === 'ui/notifications/tool-result') {
|
|
70
|
-
|
|
86
|
+
latestResult = m.params || {};
|
|
87
|
+
toolResultFns.forEach(function (f) { deliver(f, [latestResult]); });
|
|
71
88
|
} else if (m.method === 'ui/notifications/tool-input' || m.method === 'ui/notifications/tool-input-partial') {
|
|
72
89
|
// Fires while the tool is still RUNNING — lets widgets show a real
|
|
73
90
|
// "preparing" state instead of a blank card until the result lands.
|
|
74
|
-
|
|
91
|
+
latestInput = [(m.params && m.params.arguments) || {}, m.params || {}];
|
|
92
|
+
toolInputFns.forEach(function (f) { deliver(f, latestInput); });
|
|
75
93
|
} else if (m.method === 'ui/notifications/host-context-changed') {
|
|
76
94
|
hostContext = (m.params && m.params.hostContext) || m.params || hostContext;
|
|
77
95
|
themeFns.forEach(function (f) { try { f(hostContext); } catch (e) {} });
|
|
@@ -88,12 +106,13 @@ const BRIDGE_JS = `
|
|
|
88
106
|
appInfo: { name: 'kolbo-widget', version: '1.0.0' },
|
|
89
107
|
appCapabilities: {}
|
|
90
108
|
}).then(function (res) {
|
|
109
|
+
if (oa) return; // A late JSON-RPC ack must not replace OpenAI host context.
|
|
91
110
|
hostContext = (res && res.hostContext) || null;
|
|
92
111
|
notify('ui/notifications/initialized');
|
|
93
112
|
initialized = true;
|
|
94
113
|
queue.forEach(post);
|
|
95
114
|
queue = [];
|
|
96
|
-
readyFns.forEach(function (f) {
|
|
115
|
+
readyFns.forEach(function (f) { deliver(f, [hostContext]); });
|
|
97
116
|
}).catch(function () { /* host without apps support — widget stays static */ });
|
|
98
117
|
|
|
99
118
|
var fsMode = false; // fullscreen: the HOST owns layout — size reports there
|
|
@@ -176,9 +195,9 @@ const BRIDGE_JS = `
|
|
|
176
195
|
document.addEventListener('loadedmetadata', function () { queueSize(60); }, true);
|
|
177
196
|
|
|
178
197
|
window.kolbo = {
|
|
179
|
-
ready: function (f) { if (initialized) f
|
|
180
|
-
onToolResult: function (f) { toolResultFns.push(f); },
|
|
181
|
-
onToolInput: function (f) { toolInputFns.push(f); },
|
|
198
|
+
ready: function (f) { if (initialized) deliver(f, [hostContext]); else readyFns.push(f); },
|
|
199
|
+
onToolResult: function (f) { toolResultFns.push(f); if (latestResult) deliver(f, [latestResult]); },
|
|
200
|
+
onToolInput: function (f) { toolInputFns.push(f); if (latestInput) deliver(f, latestInput); },
|
|
182
201
|
onThemeChange: function (f) { themeFns.push(f); },
|
|
183
202
|
callTool: function (name, args) { return request('tools/call', { name: name, arguments: args || {} }); },
|
|
184
203
|
sendMessage: function (text) {
|
|
@@ -218,9 +237,13 @@ const BRIDGE_JS = `
|
|
|
218
237
|
// came, and sat on "Preparing" with no prompt, no settings and no result —
|
|
219
238
|
// even after the tool had returned. Everything here maps onto the same
|
|
220
239
|
// callbacks the JSON-RPC path drives, so widgets need no host-specific code.
|
|
221
|
-
var oa =
|
|
222
|
-
|
|
223
|
-
|
|
240
|
+
var oa = null;
|
|
241
|
+
var oaInputSeen, oaOutputSeen;
|
|
242
|
+
function attachOpenAI(event) {
|
|
243
|
+
var current = window.openai;
|
|
244
|
+
if (!current || typeof current !== 'object') return;
|
|
245
|
+
oa = current;
|
|
246
|
+
var globals = event && event.detail && event.detail.globals;
|
|
224
247
|
var oaName = function () {
|
|
225
248
|
var meta = oa.toolResponseMetadata || {};
|
|
226
249
|
return oa.toolName || meta['kolbo/tool'] || (oa.toolOutput && oa.toolOutput.tool) || undefined;
|
|
@@ -232,18 +255,28 @@ const BRIDGE_JS = `
|
|
|
232
255
|
if (!initialized) {
|
|
233
256
|
initialized = true;
|
|
234
257
|
queue = [];
|
|
235
|
-
readyFns.forEach(function (f) {
|
|
258
|
+
readyFns.forEach(function (f) { deliver(f, [hostContext]); });
|
|
236
259
|
} else {
|
|
237
260
|
themeFns.forEach(function (f) { try { f(hostContext); } catch (e) {} });
|
|
238
261
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
262
|
+
var input = globals && Object.prototype.hasOwnProperty.call(globals, 'toolInput') ? globals.toolInput : oa.toolInput;
|
|
263
|
+
var output = globals && Object.prototype.hasOwnProperty.call(globals, 'toolOutput') ? globals.toolOutput : oa.toolOutput;
|
|
264
|
+
var inputKey = JSON.stringify(input);
|
|
265
|
+
var metadata = globals && globals.toolResponseMetadata || oa.toolResponseMetadata || {};
|
|
266
|
+
var envelope = metadata.call_tool_result || metadata.mcp_tool_result;
|
|
267
|
+
var result = envelope && typeof envelope === 'object' ? Object.assign({}, envelope) : {};
|
|
268
|
+
if (output != null) result.structuredContent = output;
|
|
269
|
+
result._meta = Object.assign({}, result._meta || {}, metadata);
|
|
270
|
+
var outputKey = JSON.stringify(result);
|
|
271
|
+
if (input && inputKey !== oaInputSeen) {
|
|
272
|
+
oaInputSeen = inputKey;
|
|
273
|
+
latestInput = [input, { name: oaName(), arguments: input }];
|
|
274
|
+
toolInputFns.forEach(function (f) { deliver(f, latestInput); });
|
|
242
275
|
}
|
|
243
|
-
if (
|
|
244
|
-
oaOutputSeen =
|
|
245
|
-
|
|
246
|
-
toolResultFns.forEach(function (f) {
|
|
276
|
+
if ((output || envelope) && outputKey !== oaOutputSeen) {
|
|
277
|
+
oaOutputSeen = outputKey;
|
|
278
|
+
latestResult = result;
|
|
279
|
+
toolResultFns.forEach(function (f) { deliver(f, [latestResult]); });
|
|
247
280
|
}
|
|
248
281
|
};
|
|
249
282
|
var oaCall = function (method) { return typeof oa[method] === 'function'; };
|
|
@@ -272,13 +305,14 @@ const BRIDGE_JS = `
|
|
|
272
305
|
if (!oaCall('requestDisplayMode')) return Promise.resolve({ mode: 'inline' });
|
|
273
306
|
return Promise.resolve(oa.requestDisplayMode({ mode: mode })).then(function (r) { return { mode: (r && r.mode) || mode }; });
|
|
274
307
|
};
|
|
275
|
-
window.addEventListener('openai:set_globals', oaSync);
|
|
276
308
|
// Deferred: the widget's own script (which registers ready/onToolInput/
|
|
277
309
|
// onToolResult) runs AFTER this bridge block in the same document, and a
|
|
278
310
|
// synchronous sync here fired into empty listener lists and marked the
|
|
279
311
|
// input/output as seen.
|
|
280
|
-
|
|
312
|
+
oaSync();
|
|
281
313
|
}
|
|
314
|
+
window.addEventListener('openai:set_globals', attachOpenAI);
|
|
315
|
+
setTimeout(attachOpenAI, 0);
|
|
282
316
|
})();
|
|
283
317
|
`;
|
|
284
318
|
|
package/src/apps/html.js
CHANGED
|
@@ -60,6 +60,8 @@ var ICONS = {
|
|
|
60
60
|
file: _svg('<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>'),
|
|
61
61
|
copy: _svg('<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>'),
|
|
62
62
|
chevronDown: _svg('<path d="M6 9l6 6 6-6"/>'),
|
|
63
|
+
chevronLeft: _svg('<path d="M15 18l-6-6 6-6"/>'),
|
|
64
|
+
chevronRight: _svg('<path d="M9 18l6-6-6-6"/>'),
|
|
63
65
|
chevronUp: _svg('<path d="M18 15l-6-6-6 6"/>')
|
|
64
66
|
};
|
|
65
67
|
// Media-kind → icon (accepts model kind / media_type strings).
|
package/src/apps/index.js
CHANGED
|
@@ -20,8 +20,8 @@ const { generationWidgetHtml } = require('./widgets/generation');
|
|
|
20
20
|
const { mediaGridWidgetHtml } = require('./widgets/mediaGrid');
|
|
21
21
|
const { catalogWidgetHtml } = require('./widgets/catalog');
|
|
22
22
|
const { transcriptWidgetHtml } = require('./widgets/transcript');
|
|
23
|
-
const { uploadWidgetHtml } = require('./widgets/upload');
|
|
24
|
-
const { fontUploadWidgetHtml } = require('./widgets/fontUpload');
|
|
23
|
+
const { uploadWidgetHtml } = require('./widgets/upload');
|
|
24
|
+
const { fontUploadWidgetHtml } = require('./widgets/fontUpload');
|
|
25
25
|
const { listWidgetHtml } = require('./widgets/list');
|
|
26
26
|
const { HOST_MAP } = require('../cdn');
|
|
27
27
|
const { plansWidgetHtml } = require('./widgets/plans');
|
|
@@ -31,8 +31,8 @@ const UI = {
|
|
|
31
31
|
mediaGrid: 'ui://kolbo/media-grid.html',
|
|
32
32
|
catalog: 'ui://kolbo/catalog.html',
|
|
33
33
|
transcript: 'ui://kolbo/transcript.html',
|
|
34
|
-
upload: 'ui://kolbo/upload.html',
|
|
35
|
-
fontUpload: 'ui://kolbo/font-upload.html',
|
|
34
|
+
upload: 'ui://kolbo/upload.html',
|
|
35
|
+
fontUpload: 'ui://kolbo/font-upload.html',
|
|
36
36
|
list: 'ui://kolbo/list.html',
|
|
37
37
|
plans: 'ui://kolbo/plans.html',
|
|
38
38
|
};
|
|
@@ -42,8 +42,8 @@ const WIDGET_BUILDERS = {
|
|
|
42
42
|
[UI.mediaGrid]: mediaGridWidgetHtml,
|
|
43
43
|
[UI.catalog]: catalogWidgetHtml,
|
|
44
44
|
[UI.transcript]: transcriptWidgetHtml,
|
|
45
|
-
[UI.upload]: uploadWidgetHtml,
|
|
46
|
-
[UI.fontUpload]: fontUploadWidgetHtml,
|
|
45
|
+
[UI.upload]: uploadWidgetHtml,
|
|
46
|
+
[UI.fontUpload]: fontUploadWidgetHtml,
|
|
47
47
|
[UI.list]: listWidgetHtml,
|
|
48
48
|
[UI.plans]: plansWidgetHtml,
|
|
49
49
|
};
|
|
@@ -734,8 +734,8 @@ const TOOL_WIDGETS = {
|
|
|
734
734
|
// never saw it as widget-carrying. Result-level _meta alone isn't enough.
|
|
735
735
|
list_color_palettes: UI.mediaGrid,
|
|
736
736
|
// upload widget
|
|
737
|
-
media_upload_widget: UI.upload,
|
|
738
|
-
font_upload_widget: UI.fontUpload,
|
|
737
|
+
media_upload_widget: UI.upload,
|
|
738
|
+
font_upload_widget: UI.fontUpload,
|
|
739
739
|
// generic list widget — flat record lists with no natural thumbnail
|
|
740
740
|
list_projects: UI.list,
|
|
741
741
|
// Must stay list.html — mapping this to generation.html mounts "Kolbo Generation /
|
package/src/apps/theme.js
CHANGED
|
@@ -354,18 +354,64 @@ html.k-fullscreen .k-actions { flex: none; padding-top: 8px; }
|
|
|
354
354
|
.k-input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }
|
|
355
355
|
.k-input::placeholder { color: var(--text-faint); }
|
|
356
356
|
|
|
357
|
-
/* ----
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
.k-
|
|
362
|
-
|
|
363
|
-
.k-
|
|
364
|
-
|
|
365
|
-
|
|
357
|
+
/* ---- Paged media grid (library / stock / presets / DNAs / moodboards) ----
|
|
358
|
+
Media-first tiles: the artwork IS the row. A title rides a gradient scrim
|
|
359
|
+
INSIDE the tile instead of a text block under it, so a page of results reads
|
|
360
|
+
as a contact sheet rather than a list of captions. No backdrop-filter on the
|
|
361
|
+
scrim for the same reason .k-gen-badge has none — a dozen blurred strips over
|
|
362
|
+
a dozen images forces a per-frame backdrop re-sample on phones. */
|
|
363
|
+
.k-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
|
|
364
|
+
/* Narrow hosts (Kolbo Code sidebar, phone): step down instead of shrinking
|
|
365
|
+
tiles below a thumbnail worth looking at. The 520px block further down wins
|
|
366
|
+
under 520 and takes it to 2. */
|
|
367
|
+
@media (max-width: 620px) { .k-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
|
368
|
+
.k-tile { position: relative; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
|
|
369
|
+
border: 1px solid var(--border); background: var(--surface); cursor: pointer;
|
|
370
|
+
transition: transform 250ms var(--spring), box-shadow 250ms var(--smooth), border-color 150ms var(--smooth); }
|
|
371
|
+
.k-tile:hover { transform: translateY(-2px); border-color: var(--border-strong);
|
|
372
|
+
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.42); }
|
|
373
|
+
.k-tile img, .k-tile video { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
374
|
+
.k-tile-fb { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
375
|
+
color: var(--text-faint); font-size: 24px; }
|
|
376
|
+
.k-tile-cap { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; pointer-events: none;
|
|
377
|
+
padding: 20px 8px 7px; background: linear-gradient(transparent, rgba(0, 0, 0, 0.78)); }
|
|
378
|
+
.k-tile-t { font-size: 11px; font-weight: 600; color: #fff;
|
|
366
379
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
367
|
-
.k-
|
|
380
|
+
.k-tile-s { font-size: 10px; color: rgba(255, 255, 255, 0.66);
|
|
368
381
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
382
|
+
/* Hover actions, revealed on pointer devices and always-on where there is no
|
|
383
|
+
hover — a touch host must never hide the only Use affordance. */
|
|
384
|
+
.k-tile-acts { position: absolute; top: 6px; right: 6px; z-index: 3; display: flex; gap: 4px;
|
|
385
|
+
opacity: 0; transition: opacity 150ms var(--smooth); }
|
|
386
|
+
.k-tile:hover .k-tile-acts, .k-tile:focus-within .k-tile-acts { opacity: 1; }
|
|
387
|
+
@media (hover: none) { .k-tile-acts { opacity: 1; } }
|
|
388
|
+
.k-tile-act { width: 26px; height: 26px; padding: 0; border-radius: 7px;
|
|
389
|
+
border: 1px solid rgba(255, 255, 255, 0.18); background: rgba(0, 0, 0, 0.62); color: #fff;
|
|
390
|
+
display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
|
|
391
|
+
transition: background 150ms var(--smooth); }
|
|
392
|
+
.k-tile-act:hover { background: var(--brand); border-color: var(--brand); }
|
|
393
|
+
.k-tile-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; }
|
|
394
|
+
|
|
395
|
+
/* ---- Pager (media grid + list) ----
|
|
396
|
+
A page of results at a fixed card height, instead of a card that grows a row
|
|
397
|
+
every time you ask for more. Forward past the last loaded page still goes
|
|
398
|
+
through the server's page_tool + next_args contract. */
|
|
399
|
+
.k-pager { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 12px; }
|
|
400
|
+
.k-pager-btn { width: 28px; height: 28px; padding: 0; border-radius: 8px;
|
|
401
|
+
border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
|
|
402
|
+
box-shadow: var(--specular); display: inline-flex; align-items: center; justify-content: center;
|
|
403
|
+
cursor: pointer; transition: background 150ms var(--smooth); }
|
|
404
|
+
.k-pager-btn:hover:not(:disabled) { background: var(--border-strong); }
|
|
405
|
+
.k-pager-btn:disabled { opacity: 0.3; cursor: default; }
|
|
406
|
+
.k-dots { display: flex; align-items: center; gap: 6px; }
|
|
407
|
+
.k-dot { width: 7px; height: 7px; padding: 0; border: 0; border-radius: 999px; cursor: pointer;
|
|
408
|
+
background: var(--text); opacity: 0.28;
|
|
409
|
+
transition: width 220ms var(--spring), opacity 150ms var(--smooth); }
|
|
410
|
+
.k-dot:hover { opacity: 0.6; }
|
|
411
|
+
.k-dot.on { width: 22px; opacity: 0.95; cursor: default; }
|
|
412
|
+
.k-dot.ghost { cursor: default; opacity: 0.14; }
|
|
413
|
+
.k-pager-label { font-size: 11px; color: var(--text-faint);
|
|
414
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace; min-width: 58px; text-align: center; }
|
|
369
415
|
|
|
370
416
|
/* ---- Audio rows ---- */
|
|
371
417
|
.k-audio-row { display: flex; align-items: center; gap: 10px; padding: 9px 10px;
|
|
@@ -405,6 +451,8 @@ html.k-fullscreen .k-actions { flex: none; padding-top: 8px; }
|
|
|
405
451
|
.k-body { padding: 10px 12px 12px; }
|
|
406
452
|
.k-footer { padding: 8px 12px 12px; }
|
|
407
453
|
.k-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
454
|
+
.k-tile-act { width: 30px; height: 30px; }
|
|
455
|
+
.k-pager-btn { width: 34px; height: 34px; }
|
|
408
456
|
.k-actions { flex-direction: column; align-items: stretch; }
|
|
409
457
|
.k-actions .k-btn { width: 100%; min-height: 44px; justify-content: center; }
|
|
410
458
|
.k-btn { min-height: 40px; padding: 10px 14px; }
|
|
@@ -659,6 +659,7 @@ function renderCancelled(creditsRefunded) {
|
|
|
659
659
|
var MAX_POLL_MS = 35 * 60 * 1000;
|
|
660
660
|
var MAX_POLL_ERRORS = 30;
|
|
661
661
|
var pollStart = 0, pollErrors = 0;
|
|
662
|
+
var pollInFlight = false;
|
|
662
663
|
var cancelRequested = false; // set by the Stop button; freezes the poll loop
|
|
663
664
|
|
|
664
665
|
/* ---------- offscreen gate ----------
|
|
@@ -696,6 +697,7 @@ function releaseSeen() {
|
|
|
696
697
|
|
|
697
698
|
function schedulePoll(sc) {
|
|
698
699
|
if (cancelRequested) return;
|
|
700
|
+
if (!sc.generation_id && !(sc.generation_ids && sc.generation_ids.length) && !sc.status_args) return;
|
|
699
701
|
if (!seen) { whenSeenFns.push(function () { schedulePoll(sc); }); return; }
|
|
700
702
|
// The call itself long-waits server-side, for one transport-safe window
|
|
701
703
|
// (~45s over the remote connector — see WAIT_WINDOW_MS in tools/generate.js).
|
|
@@ -707,12 +709,15 @@ function schedulePoll(sc) {
|
|
|
707
709
|
pollTimer = setTimeout(function () { poll(sc); }, delay);
|
|
708
710
|
}
|
|
709
711
|
function poll(sc) {
|
|
710
|
-
if (cancelRequested) return;
|
|
712
|
+
if (cancelRequested || pollInFlight) return;
|
|
711
713
|
if (pollStart && (Date.now() - pollStart) > MAX_POLL_MS) {
|
|
712
714
|
return renderTrackingIssue('This is still running longer than the tracking window. Do not retry it — any completed result will appear in your Kolbo library.');
|
|
713
715
|
}
|
|
714
716
|
var args = sc.status_args || { generation_id: sc.generation_id, wait: true };
|
|
717
|
+
pollInFlight = true;
|
|
715
718
|
window.kolbo.callTool(sc.poll_tool || 'get_generation_status', args).then(function (res) {
|
|
719
|
+
pollInFlight = false;
|
|
720
|
+
if (cancelRequested || (state && (state.phase === 'completed' || state.phase === 'failed'))) return;
|
|
716
721
|
var st = structured(res) || {};
|
|
717
722
|
var stateName = st.state || st.phase || st.status;
|
|
718
723
|
// A failed status CALL (tool error / not-found / {success:false}) is not a
|
|
@@ -766,6 +771,8 @@ function poll(sc) {
|
|
|
766
771
|
schedulePoll(sc);
|
|
767
772
|
}
|
|
768
773
|
}).catch(function () {
|
|
774
|
+
pollInFlight = false;
|
|
775
|
+
if (cancelRequested || (state && (state.phase === 'completed' || state.phase === 'failed'))) return;
|
|
769
776
|
if (++pollErrors >= MAX_POLL_ERRORS) return renderTrackingIssue('Tracking paused after repeated connection errors. The generation may still be running.');
|
|
770
777
|
schedulePoll(sc);
|
|
771
778
|
});
|
|
@@ -1340,20 +1347,20 @@ var PRE_VIDEO_KEYS = ['source_video', 'reference_videos'];
|
|
|
1340
1347
|
var PRE_AUDIO_KEYS = ['audio', 'audio_url', 'reference_audio_urls', 'seed_reference_audio_urls'];
|
|
1341
1348
|
|
|
1342
1349
|
// The card mounts the moment the tool is CALLED, so the only thing it knows is
|
|
1343
|
-
// the raw tool input
|
|
1350
|
+
// the raw tool input — and for an edit/elements call the submit that follows is
|
|
1344
1351
|
// the LONGEST wait on the card (local files are re-hosted first). Map the input
|
|
1345
1352
|
// onto the same shape renderChips already reads for a server payload so the
|
|
1346
1353
|
// references, DNA count and settings are on screen immediately instead of after
|
|
1347
1354
|
// a minute of blank skeleton.
|
|
1348
1355
|
// Deliberately NOT shown here: model, voice, DNA and moodboard NAMES. Those are
|
|
1349
1356
|
// resolved server-side and arrive with the result (which overwrites all of
|
|
1350
|
-
// this)
|
|
1357
|
+
// this) — rendering the raw identifier the caller passed would put an id on the
|
|
1351
1358
|
// card, which is never allowed.
|
|
1352
1359
|
function preRefSc(toolName, a) {
|
|
1353
1360
|
var img = [], vid = [], aud = [];
|
|
1354
1361
|
var take = function (v, bucket) {
|
|
1355
1362
|
if (typeof v === 'string') {
|
|
1356
|
-
// http(s) only
|
|
1363
|
+
// http(s) only — an absolute local path is not loadable from the iframe.
|
|
1357
1364
|
if (/^https?:/i.test(v) && bucket.indexOf(v) < 0) bucket.push(v);
|
|
1358
1365
|
return;
|
|
1359
1366
|
}
|
|
@@ -1484,6 +1491,8 @@ function completedFromPlain(sc) {
|
|
|
1484
1491
|
}
|
|
1485
1492
|
|
|
1486
1493
|
/* ---------- wire host events ---------- */
|
|
1494
|
+
// Paint before the host handshake: tool input may arrive late or be withheld.
|
|
1495
|
+
bootPre(null, null);
|
|
1487
1496
|
window.kolbo.onToolResult(function (result) {
|
|
1488
1497
|
var sc = result.structuredContent || structured(result);
|
|
1489
1498
|
var list = listPayload(sc);
|
package/src/apps/widgets/list.js
CHANGED
|
@@ -7,7 +7,8 @@ const { widgetPage } = require('../html');
|
|
|
7
7
|
* with no natural thumbnail (projects, sessions, agents, docs, folders,
|
|
8
8
|
* knowledge-base sources). Reuses the exact row shell mediaGrid's audio rows
|
|
9
9
|
* and catalog's model rows already use (`.k-audio-row` / `.k-audio-meta` /
|
|
10
|
-
* `.k-chip` / `.k-btn` — no new CSS needed)
|
|
10
|
+
* `.k-chip` / `.k-btn` — no new CSS needed), and the same pager the media grid
|
|
11
|
+
* uses, so a long list is a fixed-height page instead of a card that grows.
|
|
11
12
|
*
|
|
12
13
|
* structuredContent contract:
|
|
13
14
|
* {
|
|
@@ -43,6 +44,11 @@ const SCRIPT = `
|
|
|
43
44
|
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
44
45
|
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
45
46
|
var state = null;
|
|
47
|
+
var page = 0;
|
|
48
|
+
var PAGE = 8;
|
|
49
|
+
|
|
50
|
+
function pageCount() { return Math.max(1, Math.ceil(((state && state.items) || []).length / PAGE)); }
|
|
51
|
+
function hasMore() { return !!(state && state.page_tool && state.next_args); }
|
|
46
52
|
|
|
47
53
|
function boot(sc) {
|
|
48
54
|
var list = listPayload(sc);
|
|
@@ -50,49 +56,73 @@ function boot(sc) {
|
|
|
50
56
|
// listPayload() normalises plain shapes; carry the paging contract through.
|
|
51
57
|
if (sc && sc.page_tool && !list.page_tool) { list.page_tool = sc.page_tool; list.next_args = sc.next_args; list.page = sc.page; }
|
|
52
58
|
state = list;
|
|
59
|
+
page = 0;
|
|
53
60
|
el('title').textContent = list.title || 'List';
|
|
54
|
-
|
|
61
|
+
render();
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function render() {
|
|
66
|
+
var total = state.total != null ? state.total : state.items.length;
|
|
55
67
|
el('count-chip').style.display = '';
|
|
56
68
|
el('count-chip').textContent = total + (total === 1 ? ' item' : ' items');
|
|
57
|
-
if (!
|
|
69
|
+
if (!state.items.length) {
|
|
58
70
|
el('stage').classList.add('k-empty');
|
|
59
71
|
el('stage').innerHTML = 'Nothing here yet';
|
|
60
72
|
window.kolbo.notifySize();
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
// Render every item the server sent (the old slice(0, 40) silently dropped
|
|
64
|
-
// rows, and nothing told the user). Load more only when the server said how.
|
|
65
|
-
var h = list.items.map(itemHTML).join('');
|
|
66
|
-
var shown = list.items.length;
|
|
67
|
-
if (list.page_tool && list.next_args) {
|
|
68
|
-
h += '<button class="k-btn" id="load-more" style="width:100%;margin-top:10px">Load more' +
|
|
69
|
-
(total > shown ? ' (' + shown + ' of ' + total + ' shown)' : '') + '</button>';
|
|
73
|
+
return;
|
|
70
74
|
}
|
|
75
|
+
if (page > pageCount() - 1) page = pageCount() - 1;
|
|
76
|
+
var h = state.items.slice(page * PAGE, page * PAGE + PAGE).map(itemHTML).join('') + pagerHTML();
|
|
71
77
|
el('stage').innerHTML = h;
|
|
72
78
|
el('stage').classList.remove('k-empty');
|
|
73
79
|
wire();
|
|
74
|
-
var lm = el('load-more');
|
|
75
|
-
if (lm) lm.onclick = function () { fetchNextPage(lm); };
|
|
76
80
|
window.kolbo.notifySize();
|
|
77
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function pagerHTML() {
|
|
84
|
+
var n = pageCount();
|
|
85
|
+
var more = hasMore();
|
|
86
|
+
if (n <= 1 && !more) return '';
|
|
87
|
+
var mid;
|
|
88
|
+
if (n <= 8) {
|
|
89
|
+
var dots = '';
|
|
90
|
+
for (var i = 0; i < n; i++) {
|
|
91
|
+
dots += '<button class="k-dot' + (i === page ? ' on' : '') + '" data-page="' + i + '" aria-label="Page ' + (i + 1) + '"></button>';
|
|
92
|
+
}
|
|
93
|
+
if (more) dots += '<span class="k-dot ghost"></span>';
|
|
94
|
+
mid = '<div class="k-dots">' + dots + '</div>';
|
|
95
|
+
} else {
|
|
96
|
+
mid = '<span class="k-pager-label">' + (page + 1) + ' / ' + n + (more ? '+' : '') + '</span>';
|
|
97
|
+
}
|
|
98
|
+
return '<div class="k-pager">' +
|
|
99
|
+
'<button class="k-pager-btn" id="page-prev" aria-label="Previous"' + (page === 0 ? ' disabled' : '') + '>' + ICONS.chevronLeft + '</button>' +
|
|
100
|
+
mid +
|
|
101
|
+
'<button class="k-pager-btn" id="page-next" aria-label="Next"' + (page >= n - 1 && !more ? ' disabled' : '') + '>' + ICONS.chevronRight + '</button>' +
|
|
102
|
+
'</div>';
|
|
78
103
|
}
|
|
79
104
|
|
|
80
105
|
function fetchNextPage(btn) {
|
|
81
106
|
if (!state || !state.page_tool || !state.next_args || btn.disabled) return;
|
|
82
107
|
btn.disabled = true;
|
|
83
|
-
|
|
84
|
-
btn.innerHTML = '<span class="k-spin"></span> Loading';
|
|
108
|
+
btn.innerHTML = '<span class="k-spin"></span>';
|
|
85
109
|
window.kolbo.callTool(state.page_tool, state.next_args).then(function (res) {
|
|
86
110
|
var sc = structured(res);
|
|
87
111
|
var next = sc ? listPayload(sc) || sc : null;
|
|
88
112
|
var more = (next && next.items) || [];
|
|
89
|
-
if ((res && res.isError) || !next) {
|
|
90
|
-
|
|
113
|
+
if ((res && res.isError) || !next) {
|
|
114
|
+
btn.disabled = false;
|
|
115
|
+
btn.innerHTML = ICONS.chevronRight;
|
|
116
|
+
btn.title = 'Could not load more — try again';
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!more.length) { state.next_args = undefined; render(); return; }
|
|
91
120
|
state.items = state.items.concat(more);
|
|
92
121
|
state.next_args = sc.next_args;
|
|
93
122
|
if (sc.total != null) state.total = sc.total;
|
|
94
|
-
|
|
95
|
-
|
|
123
|
+
page = page + 1;
|
|
124
|
+
render();
|
|
125
|
+
}).catch(function () { btn.disabled = false; btn.innerHTML = ICONS.chevronRight; });
|
|
96
126
|
}
|
|
97
127
|
|
|
98
128
|
function apply(result) {
|
|
@@ -101,7 +131,8 @@ function apply(result) {
|
|
|
101
131
|
return boot(inner.structuredContent || structured(inner) || inner);
|
|
102
132
|
}
|
|
103
133
|
|
|
104
|
-
function itemHTML(item
|
|
134
|
+
function itemHTML(item) {
|
|
135
|
+
var i = state.items.indexOf(item);
|
|
105
136
|
var clickable = !!item.id;
|
|
106
137
|
var avatar = item.thumbnail
|
|
107
138
|
? '<img class="k-audio-art k-peek-hit" src="' + esc(item.thumbnail) + '" alt="" loading="lazy"'
|
|
@@ -119,14 +150,15 @@ function itemHTML(item, i) {
|
|
|
119
150
|
}
|
|
120
151
|
|
|
121
152
|
function wire() {
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
var stage = el('stage');
|
|
154
|
+
bindPeekHits(stage);
|
|
155
|
+
Array.prototype.forEach.call(stage.querySelectorAll('[data-open]'), function (b) {
|
|
124
156
|
b.onclick = function (e) {
|
|
125
157
|
e.stopPropagation();
|
|
126
158
|
window.kolbo.openLink(state.items[+b.getAttribute('data-open')].open_url);
|
|
127
159
|
};
|
|
128
160
|
});
|
|
129
|
-
Array.prototype.forEach.call(
|
|
161
|
+
Array.prototype.forEach.call(stage.querySelectorAll('.k-audio-row'), function (row) {
|
|
130
162
|
var item = state.items[+row.getAttribute('data-i')];
|
|
131
163
|
if (!item || !item.id) return;
|
|
132
164
|
row.onclick = function (e) {
|
|
@@ -134,6 +166,16 @@ function wire() {
|
|
|
134
166
|
window.kolbo.insertText(String(item.id));
|
|
135
167
|
};
|
|
136
168
|
});
|
|
169
|
+
Array.prototype.forEach.call(stage.querySelectorAll('[data-page]'), function (d) {
|
|
170
|
+
d.onclick = function () { var p = +d.getAttribute('data-page'); if (p !== page) { page = p; render(); } };
|
|
171
|
+
});
|
|
172
|
+
var prev = el('page-prev');
|
|
173
|
+
if (prev) prev.onclick = function () { if (page > 0) { page--; render(); } };
|
|
174
|
+
var next = el('page-next');
|
|
175
|
+
if (next) next.onclick = function () {
|
|
176
|
+
if (page < pageCount() - 1) { page++; render(); return; }
|
|
177
|
+
fetchNextPage(next);
|
|
178
|
+
};
|
|
137
179
|
}
|
|
138
180
|
|
|
139
181
|
window.kolbo.onToolResult(function (result) {
|