kingkont 0.7.61 → 0.7.63
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 +74 -0
- package/renderer/generate.js +58 -1
- package/renderer/media.js +10 -0
- package/renderer/settings.js +5 -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 сейчас открыт — переоткрываем после переноса.
|
|
@@ -750,6 +817,13 @@ async function renameBoard(kind, oldName, newName) {
|
|
|
750
817
|
if (kind === 'character') await refreshCharacters();
|
|
751
818
|
else if (kind === 'location') await refreshLocations();
|
|
752
819
|
else await refreshEpisodes();
|
|
820
|
+
// Перезагрузить charactersInfo/locationsInfo чтобы @-mention popup
|
|
821
|
+
// в любом следующем gen-modal'е увидел новое имя.
|
|
822
|
+
if (kind === 'character' && typeof loadAllCharactersInfo === 'function') {
|
|
823
|
+
loadAllCharactersInfo().catch(() => {});
|
|
824
|
+
} else if (kind === 'location' && typeof loadAllLocationsInfo === 'function') {
|
|
825
|
+
loadAllLocationsInfo().catch(() => {});
|
|
826
|
+
}
|
|
753
827
|
// Если был активен — переоткрыть с новым именем
|
|
754
828
|
if (wasActive) {
|
|
755
829
|
const list = kind === 'character' ? await listCharacters(state.filmHandle)
|
package/renderer/generate.js
CHANGED
|
@@ -329,7 +329,14 @@ async function openGenModal(kind) {
|
|
|
329
329
|
syncCharLocRows();
|
|
330
330
|
if (kind === 'audio') { loadVoices(); }
|
|
331
331
|
closeMentionPopup();
|
|
332
|
+
// Освежаем информацию о персонажах/локациях, чтобы недавно добавленные/
|
|
333
|
+
// переименованные ноды появились в @-popup'е. Запускаем в фоне — не
|
|
334
|
+
// блокируем открытие модалки (если что — popup обновится при следующем
|
|
335
|
+
// keystroke; а charactersInfo тогда обновится к моменту следующего typing).
|
|
336
|
+
if (typeof loadAllCharactersInfo === 'function') loadAllCharactersInfo().catch(() => {});
|
|
337
|
+
if (typeof loadAllLocationsInfo === 'function') loadAllLocationsInfo().catch(() => {});
|
|
332
338
|
syncSourceRefRow();
|
|
339
|
+
syncDefaultPromptRow();
|
|
333
340
|
// Если открываем image-генерацию и есть исходная картинка-референс —
|
|
334
341
|
// дефолт «↺ Как у исходной» (юзер обычно хочет такой же формат как у
|
|
335
342
|
// источника). Если потом юзер выберет другой aspect — это его выбор,
|
|
@@ -746,6 +753,7 @@ document.querySelectorAll('#genModal [data-kind]').forEach(b => {
|
|
|
746
753
|
$('genPrompt').setAttribute('placeholder', ph);
|
|
747
754
|
syncSourceRefRow();
|
|
748
755
|
syncCharLocRows();
|
|
756
|
+
syncDefaultPromptRow();
|
|
749
757
|
// То же auto-default «↺ Как у исходной» при переключении на image-kind
|
|
750
758
|
// (см. комментарий в openGenModal).
|
|
751
759
|
if (state.genKind === 'image'
|
|
@@ -801,6 +809,47 @@ function nearestSupportedAspect(ratio) {
|
|
|
801
809
|
return best.name;
|
|
802
810
|
}
|
|
803
811
|
|
|
812
|
+
// Показать «Дефолт сцены» в gen-modal'е если задан scene.settings
|
|
813
|
+
// .defaultPrompts[currentKind]. Чекбокс «применить» включён по дефолту,
|
|
814
|
+
// его state хранится в state.useDefaultPrompt и сбрасывается на каждое
|
|
815
|
+
// открытие модалки.
|
|
816
|
+
function syncDefaultPromptRow() {
|
|
817
|
+
const row = document.getElementById('genDefaultPromptRow');
|
|
818
|
+
const text = document.getElementById('genDefaultPromptText');
|
|
819
|
+
const toggle = document.getElementById('genDefaultPromptToggle');
|
|
820
|
+
if (!row || !text || !toggle) return;
|
|
821
|
+
const dp = state.currentBoard?.metadata?.settings?.defaultPrompts || {};
|
|
822
|
+
const value = dp[state.genKind] || '';
|
|
823
|
+
if (value) {
|
|
824
|
+
text.textContent = value;
|
|
825
|
+
toggle.checked = true;
|
|
826
|
+
state.useDefaultPrompt = true;
|
|
827
|
+
row.style.display = '';
|
|
828
|
+
} else {
|
|
829
|
+
row.style.display = 'none';
|
|
830
|
+
state.useDefaultPrompt = false;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Слушатель чекбокса (один раз навешиваем).
|
|
835
|
+
const _dpToggleEl = document.getElementById('genDefaultPromptToggle');
|
|
836
|
+
if (_dpToggleEl) _dpToggleEl.addEventListener('change', () => {
|
|
837
|
+
state.useDefaultPrompt = _dpToggleEl.checked;
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// Применяет дефолтный промпт сцены к user-prompt'у. Префиксует с разделителем
|
|
841
|
+
// ", " (типичная конвенция для image/video моделей). Если default отключён
|
|
842
|
+
// или пуст — возвращает prompt как есть.
|
|
843
|
+
function applyDefaultPrompt(userPrompt, kind) {
|
|
844
|
+
if (!state.useDefaultPrompt) return userPrompt;
|
|
845
|
+
const dp = state.currentBoard?.metadata?.settings?.defaultPrompts || {};
|
|
846
|
+
const def = (dp[kind] || '').trim();
|
|
847
|
+
if (!def) return userPrompt;
|
|
848
|
+
const u = (userPrompt || '').trim();
|
|
849
|
+
if (!u) return def;
|
|
850
|
+
return def + ', ' + u;
|
|
851
|
+
}
|
|
852
|
+
|
|
804
853
|
// === Source frame controls ===
|
|
805
854
|
function syncSourceRefRow() {
|
|
806
855
|
const showable = state.sourceRef && (state.genKind === 'image' || state.genKind === 'video');
|
|
@@ -1263,9 +1312,11 @@ $('genSubmit').addEventListener('click', async () => {
|
|
|
1263
1312
|
alert('После раскрытия @-ссылок текст пустой. Проверь содержимое исходных нод.');
|
|
1264
1313
|
return;
|
|
1265
1314
|
}
|
|
1315
|
+
// Применяем дефолтный промпт сцены для audio (если включён в gen-modal'е).
|
|
1316
|
+
const withDefault = applyDefaultPrompt(resolvedRaw, 'audio');
|
|
1266
1317
|
// Тоны прикладываем как ElevenLabs v3 audio-tags перед текстом
|
|
1267
1318
|
const tonePrefix = state.activeTones.map(t => `[${t}]`).join(' ');
|
|
1268
|
-
const finalText = tonePrefix ? `${tonePrefix} ${
|
|
1319
|
+
const finalText = tonePrefix ? `${tonePrefix} ${withDefault}` : withDefault;
|
|
1269
1320
|
// Запоминаем имя персонажа ДО сброса timelineGenTarget — fallback на voiceId
|
|
1270
1321
|
const charKey = state.timelineGenTarget?.charName || voiceId;
|
|
1271
1322
|
const usedTones = [...state.activeTones];
|
|
@@ -1368,6 +1419,12 @@ $('genSubmit').addEventListener('click', async () => {
|
|
|
1368
1419
|
}
|
|
1369
1420
|
let resolvedPrompt = resolveMentions(rawPrompt, mediaRefs);
|
|
1370
1421
|
if (sourceMarker) resolvedPrompt = sourceMarker + resolvedPrompt;
|
|
1422
|
+
// Применяем дефолтный промпт сцены (если включён в gen-modal'е).
|
|
1423
|
+
// Префиксует «<default>, <user prompt>». Пишется в API-запрос и в
|
|
1424
|
+
// node.generated.prompt — чтобы при regenerate взялся тот же
|
|
1425
|
+
// эффективный промпт. rawPrompt (без default) сохраняем тоже —
|
|
1426
|
+
// в node.generated.rawPrompt — для UI restore при «Изменить и запустить».
|
|
1427
|
+
resolvedPrompt = applyDefaultPrompt(resolvedPrompt, state.genKind);
|
|
1371
1428
|
|
|
1372
1429
|
// Если выбран aspect 'source' — резолвим в реальный ratio из исходной
|
|
1373
1430
|
// картинки ДО того как обнулим state.sourceRef (ниже).
|
package/renderer/media.js
CHANGED
|
@@ -732,6 +732,10 @@ async function regenerateInto(node, kind, rawPrompt, opts = {}) {
|
|
|
732
732
|
? { file: opts.sourceRef.file, type: opts.sourceRef.type }
|
|
733
733
|
: (node.generated?.sourceRef || null);
|
|
734
734
|
|
|
735
|
+
// Параметры image/video, выбранные в gen-modal'е, СОХРАНЯЕМ в seedGen —
|
|
736
|
+
// иначе после regenerate node.generated.aspectRatio становится undefined,
|
|
737
|
+
// и в startGenerationJob срабатывает fallback на scene.settings.aspectRatio
|
|
738
|
+
// (по дефолту 9:16) — игнорируя то что юзер выбрал в модалке.
|
|
735
739
|
const seedGen = kind === 'audio'
|
|
736
740
|
? { kind, prompt: resolvedPrompt, rawPrompt, model: modelId, voiceId, voiceName,
|
|
737
741
|
ttsModel: state.ttsModel || node.generated?.ttsModel || 'qwen/qwen3-tts',
|
|
@@ -739,6 +743,12 @@ async function regenerateInto(node, kind, rawPrompt, opts = {}) {
|
|
|
739
743
|
: { kind, prompt: resolvedPrompt, rawPrompt, modelKey, model: modelId,
|
|
740
744
|
refs: refs ? refs.map(r => ({ name: r.name, type: r.type, file: r.file })) : [],
|
|
741
745
|
...(carryoverSourceRef ? { sourceRef: carryoverSourceRef } : {}),
|
|
746
|
+
...(kind === 'image' ? { aspectRatio: state.imageAspect } : {}),
|
|
747
|
+
...(kind === 'video' ? {
|
|
748
|
+
aspectRatio: state.videoAspect,
|
|
749
|
+
duration: state.videoDuration,
|
|
750
|
+
resolution: state.videoResolution,
|
|
751
|
+
} : {}),
|
|
742
752
|
state: 'submitting' };
|
|
743
753
|
|
|
744
754
|
// Для text-ноды сохраняем существующий .md-файл (runTextJob перезапишет
|
package/renderer/settings.js
CHANGED
|
@@ -475,7 +475,12 @@ async function openGenerateForRef(fromNode, clientX, clientY, forceKind) {
|
|
|
475
475
|
state.sourceRef = null;
|
|
476
476
|
$('genPrompt').value = '[@' + nodeRefKey(fromNode) + '] ';
|
|
477
477
|
}
|
|
478
|
+
// Lazy refresh — чтобы свежедобавленные/переименованные ноды
|
|
479
|
+
// персонажей и локаций попали в @-popup сразу после открытия модалки.
|
|
480
|
+
if (typeof loadAllCharactersInfo === 'function') loadAllCharactersInfo().catch(() => {});
|
|
481
|
+
if (typeof loadAllLocationsInfo === 'function') loadAllLocationsInfo().catch(() => {});
|
|
478
482
|
syncSourceRefRow();
|
|
483
|
+
if (typeof syncDefaultPromptRow === 'function') syncDefaultPromptRow();
|
|
479
484
|
|
|
480
485
|
// Если генерим image из существующей картинки-ноды — дефолт «↺ Как у
|
|
481
486
|
// исходной» (юзер обычно хочет такой же формат как у источника).
|
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. Когда юзер просит «напиши диалог», «придумай реплику»,
|