kingkont 0.7.61 → 0.7.62
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/index.html +38 -0
- package/lib/cli.js +18 -5
- package/package.json +1 -1
- package/renderer/board.js +67 -0
- package/renderer/generate.js +52 -1
- package/renderer/settings.js +1 -0
- package/skill/SKILL.md +16 -0
package/index.html
CHANGED
|
@@ -440,6 +440,16 @@
|
|
|
440
440
|
<label id="locPickRow" style="display:none;">Локация
|
|
441
441
|
<select id="locPickSelect"><option value="">— не выбрана —</option></select>
|
|
442
442
|
</label>
|
|
443
|
+
<!-- Дефолтный промпт сцены (read-only превью + чекбокс «применить») -->
|
|
444
|
+
<div id="genDefaultPromptRow" style="display:none; margin-bottom:8px;">
|
|
445
|
+
<label style="display:flex; align-items:flex-start; gap:8px; cursor:pointer; padding:8px 10px; background:#1a1a1a; border:1px solid #333; border-radius:6px;">
|
|
446
|
+
<input type="checkbox" id="genDefaultPromptToggle" checked style="margin-top:2px; flex-shrink:0; accent-color:#7c3aed;">
|
|
447
|
+
<div style="flex:1; min-width:0;">
|
|
448
|
+
<div style="font-size:11px; color:#888; text-transform:uppercase; letter-spacing:0.5px; margin-bottom:3px;">Дефолт сцены — добавится к промпту при генерации</div>
|
|
449
|
+
<div id="genDefaultPromptText" style="font-size:12px; color:#aaccdd; font-family:ui-monospace,monospace; white-space:pre-wrap; word-break:break-word;"></div>
|
|
450
|
+
</div>
|
|
451
|
+
</label>
|
|
452
|
+
</div>
|
|
443
453
|
<label><span style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis; display:block;">Описание · <span style="color:#aae06a; font-family:ui-monospace,monospace; text-transform:none;">@имя</span> для ссылки на ноду</span>
|
|
444
454
|
<textarea id="genPrompt" placeholder="Опиши, что должно быть. Печатай @ чтобы вставить ссылку на ноду..."></textarea>
|
|
445
455
|
<div id="mentionPopup" class="mention-popup hidden"></div>
|
|
@@ -455,6 +465,34 @@
|
|
|
455
465
|
</div>
|
|
456
466
|
|
|
457
467
|
<!-- ===== Полноэкранный просмотр (image/video) ===== -->
|
|
468
|
+
<!-- ===== Дефолтные промпты сцены (per-kind) ===== -->
|
|
469
|
+
<div class="modal hidden" id="defaultPromptsModal">
|
|
470
|
+
<div class="modal-card" style="min-width:520px;">
|
|
471
|
+
<h3 id="defaultPromptsTitle">Дефолтные промпты сцены</h3>
|
|
472
|
+
<div class="hint" style="margin-bottom:14px;">
|
|
473
|
+
Текст добавляется к каждому новому промпту в этой сцене (НЕ редактируется
|
|
474
|
+
в поле промпта — применяется в момент генерации). В gen-модалке можно
|
|
475
|
+
отключить для конкретной генерации.
|
|
476
|
+
</div>
|
|
477
|
+
<label>Картинка
|
|
478
|
+
<textarea id="dpImage" rows="2" placeholder="например: cinematic lighting, soft shadows" style="width:100%;font-family:inherit;"></textarea>
|
|
479
|
+
</label>
|
|
480
|
+
<label>Видео
|
|
481
|
+
<textarea id="dpVideo" rows="2" placeholder="например: smooth camera movement, 24fps cinematic" style="width:100%;font-family:inherit;"></textarea>
|
|
482
|
+
</label>
|
|
483
|
+
<label>Аудио (TTS / SFX / музыка)
|
|
484
|
+
<textarea id="dpAudio" rows="2" placeholder="например: тёплый, мягкий тон" style="width:100%;font-family:inherit;"></textarea>
|
|
485
|
+
</label>
|
|
486
|
+
<label>Текст
|
|
487
|
+
<textarea id="dpText" rows="2" placeholder="например: пиши кратко, в стиле сценария" style="width:100%;font-family:inherit;"></textarea>
|
|
488
|
+
</label>
|
|
489
|
+
<div class="modal-actions">
|
|
490
|
+
<button id="dpCancel">Отмена</button>
|
|
491
|
+
<button id="dpSave" class="primary">Сохранить</button>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
458
496
|
<div class="fs-modal hidden" id="fsModal">
|
|
459
497
|
<div class="fs-stage" id="fsStage"></div>
|
|
460
498
|
<button id="fsPrev" title="Предыдущая (←)" class="fs-nav fs-nav-prev">‹</button>
|
package/lib/cli.js
CHANGED
|
@@ -202,13 +202,26 @@ async function cmdGen({ positional, flags }) {
|
|
|
202
202
|
await fsLib.saveScene(root, ref, scene);
|
|
203
203
|
console.error(`[node] создана ${node.id} (status=generating)`);
|
|
204
204
|
|
|
205
|
+
// Применяем scene.settings.defaultPrompts[kind] (если задан и не отключён
|
|
206
|
+
// флагом --no-default-prompt). Префиксует «<default>, <user prompt>» —
|
|
207
|
+
// та же логика что в renderer/generate.js applyDefaultPrompt().
|
|
208
|
+
let effectivePrompt = prompt;
|
|
209
|
+
const dp = scene.settings?.defaultPrompts || {};
|
|
210
|
+
const defForKind = (dp[kind] || '').trim();
|
|
211
|
+
const useDefault = defForKind && !flags['no-default-prompt'] && !flags.noDefaultPrompt;
|
|
212
|
+
if (useDefault) {
|
|
213
|
+
const u = (prompt || '').trim();
|
|
214
|
+
effectivePrompt = u ? (defForKind + ', ' + u) : defForKind;
|
|
215
|
+
console.error(`[gen] applied scene default prompt for ${kind}: "${defForKind.slice(0, 80)}${defForKind.length > 80 ? '…' : ''}"`);
|
|
216
|
+
}
|
|
217
|
+
|
|
205
218
|
// 4) Запускаем генерацию через провайдер.
|
|
206
|
-
if (kind === 'text') return await runTextGeneration(root, ref, node, { prompt, model: flags.model, settings, flags });
|
|
219
|
+
if (kind === 'text') return await runTextGeneration(root, ref, node, { prompt: effectivePrompt, model: flags.model, settings, flags });
|
|
207
220
|
if (kind === 'audio') {
|
|
208
221
|
const subKind = flags['sub-kind'] || flags.subKind || (flags.tts ? 'tts' : null);
|
|
209
|
-
if (subKind === 'sfx') return await runSfxGeneration(root, ref, node, { text:
|
|
210
|
-
if (subKind === 'music') return await runMusicGeneration(root, ref, node, { prompt, durationMs: flags['duration-ms'] || flags.durationMs, settings, flags });
|
|
211
|
-
return await runTtsGeneration(root, ref, node, { text:
|
|
222
|
+
if (subKind === 'sfx') return await runSfxGeneration(root, ref, node, { text: effectivePrompt, durationSeconds: flags.duration, settings, flags });
|
|
223
|
+
if (subKind === 'music') return await runMusicGeneration(root, ref, node, { prompt: effectivePrompt, durationMs: flags['duration-ms'] || flags.durationMs, settings, flags });
|
|
224
|
+
return await runTtsGeneration(root, ref, node, { text: effectivePrompt, voice: flags.voice, ttsModel: flags['tts-model'] || flags.ttsModel, settings, flags });
|
|
212
225
|
}
|
|
213
226
|
// image | video — aspectRatio с фоллбеком на scene.settings.aspectRatio
|
|
214
227
|
// (per-board дефолт). Если ни флаг, ни scene не указали — провайдер
|
|
@@ -220,7 +233,7 @@ async function cmdGen({ positional, flags }) {
|
|
|
220
233
|
console.error(`[gen] using board's default aspectRatio: ${aspectFromScene}`);
|
|
221
234
|
}
|
|
222
235
|
return await runMediaGeneration(root, ref, node, {
|
|
223
|
-
kind, prompt, modelKey: flags.model, imageInputs, videoInputs,
|
|
236
|
+
kind, prompt: effectivePrompt, modelKey: flags.model, imageInputs, videoInputs,
|
|
224
237
|
aspectRatio: finalAspect,
|
|
225
238
|
resolution: flags.resolution,
|
|
226
239
|
duration: flags.duration,
|
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -679,6 +679,7 @@ function showBoardContextMenu(kind, item, clientX, clientY) {
|
|
|
679
679
|
// как fallback когда нода ещё не имеет своего aspect и юзер не выбрал
|
|
680
680
|
// его в gen-modal'е.
|
|
681
681
|
add('▭ Соотношение сторон…', () => promptBoardAspectRatio(kind, item));
|
|
682
|
+
add('📝 Дефолтные промпты…', () => openDefaultPromptsDialog(kind, item));
|
|
682
683
|
if (kind === 'character') {
|
|
683
684
|
add('⚙ Свойства персонажа', () => {
|
|
684
685
|
// Открываем панель этого персонажа и потом её character-modal
|
|
@@ -723,6 +724,72 @@ async function promptBoardAspectRatio(kind, item) {
|
|
|
723
724
|
console.log(`[board] ${kind}/${item.name} aspectRatio → ${chosen}`);
|
|
724
725
|
}
|
|
725
726
|
|
|
727
|
+
// Открыть модалку «Дефолтные промпты сцены» — 4 textarea (image/video/audio/
|
|
728
|
+
// text). Сохраняется в scene.json → settings.defaultPrompts.
|
|
729
|
+
// При генерации каждой ноды соответствующий промпт добавляется к user-input
|
|
730
|
+
// (см. handler в renderer/generate.js); юзер может отключить чекбоксом в
|
|
731
|
+
// gen-modal'e для конкретной генерации.
|
|
732
|
+
async function openDefaultPromptsDialog(kind, item) {
|
|
733
|
+
const isActive = state.currentBoard?.kind === kind && state.currentBoard.name === item.name;
|
|
734
|
+
let metaSettings;
|
|
735
|
+
if (isActive) {
|
|
736
|
+
metaSettings = state.currentBoard.metadata.settings || {};
|
|
737
|
+
} else {
|
|
738
|
+
const meta = await loadBoardMetadata(item.handle);
|
|
739
|
+
metaSettings = meta.settings || {};
|
|
740
|
+
}
|
|
741
|
+
const dp = metaSettings.defaultPrompts || {};
|
|
742
|
+
// Заголовок и заполнение полей.
|
|
743
|
+
const titleEl = document.getElementById('defaultPromptsTitle');
|
|
744
|
+
if (titleEl) titleEl.textContent = `Дефолтные промпты для «${item.name}»`;
|
|
745
|
+
document.getElementById('dpImage').value = dp.image || '';
|
|
746
|
+
document.getElementById('dpVideo').value = dp.video || '';
|
|
747
|
+
document.getElementById('dpAudio').value = dp.audio || '';
|
|
748
|
+
document.getElementById('dpText').value = dp.text || '';
|
|
749
|
+
document.getElementById('defaultPromptsModal').classList.remove('hidden');
|
|
750
|
+
setTimeout(() => document.getElementById('dpImage').focus(), 30);
|
|
751
|
+
|
|
752
|
+
// Save handler — переустанавливаем каждый раз с замыканием на текущий item.
|
|
753
|
+
const saveBtn = document.getElementById('dpSave');
|
|
754
|
+
const cancelBtn = document.getElementById('dpCancel');
|
|
755
|
+
const close = () => document.getElementById('defaultPromptsModal').classList.add('hidden');
|
|
756
|
+
const onSave = async () => {
|
|
757
|
+
const next = {
|
|
758
|
+
image: document.getElementById('dpImage').value.trim(),
|
|
759
|
+
video: document.getElementById('dpVideo').value.trim(),
|
|
760
|
+
audio: document.getElementById('dpAudio').value.trim(),
|
|
761
|
+
text: document.getElementById('dpText').value.trim(),
|
|
762
|
+
};
|
|
763
|
+
// Удаляем пустые ключи чтобы не плодить мусор в JSON.
|
|
764
|
+
for (const k of Object.keys(next)) if (!next[k]) delete next[k];
|
|
765
|
+
if (isActive) {
|
|
766
|
+
if (!state.currentBoard.metadata.settings) state.currentBoard.metadata.settings = {};
|
|
767
|
+
if (Object.keys(next).length) {
|
|
768
|
+
state.currentBoard.metadata.settings.defaultPrompts = next;
|
|
769
|
+
} else {
|
|
770
|
+
delete state.currentBoard.metadata.settings.defaultPrompts;
|
|
771
|
+
}
|
|
772
|
+
scheduleSave();
|
|
773
|
+
} else {
|
|
774
|
+
const meta = await loadBoardMetadata(item.handle);
|
|
775
|
+
meta.settings = meta.settings || {};
|
|
776
|
+
if (Object.keys(next).length) meta.settings.defaultPrompts = next;
|
|
777
|
+
else delete meta.settings.defaultPrompts;
|
|
778
|
+
await saveBoardMetadata(item.handle, meta);
|
|
779
|
+
}
|
|
780
|
+
close();
|
|
781
|
+
saveBtn.removeEventListener('click', onSave);
|
|
782
|
+
cancelBtn.removeEventListener('click', onCancel);
|
|
783
|
+
};
|
|
784
|
+
const onCancel = () => {
|
|
785
|
+
close();
|
|
786
|
+
saveBtn.removeEventListener('click', onSave);
|
|
787
|
+
cancelBtn.removeEventListener('click', onCancel);
|
|
788
|
+
};
|
|
789
|
+
saveBtn.addEventListener('click', onSave);
|
|
790
|
+
cancelBtn.addEventListener('click', onCancel);
|
|
791
|
+
}
|
|
792
|
+
|
|
726
793
|
// Переименовать board (папку): создать новую папку с тем же именем, перенести
|
|
727
794
|
// содержимое, удалить старую. На macOS-FSAH прямого rename нет, делаем
|
|
728
795
|
// copy+remove. Если board сейчас открыт — переоткрываем после переноса.
|
package/renderer/generate.js
CHANGED
|
@@ -330,6 +330,7 @@ async function openGenModal(kind) {
|
|
|
330
330
|
if (kind === 'audio') { loadVoices(); }
|
|
331
331
|
closeMentionPopup();
|
|
332
332
|
syncSourceRefRow();
|
|
333
|
+
syncDefaultPromptRow();
|
|
333
334
|
// Если открываем image-генерацию и есть исходная картинка-референс —
|
|
334
335
|
// дефолт «↺ Как у исходной» (юзер обычно хочет такой же формат как у
|
|
335
336
|
// источника). Если потом юзер выберет другой aspect — это его выбор,
|
|
@@ -746,6 +747,7 @@ document.querySelectorAll('#genModal [data-kind]').forEach(b => {
|
|
|
746
747
|
$('genPrompt').setAttribute('placeholder', ph);
|
|
747
748
|
syncSourceRefRow();
|
|
748
749
|
syncCharLocRows();
|
|
750
|
+
syncDefaultPromptRow();
|
|
749
751
|
// То же auto-default «↺ Как у исходной» при переключении на image-kind
|
|
750
752
|
// (см. комментарий в openGenModal).
|
|
751
753
|
if (state.genKind === 'image'
|
|
@@ -801,6 +803,47 @@ function nearestSupportedAspect(ratio) {
|
|
|
801
803
|
return best.name;
|
|
802
804
|
}
|
|
803
805
|
|
|
806
|
+
// Показать «Дефолт сцены» в gen-modal'е если задан scene.settings
|
|
807
|
+
// .defaultPrompts[currentKind]. Чекбокс «применить» включён по дефолту,
|
|
808
|
+
// его state хранится в state.useDefaultPrompt и сбрасывается на каждое
|
|
809
|
+
// открытие модалки.
|
|
810
|
+
function syncDefaultPromptRow() {
|
|
811
|
+
const row = document.getElementById('genDefaultPromptRow');
|
|
812
|
+
const text = document.getElementById('genDefaultPromptText');
|
|
813
|
+
const toggle = document.getElementById('genDefaultPromptToggle');
|
|
814
|
+
if (!row || !text || !toggle) return;
|
|
815
|
+
const dp = state.currentBoard?.metadata?.settings?.defaultPrompts || {};
|
|
816
|
+
const value = dp[state.genKind] || '';
|
|
817
|
+
if (value) {
|
|
818
|
+
text.textContent = value;
|
|
819
|
+
toggle.checked = true;
|
|
820
|
+
state.useDefaultPrompt = true;
|
|
821
|
+
row.style.display = '';
|
|
822
|
+
} else {
|
|
823
|
+
row.style.display = 'none';
|
|
824
|
+
state.useDefaultPrompt = false;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Слушатель чекбокса (один раз навешиваем).
|
|
829
|
+
const _dpToggleEl = document.getElementById('genDefaultPromptToggle');
|
|
830
|
+
if (_dpToggleEl) _dpToggleEl.addEventListener('change', () => {
|
|
831
|
+
state.useDefaultPrompt = _dpToggleEl.checked;
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// Применяет дефолтный промпт сцены к user-prompt'у. Префиксует с разделителем
|
|
835
|
+
// ", " (типичная конвенция для image/video моделей). Если default отключён
|
|
836
|
+
// или пуст — возвращает prompt как есть.
|
|
837
|
+
function applyDefaultPrompt(userPrompt, kind) {
|
|
838
|
+
if (!state.useDefaultPrompt) return userPrompt;
|
|
839
|
+
const dp = state.currentBoard?.metadata?.settings?.defaultPrompts || {};
|
|
840
|
+
const def = (dp[kind] || '').trim();
|
|
841
|
+
if (!def) return userPrompt;
|
|
842
|
+
const u = (userPrompt || '').trim();
|
|
843
|
+
if (!u) return def;
|
|
844
|
+
return def + ', ' + u;
|
|
845
|
+
}
|
|
846
|
+
|
|
804
847
|
// === Source frame controls ===
|
|
805
848
|
function syncSourceRefRow() {
|
|
806
849
|
const showable = state.sourceRef && (state.genKind === 'image' || state.genKind === 'video');
|
|
@@ -1263,9 +1306,11 @@ $('genSubmit').addEventListener('click', async () => {
|
|
|
1263
1306
|
alert('После раскрытия @-ссылок текст пустой. Проверь содержимое исходных нод.');
|
|
1264
1307
|
return;
|
|
1265
1308
|
}
|
|
1309
|
+
// Применяем дефолтный промпт сцены для audio (если включён в gen-modal'е).
|
|
1310
|
+
const withDefault = applyDefaultPrompt(resolvedRaw, 'audio');
|
|
1266
1311
|
// Тоны прикладываем как ElevenLabs v3 audio-tags перед текстом
|
|
1267
1312
|
const tonePrefix = state.activeTones.map(t => `[${t}]`).join(' ');
|
|
1268
|
-
const finalText = tonePrefix ? `${tonePrefix} ${
|
|
1313
|
+
const finalText = tonePrefix ? `${tonePrefix} ${withDefault}` : withDefault;
|
|
1269
1314
|
// Запоминаем имя персонажа ДО сброса timelineGenTarget — fallback на voiceId
|
|
1270
1315
|
const charKey = state.timelineGenTarget?.charName || voiceId;
|
|
1271
1316
|
const usedTones = [...state.activeTones];
|
|
@@ -1368,6 +1413,12 @@ $('genSubmit').addEventListener('click', async () => {
|
|
|
1368
1413
|
}
|
|
1369
1414
|
let resolvedPrompt = resolveMentions(rawPrompt, mediaRefs);
|
|
1370
1415
|
if (sourceMarker) resolvedPrompt = sourceMarker + resolvedPrompt;
|
|
1416
|
+
// Применяем дефолтный промпт сцены (если включён в gen-modal'е).
|
|
1417
|
+
// Префиксует «<default>, <user prompt>». Пишется в API-запрос и в
|
|
1418
|
+
// node.generated.prompt — чтобы при regenerate взялся тот же
|
|
1419
|
+
// эффективный промпт. rawPrompt (без default) сохраняем тоже —
|
|
1420
|
+
// в node.generated.rawPrompt — для UI restore при «Изменить и запустить».
|
|
1421
|
+
resolvedPrompt = applyDefaultPrompt(resolvedPrompt, state.genKind);
|
|
1371
1422
|
|
|
1372
1423
|
// Если выбран aspect 'source' — резолвим в реальный ratio из исходной
|
|
1373
1424
|
// картинки ДО того как обнулим state.sourceRef (ниже).
|
package/renderer/settings.js
CHANGED
|
@@ -476,6 +476,7 @@ async function openGenerateForRef(fromNode, clientX, clientY, forceKind) {
|
|
|
476
476
|
$('genPrompt').value = '[@' + nodeRefKey(fromNode) + '] ';
|
|
477
477
|
}
|
|
478
478
|
syncSourceRefRow();
|
|
479
|
+
if (typeof syncDefaultPromptRow === 'function') syncDefaultPromptRow();
|
|
479
480
|
|
|
480
481
|
// Если генерим image из существующей картинки-ноды — дефолт «↺ Как у
|
|
481
482
|
// исходной» (юзер обычно хочет такой же формат как у источника).
|
package/skill/SKILL.md
CHANGED
|
@@ -103,6 +103,22 @@ kingkont gen <project> 'Серия 1' --kind=image --prompt="..." --aspect-ratio
|
|
|
103
103
|
`settings.aspectRatio`, если он отличается от текущего и юзер явно сказал
|
|
104
104
|
«пусть в этой сцене будет такой формат» (или если поле было null).
|
|
105
105
|
|
|
106
|
+
### Дефолтные промпты сцены
|
|
107
|
+
|
|
108
|
+
`scene.json → settings.defaultPrompts` — объект с ключами `image / video /
|
|
109
|
+
audio / text`. Если задан, CLI `kingkont gen` автоматически префиксует его
|
|
110
|
+
к user-prompt'у при генерации (`<default>, <user prompt>`). Это позволяет
|
|
111
|
+
юзеру задать общий стиль/тон для всей сцены один раз.
|
|
112
|
+
|
|
113
|
+
Поведение CLI:
|
|
114
|
+
- Default подхватывается из scene.json автоматически — флага не нужно.
|
|
115
|
+
- Чтобы отключить для одной генерации: `--no-default-prompt`.
|
|
116
|
+
- В stderr пишется `[gen] applied scene default prompt for <kind>: "..."`.
|
|
117
|
+
|
|
118
|
+
Юзер обычно настраивает defaults через UI (правый клик на сцену →
|
|
119
|
+
«📝 Дефолтные промпты…»), но может попросить установить через CLI —
|
|
120
|
+
правь `scene.json` напрямую: `settings.defaultPrompts.<kind> = "..."`.
|
|
121
|
+
|
|
106
122
|
## ⚠️ Text-ноды — генерируй САМ, не через `kingkont gen --kind=text`
|
|
107
123
|
|
|
108
124
|
Ты — Claude. Когда юзер просит «напиши диалог», «придумай реплику»,
|