kingkont 0.16.0 → 0.16.1
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/providers.js +23 -5
- package/package.json +1 -1
- package/renderer/chat.js +52 -5
- package/renderer/styles.css +39 -5
package/lib/providers.js
CHANGED
|
@@ -602,16 +602,34 @@ async function generateMusic(args) {
|
|
|
602
602
|
const { prompt, durationMs, settings: s } = args;
|
|
603
603
|
if (!prompt) throw new Error('нужен prompt');
|
|
604
604
|
|
|
605
|
-
// Приоритет direct ElevenLabs если включён + есть
|
|
606
|
-
// быть кастомные саб-настройки и платный ElevenLabs (зачем тратить
|
|
607
|
-
// Chatium-кредиты). Аналогичная логика в generateTts/generateSfx.
|
|
605
|
+
// Приоритет direct ElevenLabs если включён + есть ключ.
|
|
608
606
|
const directElevenAvailable = s.useElevenlabs && process.env.ELEVENLABS_API_KEY;
|
|
609
607
|
|
|
608
|
+
// Пробуем Chatium первым ТОЛЬКО если direct ElevenLabs не доступен.
|
|
609
|
+
// Если Chatium упал на музыке — auto-fallback на direct ElevenLabs если он
|
|
610
|
+
// тоже включён (бывало: Chatium-side music broken, у юзера только Eleven
|
|
611
|
+
// через Chatium → 500 → надо retry direct).
|
|
610
612
|
if (s.useChatium && s.chatium?.token && s.chatium?.base && !directElevenAvailable) {
|
|
611
|
-
|
|
613
|
+
try {
|
|
614
|
+
return await audioViaChatium(s, { kind: 'music', prompt, durationMs });
|
|
615
|
+
} catch (e) {
|
|
616
|
+
// Если есть direct Eleven (даже без env-key, попробуем s.elevenKey) —
|
|
617
|
+
// используем его как fallback.
|
|
618
|
+
if (s.useElevenlabs && (process.env.ELEVENLABS_API_KEY || s.elevenKey)) {
|
|
619
|
+
console.warn('[music] Chatium failed, falling back to direct ElevenLabs:', e?.message);
|
|
620
|
+
// Промосим в env если задан в settings но не в env (race с
|
|
621
|
+
// applySettingsToEnv после старта сервера).
|
|
622
|
+
if (!process.env.ELEVENLABS_API_KEY && s.elevenKey) {
|
|
623
|
+
process.env.ELEVENLABS_API_KEY = s.elevenKey;
|
|
624
|
+
}
|
|
625
|
+
// Падаем в direct-eleven path ниже.
|
|
626
|
+
} else {
|
|
627
|
+
throw new Error(`Музыка через KingKont недоступна: ${e.message?.slice(0, 200)}. Включи прямой ElevenLabs ключ в Настройках.`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
612
630
|
}
|
|
613
631
|
if (!s.useElevenlabs) throw new Error('Войдите в KingKont или ElevenLabs для аудио.');
|
|
614
|
-
const key = process.env.ELEVENLABS_API_KEY;
|
|
632
|
+
const key = process.env.ELEVENLABS_API_KEY || s.elevenKey;
|
|
615
633
|
if (!key) throw new Error('ELEVENLABS_API_KEY не задан');
|
|
616
634
|
const body = { prompt };
|
|
617
635
|
if (durationMs) body.music_length_ms = +durationMs;
|
package/package.json
CHANGED
package/renderer/chat.js
CHANGED
|
@@ -847,6 +847,27 @@
|
|
|
847
847
|
// при send(). Файлы сохраняются в <currentBoard>/inbox/<name>.
|
|
848
848
|
let manualAttachments = []; // [{relPath, name, size, mime}] — файлы из drag-drop
|
|
849
849
|
|
|
850
|
+
// Короткая строка-итог контекста для отображения в чате рядом с user-msg.
|
|
851
|
+
// null если нечего показать.
|
|
852
|
+
function _ctxToInlineLabel(ctx) {
|
|
853
|
+
const parts = [];
|
|
854
|
+
if (ctx.scene) parts.push(`🎬 ${ctx.scene.name}`);
|
|
855
|
+
const byType = {};
|
|
856
|
+
for (const sel of ctx.selected) (byType[sel.type] = byType[sel.type] || []).push(sel);
|
|
857
|
+
for (const [type, items] of Object.entries(byType)) {
|
|
858
|
+
const icon = type === 'image' ? '🖼' : type === 'video' ? '🎬' : type === 'audio' ? '🎙' : type === 'text' ? '📝' : '◉';
|
|
859
|
+
if (items.length === 1) parts.push(`${icon} ${items[0].name || items[0].id?.slice(0,6)}`);
|
|
860
|
+
else {
|
|
861
|
+
const noun = (typeof _typeForms !== 'undefined' && _typeForms[type]) || ['нода','ноды','нод'];
|
|
862
|
+
parts.push(`${icon} ${items.length} ${_ru_plural(items.length, noun)}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (ctx.attachments?.length) {
|
|
866
|
+
parts.push(`📎 ${ctx.attachments.length} файл${ctx.attachments.length > 1 ? 'а' : ''}`);
|
|
867
|
+
}
|
|
868
|
+
return parts.length ? parts.join(' · ') : null;
|
|
869
|
+
}
|
|
870
|
+
|
|
850
871
|
function buildContextSnapshot() {
|
|
851
872
|
const ctx = { scene: null, selected: [], attachments: manualAttachments.slice() };
|
|
852
873
|
if (state.currentBoard) {
|
|
@@ -1268,14 +1289,26 @@
|
|
|
1268
1289
|
// Snapshot НЕ берём автоматически — теперь это явный tool take_snapshot,
|
|
1269
1290
|
// который Claude вызывает перед destructive-действиями. См. system-prompt.
|
|
1270
1291
|
// Контекст + system-prompt: формируем тут (на клиенте) и отдаём серверу.
|
|
1292
|
+
// Также сохраняем context-snapshot в user-msg чтобы юзер видел в чате
|
|
1293
|
+
// на каком контексте он спрашивал (через много turns'ов это полезно).
|
|
1271
1294
|
const ctxSnap = buildContextSnapshot();
|
|
1272
1295
|
const system = buildSystemPrompt() + '\n\n' + buildContextBlock(ctxSnap);
|
|
1296
|
+
// Inline context summary в visible тексте сообщения для UI render'a.
|
|
1297
|
+
// Claude получает полный context через system, юзер — через ctxLabel
|
|
1298
|
+
// в самом message (см. renderHistoryFiltered).
|
|
1299
|
+
const ctxLabel = _ctxToInlineLabel(ctxSnap);
|
|
1300
|
+
// Если есть контекст — добавляем его как первой строкой в сообщение
|
|
1301
|
+
// (с маркером [ctx:...]). Это вид'но и юзеру (renderer стилизует),
|
|
1302
|
+
// и Claude (повторяет инструкцию из system-prompt в самом turn'е).
|
|
1303
|
+
const textWithCtx = ctxLabel
|
|
1304
|
+
? `[ctx: ${ctxLabel}]\n${userText}`
|
|
1305
|
+
: userText;
|
|
1273
1306
|
appendStatus('KingKont думает…');
|
|
1274
1307
|
try {
|
|
1275
1308
|
const r = await fetch('/api/chat/send', {
|
|
1276
1309
|
method: 'POST',
|
|
1277
1310
|
headers: { 'Content-Type': 'application/json' },
|
|
1278
|
-
body: JSON.stringify({ key, text:
|
|
1311
|
+
body: JSON.stringify({ key, text: textWithCtx, system }),
|
|
1279
1312
|
});
|
|
1280
1313
|
if (!r.ok) {
|
|
1281
1314
|
const err = await r.json().catch(() => ({}));
|
|
@@ -1335,10 +1368,24 @@
|
|
|
1335
1368
|
}
|
|
1336
1369
|
div.appendChild(lbl);
|
|
1337
1370
|
if (hasContent) {
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1371
|
+
// Парсим [ctx: ...] префикс если есть — рендерим как маленький
|
|
1372
|
+
// сабтайтл над основным текстом.
|
|
1373
|
+
let mainText = m.content;
|
|
1374
|
+
let ctxLine = null;
|
|
1375
|
+
const cm = m.content.match(/^\[ctx:\s*([^\]\n]+)\]\n?([\s\S]*)$/);
|
|
1376
|
+
if (cm) { ctxLine = cm[1].trim(); mainText = cm[2].trim(); }
|
|
1377
|
+
if (ctxLine) {
|
|
1378
|
+
const cx = document.createElement('div');
|
|
1379
|
+
cx.className = 'chat-msg-ctx';
|
|
1380
|
+
cx.textContent = ctxLine;
|
|
1381
|
+
div.appendChild(cx);
|
|
1382
|
+
}
|
|
1383
|
+
if (mainText) {
|
|
1384
|
+
const body = document.createElement('div');
|
|
1385
|
+
body.className = 'chat-msg-body';
|
|
1386
|
+
body.textContent = mainText;
|
|
1387
|
+
div.appendChild(body);
|
|
1388
|
+
}
|
|
1342
1389
|
}
|
|
1343
1390
|
if (hasTools) {
|
|
1344
1391
|
// Все tools в ОДНУ строку (separated " · "), без emoji-icons —
|
package/renderer/styles.css
CHANGED
|
@@ -365,20 +365,35 @@
|
|
|
365
365
|
.chat-msg-body {
|
|
366
366
|
color: #e0e0e0; white-space: pre-wrap; word-break: break-word;
|
|
367
367
|
}
|
|
368
|
+
/* [ctx: ...] префикс — маленький subtitle над user-msg, светло-серый. */
|
|
369
|
+
.chat-msg-ctx {
|
|
370
|
+
font-size: 10.5px; color: #889;
|
|
371
|
+
background: rgba(255,255,255,0.04); padding: 2px 6px;
|
|
372
|
+
border-radius: 4px; margin-bottom: 3px;
|
|
373
|
+
align-self: flex-start; max-width: 100%;
|
|
374
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
375
|
+
}
|
|
368
376
|
.chat-tool {
|
|
369
377
|
margin-top: 2px; background: transparent; border: none;
|
|
370
378
|
padding: 0; font-size: 10.5px; color: #666;
|
|
379
|
+
/* Гарантия что tool-блок занимает ровно одну строку (плюс развёрнутый pre)
|
|
380
|
+
— иначе при множестве tools или длинных args блок разрастался. */
|
|
381
|
+
max-width: 100%; min-width: 0;
|
|
371
382
|
}
|
|
372
383
|
.chat-tool summary {
|
|
373
|
-
cursor: pointer; color: #666; outline: none; opacity: 0.
|
|
384
|
+
cursor: pointer; color: #666; outline: none; opacity: 0.7;
|
|
374
385
|
font-family: ui-monospace, 'SF Mono', monospace;
|
|
375
386
|
list-style: none; padding: 1px 4px;
|
|
376
387
|
transition: color 0.1s, opacity 0.1s;
|
|
388
|
+
/* Один в строку, длинное → ellipsis. Click раскрывает <details>. */
|
|
389
|
+
display: block; white-space: nowrap; overflow: hidden;
|
|
390
|
+
text-overflow: ellipsis; max-width: 100%;
|
|
377
391
|
}
|
|
378
392
|
.chat-tool summary::-webkit-details-marker { display: none; }
|
|
379
393
|
.chat-tool summary::before {
|
|
380
394
|
content: '▸ '; color: #555; font-size: 9px;
|
|
381
395
|
}
|
|
396
|
+
.chat-tool[open] summary { white-space: normal; text-overflow: clip; }
|
|
382
397
|
.chat-tool[open] summary::before { content: '▾ '; }
|
|
383
398
|
.chat-tool summary:hover { color: #aaa; opacity: 1; }
|
|
384
399
|
.chat-tool pre {
|
|
@@ -478,10 +493,21 @@
|
|
|
478
493
|
}
|
|
479
494
|
.welcome-card.open-card .welcome-card-ts { color: #aac0d8; }
|
|
480
495
|
.welcome-card:hover { border-color: #4a6a9a; transform: translateY(-2px); }
|
|
496
|
+
/* Card теперь сама с фиксированным aspect-ratio + overflow:hidden →
|
|
497
|
+
обложка заполняет всю карту (вертикальная картинка кроп'ится по верху/
|
|
498
|
+
низу через object-fit:cover, не растягивает карту бесконечно вверх).
|
|
499
|
+
Meta (название + дата) — absolute поверх обложки внизу с тёмным
|
|
500
|
+
градиентом, читается на ЛЮБОЙ обложке. */
|
|
501
|
+
.welcome-card {
|
|
502
|
+
aspect-ratio: 16 / 9;
|
|
503
|
+
overflow: hidden;
|
|
504
|
+
}
|
|
481
505
|
.welcome-card-thumb {
|
|
482
|
-
|
|
506
|
+
position: absolute; inset: 0;
|
|
507
|
+
background: #1a1a1a;
|
|
483
508
|
display: flex; align-items: center; justify-content: center;
|
|
484
509
|
color: #444; font-size: 32px;
|
|
510
|
+
overflow: hidden; /* страховка чтобы img/badge не выезжали */
|
|
485
511
|
}
|
|
486
512
|
.welcome-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
487
513
|
/* ☁-бейдж в углу обложки облачного проекта. Делает cloud/folder-проекты
|
|
@@ -511,9 +537,17 @@
|
|
|
511
537
|
0%, 100% { opacity: 0.85; }
|
|
512
538
|
50% { opacity: 1; }
|
|
513
539
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
.welcome-card-
|
|
540
|
+
/* Meta теперь оверлеем поверх обложки внизу с тёмным градиентом —
|
|
541
|
+
название + дата всегда читаемы независимо от цвета обложки. */
|
|
542
|
+
.welcome-card-meta {
|
|
543
|
+
position: absolute; left: 0; right: 0; bottom: 0;
|
|
544
|
+
padding: 28px 12px 8px 12px;
|
|
545
|
+
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.55) 30%, rgba(0,0,0,0.92) 100%);
|
|
546
|
+
color: #fff;
|
|
547
|
+
pointer-events: none; /* клик идёт на card */
|
|
548
|
+
}
|
|
549
|
+
.welcome-card-name { font-size: 13px; color: #fff; word-break: break-all; font-weight: 500; text-shadow: 0 1px 2px rgba(0,0,0,0.6); }
|
|
550
|
+
.welcome-card-ts { font-size: 11px; color: #ccc; margin-top: 2px; text-shadow: 0 1px 2px rgba(0,0,0,0.6); }
|
|
517
551
|
.welcome-card-del {
|
|
518
552
|
position: absolute; top: 6px; right: 6px;
|
|
519
553
|
background: rgba(0,0,0,0.6); color: #aaa;
|