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 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: flags['aspect-ratio'] || flags.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.46",
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);
@@ -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) для управления нодами и запуска генераций — ключи берутся автоматически из настроек приложения. ВАЖНО: каждая нода живёт в конкретной доске (сцене/персонаже/локации) — если юзер не указал доску явно, СПРОСИ его в какой добавить, не создавай новые сцены без явного запроса. Для визуальной работы поднимает UI через `npx -y kingkont serve`.
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