kingkont 0.7.46 → 0.7.47
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/cli.js +10 -2
- package/lib/projectFs.js +3 -0
- package/package.json +1 -1
- package/renderer/board.js +47 -0
- package/renderer/generate.js +8 -2
- package/renderer/state.js +7 -1
- package/skill/SKILL.md +45 -1
package/lib/cli.js
CHANGED
|
@@ -210,10 +210,18 @@ async function cmdGen({ positional, flags }) {
|
|
|
210
210
|
if (subKind === 'music') return await runMusicGeneration(root, ref, node, { prompt, durationMs: flags['duration-ms'] || flags.durationMs, settings, flags });
|
|
211
211
|
return await runTtsGeneration(root, ref, node, { text: prompt, voice: flags.voice, ttsModel: flags['tts-model'] || flags.ttsModel, settings, flags });
|
|
212
212
|
}
|
|
213
|
-
// image | video
|
|
213
|
+
// image | video — aspectRatio с фоллбеком на scene.settings.aspectRatio
|
|
214
|
+
// (per-board дефолт). Если ни флаг, ни scene не указали — провайдер
|
|
215
|
+
// получит undefined и применит свой дефолт (обычно '16:9' / модель-зависимо).
|
|
216
|
+
const aspectFromFlag = flags['aspect-ratio'] || flags.aspectRatio;
|
|
217
|
+
const aspectFromScene = scene.settings?.aspectRatio;
|
|
218
|
+
const finalAspect = aspectFromFlag || aspectFromScene;
|
|
219
|
+
if (!aspectFromFlag && aspectFromScene) {
|
|
220
|
+
console.error(`[gen] using board's default aspectRatio: ${aspectFromScene}`);
|
|
221
|
+
}
|
|
214
222
|
return await runMediaGeneration(root, ref, node, {
|
|
215
223
|
kind, prompt, modelKey: flags.model, imageInputs, videoInputs,
|
|
216
|
-
aspectRatio:
|
|
224
|
+
aspectRatio: finalAspect,
|
|
217
225
|
resolution: flags.resolution,
|
|
218
226
|
duration: flags.duration,
|
|
219
227
|
firstFrame: flags['first-frame'] || flags.firstFrame,
|
package/lib/projectFs.js
CHANGED
|
@@ -119,6 +119,7 @@ async function loadScene(root, ref) {
|
|
|
119
119
|
location: data.location || null,
|
|
120
120
|
timeline: data.timeline || null,
|
|
121
121
|
history: data.history || null,
|
|
122
|
+
settings: data.settings || null,
|
|
122
123
|
};
|
|
123
124
|
// Подгрузить .md для text-нод (как делает renderer в loadBoardMetadata).
|
|
124
125
|
for (const n of scene.nodes) {
|
|
@@ -158,6 +159,8 @@ async function saveScene(root, ref, scene) {
|
|
|
158
159
|
location: scene.location || null,
|
|
159
160
|
timeline: scene.timeline || null,
|
|
160
161
|
history: scene.history && (scene.history.past?.length || scene.history.future?.length) ? scene.history : null,
|
|
162
|
+
// settings: { aspectRatio: '9:16' | ... } — per-board дефолтные параметры.
|
|
163
|
+
...(scene.settings && Object.keys(scene.settings).length ? { settings: scene.settings } : {}),
|
|
161
164
|
};
|
|
162
165
|
await fsp.writeFile(path.join(dir, 'scene.json'), JSON.stringify(payload, null, 2));
|
|
163
166
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kingkont",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.47",
|
|
4
4
|
"description": "KingKont \u00b7 Chatium \u2014 \u043d\u043e\u0434-\u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u0441\u0446\u0435\u043d \u0441 AI-\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0435\u0439 (\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438/\u0432\u0438\u0434\u0435\u043e/\u0433\u043e\u043b\u043e\u0441/SFX/\u043c\u0443\u0437\u044b\u043a\u0430/\u0442\u0435\u043a\u0441\u0442)",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"bin": {
|
package/renderer/board.js
CHANGED
|
@@ -674,6 +674,11 @@ function showBoardContextMenu(kind, item, clientX, clientY) {
|
|
|
674
674
|
try { await renameBoard(kind, item.name, newName); }
|
|
675
675
|
catch (err) { alert('Не удалось переименовать: ' + (err?.message || err)); }
|
|
676
676
|
});
|
|
677
|
+
// Соотношение сторон по умолчанию для генераций image/video в этой
|
|
678
|
+
// доске. Хранится в scene.json → settings.aspectRatio. Используется
|
|
679
|
+
// как fallback когда нода ещё не имеет своего aspect и юзер не выбрал
|
|
680
|
+
// его в gen-modal'е.
|
|
681
|
+
add('▭ Соотношение сторон…', () => promptBoardAspectRatio(kind, item));
|
|
677
682
|
if (kind === 'character') {
|
|
678
683
|
add('⚙ Свойства персонажа', () => {
|
|
679
684
|
// Открываем панель этого персонажа и потом её character-modal
|
|
@@ -689,6 +694,44 @@ function showBoardContextMenu(kind, item, clientX, clientY) {
|
|
|
689
694
|
setTimeout(() => document.addEventListener('mousedown', closeNodeMenu, { once: true }), 0);
|
|
690
695
|
}
|
|
691
696
|
|
|
697
|
+
// Спросить у юзера новый default aspectRatio для доски.
|
|
698
|
+
// Сохраняется в scene.json → settings.aspectRatio. Если доска сейчас
|
|
699
|
+
// активна (state.currentBoard) — обновляем in-memory и сразу saveScheduled.
|
|
700
|
+
// Иначе пишем напрямую через loadBoardMetadata + saveBoardMetadata.
|
|
701
|
+
const ASPECT_OPTIONS = ['9:16', '16:9', '1:1', '4:5', '5:4', '4:3', '3:4', '2:3', '3:2', '21:9'];
|
|
702
|
+
async function promptBoardAspectRatio(kind, item) {
|
|
703
|
+
const isActive = state.currentBoard?.kind === kind && state.currentBoard.name === item.name;
|
|
704
|
+
const current = isActive
|
|
705
|
+
? (state.currentBoard.metadata.settings?.aspectRatio || '9:16')
|
|
706
|
+
: (await loadBoardMetadata(item.handle)).settings?.aspectRatio || '9:16';
|
|
707
|
+
const choices = ASPECT_OPTIONS.join(' / ');
|
|
708
|
+
const ans = window.prompt(
|
|
709
|
+
`Соотношение сторон для генераций image/video в «${item.name}».\n` +
|
|
710
|
+
`Применяется когда нода или gen-модалка не указывают своё.\n\n` +
|
|
711
|
+
`Доступные значения: ${choices}\n` +
|
|
712
|
+
`Текущее: ${current}`,
|
|
713
|
+
current,
|
|
714
|
+
);
|
|
715
|
+
if (ans == null) return;
|
|
716
|
+
const trimmed = ans.trim();
|
|
717
|
+
if (!trimmed) return;
|
|
718
|
+
if (!ASPECT_OPTIONS.includes(trimmed)) {
|
|
719
|
+
alert('Неподдерживаемое значение. Доступны: ' + choices);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (isActive) {
|
|
723
|
+
if (!state.currentBoard.metadata.settings) state.currentBoard.metadata.settings = {};
|
|
724
|
+
state.currentBoard.metadata.settings.aspectRatio = trimmed;
|
|
725
|
+
scheduleSave();
|
|
726
|
+
} else {
|
|
727
|
+
// Доска не активна — пишем напрямую.
|
|
728
|
+
const meta = await loadBoardMetadata(item.handle);
|
|
729
|
+
meta.settings = { ...(meta.settings || {}), aspectRatio: trimmed };
|
|
730
|
+
await saveBoardMetadata(item.handle, meta);
|
|
731
|
+
}
|
|
732
|
+
console.log(`[board] ${kind}/${item.name} aspectRatio → ${trimmed}`);
|
|
733
|
+
}
|
|
734
|
+
|
|
692
735
|
// Переименовать board (папку): создать новую папку с тем же именем, перенести
|
|
693
736
|
// содержимое, удалить старую. На macOS-FSAH прямого rename нет, делаем
|
|
694
737
|
// copy+remove. Если board сейчас открыт — переоткрываем после переноса.
|
|
@@ -1007,6 +1050,9 @@ async function selectBoard(board) {
|
|
|
1007
1050
|
location: meta.location || null,
|
|
1008
1051
|
timeline: meta.timeline || [],
|
|
1009
1052
|
history: meta.history || { past: [], future: [] },
|
|
1053
|
+
// settings: дефолтные параметры генерации в этой доске.
|
|
1054
|
+
// aspectRatio фоллбэчит на '9:16' если поле не задано.
|
|
1055
|
+
settings: meta.settings || { aspectRatio: '9:16' },
|
|
1010
1056
|
},
|
|
1011
1057
|
urls: {},
|
|
1012
1058
|
dirty: false,
|
|
@@ -1166,6 +1212,7 @@ async function reloadCurrentBoardFromDisk() {
|
|
|
1166
1212
|
location: meta.location || null,
|
|
1167
1213
|
timeline: meta.timeline || [],
|
|
1168
1214
|
history: meta.history || { past: [], future: [] },
|
|
1215
|
+
settings: meta.settings || board.metadata.settings || { aspectRatio: '9:16' },
|
|
1169
1216
|
};
|
|
1170
1217
|
board.dirty = false;
|
|
1171
1218
|
board.lastDiskMtime = await _readSceneMtime(board.handle);
|
package/renderer/generate.js
CHANGED
|
@@ -1484,14 +1484,20 @@ async function startGenerationJob(node, kind, prompt, mediaRefs, boardHandle, bK
|
|
|
1484
1484
|
n.generated = { ...(n.generated || {}), state: 'submitting' };
|
|
1485
1485
|
});
|
|
1486
1486
|
const submitBody = { kind, prompt, imageInputs, videoInputs, model: modelKey };
|
|
1487
|
+
// Приоритет aspectRatio:
|
|
1488
|
+
// 1) node.generated.aspectRatio (если ноду уже генерили с этим)
|
|
1489
|
+
// 2) state.currentBoard.metadata.settings.aspectRatio (доска-уровень)
|
|
1490
|
+
// 3) state.imageAspect / state.videoAspect (глобальный localStorage)
|
|
1491
|
+
// Так per-board настройка перебивает глобальный фоллбэк.
|
|
1492
|
+
const boardAspect = state.currentBoard?.metadata?.settings?.aspectRatio || null;
|
|
1487
1493
|
if (kind === 'video') {
|
|
1488
1494
|
submitBody.duration = node.generated?.duration ?? state.videoDuration;
|
|
1489
1495
|
submitBody.resolution = node.generated?.resolution ?? state.videoResolution;
|
|
1490
|
-
submitBody.aspectRatio = node.generated?.aspectRatio ?? state.videoAspect;
|
|
1496
|
+
submitBody.aspectRatio = node.generated?.aspectRatio ?? boardAspect ?? state.videoAspect;
|
|
1491
1497
|
} else if (kind === 'image') {
|
|
1492
1498
|
// Grok-imagine требует aspect_ratio из {2:3, 3:2, 1:1, 9:16, 16:9}.
|
|
1493
1499
|
// Остальные модели (nano-banana-2, seedream и др.) тоже принимают.
|
|
1494
|
-
submitBody.aspectRatio = node.generated?.aspectRatio ?? state.imageAspect;
|
|
1500
|
+
submitBody.aspectRatio = node.generated?.aspectRatio ?? boardAspect ?? state.imageAspect;
|
|
1495
1501
|
}
|
|
1496
1502
|
logJob(node.id, `POST /api/generate body: ${logSafe(submitBody)}`);
|
|
1497
1503
|
logJob(node.id, `POST /api/generate (image_input=${imageInputs.length}, video_input=${videoInputs.length}, model=${modelKey})`);
|
package/renderer/state.js
CHANGED
|
@@ -248,6 +248,7 @@ async function loadBoardMetadata(boardHandle) {
|
|
|
248
248
|
let location = null;
|
|
249
249
|
let timeline = null;
|
|
250
250
|
let history = null;
|
|
251
|
+
let settings = null;
|
|
251
252
|
// 1) Пробуем scene.json
|
|
252
253
|
try {
|
|
253
254
|
const fh = await boardHandle.getFileHandle('scene.json');
|
|
@@ -259,6 +260,7 @@ async function loadBoardMetadata(boardHandle) {
|
|
|
259
260
|
location = data.location || null;
|
|
260
261
|
timeline = data.timeline || null;
|
|
261
262
|
history = data.history || null;
|
|
263
|
+
settings = data.settings || null;
|
|
262
264
|
} catch {
|
|
263
265
|
// 2) Легаси: .editor.json
|
|
264
266
|
try {
|
|
@@ -270,6 +272,7 @@ async function loadBoardMetadata(boardHandle) {
|
|
|
270
272
|
character = data.character || null;
|
|
271
273
|
location = data.location || null;
|
|
272
274
|
timeline = data.timeline || null;
|
|
275
|
+
settings = data.settings || null;
|
|
273
276
|
legacyEditor = true;
|
|
274
277
|
migrated = true;
|
|
275
278
|
} catch {}
|
|
@@ -298,7 +301,7 @@ async function loadBoardMetadata(boardHandle) {
|
|
|
298
301
|
try { await boardHandle.removeEntry('.editor.json'); } catch {}
|
|
299
302
|
}
|
|
300
303
|
|
|
301
|
-
return { nodes, connections, view, character, location, timeline, history, _migrated: migrated };
|
|
304
|
+
return { nodes, connections, view, character, location, timeline, history, settings, _migrated: migrated };
|
|
302
305
|
}
|
|
303
306
|
|
|
304
307
|
async function saveBoardMetadata(boardHandle, meta) {
|
|
@@ -322,6 +325,9 @@ async function saveBoardMetadata(boardHandle, meta) {
|
|
|
322
325
|
timeline: meta.timeline || null,
|
|
323
326
|
history: meta.history && (meta.history.past?.length || meta.history.future?.length)
|
|
324
327
|
? meta.history : null,
|
|
328
|
+
// settings: { aspectRatio: '9:16' | ... } — per-board дефолтные параметры
|
|
329
|
+
// генерации. Если null/пусто — не пишем поле в JSON, чтобы файл был чище.
|
|
330
|
+
...(meta.settings && Object.keys(meta.settings).length ? { settings: meta.settings } : {}),
|
|
325
331
|
};
|
|
326
332
|
await writeFile(boardHandle, 'scene.json', JSON.stringify(payload, null, 2));
|
|
327
333
|
// Возвращаем свежий mtime — внешний file-watcher (poller в board.js) сравнит
|
package/skill/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kingkont
|
|
3
|
-
description: Use this skill when the user works with a KingKont project — a film/series scene editor where each scene is a folder with scene.json. Triggers — фразы вроде «открой/запусти kingkont», «открой редактор сцен», «добавь сцену/персонажа/локацию», «добавь ноду на холст», «сгенерируй картинку/видео/голос/SFX/музыку/текст», или работа с папкой содержащей _characters/ _locations/ <scene>/scene.json. Skill знает формат данных и использует CLI `kingkont <cmd>` (open/list/board/add-node/gen/connect/rm-node/upload/voices/balance) для управления нодами и запуска генераций — ключи берутся автоматически из настроек приложения. ВАЖНО: каждая нода живёт в конкретной доске
|
|
3
|
+
description: Use this skill when the user works with a KingKont project — a film/series scene editor where each scene is a folder with scene.json. Triggers — фразы вроде «открой/запусти kingkont», «открой редактор сцен», «добавь сцену/персонажа/локацию», «добавь ноду на холст», «сгенерируй картинку/видео/голос/SFX/музыку/текст», или работа с папкой содержащей _characters/ _locations/ <scene>/scene.json. Skill знает формат данных и использует CLI `kingkont <cmd>` (open/list/board/add-node/gen/connect/rm-node/upload/voices/balance) для управления нодами и запуска генераций — ключи берутся автоматически из настроек приложения. ВАЖНО: (1) каждая нода живёт в конкретной доске — если юзер не указал доску, СПРОСИ; не создавай новые сцены без явного запроса. (2) Перед генерацией image/video проверь scene.json → settings.aspectRatio; если поле отсутствует — СПРОСИ юзера под какой формат генерить (9:16/16:9/1:1/...), сохрани его выбор в scene.json как дефолт. Для визуальной работы поднимает UI через `npx -y kingkont serve`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# KingKont · Chatium — Skill
|
|
@@ -59,6 +59,50 @@ echo '{"nodes":[],"connections":[]}' > <project>/<имя_новой_сцены>/
|
|
|
59
59
|
Аналогично для персонажа: `<project>/_characters/<имя>/scene.json`.
|
|
60
60
|
Для локации: `<project>/_locations/<имя>/scene.json`.
|
|
61
61
|
|
|
62
|
+
## ⚠️ Aspect ratio для image/video — обязательно уточняй
|
|
63
|
+
|
|
64
|
+
Каждая доска имеет дефолтный aspect ratio в `scene.json` →
|
|
65
|
+
`settings.aspectRatio`. Это поле управляет под какой формат генерится
|
|
66
|
+
картинка/видео.
|
|
67
|
+
|
|
68
|
+
### Алгоритм перед `kingkont gen --kind=image|video`
|
|
69
|
+
|
|
70
|
+
1. **Прочитай `scene.json` выбранной доски.**
|
|
71
|
+
2. **Если `settings.aspectRatio` задан** → используй его автоматически
|
|
72
|
+
(CLI сам подхватит — флаг `--aspect-ratio` не нужен).
|
|
73
|
+
3. **Если `settings.aspectRatio` отсутствует** — НЕ предполагай и НЕ
|
|
74
|
+
ставь дефолт сам. **Спроси юзера**: «Под какой формат генерим? —
|
|
75
|
+
9:16 (вертикаль), 16:9 (горизонталь), 1:1 (квадрат), 4:5, 4:3, 3:4,
|
|
76
|
+
2:3, 3:2, 21:9. По умолчанию — 9:16.»
|
|
77
|
+
4. После ответа юзера **сохрани его выбор как дефолт** для этой доски,
|
|
78
|
+
чтобы в следующий раз не спрашивать. Сделай это правкой `scene.json`:
|
|
79
|
+
добавь/обнови ключ `"settings": { "aspectRatio": "<выбранный>" }`.
|
|
80
|
+
5. После этого можешь запускать `kingkont gen --aspect-ratio=<выбор>` —
|
|
81
|
+
или без флага, CLI прочитает scene.
|
|
82
|
+
|
|
83
|
+
### Пример
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Юзер: "сгенери картинку героя в Серии 1"
|
|
87
|
+
# Шаг 1: проверяем текущий aspect
|
|
88
|
+
kingkont board <project> 'Серия 1' --json | jq '.settings.aspectRatio'
|
|
89
|
+
# null → не задан
|
|
90
|
+
|
|
91
|
+
# Шаг 2: спрашиваем юзера. Получаем «вертикаль».
|
|
92
|
+
# Шаг 3: правим scene.json — ставим aspectRatio: '9:16'
|
|
93
|
+
|
|
94
|
+
# Шаг 4: генерим (CLI прочитает settings.aspectRatio автоматически)
|
|
95
|
+
kingkont gen <project> 'Серия 1' --kind=image --prompt="..."
|
|
96
|
+
|
|
97
|
+
# Альтернативно: указать явно (CLI отдаст приоритет флагу)
|
|
98
|
+
kingkont gen <project> 'Серия 1' --kind=image --prompt="..." --aspect-ratio=9:16
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Если юзер сам в просьбе указал формат («сгенери картинку 16:9», «вертикальную»,
|
|
102
|
+
«квадратное видео») — используй его сразу, не спрашивай. Заодно обнови
|
|
103
|
+
`settings.aspectRatio`, если он отличается от текущего и юзер явно сказал
|
|
104
|
+
«пусть в этой сцене будет такой формат» (или если поле было null).
|
|
105
|
+
|
|
62
106
|
## Как открыть UI редактора
|
|
63
107
|
|
|
64
108
|
```bash
|