kingkont 0.7.0 → 0.7.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/index.html CHANGED
@@ -949,7 +949,7 @@
949
949
  <aside class="sidebar">
950
950
  <div class="sidebar-header">
951
951
  <div class="brand">
952
- <img src="assets/logo-square.svg" alt="" draggable="false">
952
+ <img src="assets/logo-square.svg" alt="" draggable="false" id="brandLogo" title="Дабл-клик — настройки" style="cursor:pointer;">
953
953
  <div style="min-width:0; flex:1;">
954
954
  <div class="title">KingKont</div>
955
955
  <div class="sub" id="brandSub">Видео-редактор</div>
@@ -979,7 +979,7 @@
979
979
  <div class="sidebar-list" id="episodeList"></div>
980
980
  </div>
981
981
  <div class="sidebar-footer">
982
- <span id="balanceInfo" class="balance-info" style="display:none;" title="Баланс Chatium-аккаунта · клик — лог списаний" onclick="window.openTxLog && window.openTxLog()">
982
+ <span id="balanceInfo" class="balance-info" style="display:none;" title="Баланс KingKont-аккаунта · клик — лог списаний" onclick="window.openTxLog && window.openTxLog()">
983
983
  <span class="dot"></span>
984
984
  <span id="balanceValue">— credits</span>
985
985
  </span>
@@ -991,7 +991,7 @@
991
991
  <!-- Welcome-экран: виден только когда body.no-project -->
992
992
  <div class="welcome" id="welcome">
993
993
  <div class="welcome-inner">
994
- <img class="welcome-logo" src="assets/logo-square.svg" alt="" draggable="false">
994
+ <img class="welcome-logo" src="assets/logo-square.svg" alt="" draggable="false" id="welcomeLogo" title="Дабл-клик — настройки" style="cursor:pointer;">
995
995
  <h1 class="welcome-title">KingKont</h1>
996
996
  <div class="welcome-sub">Видео-редактор</div>
997
997
  <button id="welcomeOpen" class="welcome-open primary">Открыть проект</button>
@@ -1301,6 +1301,14 @@
1301
1301
  <button class="seg" data-img-model="sdxl-lightning" type="button" title="Очень быстрая (3-5с)">⚡ SDXL Lightning</button>
1302
1302
  </div>
1303
1303
  </label>
1304
+ <label id="videoModelRow" style="display: none;">Модель для видео
1305
+ <div class="seg-control">
1306
+ <button class="seg active" data-vid-model="seedance-2" type="button" title="ByteDance Seedance 2 — основная">Seedance 2</button>
1307
+ <button class="seg" data-vid-model="seedance-2-fast" type="button" title="Быстрее, чуть проще качество">⚡ Seedance 2 Fast</button>
1308
+ <button class="seg" data-vid-model="kling-o1" type="button" title="Kling O1 — креативный, поддерживает reference-видео">Kling O1</button>
1309
+ <button class="seg" data-vid-model="kling-3.0" type="button" title="Kling 3.0 — multi-shot до 15с">Kling 3.0</button>
1310
+ </div>
1311
+ </label>
1304
1312
  <label id="videoOptionsRow" style="display: none;">Параметры видео
1305
1313
  <div style="display: flex; gap: 12px; flex-wrap: wrap; margin-top: 6px;">
1306
1314
  <div style="flex: 1; min-width: 200px;">
@@ -1719,7 +1727,8 @@ const state = {
1719
1727
  filmHandle: null,
1720
1728
  currentBoard: null, // { kind, name, key, handle, metadata, urls }
1721
1729
  genKind: 'image',
1722
- imageModel: 'nano-banana-2', // 'nano-banana-2' | 'grok'
1730
+ imageModel: 'nano-banana-2', // 'nano-banana-2' | 'grok' | ...
1731
+ videoModel: localStorage.getItem('videoModel') || 'seedance-2', // 'seedance-2' | 'kling-o1' | 'kling-3.0' | ...
1723
1732
  videoDuration: +(localStorage.getItem('videoDuration') || 5),
1724
1733
  videoResolution: localStorage.getItem('videoResolution') || '720p',
1725
1734
  videoAspect: localStorage.getItem('videoAspect') || '9:16',
@@ -1968,7 +1977,7 @@ window.addEventListener('DOMContentLoaded', async () => {
1968
1977
  // Восстановить состояние панелей таймлайна/превью/реплик
1969
1978
  const tlOpen = localStorage.getItem('timelineOpen') === '1';
1970
1979
  const pvOpen = localStorage.getItem('previewOpen') === '1';
1971
- const rqOpen = localStorage.getItem('repliquesOpen') === '1';
1980
+ const rqOpen = false; // localStorage.getItem('repliquesOpen') === '1'; — панель реплик скрыта
1972
1981
  if (tlOpen) $('timelinePanel').classList.remove('hidden');
1973
1982
  // Превью больше не скрывается — состояние управляется previewCollapsed
1974
1983
  if (rqOpen) $('repliquesPanel').classList.remove('hidden');
@@ -2006,6 +2015,13 @@ window.addEventListener('DOMContentLoaded', async () => {
2006
2015
  // Стартуем обновление баланса. Сразу + каждые 60 сек.
2007
2016
  refreshBalance().catch(() => {});
2008
2017
  setInterval(() => { refreshBalance().catch(() => {}); }, 60_000);
2018
+
2019
+ // Дабл-клик на логотипе (sidebar или welcome) → открытие окна настроек.
2020
+ const openSettingsFromLogo = () => {
2021
+ if (window.appSettings?.openSettingsWindow) window.appSettings.openSettingsWindow();
2022
+ };
2023
+ document.getElementById('brandLogo')?.addEventListener('dblclick', openSettingsFromLogo);
2024
+ document.getElementById('welcomeLogo')?.addEventListener('dblclick', openSettingsFromLogo);
2009
2025
  });
2010
2026
 
2011
2027
  // === Баланс Chatium ===
@@ -2896,13 +2912,14 @@ async function selectBoard(board) {
2896
2912
  }
2897
2913
  emptyState.classList.add('hidden');
2898
2914
  $('charSettingsBtn').style.display = board.kind === 'character' ? '' : 'none';
2899
- // Авто-открытие/закрытие панели реплик в зависимости от типа board.
2900
- if (board.kind === 'character') {
2901
- openRepliquesFor(board.name).catch(e => console.warn('replicas open failed', e));
2902
- } else {
2903
- $('repliquesPanel').classList.add('hidden');
2904
- localStorage.setItem('repliquesOpen', '0');
2905
- }
2915
+ // Панель реплик скрыта (юзер попросил убрать из карточки персонажа).
2916
+ // Сами данные реплик в scene.json не трогаем — таймлайн и character-озвучка
2917
+ // продолжают работать. Если понадобится вернуть — расскомментируй ниже.
2918
+ $('repliquesPanel').classList.add('hidden');
2919
+ localStorage.setItem('repliquesOpen', '0');
2920
+ // if (board.kind === 'character') {
2921
+ // openRepliquesFor(board.name).catch(e => console.warn('replicas open failed', e));
2922
+ // }
2906
2923
  localStorage.setItem(`lastBoard:${state.filmHandle.name}`,
2907
2924
  JSON.stringify({ kind: board.kind, name: board.name }));
2908
2925
  await refreshSidebar();
@@ -3740,6 +3757,8 @@ async function openGenerateForRef(fromNode, clientX, clientY, forceKind) {
3740
3757
  b.classList.toggle('active', b.dataset.kind === forceKind));
3741
3758
  $('imageModelRow').style.display = forceKind === 'image' ? '' : 'none';
3742
3759
  $('videoOptionsRow').style.display = forceKind === 'video' ? '' : 'none';
3760
+
3761
+ $('videoModelRow').style.display = forceKind === 'video' ? '' : 'none';
3743
3762
  $('voiceRow').style.display = forceKind === 'audio' ? '' : 'none';
3744
3763
  $('tonesRow').style.display = forceKind === 'audio' ? '' : 'none';
3745
3764
  const titleEl = $('genTitle');
@@ -4990,6 +5009,8 @@ async function regenerateNode(node) {
4990
5009
  b.classList.toggle('active', b.dataset.kind === state.genKind));
4991
5010
  $('imageModelRow').style.display = state.genKind === 'image' ? '' : 'none';
4992
5011
  $('videoOptionsRow').style.display = state.genKind === 'video' ? '' : 'none';
5012
+
5013
+ $('videoModelRow').style.display = state.genKind === 'video' ? '' : 'none';
4993
5014
  $('voiceRow').style.display = state.genKind === 'audio' ? '' : 'none';
4994
5015
 
4995
5016
  if (g.modelKey && state.genKind === 'image') {
@@ -5002,7 +5023,9 @@ async function regenerateNode(node) {
5002
5023
  if (g.duration) state.videoDuration = g.duration;
5003
5024
  if (g.resolution) state.videoResolution = g.resolution;
5004
5025
  if (g.aspectRatio) state.videoAspect = g.aspectRatio;
5026
+ if (g.modelKey) state.videoModel = g.modelKey;
5005
5027
  syncVideoOptionsActive();
5028
+ syncVideoModelActive();
5006
5029
  }
5007
5030
  if (state.genKind === 'audio') {
5008
5031
  await loadVoices();
@@ -5092,7 +5115,7 @@ async function regenerateInto(node, kind, rawPrompt, opts = {}) {
5092
5115
  }
5093
5116
  resolvedPrompt = resolveMentions(rawPrompt, refs);
5094
5117
  } else {
5095
- modelKey = kind === 'image' ? state.imageModel : 'seedance-2';
5118
+ modelKey = kind === 'image' ? state.imageModel : (state.videoModel || 'seedance-2');
5096
5119
  refs = gatherMediaRefs(rawPrompt);
5097
5120
  // Подмешиваем "исходный кадр" — текущий файл ноды как референс при редактировании
5098
5121
  let sourceMarker = '';
@@ -5133,7 +5156,12 @@ async function regenerateInto(node, kind, rawPrompt, opts = {}) {
5133
5156
  'flux-schnell': 'flux/schnell',
5134
5157
  'sdxl-lightning': 'sdxl/lightning',
5135
5158
  'nano-banana-2': 'nano-banana-2' }[modelKey] || 'nano-banana-2')
5136
- : kind === 'video' ? 'bytedance/seedance-2'
5159
+ : kind === 'video' ? ({
5160
+ 'seedance-2': 'bytedance/seedance-2',
5161
+ 'seedance-2-fast': 'bytedance/seedance-2-fast',
5162
+ 'kling-o1': 'kwaivgi/kling-o1',
5163
+ 'kling-3.0': 'kling-3.0/video',
5164
+ }[modelKey] || 'bytedance/seedance-2')
5137
5165
  : kind === 'text' ? modelKey // полный slug OpenRouter
5138
5166
  : 'eleven_v3'; // audio
5139
5167
 
@@ -5930,6 +5958,8 @@ function openPhraseFor(charInfo) {
5930
5958
  b.classList.toggle('active', b.dataset.kind === 'audio'));
5931
5959
  $('imageModelRow').style.display = 'none';
5932
5960
  $('videoOptionsRow').style.display = 'none';
5961
+
5962
+ $('videoModelRow').style.display = 'none';
5933
5963
  $('voiceRow').style.display = '';
5934
5964
  $('tonesRow').style.display = '';
5935
5965
  loadVoices().then(() => {
@@ -6109,6 +6139,8 @@ async function openGenModal(kind) {
6109
6139
  // Видимость рядов под текущий kind
6110
6140
  $('imageModelRow').style.display = kind === 'image' ? '' : 'none';
6111
6141
  $('videoOptionsRow').style.display = kind === 'video' ? '' : 'none';
6142
+
6143
+ $('videoModelRow').style.display = kind === 'video' ? '' : 'none';
6112
6144
  $('voiceRow').style.display = kind === 'audio' ? '' : 'none';
6113
6145
  $('tonesRow').style.display = kind === 'audio' ? '' : 'none';
6114
6146
  // Заголовок модалки = действие
@@ -6527,6 +6559,8 @@ document.querySelectorAll('#genModal [data-kind]').forEach(b => {
6527
6559
  state.genKind = b.dataset.kind;
6528
6560
  $('imageModelRow').style.display = state.genKind === 'image' ? '' : 'none';
6529
6561
  $('videoOptionsRow').style.display = state.genKind === 'video' ? '' : 'none';
6562
+
6563
+ $('videoModelRow').style.display = state.genKind === 'video' ? '' : 'none';
6530
6564
  $('voiceRow').style.display = state.genKind === 'audio' ? '' : 'none';
6531
6565
  $('tonesRow').style.display = state.genKind === 'audio' ? '' : 'none';
6532
6566
  if (state.genKind === 'audio') { loadVoices(); renderTones(); }
@@ -6782,6 +6816,20 @@ document.querySelectorAll('#genModal [data-img-model]').forEach(b => {
6782
6816
  state.imageModel = b.dataset.imgModel;
6783
6817
  });
6784
6818
  });
6819
+ // Переключатель модели видео
6820
+ document.querySelectorAll('#genModal [data-vid-model]').forEach(b => {
6821
+ b.addEventListener('click', () => {
6822
+ document.querySelectorAll('#genModal [data-vid-model]').forEach(x => x.classList.remove('active'));
6823
+ b.classList.add('active');
6824
+ state.videoModel = b.dataset.vidModel;
6825
+ localStorage.setItem('videoModel', state.videoModel);
6826
+ });
6827
+ });
6828
+ // Подсветить активную video-модель при открытии modal'а
6829
+ function syncVideoModelActive() {
6830
+ document.querySelectorAll('#genModal [data-vid-model]').forEach(b =>
6831
+ b.classList.toggle('active', b.dataset.vidModel === state.videoModel));
6832
+ }
6785
6833
  // Переключатели длительности и разрешения для видео
6786
6834
  document.querySelectorAll('#genModal [data-vid-dur]').forEach(b => {
6787
6835
  b.addEventListener('click', () => {
@@ -6818,6 +6866,7 @@ function syncVideoOptionsActive() {
6818
6866
  b.classList.toggle('active', b.dataset.vidAsp === state.videoAspect));
6819
6867
  }
6820
6868
  syncVideoOptionsActive();
6869
+ syncVideoModelActive();
6821
6870
 
6822
6871
  $('genCancel').addEventListener('click', () => {
6823
6872
  state.pendingConnectionFrom = null;
@@ -7014,7 +7063,7 @@ $('genSubmit').addEventListener('click', async () => {
7014
7063
  kind,
7015
7064
  prompt: resolvedPrompt,
7016
7065
  rawPrompt,
7017
- modelKey: kind === 'image' ? state.imageModel : 'seedance-2',
7066
+ modelKey: kind === 'image' ? state.imageModel : (state.videoModel || 'seedance-2'),
7018
7067
  model: kind === 'image'
7019
7068
  ? ({
7020
7069
  'grok': 'grok-imagine/text-to-image',
@@ -7022,7 +7071,12 @@ $('genSubmit').addEventListener('click', async () => {
7022
7071
  'seedream-5-lite': 'seedream/5-lite-text-to-image',
7023
7072
  'nano-banana-2': 'nano-banana-2',
7024
7073
  }[state.imageModel] || 'nano-banana-2')
7025
- : 'bytedance/seedance-2',
7074
+ : ({
7075
+ 'seedance-2': 'bytedance/seedance-2',
7076
+ 'seedance-2-fast': 'bytedance/seedance-2-fast',
7077
+ 'kling-o1': 'kwaivgi/kling-o1',
7078
+ 'kling-3.0': 'kling-3.0/video',
7079
+ }[state.videoModel || 'seedance-2'] || 'bytedance/seedance-2'),
7026
7080
  refs: mediaRefs.map(r => ({ name: r.name, type: r.type, file: r.file })),
7027
7081
  // Связь с родительской нодой (выведена через "вытягивание"). Хранится
7028
7082
  // даже если sourceRef.use=false в state — сохраняем структурную связь.
@@ -7728,6 +7782,8 @@ async function openGenAudioForTimeline(charInfo, track, time) {
7728
7782
  b.classList.toggle('active', b.dataset.kind === 'audio'));
7729
7783
  $('imageModelRow').style.display = 'none';
7730
7784
  $('videoOptionsRow').style.display = 'none';
7785
+
7786
+ $('videoModelRow').style.display = 'none';
7731
7787
  $('voiceRow').style.display = '';
7732
7788
  $('tonesRow').style.display = '';
7733
7789
  $('sourceRefRow').style.display = 'none';
package/main.js CHANGED
@@ -346,6 +346,85 @@ function openSettingsWindow() {
346
346
  ipcMain.handle('settings-window:close', () => {
347
347
  if (settingsWin && !settingsWin.isDestroyed()) settingsWin.close();
348
348
  });
349
+ ipcMain.handle('settings-window:open', () => openSettingsWindow());
350
+
351
+ // ===== Updates window + npm version check =====
352
+ let updatesWin = null;
353
+ function openUpdatesWindow() {
354
+ if (updatesWin && !updatesWin.isDestroyed()) {
355
+ updatesWin.focus();
356
+ return;
357
+ }
358
+ updatesWin = new BrowserWindow({
359
+ width: 480,
360
+ height: 380,
361
+ title: 'Обновления',
362
+ parent: win || undefined,
363
+ modal: false,
364
+ resizable: false,
365
+ minimizable: false,
366
+ maximizable: false,
367
+ backgroundColor: '#1a1a1a',
368
+ webPreferences: {
369
+ contextIsolation: true,
370
+ nodeIntegration: false,
371
+ preload: path.join(__dirname, 'preload.js'),
372
+ },
373
+ });
374
+ updatesWin.removeMenu();
375
+ updatesWin.loadFile(path.join(__dirname, 'updates.html'));
376
+ updatesWin.on('closed', () => { updatesWin = null; });
377
+ }
378
+ ipcMain.handle('updates-window:close', () => {
379
+ if (updatesWin && !updatesWin.isDestroyed()) updatesWin.close();
380
+ });
381
+
382
+ // Простой semver-сравнитель: возвращает true если b > a (есть более свежая).
383
+ // Поддерживает X.Y.Z и pre-release suffix через '-' (rc/beta пропускаем как not-newer).
384
+ function isNewerVersion(a, b) {
385
+ const pa = String(a).split('-')[0].split('.').map(n => parseInt(n, 10) || 0);
386
+ const pb = String(b).split('-')[0].split('.').map(n => parseInt(n, 10) || 0);
387
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
388
+ const x = pa[i] || 0, y = pb[i] || 0;
389
+ if (y > x) return true;
390
+ if (y < x) return false;
391
+ }
392
+ return false;
393
+ }
394
+
395
+ async function fetchLatestVersion() {
396
+ // npm registry GET /<pkg>/latest → { version, ... }
397
+ const r = await fetch('https://registry.npmjs.org/kingkont/latest', {
398
+ headers: { 'Accept': 'application/json' },
399
+ });
400
+ if (!r.ok) throw new Error(`npm registry HTTP ${r.status}`);
401
+ const d = await r.json();
402
+ if (!d.version) throw new Error('registry без поля version');
403
+ return d.version;
404
+ }
405
+
406
+ ipcMain.handle('updates:check', async () => {
407
+ const current = app.getVersion();
408
+ const latest = await fetchLatestVersion();
409
+ return { current, latest, isNew: isNewerVersion(current, latest) };
410
+ });
411
+
412
+ // Фоновая проверка при старте: если есть свежая — открываем окно автоматом.
413
+ // Чтобы не доставать юзера до открытия проекта, ждём 5 сек после createWindow.
414
+ async function backgroundCheckUpdates() {
415
+ try {
416
+ const current = app.getVersion();
417
+ const latest = await fetchLatestVersion();
418
+ if (isNewerVersion(current, latest)) {
419
+ console.log(`[updates] новая версия ${latest} (текущая ${current})`);
420
+ openUpdatesWindow();
421
+ } else {
422
+ console.log(`[updates] актуально (${current})`);
423
+ }
424
+ } catch (e) {
425
+ console.warn('[updates] check failed:', e?.message);
426
+ }
427
+ }
349
428
 
350
429
  // ===== Recents persisted в userData/recents.json (переживает quota-reset IDB) =====
351
430
  function recentsPath() {
@@ -405,6 +484,7 @@ function buildMenu() {
405
484
  label: 'KingKont',
406
485
  submenu: [
407
486
  { label: 'О программе KingKont', role: 'about' },
487
+ { label: 'Проверить обновления…', click: () => openUpdatesWindow() },
408
488
  { type: 'separator' },
409
489
  { label: 'Настройки…', accelerator: 'Cmd+,', click: () => openSettingsWindow() },
410
490
  { type: 'separator' },
@@ -453,6 +533,7 @@ function buildMenu() {
453
533
  ...(isMac ? [] : [
454
534
  { type: 'separator' },
455
535
  { label: 'Настройки…', accelerator: 'CmdOrCtrl+,', click: () => openSettingsWindow() },
536
+ { label: 'Проверить обновления…', click: () => openUpdatesWindow() },
456
537
  { type: 'separator' },
457
538
  { role: 'quit' },
458
539
  ]),
@@ -579,6 +660,10 @@ app.whenReady().then(async () => {
579
660
  await createWindow();
580
661
  buildMenu();
581
662
 
663
+ // Background check обновлений: ждём чтобы юзер увидел welcome/restore,
664
+ // потом тихо ходим в npm registry. Если есть свежая — показываем окно.
665
+ setTimeout(() => { backgroundCheckUpdates(); }, 5000);
666
+
582
667
  app.on('activate', () => {
583
668
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
584
669
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/preload.js CHANGED
@@ -45,6 +45,13 @@ contextBridge.exposeInMainWorld('appSettings', {
45
45
  get: () => ipcRenderer.invoke('settings:get'),
46
46
  save: (partial) => ipcRenderer.invoke('settings:save', partial),
47
47
  closeSettingsWindow: () => ipcRenderer.invoke('settings-window:close'),
48
+ openSettingsWindow: () => ipcRenderer.invoke('settings-window:open'),
49
+ });
50
+
51
+ // Проверка обновлений через npm registry (в main-процессе).
52
+ contextBridge.exposeInMainWorld('appUpdates', {
53
+ check: () => ipcRenderer.invoke('updates:check'),
54
+ closeWindow: () => ipcRenderer.invoke('updates-window:close'),
48
55
  });
49
56
 
50
57
  // Авторизация в Chatium через loopback OAuth-flow.
package/settings.html CHANGED
@@ -101,26 +101,26 @@
101
101
  <div class="wrap">
102
102
  <h1>Настройки</h1>
103
103
 
104
- <h2>Chatium</h2>
104
+ <h2>KingKont</h2>
105
105
  <div class="card" id="chatiumCard">
106
106
  <label class="use-toggle" for="useChatium">
107
107
  <input type="checkbox" id="useChatium" />
108
- <span class="label-text">Использовать Chatium <span class="sub">— текст / аудио / видео через kingkont.ru</span></span>
108
+ <span class="label-text">Использовать KingKont <span class="sub">— текст / аудио / видео через kingkont.ru</span></span>
109
109
  </label>
110
110
  <div class="chatium-state">
111
111
  <div class="dot off" id="chatiumDot"></div>
112
112
  <div class="who" id="chatiumWho">
113
113
  <b>Не подключено</b>
114
- <small>Авторизация нужна для генерации текста, аудио и видео через Chatium.</small>
114
+ <small>Авторизация нужна для генерации текста, аудио и видео через KingKont.</small>
115
115
  </div>
116
116
  </div>
117
117
  <div class="row">
118
- <button id="chatiumLogin">Войти в Chatium</button>
118
+ <button id="chatiumLogin">Войти в KingKont</button>
119
119
  <button id="chatiumLogout" class="danger" style="display:none;">Выйти</button>
120
120
  <button id="chatiumRefresh" class="secondary">Проверить</button>
121
121
  </div>
122
122
  <div class="error" id="chatiumError" style="display:none;"></div>
123
- <div class="hint">Откроется браузер для входа в Chatium. После подтверждения авторизация сохранится локально и будет использоваться приложением для запросов к нейросетям.</div>
123
+ <div class="hint">Откроется браузер для входа в KingKont. После подтверждения авторизация сохранится локально и будет использоваться приложением для запросов к нейросетям.</div>
124
124
  </div>
125
125
 
126
126
  <h2>Прямые коннекторы</h2>
@@ -231,7 +231,7 @@ function renderChatiumState(chatium) {
231
231
  logoutBtn.style.display = '';
232
232
  } else {
233
233
  dot.classList.remove('on'); dot.classList.add('off');
234
- who.innerHTML = `<b>Не подключено</b><small>Авторизация нужна для генерации текста, аудио и видео через Chatium.</small>`;
234
+ who.innerHTML = `<b>Не подключено</b><small>Авторизация нужна для генерации текста, аудио и видео через KingKont.</small>`;
235
235
  loginBtn.style.display = '';
236
236
  logoutBtn.style.display = 'none';
237
237
  }