kingkont 0.7.24 → 0.7.26
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 +79 -13
- package/package.json +1 -1
- package/updates.html +13 -2
package/index.html
CHANGED
|
@@ -834,10 +834,17 @@
|
|
|
834
834
|
.node.picked { border-color: #f0a040; box-shadow: 0 0 0 2px rgba(240,160,64,0.55), 0 4px 12px rgba(0,0,0,0.4); }
|
|
835
835
|
.node-header {
|
|
836
836
|
padding: 4px 8px; border-bottom: 1px solid #383838;
|
|
837
|
-
display: flex; justify-content:
|
|
837
|
+
display: flex; justify-content: space-between; align-items: center; gap: 6px;
|
|
838
838
|
min-height: 22px; cursor: move;
|
|
839
839
|
}
|
|
840
|
-
.node-header .
|
|
840
|
+
.node-header .name {
|
|
841
|
+
flex: 1; min-width: 0;
|
|
842
|
+
color: #ccc; font-size: 12px; line-height: 1.2;
|
|
843
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
844
|
+
user-select: none;
|
|
845
|
+
}
|
|
846
|
+
.node-header .name.empty { color: #555; font-style: italic; }
|
|
847
|
+
.node-header .delete { cursor: pointer; color: #888; font-size: 16px; line-height: 1; padding: 0 4px; flex-shrink: 0; }
|
|
841
848
|
.node-header .delete:hover { color: #f88; }
|
|
842
849
|
|
|
843
850
|
/* Имя ноды убрано из header — переименование через ПКМ → ✏ Переименовать.
|
|
@@ -3180,10 +3187,20 @@ async function createNodeEl(node) {
|
|
|
3180
3187
|
|
|
3181
3188
|
const header = document.createElement('div');
|
|
3182
3189
|
header.className = 'node-header';
|
|
3183
|
-
header.title = 'Тяни, чтобы переместить.
|
|
3190
|
+
header.title = 'Тяни, чтобы переместить. Двойной клик по имени или ПКМ → Переименовать.';
|
|
3191
|
+
|
|
3192
|
+
// Имя ноды (если задано). Узкий header — обрезаем ellipsis'ом.
|
|
3193
|
+
const nameEl = document.createElement('span');
|
|
3194
|
+
nameEl.className = 'name';
|
|
3195
|
+
if (node.name) nameEl.textContent = node.name;
|
|
3196
|
+
else { nameEl.textContent = '—'; nameEl.classList.add('empty'); }
|
|
3197
|
+
// Дабл-клик по имени → переименовать. Не блокируем drag-mousedown.
|
|
3198
|
+
nameEl.addEventListener('dblclick', e => {
|
|
3199
|
+
e.stopPropagation();
|
|
3200
|
+
renameNode(node);
|
|
3201
|
+
});
|
|
3202
|
+
header.appendChild(nameEl);
|
|
3184
3203
|
|
|
3185
|
-
// Имя ноды НЕ показываем в header — было слишком тесно (overflow-проблемы),
|
|
3186
|
-
// переименование вынесено в контекстное меню (ПКМ → ✏ Переименовать).
|
|
3187
3204
|
const del = document.createElement('span');
|
|
3188
3205
|
del.className = 'delete'; del.textContent = '×'; del.title = 'Удалить ноду';
|
|
3189
3206
|
header.appendChild(del);
|
|
@@ -3265,6 +3282,18 @@ async function renameNode(node) {
|
|
|
3265
3282
|
}
|
|
3266
3283
|
node.name = trimmed;
|
|
3267
3284
|
scheduleSave();
|
|
3285
|
+
// Обновим header — имя ноды показывается в .node-header .name.
|
|
3286
|
+
const nodeEl = canvas.querySelector(`.node[data-id="${node.id}"]`);
|
|
3287
|
+
const nameEl = nodeEl?.querySelector('.node-header .name');
|
|
3288
|
+
if (nameEl) {
|
|
3289
|
+
if (trimmed) {
|
|
3290
|
+
nameEl.textContent = trimmed;
|
|
3291
|
+
nameEl.classList.remove('empty');
|
|
3292
|
+
} else {
|
|
3293
|
+
nameEl.textContent = '—';
|
|
3294
|
+
nameEl.classList.add('empty');
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3268
3297
|
}
|
|
3269
3298
|
|
|
3270
3299
|
function showNodeContextMenu(node, clientX, clientY) {
|
|
@@ -6925,14 +6954,38 @@ let voicesLoaded = false;
|
|
|
6925
6954
|
const VOICES_CACHE_KEY = 'voicesCache';
|
|
6926
6955
|
const VOICES_CACHE_TTL = 24 * 60 * 60 * 1000;
|
|
6927
6956
|
|
|
6957
|
+
// ElevenLabs v3 в KingKont/Chatium ждёт voice СТРОГО из этого enum'а.
|
|
6958
|
+
// (см. server-side validation: ValidationError: voice: invalid enum value).
|
|
6959
|
+
// Это не воссы из ElevenLabs API, а hardcoded набор поддерживаемых @start/sdk.
|
|
6960
|
+
const ELEVENLABS_V3_VOICES = [
|
|
6961
|
+
'Rachel', 'Drew', 'Clyde', 'Paul', 'Aria', 'Domi', 'Dave', 'Roger',
|
|
6962
|
+
'Fin', 'Sarah', 'James', 'Jane', 'Juniper', 'Arabella', 'Hope',
|
|
6963
|
+
'Bradford', 'Reginald', 'Gaming', 'Austin', 'Kuon', 'Blondie',
|
|
6964
|
+
'Priyanka', 'Alexandra', 'Monika', 'Mark', 'Grimblewood',
|
|
6965
|
+
];
|
|
6966
|
+
|
|
6928
6967
|
function populateVoicesSelect(voices) {
|
|
6929
6968
|
const select = $('genVoice');
|
|
6930
6969
|
select.innerHTML = '';
|
|
6970
|
+
// Если активна ElevenLabs v3 (через KingKont) — показываем только enum.
|
|
6971
|
+
if (state.ttsModel === 'elevenlabs/v3') {
|
|
6972
|
+
for (const name of ELEVENLABS_V3_VOICES) {
|
|
6973
|
+
const opt = document.createElement('option');
|
|
6974
|
+
opt.value = name; // value = name (для KingKont)
|
|
6975
|
+
opt.dataset.voiceName = name;
|
|
6976
|
+
opt.textContent = name;
|
|
6977
|
+
select.appendChild(opt);
|
|
6978
|
+
}
|
|
6979
|
+
const last = localStorage.getItem('lastElevenV3Voice');
|
|
6980
|
+
if (last && ELEVENLABS_V3_VOICES.includes(last)) select.value = last;
|
|
6981
|
+
return;
|
|
6982
|
+
}
|
|
6983
|
+
// Иначе — список из ElevenLabs API (для прямого ElevenLabs, не KingKont).
|
|
6931
6984
|
voices.sort((a, b) => a.name.localeCompare(b.name));
|
|
6932
6985
|
for (const v of voices) {
|
|
6933
6986
|
const opt = document.createElement('option');
|
|
6934
6987
|
opt.value = v.id;
|
|
6935
|
-
opt.dataset.voiceName = v.name;
|
|
6988
|
+
opt.dataset.voiceName = v.name;
|
|
6936
6989
|
const lbl = v.labels || {};
|
|
6937
6990
|
const tags = [lbl.gender, lbl.language || lbl.accent, lbl.age].filter(Boolean).join(' / ');
|
|
6938
6991
|
opt.textContent = tags ? `${v.name} — ${tags}` : v.name;
|
|
@@ -7006,17 +7059,21 @@ document.querySelectorAll('#genModal [data-tts-model]').forEach(b => {
|
|
|
7006
7059
|
b.classList.add('active');
|
|
7007
7060
|
state.ttsModel = b.dataset.ttsModel;
|
|
7008
7061
|
localStorage.setItem('ttsModel', state.ttsModel);
|
|
7009
|
-
|
|
7010
|
-
const showVoice = state.ttsModel === 'elevenlabs/v3';
|
|
7011
|
-
$('voiceRow').style.display = showVoice ? '' : 'none';
|
|
7062
|
+
syncTtsVoiceList();
|
|
7012
7063
|
});
|
|
7013
7064
|
});
|
|
7065
|
+
function syncTtsVoiceList() {
|
|
7066
|
+
const showVoice = state.ttsModel === 'elevenlabs/v3';
|
|
7067
|
+
$('voiceRow').style.display = showVoice ? '' : 'none';
|
|
7068
|
+
if (showVoice) {
|
|
7069
|
+
// Перерисовываем select под elevenlabs/v3 enum (hardcoded 26 имён).
|
|
7070
|
+
populateVoicesSelect([]);
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7014
7073
|
function syncTtsModelActive() {
|
|
7015
7074
|
document.querySelectorAll('#genModal [data-tts-model]').forEach(b =>
|
|
7016
7075
|
b.classList.toggle('active', b.dataset.ttsModel === state.ttsModel));
|
|
7017
|
-
|
|
7018
|
-
const showVoice = state.ttsModel === 'elevenlabs/v3';
|
|
7019
|
-
$('voiceRow').style.display = showVoice ? '' : 'none';
|
|
7076
|
+
syncTtsVoiceList();
|
|
7020
7077
|
}
|
|
7021
7078
|
// Подсветить активную video-модель при открытии modal'а
|
|
7022
7079
|
function syncVideoModelActive() {
|
|
@@ -7112,7 +7169,16 @@ $('genSubmit').addEventListener('click', async () => {
|
|
|
7112
7169
|
if (kind === 'audio') {
|
|
7113
7170
|
const voiceId = $('genVoice').value || 'JBFqnCBsd6RMkjVDRZzb';
|
|
7114
7171
|
const voiceName = $('genVoice').selectedOptions[0]?.textContent || '';
|
|
7115
|
-
if (voiceId)
|
|
7172
|
+
if (voiceId) {
|
|
7173
|
+
// Для ElevenLabs v3 (через KingKont) value === name из enum.
|
|
7174
|
+
// Сохраняем в отдельный ключ, иначе при переключении модели
|
|
7175
|
+
// подставится UUID-id и select не выберет его.
|
|
7176
|
+
if (state.ttsModel === 'elevenlabs/v3') {
|
|
7177
|
+
localStorage.setItem('lastElevenV3Voice', voiceId);
|
|
7178
|
+
} else {
|
|
7179
|
+
localStorage.setItem('lastVoiceId', voiceId);
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7116
7182
|
// Резолвим @-mentions ДО добавления tone-prefix: text-ноды инлайнятся
|
|
7117
7183
|
// в их .md-содержимое, иначе ElevenLabs получит «[@name]» → пусто после
|
|
7118
7184
|
// strip-spec-tags и упадёт «empty text».
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kingkont",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.26",
|
|
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/updates.html
CHANGED
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
71
71
|
|
|
72
72
|
.error { color: #ef4444; font-size: 12px; margin-top: 12px; }
|
|
73
|
+
@keyframes pulse {
|
|
74
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(22,163,74,0.55); }
|
|
75
|
+
50% { box-shadow: 0 0 0 8px rgba(22,163,74,0); }
|
|
76
|
+
}
|
|
73
77
|
</style>
|
|
74
78
|
</head>
|
|
75
79
|
<body>
|
|
@@ -89,9 +93,10 @@
|
|
|
89
93
|
</div>
|
|
90
94
|
</div>
|
|
91
95
|
<div id="updateBlock" style="display:none;">
|
|
92
|
-
<div style="display:flex; gap:8px; align-items:center; margin-top:6px; flex-wrap:wrap;
|
|
96
|
+
<div style="display:flex; gap:8px; align-items:center; margin-top:6px; flex-wrap:wrap;
|
|
97
|
+
position: sticky; top: 0; background: var(--card); z-index: 5; padding: 6px 0;">
|
|
93
98
|
<button class="primary" id="installBtn" style="padding:8px 16px;">⬇ Установить и перезапустить</button>
|
|
94
|
-
<button class="primary" id="relaunchBtn" style="padding:8px 16px; display:none; background: var(--ok); border-color: var(--ok);">↻ Перезапустить сейчас</button>
|
|
99
|
+
<button class="primary" id="relaunchBtn" style="padding:8px 16px; display:none; background: var(--ok); border-color: var(--ok); animation: pulse 1.5s ease-in-out infinite;">↻ Перезапустить сейчас</button>
|
|
95
100
|
<span style="color:#888; font-size:11px;">или вручную:</span>
|
|
96
101
|
</div>
|
|
97
102
|
<div class="install-cmd">
|
|
@@ -187,6 +192,12 @@ $('installBtn').addEventListener('click', async () => {
|
|
|
187
192
|
done.style.display = '';
|
|
188
193
|
installBtn.style.display = 'none';
|
|
189
194
|
$('relaunchBtn').style.display = '';
|
|
195
|
+
// Прокручиваем окно к верху чтобы кнопка «Перезапустить» точно
|
|
196
|
+
// была в видимой области (install-лог мог уехать на пол-экрана).
|
|
197
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
198
|
+
document.querySelector('.wrap')?.scrollTo?.({ top: 0, behavior: 'smooth' });
|
|
199
|
+
// Подсветка-привлечение внимания.
|
|
200
|
+
$('relaunchBtn').focus();
|
|
190
201
|
} catch (e) {
|
|
191
202
|
log.style.color = '#f88';
|
|
192
203
|
log.textContent += `\n[error] ${e?.message || String(e)}`;
|