@kolbo/mcp 1.72.4 → 1.73.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/package.json +1 -1
- package/skill/SKILL.md +13 -0
- package/skill/references/workflows/visual-dna.md +1 -0
- package/src/apps/bridge.js +42 -3
- package/src/apps/theme.js +11 -1
- package/src/apps/widgets/generation.js +112 -9
- package/src/apps/widgets/mediaGrid.js +8 -3
- package/src/tools/_shared.js +62 -4
- package/src/tools/generate.js +252 -185
- package/src/tools/media.js +0 -7
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -139,6 +139,19 @@ Passing `visual_dna_ids` is **not enough**. For every DNA in that array you MUST
|
|
|
139
139
|
|
|
140
140
|
Resolve names with `list_visual_dnas` first. Full binding rules: `references/workflows/visual-dna.md`.
|
|
141
141
|
|
|
142
|
+
## ⚠️ `enhance_prompt` — leave it OFF (HARD RULE)
|
|
143
|
+
|
|
144
|
+
**Never pass `enhance_prompt: true` unless the user asked for it in words.** It is
|
|
145
|
+
not a quality knob you turn on to be helpful — it sends the prompt to a rewriter
|
|
146
|
+
first, so the model renders *different words than the ones the user wrote*.
|
|
147
|
+
|
|
148
|
+
- The user says "make it more cinematic" → that is a request to change **your**
|
|
149
|
+
prompt. Write the better prompt yourself. It is NOT permission to enhance.
|
|
150
|
+
- Only "enhance the prompt" / "improve my prompt" / "expand this prompt" is.
|
|
151
|
+
- Passing it silently is the worst case: the card shows an `enhanced` chip the
|
|
152
|
+
user never asked for, and their own wording never reached the model.
|
|
153
|
+
- The default is `false` in every generation tool. Leave the argument out.
|
|
154
|
+
|
|
142
155
|
## ⚠️ Seedance / Elements prompt contract (HARD RULE)
|
|
143
156
|
|
|
144
157
|
`generate_elements`, Seedance 2, and Seedance 2.5 share **one** compile shape — the Locked Intro in `references/models/seedance.md`:
|
|
@@ -112,6 +112,7 @@ The match is **literal and case-insensitive**, so:
|
|
|
112
112
|
- The `@name` must equal the stored `name` field (e.g. if `name: "esther_model"` → write `@esther_model`, not `@Esther`, not `@אסתר`, not `@the model`).
|
|
113
113
|
- Any-language characters are supported — if the DNA was created with `name: "אסתר"` you write `@אסתר`. Use the EXACT stored string.
|
|
114
114
|
- Mentions terminate at punctuation (`.,!?`), double-spaces, another `@`, or end of string. So `@maya, wearing...` matches `maya`.
|
|
115
|
+
- **Never glue a possessive onto the tag.** Write `the face of @maya stays stable`, not `@maya's face stays stable` — the tag must end at the stored name. kolbo-api does strip a trailing `'s` / `’s` before the lookup, so a possessive no longer loses the DNA, but the apostrophe is left sitting in the prompt text the model sees.
|
|
115
116
|
|
|
116
117
|
This composes with `@image1` / `@image2` positional tags for plain reference/source images — see "Reference Tagging" below.
|
|
117
118
|
|
package/src/apps/bridge.js
CHANGED
|
@@ -103,12 +103,46 @@ const BRIDGE_JS = `
|
|
|
103
103
|
var card = document.querySelector('.k-card');
|
|
104
104
|
var rect = card ? card.getBoundingClientRect() : null;
|
|
105
105
|
var height = rect ? Math.ceil(rect.bottom + 8) : document.documentElement.scrollHeight;
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
106
|
+
// Ceiling comes from the SCREEN, never window.innerHeight. Inside an iframe
|
|
107
|
+
// innerHeight IS the height the host already granted, so clamping to it made
|
|
108
|
+
// the request a feedback loop: once the host capped us, we could never ask
|
|
109
|
+
// for more than the cap, and any content past it became an inner scrollbar
|
|
110
|
+
// that no amount of growing could clear. The host clamps too, so this is
|
|
111
|
+
// only a sanity ceiling.
|
|
112
|
+
var ceiling = (window.screen && window.screen.availHeight) || 1200;
|
|
113
|
+
height = Math.min(height, Math.max(ceiling, 500));
|
|
109
114
|
notify('ui/notifications/size-changed', {
|
|
110
115
|
width: document.documentElement.scrollWidth, height: height
|
|
111
116
|
});
|
|
117
|
+
markDraggable();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Every image/video in a widget can be dragged straight into the host's
|
|
121
|
+
// composer. <img> is natively draggable but <video> is not, and neither sets
|
|
122
|
+
// a clean URL, so both get an explicit dragstart carrying text/uri-list —
|
|
123
|
+
// exactly the format the composer's drop handler already reads.
|
|
124
|
+
// Hooked off notifySize because that is the one thing every render path
|
|
125
|
+
// already calls, so new render sites are covered without touching them.
|
|
126
|
+
function markDraggable() {
|
|
127
|
+
var nodes = document.querySelectorAll('img[src], video[src]');
|
|
128
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
129
|
+
var node = nodes[i];
|
|
130
|
+
if (node.getAttribute('data-kolbo-drag')) continue;
|
|
131
|
+
node.setAttribute('data-kolbo-drag', '1');
|
|
132
|
+
node.setAttribute('draggable', 'true');
|
|
133
|
+
node.addEventListener('dragstart', onMediaDragStart);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function onMediaDragStart(e) {
|
|
138
|
+
var node = e.currentTarget;
|
|
139
|
+
// Strip the #t=0.05 poster fragment videos carry, or the host would attach
|
|
140
|
+
// a URL the CDN answers differently.
|
|
141
|
+
var url = String(node.currentSrc || node.getAttribute('src') || '').split('#')[0];
|
|
142
|
+
if (!/^https?:/i.test(url) || !e.dataTransfer) return;
|
|
143
|
+
e.dataTransfer.setData('text/uri-list', url);
|
|
144
|
+
e.dataTransfer.setData('text/plain', url);
|
|
145
|
+
e.dataTransfer.effectAllowed = 'copy';
|
|
112
146
|
}
|
|
113
147
|
|
|
114
148
|
var sizeTimer = null;
|
|
@@ -149,6 +183,11 @@ const BRIDGE_JS = `
|
|
|
149
183
|
return request('ui/message', { role: 'user', content: [{ type: 'text', text: text }] });
|
|
150
184
|
},
|
|
151
185
|
openLink: function (url) { return request('ui/open-link', { url: url }); },
|
|
186
|
+
// Hand a piece of this widget's media to the host's composer. Dragging it
|
|
187
|
+
// out cannot work: a widget is a sandboxed cross-origin iframe, so a native
|
|
188
|
+
// HTML5 drag started in here never delivers its dataTransfer to the host
|
|
189
|
+
// document. Hosts that ignore this method simply do nothing.
|
|
190
|
+
attachMedia: function (url) { return request('ui/attach-media', { url: url }); },
|
|
152
191
|
updateModelContext: function (text) {
|
|
153
192
|
return request('ui/update-model-context', { content: [{ type: 'text', text: text }] });
|
|
154
193
|
},
|
package/src/apps/theme.js
CHANGED
|
@@ -122,6 +122,12 @@ body {
|
|
|
122
122
|
}
|
|
123
123
|
.k-chip img.k-voice-thumb { width: 18px; height: 18px; border-radius: 999px; margin-left: -3px; }
|
|
124
124
|
.k-ref-thumb { width: 26px; height: 26px; border-radius: 6px; object-fit: cover; border: 1px solid var(--border-strong); }
|
|
125
|
+
/* Visual DNA chips: the character's face, so you can see WHICH DNA is locked in. */
|
|
126
|
+
.k-dna-face { width: 18px; height: 18px; border-radius: 999px; object-fit: cover; margin-left: -3px; background: var(--border-strong); }
|
|
127
|
+
.k-dna-stack .k-dna-stack-item { display: inline-flex; }
|
|
128
|
+
.k-dna-stack .k-dna-stack-item + .k-dna-stack-item .k-dna-face { margin-left: -9px; }
|
|
129
|
+
.k-dna-stack .k-dna-face { box-shadow: 0 0 0 1.5px var(--card-solid); }
|
|
130
|
+
.k-dna-stack { cursor: default; }
|
|
125
131
|
|
|
126
132
|
/* ---- Generating state ---- */
|
|
127
133
|
.k-gen-grid { display: grid; gap: 8px; }
|
|
@@ -192,6 +198,8 @@ body {
|
|
|
192
198
|
}
|
|
193
199
|
.k-media:hover .k-dl, .k-viewer:hover .k-dl, .k-skel:hover .k-dl { opacity: 1; }
|
|
194
200
|
.k-dl:hover { background: var(--brand); border-color: var(--brand); }
|
|
201
|
+
/* Attach-to-prompt sits immediately left of Download (30px button + 8px gap). */
|
|
202
|
+
.k-attach { right: 46px; }
|
|
195
203
|
.k-viewer { position: relative; }
|
|
196
204
|
|
|
197
205
|
/* Keep the whole completed card under the host's iframe height cap (~800px):
|
|
@@ -199,7 +207,9 @@ body {
|
|
|
199
207
|
or claude.ai adds an inner scrollbar. Click the image to expand in-Claude. */
|
|
200
208
|
.k-viewer { margin-bottom: 10px; }
|
|
201
209
|
.k-viewer img, .k-viewer video { display: block; width: 100%;
|
|
202
|
-
|
|
210
|
+
/* Fixed px, no vh: vh inside an iframe is the height the host granted, so a
|
|
211
|
+
vh-based cap grew as the iframe grew and the card chased its own tail. */
|
|
212
|
+
max-height: 320px; object-fit: contain;
|
|
203
213
|
border-radius: 12px; background: #000; border: 1px solid var(--border); cursor: zoom-in; }
|
|
204
214
|
.k-viewer video { cursor: default; }
|
|
205
215
|
|
|
@@ -148,7 +148,19 @@ function voiceLabel(sc) { return sc.voice_name || sc.voice || (sc.settings || {}
|
|
|
148
148
|
// real reference assets — rendering them as flat prose hid the single most
|
|
149
149
|
// consequential part of the prompt. Escape FIRST, then wrap: the pattern only
|
|
150
150
|
// matches after a boundary, so an email or a #fff hex never lights up.
|
|
151
|
-
|
|
151
|
+
// DOUBLE backslashes: this whole file is a JS template literal, so a single
|
|
152
|
+
// backslash-s / backslash-w is eaten before the browser ever sees it. This was
|
|
153
|
+
// emitting /(^|[s([{"'>])([@#][A-Za-z][w-]*)/g — character classes of the
|
|
154
|
+
// LITERAL letters s and w — so "@zohar_apocalypse" highlighted as just "@z",
|
|
155
|
+
// and a mention after a space (rather than at the very start of the prompt)
|
|
156
|
+
// did not highlight at all.
|
|
157
|
+
// The boundary is a NEGATIVE set, not a whitelist of openers. The old
|
|
158
|
+
// whitelist ([\s([{"'>]) meant any other character glued to a mention killed
|
|
159
|
+
// the chip — "×@tel_aviv_invasion" rendered as flat text. Excluding word chars
|
|
160
|
+
// keeps the thing that whitelist was really protecting: an email's "a@b.com"
|
|
161
|
+
// has a word char before the @, so it still never lights up. (. and - are in
|
|
162
|
+
// the set for the same reason: "file.name@host", "co-op@x".)
|
|
163
|
+
var MENTION_RE = /(^|[^\\w@.-])([@#][A-Za-z][\\w-]*)/g;
|
|
152
164
|
// #ff8800 / #fff are hex colors, and prompts are full of them. A moodboard tag
|
|
153
165
|
// that happens to be 3 or 6 hex letters loses this coin flip; a grading note
|
|
154
166
|
// mistaken for a moodboard is the worse read.
|
|
@@ -174,9 +186,7 @@ function renderChips(sc) {
|
|
|
174
186
|
// Ids where we have them (title = the id, so it can be copied / reused),
|
|
175
187
|
// falling back to the old count/boolean shape for payloads generated before
|
|
176
188
|
// the ids were carried.
|
|
177
|
-
|
|
178
|
-
if (dnaIds.length) h += chipT(dnaIds.length + ' Visual DNA', dnaIds.join('\\n'));
|
|
179
|
-
else if (s.visual_dna) h += chip(s.visual_dna + ' Visual DNA');
|
|
189
|
+
h += dnaChipsHTML(sc, s);
|
|
180
190
|
var mbIds = s.moodboard_ids || (s.moodboard_id ? [s.moodboard_id] : []);
|
|
181
191
|
if (mbIds.length) h += chipT(mbIds.length > 1 ? mbIds.length + ' moodboards' : 'moodboard', mbIds.join('\\n'));
|
|
182
192
|
else if (s.moodboard) h += chip('moodboard');
|
|
@@ -193,12 +203,94 @@ function renderChips(sc) {
|
|
|
193
203
|
}
|
|
194
204
|
if (s.mode) h += chip(esc(s.mode));
|
|
195
205
|
if (sc.count > 1) h += chip('×' + sc.count);
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
h += referenceHTML(sc);
|
|
207
|
+
el('chips').innerHTML = h;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var REF_VIDEO_RE = /\\.(mp4|mov|webm|mkv|avi|m4v)(\\?|#|$)/i;
|
|
211
|
+
var REF_AUDIO_RE = /\\.(mp3|wav|m4a|aac|ogg|flac)(\\?|#|$)/i;
|
|
212
|
+
|
|
213
|
+
// Every reference the generation was actually given — images, videos AND audio.
|
|
214
|
+
// Kind is taken from the URL rather than from which field it arrived in: callers
|
|
215
|
+
// legitimately pack a video into reference_images (Elements files, v2v
|
|
216
|
+
// elements), and an <img> pointed at an .mp4 renders nothing, so those
|
|
217
|
+
// references were silently invisible. Extension wins, field is the fallback.
|
|
218
|
+
function refKind(url, fallback) {
|
|
219
|
+
if (REF_VIDEO_RE.test(url)) return 'video';
|
|
220
|
+
if (REF_AUDIO_RE.test(url)) return 'audio';
|
|
221
|
+
return fallback;
|
|
222
|
+
}
|
|
223
|
+
function collectRefs(sc) {
|
|
224
|
+
var out = [];
|
|
225
|
+
var push = function (list, fallback) {
|
|
226
|
+
(Array.isArray(list) ? list : []).forEach(function (url) {
|
|
227
|
+
if (typeof url !== 'string' || !url) return;
|
|
228
|
+
if (out.some(function (r) { return r.url === url; })) return;
|
|
229
|
+
out.push({ url: url, kind: refKind(url, fallback) });
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
push(sc.reference_images && sc.reference_images.length ? sc.reference_images
|
|
233
|
+
: (sc.reference_image ? [sc.reference_image] : []), 'image');
|
|
234
|
+
push(sc.reference_videos, 'video');
|
|
235
|
+
push(sc.reference_audio, 'audio');
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
function referenceHTML(sc) {
|
|
239
|
+
var refs = collectRefs(sc);
|
|
240
|
+
var h = '';
|
|
198
241
|
for (var i = 0; i < refs.length; i++) {
|
|
199
|
-
|
|
242
|
+
var url = esc(refs[i].url);
|
|
243
|
+
var title = 'Reference ' + refs[i].kind + ' ' + (i + 1) + ' of ' + refs.length;
|
|
244
|
+
if (refs[i].kind === 'video') {
|
|
245
|
+
// #t=0.1 so the poster is a real frame, not a black canvas.
|
|
246
|
+
h += '<video class="k-ref-thumb" src="' + url + '#t=0.1" muted playsinline preload="metadata" title="'
|
|
247
|
+
+ title + '" onerror="this.style.display=\\'none\\'"></video>';
|
|
248
|
+
} else if (refs[i].kind === 'audio') {
|
|
249
|
+
h += '<span class="k-chip" title="' + title + '">' + ICONS.sound + ' audio ref</span>';
|
|
250
|
+
} else {
|
|
251
|
+
h += '<img class="k-ref-thumb" src="' + url + '" alt="" loading="lazy" title="' + title
|
|
252
|
+
+ '" onerror="this.style.display=\\'none\\'">';
|
|
253
|
+
}
|
|
200
254
|
}
|
|
201
|
-
|
|
255
|
+
return h;
|
|
256
|
+
}
|
|
257
|
+
// Which characters/looks are locked into this generation — by face and name,
|
|
258
|
+
// resolved from visual_dna_ids server-side. "1 Visual DNA" told the user nothing
|
|
259
|
+
// about WHICH DNA. Up to DNA_CHIP_MAX get their own named chip; beyond that they
|
|
260
|
+
// collapse into one stack of overlapping faces whose tooltip lists every name,
|
|
261
|
+
// so a 12-DNA scene can't push the model and aspect chips off the card.
|
|
262
|
+
var DNA_CHIP_MAX = 3;
|
|
263
|
+
function dnaFaceHTML(dna, cls) {
|
|
264
|
+
if (dna.thumbnail) {
|
|
265
|
+
return '<img class="' + cls + '" src="' + esc(dna.thumbnail) + '" alt="" loading="lazy" onerror="this.style.display=\\'none\\'">';
|
|
266
|
+
}
|
|
267
|
+
return '';
|
|
268
|
+
}
|
|
269
|
+
function dnaChipsHTML(sc, s) {
|
|
270
|
+
var dnas = Array.isArray(sc.visual_dnas) ? sc.visual_dnas : [];
|
|
271
|
+
if (!dnas.length) {
|
|
272
|
+
// Payloads from before the ids were resolved (or an offline resolve).
|
|
273
|
+
var ids = s.visual_dna_ids || [];
|
|
274
|
+
if (ids.length) return chipT(ids.length + ' Visual DNA', ids.join('\\n'));
|
|
275
|
+
if (s.visual_dna) return chip(s.visual_dna + ' Visual DNA');
|
|
276
|
+
return '';
|
|
277
|
+
}
|
|
278
|
+
var h = '';
|
|
279
|
+
if (dnas.length <= DNA_CHIP_MAX) {
|
|
280
|
+
for (var i = 0; i < dnas.length; i++) {
|
|
281
|
+
h += '<span class="k-chip" title="' + esc(dnas[i].id) + '">'
|
|
282
|
+
+ dnaFaceHTML(dnas[i], 'k-dna-face') + esc(dnas[i].name) + '</span>';
|
|
283
|
+
}
|
|
284
|
+
return h;
|
|
285
|
+
}
|
|
286
|
+
var names = [];
|
|
287
|
+
var stack = '';
|
|
288
|
+
for (var j = 0; j < dnas.length; j++) {
|
|
289
|
+
names.push(dnas[j].name);
|
|
290
|
+
if (j < 4) stack += '<span class="k-dna-stack-item">' + dnaFaceHTML(dnas[j], 'k-dna-face') + '</span>';
|
|
291
|
+
}
|
|
292
|
+
return '<span class="k-chip k-dna-stack" title="' + esc(names.join('\\n')) + '">'
|
|
293
|
+
+ stack + dnas.length + ' Visual DNA</span>';
|
|
202
294
|
}
|
|
203
295
|
function chip(inner) { return '<span class="k-chip">' + inner + '</span>'; }
|
|
204
296
|
// Same chip with a hover title — used to surface the asset id behind a
|
|
@@ -618,7 +710,12 @@ function renderLinks(urls) {
|
|
|
618
710
|
// Small hover download button attached to a media cell (per-item downloads —
|
|
619
711
|
// batch grids and CD scenes have no single "current" url for the action row).
|
|
620
712
|
function dlBtnHTML(u) {
|
|
621
|
-
|
|
713
|
+
// Attach sits beside Download on the same hover overlay. It is the reliable
|
|
714
|
+
// route into the composer — see window.kolbo.attachMedia for why dragging the
|
|
715
|
+
// media out of the iframe cannot be made to work.
|
|
716
|
+
return '<button class="k-dl k-attach" data-attach="' + esc(u) + '" title="Attach to prompt" aria-label="Attach to prompt">'
|
|
717
|
+
+ ICONS.upload + '</button>'
|
|
718
|
+
+ '<button class="k-dl" data-dl="' + esc(u) + '" title="Download" aria-label="Download">' + ICONS.download + '</button>';
|
|
622
719
|
}
|
|
623
720
|
function wireDlButtons(root) {
|
|
624
721
|
Array.prototype.forEach.call((root || document).querySelectorAll('.k-dl[data-dl]'), function (b) {
|
|
@@ -627,6 +724,12 @@ function wireDlButtons(root) {
|
|
|
627
724
|
window.kolbo.openLink(downloadUrl(b.getAttribute('data-dl')));
|
|
628
725
|
};
|
|
629
726
|
});
|
|
727
|
+
Array.prototype.forEach.call((root || document).querySelectorAll('.k-attach[data-attach]'), function (b) {
|
|
728
|
+
b.onclick = function (e) {
|
|
729
|
+
e.stopPropagation();
|
|
730
|
+
window.kolbo.attachMedia(b.getAttribute('data-attach'));
|
|
731
|
+
};
|
|
732
|
+
});
|
|
630
733
|
}
|
|
631
734
|
|
|
632
735
|
// CD scenes render as the SAME viewer+thumbnail carousel as image batches —
|
|
@@ -63,10 +63,15 @@ function boot(sc) {
|
|
|
63
63
|
function cellHTML(item, i) {
|
|
64
64
|
var idx = state.items.indexOf(item);
|
|
65
65
|
var isVideo = item.media_type === 'video' && item.url;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// onerror matters: a thumbnail that 404s / expires / can't be fetched by the
|
|
67
|
+
// host webview left a BLACK tile with no glyph and no label hint — visually
|
|
68
|
+
// identical to "corrupted media". Fall back to the same kind icon a
|
|
69
|
+
// thumbnail-less item gets, so a dead URL degrades instead of looking broken.
|
|
70
|
+
var fallbackCell = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-faint);font-size:22px">' +
|
|
69
71
|
kindIcon(item.media_type) + '</div>';
|
|
72
|
+
var media = item.thumbnail
|
|
73
|
+
? '<img src="' + esc(item.thumbnail) + '" loading="lazy" alt="" onerror="this.parentNode.innerHTML=this.getAttribute(\\'data-fb\\')" data-fb="' + esc(fallbackCell) + '">'
|
|
74
|
+
: fallbackCell;
|
|
70
75
|
return '<div class="k-cell" data-i="' + idx + '">' +
|
|
71
76
|
'<div class="k-cell-media">' + media +
|
|
72
77
|
(isVideo ? '<button class="k-play k-cell-play" data-video-play="' + esc(item.url) + '">' + ICONS.play + '</button>' : '') +
|
package/src/tools/_shared.js
CHANGED
|
@@ -512,6 +512,48 @@ async function modelChipFields(client, model) {
|
|
|
512
512
|
};
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Resolve `visual_dna_ids` to {id, name, thumbnail} so the card can show WHICH
|
|
517
|
+
* characters are locked in, not just how many. An id tells the user nothing;
|
|
518
|
+
* the face does.
|
|
519
|
+
*
|
|
520
|
+
* One list fetch per process, cached — the DNA catalog barely moves within a
|
|
521
|
+
* session, and a generation card must never add a round-trip per chip. A miss
|
|
522
|
+
* (id not in the caller's own DNAs) degrades to the bare id, which is exactly
|
|
523
|
+
* what the card showed before.
|
|
524
|
+
*/
|
|
525
|
+
const _dnaChipCache = new Map();
|
|
526
|
+
let _dnaChipLoaded = 0;
|
|
527
|
+
const DNA_CHIP_TTL = 5 * 60 * 1000;
|
|
528
|
+
|
|
529
|
+
async function resolveVisualDnas(client, ids) {
|
|
530
|
+
const list = Array.isArray(ids) ? ids.filter((id) => typeof id === 'string' && id) : [];
|
|
531
|
+
if (!list.length) return [];
|
|
532
|
+
|
|
533
|
+
const stale = Date.now() - _dnaChipLoaded > DNA_CHIP_TTL;
|
|
534
|
+
if (stale || list.some((id) => !_dnaChipCache.has(id))) {
|
|
535
|
+
try {
|
|
536
|
+
const res = await client.get('/v1/visual-dna?scope=mine');
|
|
537
|
+
const rows = res?.visual_dnas || res?.data || [];
|
|
538
|
+
for (const row of rows) {
|
|
539
|
+
const id = row?.id || row?._id;
|
|
540
|
+
if (!id) continue;
|
|
541
|
+
_dnaChipCache.set(String(id), {
|
|
542
|
+
id: String(id),
|
|
543
|
+
name: row.name || String(id),
|
|
544
|
+
// Same hero-image rule as the app: reference sheet, then thumbnail.
|
|
545
|
+
thumbnail: row.sheet_url || row.thumbnail_url || (Array.isArray(row.images) ? row.images[0] : null) || null,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
_dnaChipLoaded = Date.now();
|
|
549
|
+
} catch {
|
|
550
|
+
// Offline / rate-limited — fall through to bare ids.
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return list.map((id) => _dnaChipCache.get(id) || { id, name: id, thumbnail: null });
|
|
555
|
+
}
|
|
556
|
+
|
|
515
557
|
/**
|
|
516
558
|
* Build the "submitted — widget is live" tool result for a UI host.
|
|
517
559
|
* @param {object} p
|
|
@@ -543,6 +585,7 @@ async function uiGenerating(p) {
|
|
|
543
585
|
prompt: p.prompt,
|
|
544
586
|
count: p.count || 1,
|
|
545
587
|
settings: p.settings || {},
|
|
588
|
+
visual_dnas: await resolveVisualDnas(p.client, (p.settings || {}).visual_dna_ids),
|
|
546
589
|
// `reference_image` is retained for older widget builds. New widgets render
|
|
547
590
|
// every browser-loadable image supplied to the generation.
|
|
548
591
|
reference_images: Array.isArray(p.reference_images)
|
|
@@ -573,10 +616,20 @@ async function uiGenerating(p) {
|
|
|
573
616
|
}
|
|
574
617
|
|
|
575
618
|
/**
|
|
576
|
-
* Wrap an already-completed generation result with the widget
|
|
577
|
-
*
|
|
619
|
+
* Wrap an already-completed generation result with the widget.
|
|
620
|
+
*
|
|
621
|
+
* Used by tools that stay blocking even on UI hosts (creative director), AND —
|
|
622
|
+
* since structuredContent costs a text host nothing — by every generation tool
|
|
623
|
+
* on its normal blocking return. That second case is why model names, model
|
|
624
|
+
* avatars and Visual DNA chips were missing in Kolbo Code: it does not advertise
|
|
625
|
+
* MCP Apps, so `ui()` is false, `uiGenerating` never runs, and the host had to
|
|
626
|
+
* rebuild the card from raw text that carries only a model IDENTIFIER. Shipping
|
|
627
|
+
* the resolved payload here fixes every non-Apps host at once, exactly the way
|
|
628
|
+
* the list tools already do it (see listResult).
|
|
629
|
+
*
|
|
630
|
+
* The TEXT is unchanged, so text-only hosts see precisely what they saw before.
|
|
578
631
|
*/
|
|
579
|
-
async function uiCompleted(p, textPayload) {
|
|
632
|
+
async function uiCompleted(p, textPayload, extraContent) {
|
|
580
633
|
const chip = await modelChipFields(p.client, p.model);
|
|
581
634
|
const structured = {
|
|
582
635
|
phase: 'completed',
|
|
@@ -587,6 +640,7 @@ async function uiCompleted(p, textPayload) {
|
|
|
587
640
|
prompt: p.prompt,
|
|
588
641
|
count: p.count || 1,
|
|
589
642
|
settings: p.settings || {},
|
|
643
|
+
visual_dnas: await resolveVisualDnas(p.client, (p.settings || {}).visual_dna_ids),
|
|
590
644
|
reference_images: Array.isArray(p.reference_images)
|
|
591
645
|
? p.reference_images.filter(Boolean)
|
|
592
646
|
: (p.reference_image ? [p.reference_image] : []),
|
|
@@ -599,7 +653,11 @@ async function uiCompleted(p, textPayload) {
|
|
|
599
653
|
credits_used: p.credits_used,
|
|
600
654
|
open_url: buildOpenUrl(p.tool, p.gen),
|
|
601
655
|
};
|
|
602
|
-
|
|
656
|
+
const out = uiResult(UI.generation, textPayload, structured);
|
|
657
|
+
if (Array.isArray(extraContent) && extraContent.length) {
|
|
658
|
+
out.content = [...out.content, ...extraContent];
|
|
659
|
+
}
|
|
660
|
+
return out;
|
|
603
661
|
}
|
|
604
662
|
|
|
605
663
|
// ─── Text-payload budget ─────────────────────────────────────────────────────
|
package/src/tools/generate.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
7
|
const FormData = require('form-data');
|
|
8
8
|
const { pollUntilDone, waitWindowMs } = require('../polling');
|
|
9
|
-
const { resolveToBuffer, pollOrTimedOut, creditFields, projectIdField, sessionIdField, inlineImageBlocks, buildOpenUrl, uiGenerating, appsEnabled } = require('./_shared');
|
|
9
|
+
const { resolveToBuffer, pollOrTimedOut, creditFields, projectIdField, sessionIdField, inlineImageBlocks, buildOpenUrl, uiGenerating, uiCompleted, appsEnabled } = require('./_shared');
|
|
10
10
|
const { UI, uiResult, canonicalModelId, modelInfo, voiceInfo } = require('../apps');
|
|
11
11
|
|
|
12
12
|
// ─── Cinematic Dimensions schema (shared by generate_image + generate_image_edit) ───
|
|
@@ -224,19 +224,20 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
224
224
|
const result = poll.result;
|
|
225
225
|
|
|
226
226
|
const images = await inlineImageBlocks(result.result.urls, { enabled: inlineImages });
|
|
227
|
-
return {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
227
|
+
return uiCompleted({
|
|
228
|
+
tool: 'generate_image', kind: 'image', gen, client, model, prompt,
|
|
229
|
+
count: num_images, settings: imageSettings(shared),
|
|
230
|
+
reference_images,
|
|
231
|
+
urls: result.result.urls,
|
|
232
|
+
credits_used: creditFields(result).credits_used,
|
|
233
|
+
}, JSON.stringify({
|
|
234
|
+
...creditFields(result),
|
|
235
|
+
session_id: gen.session_id,
|
|
236
|
+
urls: result.result.urls,
|
|
237
|
+
model: result.result.model,
|
|
238
|
+
prompt_used: result.result.prompt_used,
|
|
239
|
+
_followup_hint: 'If the user asks to edit/change/modify this image next (scene, lighting, objects, style, color — any content edit), pass urls[0] to generate_image_edit. Use edit_image ONLY for mechanical ops (upscale/reframe/removebg/enhance_skin). Do NOT call generate_image again.'
|
|
240
|
+
}, null, 2), images);
|
|
240
241
|
}
|
|
241
242
|
);
|
|
242
243
|
|
|
@@ -311,19 +312,20 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
311
312
|
const result = poll.result;
|
|
312
313
|
|
|
313
314
|
const images = await inlineImageBlocks(result.result.urls, { enabled: inlineImages });
|
|
314
|
-
return {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
315
|
+
return uiCompleted({
|
|
316
|
+
tool: 'generate_image_edit', kind: 'image', gen, client, model, prompt,
|
|
317
|
+
count: num_images, settings: imageSettings(shared),
|
|
318
|
+
reference_images: source_images,
|
|
319
|
+
urls: result.result.urls,
|
|
320
|
+
credits_used: creditFields(result).credits_used,
|
|
321
|
+
}, JSON.stringify({
|
|
322
|
+
...creditFields(result),
|
|
323
|
+
session_id: gen.session_id,
|
|
324
|
+
urls: result.result.urls,
|
|
325
|
+
model: result.result.model,
|
|
326
|
+
prompt_used: result.result.prompt_used,
|
|
327
|
+
_followup_hint: 'If the user asks for another edit on this output, pass urls[0] back into generate_image_edit as source_images. For targeted ops (upscale/reframe/removebg/enhance_skin) use edit_image instead. Do NOT call generate_image from scratch.'
|
|
328
|
+
}, null, 2), images);
|
|
327
329
|
}
|
|
328
330
|
);
|
|
329
331
|
|
|
@@ -538,21 +540,24 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
538
540
|
if (poll.timedOut) return poll.timedOut;
|
|
539
541
|
const result = poll.result;
|
|
540
542
|
|
|
541
|
-
return {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
543
|
+
return uiCompleted({
|
|
544
|
+
tool: 'generate_video', kind: 'video', gen, client, model, prompt,
|
|
545
|
+
settings: videoSettings({ duration, resolution, aspect_ratio, enhance_prompt, preset_id }),
|
|
546
|
+
reference_images,
|
|
547
|
+
urls: result.result.urls,
|
|
548
|
+
thumbnail_url: result.result.thumbnail_url,
|
|
549
|
+
duration: result.result.duration,
|
|
550
|
+
credits_used: creditFields(result).credits_used,
|
|
551
|
+
}, JSON.stringify({
|
|
552
|
+
...creditFields(result),
|
|
553
|
+
session_id: gen.session_id,
|
|
554
|
+
urls: result.result.urls,
|
|
555
|
+
model: result.result.model,
|
|
556
|
+
duration: result.result.duration,
|
|
557
|
+
thumbnail_url: result.result.thumbnail_url,
|
|
558
|
+
prompt_used: result.result.prompt_used,
|
|
559
|
+
_followup_hint: 'If the user asks to edit/restyle/extend this video next, pass urls[0] to edit_video (upscale/reframe/face_swap/extend/generate_audio/lipsync/magic_edit) or generate_video_from_video (restyle). Do NOT call generate_video from scratch.'
|
|
560
|
+
}, null, 2));
|
|
556
561
|
}
|
|
557
562
|
);
|
|
558
563
|
|
|
@@ -621,20 +626,23 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
621
626
|
if (poll.timedOut) return poll.timedOut;
|
|
622
627
|
const result = poll.result;
|
|
623
628
|
|
|
624
|
-
return {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
629
|
+
return uiCompleted({
|
|
630
|
+
tool: 'generate_video_from_image', kind: 'video', gen, client, model, prompt,
|
|
631
|
+
settings: videoSettings({ duration, resolution, aspect_ratio, enhance_prompt, visual_dna_ids }),
|
|
632
|
+
reference_images: [image_url],
|
|
633
|
+
urls: result.result.urls,
|
|
634
|
+
thumbnail_url: result.result.thumbnail_url,
|
|
635
|
+
duration: result.result.duration,
|
|
636
|
+
credits_used: creditFields(result).credits_used,
|
|
637
|
+
}, JSON.stringify({
|
|
638
|
+
...creditFields(result),
|
|
639
|
+
session_id: gen.session_id,
|
|
640
|
+
urls: result.result.urls,
|
|
641
|
+
model: result.result.model,
|
|
642
|
+
duration: result.result.duration,
|
|
643
|
+
thumbnail_url: result.result.thumbnail_url,
|
|
644
|
+
_followup_hint: 'If the user asks to edit/restyle/extend this video next, pass urls[0] to edit_video or generate_video_from_video. Do NOT re-run generate_video_from_image unless they want a fresh animation from a different source image. Animating more shots of THIS same sequence? Pass the session_id above back on each of those calls so they all land in one session.'
|
|
645
|
+
}, null, 2));
|
|
638
646
|
}
|
|
639
647
|
);
|
|
640
648
|
|
|
@@ -686,19 +694,21 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
686
694
|
if (poll.timedOut) return poll.timedOut;
|
|
687
695
|
const result = poll.result;
|
|
688
696
|
|
|
689
|
-
return {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
697
|
+
return uiCompleted({
|
|
698
|
+
tool: 'generate_music', kind: 'audio', gen, client, model: model || 'Suno', prompt,
|
|
699
|
+
settings: { mode: instrumental ? 'instrumental' : (style || undefined) },
|
|
700
|
+
urls: result.result.urls,
|
|
701
|
+
title: result.result.title,
|
|
702
|
+
duration: result.result.duration,
|
|
703
|
+
credits_used: creditFields(result).credits_used,
|
|
704
|
+
}, JSON.stringify({
|
|
705
|
+
...creditFields(result),
|
|
706
|
+
session_id: gen.session_id,
|
|
707
|
+
urls: result.result.urls,
|
|
708
|
+
title: result.result.title,
|
|
709
|
+
duration: result.result.duration,
|
|
710
|
+
lyrics: result.result.lyrics
|
|
711
|
+
}, null, 2));
|
|
702
712
|
}
|
|
703
713
|
);
|
|
704
714
|
|
|
@@ -788,19 +798,26 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
788
798
|
if (poll.timedOut) return poll.timedOut;
|
|
789
799
|
const result = poll.result;
|
|
790
800
|
|
|
791
|
-
return {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
801
|
+
return uiCompleted({
|
|
802
|
+
tool: 'generate_speech', kind: 'audio', gen, client, model, prompt: text,
|
|
803
|
+
voice: voiceRecord,
|
|
804
|
+
settings: {
|
|
805
|
+
voice: voice || 'Rachel',
|
|
806
|
+
style: selected_style || emotion || style_instructions_preset_id || style_instructions,
|
|
807
|
+
speaking_speed,
|
|
808
|
+
language,
|
|
809
|
+
},
|
|
810
|
+
urls: result.result.urls,
|
|
811
|
+
duration: result.result.duration,
|
|
812
|
+
credits_used: creditFields(result).credits_used,
|
|
813
|
+
}, JSON.stringify({
|
|
814
|
+
...creditFields(result),
|
|
815
|
+
session_id: gen.session_id,
|
|
816
|
+
urls: result.result.urls,
|
|
817
|
+
voice: result.result.voice,
|
|
818
|
+
duration: result.result.duration,
|
|
819
|
+
...(unknownVoice ? { _warning: unknownVoice } : {})
|
|
820
|
+
}, null, 2));
|
|
804
821
|
}
|
|
805
822
|
);
|
|
806
823
|
|
|
@@ -851,17 +868,19 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
851
868
|
if (poll.timedOut) return poll.timedOut;
|
|
852
869
|
const result = poll.result;
|
|
853
870
|
|
|
854
|
-
return {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
871
|
+
return uiCompleted({
|
|
872
|
+
tool: 'generate_sound', kind: 'audio', gen, client, model, prompt,
|
|
873
|
+
settings: { duration },
|
|
874
|
+
reference_images: seed_reference_image_url ? [seed_reference_image_url] : [],
|
|
875
|
+
urls: result.result.urls,
|
|
876
|
+
duration: result.result.duration,
|
|
877
|
+
credits_used: creditFields(result).credits_used,
|
|
878
|
+
}, JSON.stringify({
|
|
879
|
+
...creditFields(result),
|
|
880
|
+
session_id: gen.session_id,
|
|
881
|
+
urls: result.result.urls,
|
|
882
|
+
duration: result.result.duration
|
|
883
|
+
}, null, 2));
|
|
865
884
|
}
|
|
866
885
|
);
|
|
867
886
|
|
|
@@ -1109,7 +1128,12 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1109
1128
|
...(reference_images || []),
|
|
1110
1129
|
...(keyframes || []).map((keyframe) => keyframe.image_url),
|
|
1111
1130
|
...(files || []).filter((source) => /^https?:\/\//i.test(source))
|
|
1112
|
-
]
|
|
1131
|
+
],
|
|
1132
|
+
// Elements is the one tool that takes all three modalities. The widget
|
|
1133
|
+
// sorts kind by extension, so a video that arrived via `files` is still
|
|
1134
|
+
// rendered as a video — these two just make sure nothing is dropped.
|
|
1135
|
+
reference_videos: reference_videos || [],
|
|
1136
|
+
reference_audio: [...(reference_audio_urls || []), ...(audio_url ? [audio_url] : [])]
|
|
1113
1137
|
});
|
|
1114
1138
|
|
|
1115
1139
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
@@ -1119,19 +1143,33 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1119
1143
|
if (poll.timedOut) return poll.timedOut;
|
|
1120
1144
|
const result = poll.result;
|
|
1121
1145
|
|
|
1122
|
-
return {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1146
|
+
return uiCompleted({
|
|
1147
|
+
tool: 'generate_elements', kind: 'video', gen: startResponse, client, model, prompt,
|
|
1148
|
+
settings: videoSettings({
|
|
1149
|
+
duration,
|
|
1150
|
+
reported_duration: result.result?.duration,
|
|
1151
|
+
shots: multi_shot_count ?? (Array.isArray(multi_shots) ? multi_shots.length : undefined),
|
|
1152
|
+
resolution, aspect_ratio, enhance_prompt, visual_dna_ids, preset_id,
|
|
1153
|
+
}),
|
|
1154
|
+
reference_images: [
|
|
1155
|
+
...(reference_images || []),
|
|
1156
|
+
...(keyframes || []).map((keyframe) => keyframe.image_url),
|
|
1157
|
+
...(files || []).filter((source) => /^https?:\/\//i.test(source))
|
|
1158
|
+
],
|
|
1159
|
+
reference_videos: reference_videos || [],
|
|
1160
|
+
reference_audio: [...(reference_audio_urls || []), ...(audio_url ? [audio_url] : [])],
|
|
1161
|
+
urls: result.result?.urls || [],
|
|
1162
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1163
|
+
duration: result.result?.duration || null,
|
|
1164
|
+
credits_used: creditFields(result).credits_used,
|
|
1165
|
+
}, JSON.stringify({
|
|
1166
|
+
...creditFields(result),
|
|
1167
|
+
session_id: startResponse.session_id,
|
|
1168
|
+
urls: result.result?.urls || [],
|
|
1169
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1170
|
+
duration: result.result?.duration || null,
|
|
1171
|
+
model: result.result?.model || null
|
|
1172
|
+
}, null, 2));
|
|
1135
1173
|
}
|
|
1136
1174
|
);
|
|
1137
1175
|
|
|
@@ -1204,19 +1242,23 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1204
1242
|
if (poll.timedOut) return poll.timedOut;
|
|
1205
1243
|
const result = poll.result;
|
|
1206
1244
|
|
|
1207
|
-
return {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1245
|
+
return uiCompleted({
|
|
1246
|
+
tool: 'generate_first_last_frame', kind: 'video', gen: startResponse, client, model, prompt,
|
|
1247
|
+
settings: videoSettings({ duration, resolution, aspect_ratio, enhance_prompt, visual_dna_ids }),
|
|
1248
|
+
reference_images: [first_frame_url || first_frame, last_frame_url || last_frame]
|
|
1249
|
+
.filter((source) => /^https?:\/\//i.test(source || '')),
|
|
1250
|
+
urls: result.result?.urls || [],
|
|
1251
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1252
|
+
duration: result.result?.duration || null,
|
|
1253
|
+
credits_used: creditFields(result).credits_used,
|
|
1254
|
+
}, JSON.stringify({
|
|
1255
|
+
...creditFields(result),
|
|
1256
|
+
session_id: startResponse.session_id,
|
|
1257
|
+
urls: result.result?.urls || [],
|
|
1258
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1259
|
+
duration: result.result?.duration || null,
|
|
1260
|
+
model: result.result?.model || null
|
|
1261
|
+
}, null, 2));
|
|
1220
1262
|
}
|
|
1221
1263
|
);
|
|
1222
1264
|
|
|
@@ -1319,19 +1361,23 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1319
1361
|
if (poll.timedOut) return poll.timedOut;
|
|
1320
1362
|
const result = poll.result;
|
|
1321
1363
|
|
|
1322
|
-
return {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1364
|
+
return uiCompleted({
|
|
1365
|
+
tool: 'generate_lipsync', kind: 'video', gen: startResponse, client, model,
|
|
1366
|
+
prompt: text_prompt, settings: { mode: 'lipsync' },
|
|
1367
|
+
reference_images: sourceIsUrl && !/\.(mp4|mov|webm|mkv|avi|m4v)(\?|$)/i.test(source) ? [source] : [],
|
|
1368
|
+
reference_videos: sourceIsUrl && /\.(mp4|mov|webm|mkv|avi|m4v)(\?|$)/i.test(source) ? [source] : [],
|
|
1369
|
+
urls: result.result?.urls || [],
|
|
1370
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1371
|
+
duration: result.result?.duration || null,
|
|
1372
|
+
credits_used: creditFields(result).credits_used,
|
|
1373
|
+
}, JSON.stringify({
|
|
1374
|
+
...creditFields(result),
|
|
1375
|
+
session_id: startResponse.session_id,
|
|
1376
|
+
urls: result.result?.urls || [],
|
|
1377
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1378
|
+
duration: result.result?.duration || null,
|
|
1379
|
+
model: result.result?.model || null
|
|
1380
|
+
}, null, 2));
|
|
1335
1381
|
}
|
|
1336
1382
|
);
|
|
1337
1383
|
|
|
@@ -1414,7 +1460,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1414
1460
|
tool: 'generate_video_from_video', kind: 'video', gen: startResponse, client, model,
|
|
1415
1461
|
prompt: prompt || (preset ? `Subtitles preset: ${preset}` : undefined),
|
|
1416
1462
|
settings: videoSettings({ duration, resolution, aspect_ratio, mode: preset ? 'subtitles' : 'restyle', enhance_prompt, visual_dna_ids }),
|
|
1417
|
-
reference_images: [...(reference_images || []), ...(elements || [])]
|
|
1463
|
+
reference_images: [...(reference_images || []), ...(elements || [])],
|
|
1464
|
+
// The source clip IS the primary reference for a restyle — showing the
|
|
1465
|
+
// extra references while hiding the video being transformed was backwards.
|
|
1466
|
+
reference_videos: [
|
|
1467
|
+
...(isUrl ? [source_video] : []),
|
|
1468
|
+
...(reference_videos || [])
|
|
1469
|
+
]
|
|
1418
1470
|
});
|
|
1419
1471
|
|
|
1420
1472
|
const poll = await pollOrTimedOut(client, startResponse.generation_id, {
|
|
@@ -1424,19 +1476,27 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1424
1476
|
if (poll.timedOut) return poll.timedOut;
|
|
1425
1477
|
const result = poll.result;
|
|
1426
1478
|
|
|
1427
|
-
return {
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1479
|
+
return uiCompleted({
|
|
1480
|
+
tool: 'generate_video_from_video', kind: 'video', gen: startResponse, client, model,
|
|
1481
|
+
prompt: prompt || (preset ? `Subtitles preset: ${preset}` : undefined),
|
|
1482
|
+
settings: videoSettings({ duration, resolution, aspect_ratio, mode: preset ? 'subtitles' : 'restyle', enhance_prompt, visual_dna_ids }),
|
|
1483
|
+
reference_images: [...(reference_images || []), ...(elements || [])],
|
|
1484
|
+
reference_videos: [
|
|
1485
|
+
...(isUrl ? [source_video] : []),
|
|
1486
|
+
...(reference_videos || [])
|
|
1487
|
+
],
|
|
1488
|
+
urls: result.result?.urls || [],
|
|
1489
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1490
|
+
duration: result.result?.duration || null,
|
|
1491
|
+
credits_used: creditFields(result).credits_used,
|
|
1492
|
+
}, JSON.stringify({
|
|
1493
|
+
...creditFields(result),
|
|
1494
|
+
session_id: startResponse.session_id,
|
|
1495
|
+
urls: result.result?.urls || [],
|
|
1496
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1497
|
+
duration: result.result?.duration || null,
|
|
1498
|
+
model: result.result?.model || null
|
|
1499
|
+
}, null, 2));
|
|
1440
1500
|
}
|
|
1441
1501
|
);
|
|
1442
1502
|
|
|
@@ -1567,18 +1627,20 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1567
1627
|
if (poll.timedOut) return poll.timedOut;
|
|
1568
1628
|
const result = poll.result;
|
|
1569
1629
|
|
|
1570
|
-
return {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1630
|
+
return uiCompleted({
|
|
1631
|
+
tool: 'generate_3d', kind: '3d', gen: startResponse, client, model, prompt,
|
|
1632
|
+
settings: { mode: mode || (reference_images?.length > 1 ? 'multi' : reference_images?.length === 1 ? 'single' : 'text') },
|
|
1633
|
+
reference_images,
|
|
1634
|
+
urls: result.result?.urls || [],
|
|
1635
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1636
|
+
credits_used: creditFields(result).credits_used,
|
|
1637
|
+
}, JSON.stringify({
|
|
1638
|
+
...creditFields(result),
|
|
1639
|
+
urls: result.result?.urls || [],
|
|
1640
|
+
thumbnail_url: result.result?.thumbnail_url || null,
|
|
1641
|
+
mode: result.result?.mode || null,
|
|
1642
|
+
prompt_used: result.result?.prompt_used || null
|
|
1643
|
+
}, null, 2));
|
|
1582
1644
|
}
|
|
1583
1645
|
);
|
|
1584
1646
|
// ─── edit_image ────────────────────────────────────────────
|
|
@@ -1713,18 +1775,20 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1713
1775
|
if (poll.timedOut) return poll.timedOut;
|
|
1714
1776
|
const result = poll.result;
|
|
1715
1777
|
|
|
1716
|
-
return {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1778
|
+
return uiCompleted({
|
|
1779
|
+
tool: 'edit_image', kind: 'image', gen, client, model,
|
|
1780
|
+
prompt: prompt || operation,
|
|
1781
|
+
settings: { mode: operation, aspect_ratio, scale, resolution },
|
|
1782
|
+
reference_images: [image_url, mask_image_url, ...(additional_images || [])].filter(Boolean),
|
|
1783
|
+
urls: result.result?.urls || [],
|
|
1784
|
+
credits_used: creditFields(result).credits_used,
|
|
1785
|
+
}, JSON.stringify({
|
|
1786
|
+
...creditFields(result),
|
|
1787
|
+
session_id: gen.session_id,
|
|
1788
|
+
urls: result.result?.urls || [],
|
|
1789
|
+
edit_type: result.result?.edit_type || null,
|
|
1790
|
+
model: result.result?.model || null
|
|
1791
|
+
}, null, 2));
|
|
1728
1792
|
}
|
|
1729
1793
|
);
|
|
1730
1794
|
|
|
@@ -1878,20 +1942,23 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1878
1942
|
if (poll.timedOut) return poll.timedOut;
|
|
1879
1943
|
const result = poll.result;
|
|
1880
1944
|
|
|
1881
|
-
return {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1945
|
+
return uiCompleted({
|
|
1946
|
+
tool: 'edit_video', kind: 'video', gen, client, model,
|
|
1947
|
+
prompt: prompt || operation,
|
|
1948
|
+
settings: { mode: operation, duration, aspect_ratio, resolution },
|
|
1949
|
+
reference_images: image_url ? [image_url] : [],
|
|
1950
|
+
urls: result.result?.urls || [],
|
|
1951
|
+
duration: result.result?.duration || null,
|
|
1952
|
+
credits_used: creditFields(result).credits_used,
|
|
1953
|
+
}, JSON.stringify({
|
|
1954
|
+
...creditFields(result),
|
|
1955
|
+
session_id: gen.session_id,
|
|
1956
|
+
urls: result.result?.urls || [],
|
|
1957
|
+
download_url: result.result?.download_url || null,
|
|
1958
|
+
edit_type: result.result?.edit_type || null,
|
|
1959
|
+
duration: result.result?.duration || null,
|
|
1960
|
+
model: result.result?.model || null
|
|
1961
|
+
}, null, 2));
|
|
1895
1962
|
}
|
|
1896
1963
|
);
|
|
1897
1964
|
|
package/src/tools/media.js
CHANGED
|
@@ -291,13 +291,6 @@ function registerMediaTools(server, client, options = {}) {
|
|
|
291
291
|
total: totalItems != null ? totalItems : media.length,
|
|
292
292
|
shown: Math.min(media.length, GRID_CAP)
|
|
293
293
|
});
|
|
294
|
-
|
|
295
|
-
return {
|
|
296
|
-
content: [{
|
|
297
|
-
type: 'text',
|
|
298
|
-
text
|
|
299
|
-
}]
|
|
300
|
-
};
|
|
301
294
|
}
|
|
302
295
|
);
|
|
303
296
|
|