kingkont 0.11.1 → 0.11.3

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.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer/chat.js CHANGED
@@ -269,9 +269,13 @@
269
269
  // <tool>JSON</tool> — наш command-protocol (модель так зовёт tools)
270
270
  // <tool_result>JSON</tool_result> — модель иногда его галюцинирует
271
271
  // в outputе, копируя format из user-msg.
272
+ // После стрипа нормализуем whitespace — иначе остаются «дыры» и
273
+ // подряд идущие space'ы (3+ → 1, blank lines 3+ → 2).
272
274
  return text
273
275
  .replace(/<tool>[\s\S]*?<\/tool>/g, '')
274
276
  .replace(/<tool_result>[\s\S]*?<\/tool_result>/g, '')
277
+ .replace(/[ \t]{2,}/g, ' ')
278
+ .replace(/\n{3,}/g, '\n\n')
275
279
  .trim();
276
280
  }
277
281
 
@@ -518,26 +522,42 @@
518
522
  for (const m of history) {
519
523
  if (m.role === 'system') continue;
520
524
  if (m.role === 'user' && m.content?.startsWith('<tool_result>')) continue; // системный turn
525
+ // Пустой assistant без tools — не рендерим (бывает при streaming/parse-fail).
526
+ const hasContent = !!(m.content && m.content.trim());
527
+ const hasTools = Array.isArray(m.tools) && m.tools.length;
528
+ if (!hasContent && !hasTools) continue;
521
529
  const div = document.createElement('div');
522
530
  div.className = 'chat-msg chat-msg-' + m.role;
523
531
  const lbl = document.createElement('div');
524
532
  lbl.className = 'chat-msg-role';
525
533
  lbl.textContent = m.role === 'user' ? 'Вы' : 'Claude';
526
534
  div.appendChild(lbl);
527
- const body = document.createElement('div');
528
- body.className = 'chat-msg-body';
529
- body.textContent = m.content || '';
530
- div.appendChild(body);
531
- if (Array.isArray(m.tools) && m.tools.length) {
535
+ if (hasContent) {
536
+ const body = document.createElement('div');
537
+ body.className = 'chat-msg-body';
538
+ body.textContent = m.content;
539
+ div.appendChild(body);
540
+ }
541
+ if (hasTools) {
532
542
  for (const tc of m.tools) {
533
543
  const t = document.createElement('details');
534
544
  t.className = 'chat-tool';
535
545
  const sum = document.createElement('summary');
536
- sum.textContent = `🔧 ${tc.name}` + (tc._error ? ' (ошибка)' : tc._ok ? ' ✓' : '');
546
+ // Компактный summary: имя tool + ключевой arg (если короткий) + статус.
547
+ const argHint = _argHint(tc.args);
548
+ const status = tc._error ? ' ⚠' : tc._ok ? ' ✓' : '';
549
+ sum.textContent = `🔧 ${tc.name}${argHint}${status}`;
537
550
  t.appendChild(sum);
538
- const pre = document.createElement('pre');
539
- pre.textContent = JSON.stringify({ args: tc.args, result: tc.result, error: tc._error }, null, 2);
540
- t.appendChild(pre);
551
+ // pre добавляем только если есть что показать (args/result/error не пусты).
552
+ const dump = {};
553
+ if (tc.args && Object.keys(tc.args).length) dump.args = tc.args;
554
+ if (tc.result !== undefined) dump.result = tc.result;
555
+ if (tc._error) dump.error = tc._error;
556
+ if (Object.keys(dump).length) {
557
+ const pre = document.createElement('pre');
558
+ pre.textContent = JSON.stringify(dump, null, 2);
559
+ t.appendChild(pre);
560
+ }
541
561
  div.appendChild(t);
542
562
  }
543
563
  }
@@ -545,6 +565,22 @@
545
565
  }
546
566
  list.scrollTop = list.scrollHeight;
547
567
  }
568
+
569
+ // Короткая подсказка про args для summary tool-блока.
570
+ // 'add_node({type:image, name:"..."})' — без полного JSON.
571
+ function _argHint(args) {
572
+ if (!args || typeof args !== 'object') return '';
573
+ const keys = Object.keys(args);
574
+ if (!keys.length) return '';
575
+ const items = [];
576
+ for (const k of keys) {
577
+ const v = args[k];
578
+ let s = typeof v === 'string' ? `"${v.length > 30 ? v.slice(0, 30) + '…' : v}"` : String(v);
579
+ items.push(`${k}:${s}`);
580
+ if (items.join(',').length > 60) break;
581
+ }
582
+ return ` (${items.join(', ')})`;
583
+ }
548
584
  // Перебиндим
549
585
  renderHistory = renderHistoryFiltered;
550
586
 
@@ -269,13 +269,13 @@
269
269
  }
270
270
  .chat-header button:hover { background: #2a2a2a; color: #fff; }
271
271
  .chat-list {
272
- flex: 1; overflow-y: auto; padding: 12px;
273
- display: flex; flex-direction: column; gap: 12px;
272
+ flex: 1; overflow-y: auto; padding: 10px;
273
+ display: flex; flex-direction: column; gap: 6px;
274
274
  }
275
275
  .chat-msg {
276
- display: flex; flex-direction: column; gap: 4px;
277
- padding: 10px 12px; border-radius: 8px;
278
- font-size: 13px; line-height: 1.5;
276
+ display: flex; flex-direction: column; gap: 2px;
277
+ padding: 6px 10px; border-radius: 6px;
278
+ font-size: 13px; line-height: 1.45;
279
279
  }
280
280
  .chat-msg-user { background: #1f2a3a; border: 1px solid #2a3a4a; }
281
281
  .chat-msg-assistant { background: #1f1f1f; border: 1px solid #2a2a2a; }
@@ -289,8 +289,8 @@
289
289
  color: #e0e0e0; white-space: pre-wrap; word-break: break-word;
290
290
  }
291
291
  .chat-tool {
292
- margin-top: 6px; background: #0e0e0e; border: 1px solid #2a2a2a;
293
- border-radius: 4px; padding: 4px 8px; font-size: 11px;
292
+ margin-top: 2px; background: #0e0e0e; border: 1px solid #2a2a2a;
293
+ border-radius: 3px; padding: 2px 6px; font-size: 11px;
294
294
  }
295
295
  .chat-tool summary {
296
296
  cursor: pointer; color: #aac; outline: none;
@@ -599,7 +599,7 @@ async function openProjectTemplate(templateId, suggestedName) {
599
599
  'Имя нового проекта:',
600
600
  suggestedName || 'Из шаблона',
601
601
  suggestedName || '',
602
- { okText: 'Создать в облаке' },
602
+ { okText: 'Создать проект' },
603
603
  );
604
604
  if (!projectName) return;
605
605
 
@@ -662,6 +662,27 @@ async function openProjectTemplate(templateId, suggestedName) {
662
662
  } else {
663
663
  throw new Error('cloudProjects.open недоступен');
664
664
  }
665
+
666
+ // Auto-select первую сцену (или первый character/location если сцен нет).
667
+ // Юзер ожидает что после открытия проекта-из-шаблона он сразу видит
668
+ // содержимое, а не пустой холст.
669
+ try {
670
+ // Приоритет: episode → character → location.
671
+ const order = ['episode', 'character', 'location'];
672
+ const firstByOrder = order
673
+ .map(k => cloudBoards.find(b => b.kind === k))
674
+ .find(Boolean);
675
+ const target = firstByOrder || cloudBoards[0];
676
+ if (target && state.filmHandle) {
677
+ const list = target.kind === 'character' ? await listCharacters(state.filmHandle)
678
+ : target.kind === 'location' ? await listLocations(state.filmHandle)
679
+ : await listEpisodes(state.filmHandle);
680
+ const found = list.find(x => x.name === target.name);
681
+ if (found) await selectBoard({ kind: target.kind, ...found });
682
+ }
683
+ } catch (e) {
684
+ console.warn('auto-select first board failed:', e?.message || e);
685
+ }
665
686
  } catch (e) {
666
687
  TPL_PROGRESS.hide();
667
688
  tplStatus('Ошибка открытия проекта: ' + e.message, true);