kingkont 0.15.0 → 0.15.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/lib/providers.js +6 -1
- package/package.json +1 -1
- package/renderer/chat.js +68 -6
package/lib/providers.js
CHANGED
|
@@ -602,7 +602,12 @@ async function generateMusic(args) {
|
|
|
602
602
|
const { prompt, durationMs, settings: s } = args;
|
|
603
603
|
if (!prompt) throw new Error('нужен prompt');
|
|
604
604
|
|
|
605
|
-
|
|
605
|
+
// Приоритет direct ElevenLabs если включён + есть ключ — у юзера могут
|
|
606
|
+
// быть кастомные саб-настройки и платный ElevenLabs (зачем тратить
|
|
607
|
+
// Chatium-кредиты). Аналогичная логика в generateTts/generateSfx.
|
|
608
|
+
const directElevenAvailable = s.useElevenlabs && process.env.ELEVENLABS_API_KEY;
|
|
609
|
+
|
|
610
|
+
if (s.useChatium && s.chatium?.token && s.chatium?.base && !directElevenAvailable) {
|
|
606
611
|
return await audioViaChatium(s, { kind: 'music', prompt, durationMs });
|
|
607
612
|
}
|
|
608
613
|
if (!s.useElevenlabs) throw new Error('Войдите в KingKont или ElevenLabs для аудио.');
|
package/package.json
CHANGED
package/renderer/chat.js
CHANGED
|
@@ -183,6 +183,50 @@
|
|
|
183
183
|
},
|
|
184
184
|
},
|
|
185
185
|
|
|
186
|
+
move_node: {
|
|
187
|
+
description: 'Передвинуть ноду в новые координаты (или сдвинуть относительно текущих через dx/dy). x/y абсолютные в canvas-coord. Если нужно сдвинуть несколько нод подряд — выдавай несколько move_node.',
|
|
188
|
+
params: '{"id":"<node-id>","x":<absolute>,"y":<absolute>,"dx":<relative-shift>,"dy":<relative-shift>}',
|
|
189
|
+
async handler({ id, x, y, dx, dy }) {
|
|
190
|
+
const b = state.currentBoard;
|
|
191
|
+
if (!b) throw new Error('доска не выбрана');
|
|
192
|
+
const node = b.metadata.nodes.find(n => n.id === id);
|
|
193
|
+
if (!node) throw new Error(`нода не найдена: ${id}`);
|
|
194
|
+
if (typeof x === 'number') node.x = x;
|
|
195
|
+
if (typeof y === 'number') node.y = y;
|
|
196
|
+
if (typeof dx === 'number') node.x = (node.x || 0) + dx;
|
|
197
|
+
if (typeof dy === 'number') node.y = (node.y || 0) + dy;
|
|
198
|
+
// Clamp к >= 50 чтобы не уехала в негатив за viewport.
|
|
199
|
+
if (node.x < 50) node.x = 50;
|
|
200
|
+
if (node.y < 50) node.y = 50;
|
|
201
|
+
scheduleSave();
|
|
202
|
+
if (typeof renderCanvas === 'function') await renderCanvas();
|
|
203
|
+
return { ok: true, x: node.x, y: node.y };
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
edit_scene_json: {
|
|
208
|
+
description: 'Прямое редактирование scene.json текущей доски — для нестандартных операций (массовая правка позиций, sort нод, чистка metadata, и т.п.). Передай patch как объект — поля заменяют существующие. Для замены ВСЕГО массива nodes — передай {nodes: [...]}. ВНИМАНИЕ: ошибка тут разрушит сцену — обязательно вызови take_snapshot ПЕРЕД.',
|
|
209
|
+
params: '{"patch":{...}}',
|
|
210
|
+
async handler({ patch }) {
|
|
211
|
+
const b = state.currentBoard;
|
|
212
|
+
if (!b) throw new Error('доска не выбрана');
|
|
213
|
+
if (!patch || typeof patch !== 'object') throw new Error('patch должен быть объектом');
|
|
214
|
+
// Применяем patch к metadata (top-level merge). Для nodes/connections
|
|
215
|
+
// если переданы — заменяем массив целиком (не merge).
|
|
216
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
217
|
+
b.metadata[k] = v;
|
|
218
|
+
}
|
|
219
|
+
scheduleSave();
|
|
220
|
+
if (typeof renderCanvas === 'function') await renderCanvas();
|
|
221
|
+
if (typeof renderConnections === 'function') renderConnections();
|
|
222
|
+
return {
|
|
223
|
+
ok: true,
|
|
224
|
+
nodesCount: (b.metadata.nodes || []).length,
|
|
225
|
+
connectionsCount: (b.metadata.connections || []).length,
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
|
|
186
230
|
update_node_prompt: {
|
|
187
231
|
description: 'Обновить промпт у image/video/audio ноды (без re-генерации).',
|
|
188
232
|
params: '{"id":"<node-id>","prompt":"<new>"}',
|
|
@@ -278,9 +322,18 @@
|
|
|
278
322
|
async handler({ nodeId, trackKind, start, duration }) {
|
|
279
323
|
const b = state.currentBoard;
|
|
280
324
|
if (!b) throw new Error('доска не выбрана');
|
|
325
|
+
// Re-read scene с диска чтобы видеть свежий node.file (после
|
|
326
|
+
// background-генерации). Без этого chat'оподобный flow «добавь и
|
|
327
|
+
// сразу в таймлайн» — файла ещё нет в node.file in-memory.
|
|
328
|
+
try {
|
|
329
|
+
const sceneFh = await b.handle.getFileHandle('scene.json');
|
|
330
|
+
const txt = await (await sceneFh.getFile()).text();
|
|
331
|
+
const scene = JSON.parse(txt);
|
|
332
|
+
if (Array.isArray(scene.nodes)) b.metadata.nodes = scene.nodes;
|
|
333
|
+
} catch {}
|
|
281
334
|
const node = b.metadata.nodes.find(n => n.id === nodeId);
|
|
282
335
|
if (!node) throw new Error(`нода не найдена: ${nodeId}`);
|
|
283
|
-
if (!node.file) throw new Error('у ноды нет файла (нужно сначала сгенерировать)');
|
|
336
|
+
if (!node.file) throw new Error('у ноды нет файла (нужно сначала сгенерировать и дождаться)');
|
|
284
337
|
if (typeof getTimeline !== 'function') throw new Error('getTimeline недоступен');
|
|
285
338
|
const tl = getTimeline();
|
|
286
339
|
const tk = trackKind === 'audio' ? 'audio' : 'video';
|
|
@@ -304,8 +357,14 @@
|
|
|
304
357
|
};
|
|
305
358
|
track.clips.push(clip);
|
|
306
359
|
scheduleSave();
|
|
307
|
-
|
|
308
|
-
|
|
360
|
+
// Force open + render: иначе thumbnails не загружаются (рендер скип'ался
|
|
361
|
+
// при скрытой панели, и при следующем открытии состояние UI было stale).
|
|
362
|
+
const tlPanel = document.getElementById('timelinePanel');
|
|
363
|
+
if (tlPanel?.classList.contains('hidden')) {
|
|
364
|
+
// Открываем таймлайн чтобы юзер видел что добавилось.
|
|
365
|
+
document.getElementById('timelineBtn')?.click();
|
|
366
|
+
} else if (typeof renderTimeline === 'function') {
|
|
367
|
+
await renderTimeline();
|
|
309
368
|
}
|
|
310
369
|
return { ok: true, clipId: clip.id, trackId: track.id, start: clip.start, duration: clip.duration };
|
|
311
370
|
},
|
|
@@ -1423,12 +1482,15 @@
|
|
|
1423
1482
|
history = []; snapshots = [];
|
|
1424
1483
|
renderHistory(); renderSnapshotBar();
|
|
1425
1484
|
},
|
|
1426
|
-
// Reset-on-close: останавливаем polling
|
|
1427
|
-
//
|
|
1485
|
+
// Reset-on-close: НЕ останавливаем polling — серверный loop может ещё
|
|
1486
|
+
// ждать tool-results. Если бросим polling, loop зависает (tools никто
|
|
1487
|
+
// не исполнит). Клиентский UI чистим, но poll продолжается в фоне на
|
|
1488
|
+
// сессию ПОСЛЕДНЕГО открытого проекта (она же станет «фоновым chat'ом»).
|
|
1489
|
+
// При открытии другого проекта — startPolling переключит sessionKey.
|
|
1428
1490
|
resetInMemory: async () => {
|
|
1429
|
-
stopPolling();
|
|
1430
1491
|
history = []; snapshots = [];
|
|
1431
1492
|
renderHistory(); renderSnapshotBar();
|
|
1493
|
+
// Polling оставляем активным — это и есть «бг-чат».
|
|
1432
1494
|
},
|
|
1433
1495
|
// board.js зовёт после openFilm чтобы подгрузить chat-историю проекта.
|
|
1434
1496
|
loadFromCurrentProject: () => loadHistoryFromCurrentProject(),
|