kingkont 0.7.94 → 0.7.96

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.7.94",
3
+ "version": "0.7.96",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
@@ -396,10 +396,22 @@ async function renderNodeBody(node, body) {
396
396
  media = document.createElement('img');
397
397
  media.src = url; media.alt = node.file; media.draggable = false;
398
398
  media.decoding = 'async';
399
+ // Auto-fit высоты ноды под aspect картинки. Иначе после генерации
400
+ // (где node.width/height заморожены до прихода файла) или после
401
+ // замены картинки через history новый <img> с object-fit:contain
402
+ // оставляет пустое место сверху/снизу — node.height не совпадает
403
+ // с display-height image'а.
404
+ media.addEventListener('load', () => fitNodeHeightToMedia(node, nodeEl, media), { once: true });
399
405
  } else {
400
406
  media = document.createElement(node.type);
401
407
  media.src = url;
402
- if (node.type === 'video') media.controls = true;
408
+ if (node.type === 'video') {
409
+ media.controls = true;
410
+ // То же для видео — но через loadedmetadata, который у preload:none
411
+ // выстрелит при первой попытке play. Когда юзер запустит — нода
412
+ // подстроится под реальный aspect.
413
+ media.addEventListener('loadedmetadata', () => fitNodeHeightToMedia(node, nodeEl, media), { once: true });
414
+ }
403
415
  // preload="none" — заголовки video/audio НЕ читаются пока юзер не запустит play.
404
416
  // Раньше "metadata" грузил chunk для duration/dimensions с каждой ноды на selectBoard.
405
417
  media.preload = 'none';
@@ -488,6 +500,32 @@ async function renderNodeBody(node, body) {
488
500
  }
489
501
  }
490
502
 
503
+ // Подгоняет node.height под natural-aspect медиа (image/video). Сохраняет
504
+ // текущую node.width, пересчитывает height = width * (natH/natW) + chrome.
505
+ // Без этого после генерации (или history-undo на ноду с другим aspect'ом)
506
+ // нода оставалась прежнего размера, и <img width:100%; height:auto> не
507
+ // заполнял её по высоте → пустое место снизу.
508
+ function fitNodeHeightToMedia(node, nodeEl, mediaEl) {
509
+ if (!nodeEl || !mediaEl) return;
510
+ const natW = mediaEl.naturalWidth || mediaEl.videoWidth || 0;
511
+ const natH = mediaEl.naturalHeight || mediaEl.videoHeight || 0;
512
+ if (!natW || !natH) return;
513
+ // Chrome — header + footer (если есть). image-node padding=0, ничего не отъедает.
514
+ const headerH = nodeEl.querySelector('.node-header')?.offsetHeight || 0;
515
+ const footerH = nodeEl.querySelector('.node-footer')?.offsetHeight || 0;
516
+ const chromeH = headerH + footerH;
517
+ // Берём актуальную width — node.width если задано, иначе текущий offsetWidth.
518
+ const w = node.width || nodeEl.offsetWidth;
519
+ if (!w) return;
520
+ const newH = Math.round(w * (natH / natW) + chromeH);
521
+ // 2px tolerance — не шумим в save/connections при суб-пиксельных колебаниях.
522
+ if (Math.abs(newH - (node.height || 0)) <= 2) return;
523
+ node.height = newH;
524
+ nodeEl.style.height = newH + 'px';
525
+ if (typeof renderConnections === 'function') renderConnections();
526
+ scheduleSave();
527
+ }
528
+
491
529
  function attachAnchor(node, el, anchor) {
492
530
  anchor.addEventListener('mousedown', e => {
493
531
  e.preventDefault();
package/renderer/state.js CHANGED
@@ -426,6 +426,19 @@ function getFileType(file) {
426
426
  }
427
427
 
428
428
  // =================== State ===================
429
+ // One-time migration: если юзер ранее залипал на Pro — сбрасываем на 2.
430
+ // Pro дороже и медленнее, как дефолт не оптимально. Маркер не даёт
431
+ // миграции отрабатывать повторно: после ручного выбора Pro он останется.
432
+ (function migrateImageModelDefault() {
433
+ try {
434
+ if (localStorage.getItem('imageModelResetV2') === '1') return;
435
+ if (localStorage.getItem('imageModel') === 'nano-banana-pro') {
436
+ localStorage.setItem('imageModel', 'nano-banana-2');
437
+ }
438
+ localStorage.setItem('imageModelResetV2', '1');
439
+ } catch {}
440
+ })();
441
+
429
442
  const state = {
430
443
  filmHandle: null,
431
444
  currentBoard: null, // { kind, name, key, handle, metadata, urls }