kingkont 0.18.2 → 0.18.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.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
@@ -54,13 +54,19 @@
54
54
  $('notifyClear').addEventListener('click', () => { events.length = 0; unread = 0; render(); });
55
55
  }
56
56
 
57
- function setOpen(open) {
57
+ // openMode: 'manual' (юзер сам нажал на 🔔 — НЕ автозакрываем),
58
+ // 'auto' (открыли по событию — закроется через таймер),
59
+ // null (закрыто).
60
+ let openMode = null;
61
+ function setOpen(open, mode) {
58
62
  _ensureUI();
59
63
  panelOpen = open;
64
+ openMode = open ? (mode || 'manual') : null;
60
65
  $('notifyPanel').style.display = open ? 'flex' : 'none';
61
66
  if (open) { unread = 0; updateBadge(); render(); }
62
67
  }
63
- function toggle() { setOpen(!panelOpen); }
68
+ function toggle() { setOpen(!panelOpen, 'manual'); }
69
+ function isManuallyOpen() { return panelOpen && openMode === 'manual'; }
64
70
 
65
71
  function updateBadge() {
66
72
  const b = $('notifyBadge');
@@ -158,6 +164,22 @@
158
164
  } else {
159
165
  render();
160
166
  }
167
+ // Авто-открытие панели на 3.5s — юзер видит событие сразу, потом
168
+ // панель прячется. Если юзер сам открыл — не трогаем (проверка в
169
+ // openAuto). Каждое новое событие сбрасывает таймер.
170
+ _scheduleAutoFlash();
171
+ }
172
+ // Auto-flash: открыть в auto-режиме и закрыть через таймер. Срабатывает
173
+ // на КАЖДОЕ addEvent. Если юзер уже manual'но открыл — открытие no-op'ится.
174
+ let _autoFlashT = null;
175
+ function _scheduleAutoFlash() {
176
+ if (panelOpen && openMode === 'manual') return; // юзер сам открыл — не лезем
177
+ if (!panelOpen) setOpen(true, 'auto');
178
+ if (_autoFlashT) clearTimeout(_autoFlashT);
179
+ _autoFlashT = setTimeout(() => {
180
+ _autoFlashT = null;
181
+ if (openMode === 'auto') setOpen(false);
182
+ }, 3500);
161
183
  }
162
184
 
163
185
  // Navigate from notification to scene/node.
@@ -224,20 +246,30 @@
224
246
  setTimeout(() => { el.style.boxShadow = ''; }, 2500);
225
247
  }, 200);
226
248
  }
227
- window.notifyPanel = { open: () => setOpen(true), close: () => setOpen(false), toggle, addEvent, render };
249
+ // openAuto для showToast: открывает в режиме auto (закроется по таймеру
250
+ // если showToast.close('auto') не отменён). open() без аргумента — manual.
251
+ window.notifyPanel = {
252
+ open: (mode) => setOpen(true, mode || 'manual'),
253
+ openAuto: () => setOpen(true, 'auto'),
254
+ closeAuto: () => { if (openMode === 'auto') setOpen(false); },
255
+ close: () => setOpen(false),
256
+ toggle, addEvent, render,
257
+ isManuallyOpen,
258
+ };
228
259
 
229
- // Init: ensure UI exists ASAP, hook into showToast и WS-events.
260
+ // Init: ensure UI exists ASAP, drain pending toast queue, subscribe WS.
230
261
  document.addEventListener('DOMContentLoaded', () => {
231
262
  _ensureUI();
232
- // Wrap showToast чтобы дублировать в панель + forward target для
233
- // click-навигации (showToast(text, kind, {target: {...}})).
234
- if (typeof window.showToast === 'function') {
235
- const orig = window.showToast;
236
- window.showToast = function (text, kind, opts) {
237
- try { addEvent({ kind: kind || 'info', text, target: opts?.target || null }); } catch {}
238
- return orig.apply(this, arguments);
239
- };
240
- }
263
+ // Дренируем showToast которые вызвались до загрузки notifyPanel.
264
+ // (state.js:showToast копит в __pendingToastQueue если addEvent
265
+ // ещё не доступен.)
266
+ try {
267
+ const q = window.__pendingToastQueue;
268
+ if (Array.isArray(q)) {
269
+ for (const ev of q) addEvent(ev);
270
+ q.length = 0;
271
+ }
272
+ } catch {}
241
273
  // WS-канал jobs:all для start/end/done/failed events. Connect к /ws.
242
274
  function _connect() {
243
275
  let ws;
package/renderer/state.js CHANGED
@@ -177,8 +177,10 @@ async function systemNotify(title, body, opts = {}) {
177
177
  try { perm = await Notification.requestPermission(); } catch {}
178
178
  }
179
179
  if (perm !== 'granted') {
180
- // Не вышлоfallback на toast (юзер хоть так увидит).
181
- if (typeof showToast === 'function') showToast('🔔 ' + title + (body ? ': ' + body : ''), 'info');
180
+ // Без OS-нотификациисобытие УЖЕ добавлено в notifyPanel через
181
+ // showToast-wrapper (single-source-of-truth). Раньше тут был fallback
182
+ // на showToast → дубль события с префиксом «🔔 KingKont:» БЕЗ target,
183
+ // поэтому без клика. Теперь молча пропускаем.
182
184
  return null;
183
185
  }
184
186
  try {
@@ -197,36 +199,27 @@ async function systemNotify(title, body, opts = {}) {
197
199
  }
198
200
  window.systemNotify = systemNotify;
199
201
 
200
- // Глобальный toast для информативных уведомлений (генерация завершилась,
201
- // resume сработал, ...). Стек справа сверху. Auto-dismiss:
202
- // - ok/info 10s (юзер должен заметить успех)
203
- // - error — 30s (важно, не пропустить)
202
+ // Глобальная сигналка событий. Раньше показывался bottom-left toast,
203
+ // но дубль с notifyPanel'ом юзера утомил («toast не нужен»). Теперь:
204
+ // - событие добавляется в notifyPanel (через wrapper в notifyPanel.js)
205
+ // - панель открывается АВТОМАТИЧЕСКИ ненадолго (3s) юзер видит
206
+ // событие сразу, потом панель скрывается, бейдж остаётся.
207
+ // Возвращаем фейковый «токен» для совместимости с местами которые ждут
208
+ // результат showToast (там ничего с ним не делают, но на всякий случай).
204
209
  function showToast(text, kind, opts) {
205
- // opts.target: {projectKey, boardKey, nodeId} toast становится кликабельным.
206
- let host = document.getElementById('toastHost');
207
- if (!host) {
208
- host = document.createElement('div');
209
- host.id = 'toastHost';
210
- // Bottom-left стек — рядом с 🔔 кнопкой нотификаций. Новые toast'ы
211
- // появляются СНИЗУ (flex-direction: column-reverse), так что они
212
- // «всплывают» вверх от кнопки.
213
- host.style.cssText = 'position:fixed; left:52px; bottom:12px; z-index:9999; display:flex; flex-direction:column-reverse; gap:8px; pointer-events:none; max-width:340px;';
214
- document.body.appendChild(host);
215
- }
216
- const t = document.createElement('div');
217
- const colors = {
218
- ok: 'background:#1f4a1f; border:1px solid #4cc44c; color:#dcffdc;',
219
- error: 'background:#4a1f1f; border:1px solid #c44c4c; color:#ffdcdc;',
220
- info: 'background:#1f2f4a; border:1px solid #4c7cc4; color:#dceaff;',
221
- };
222
- t.style.cssText = `${colors[kind] || colors.info} padding:10px 14px; border-radius:8px; font-size:13px; font-weight:500; pointer-events:auto; box-shadow:0 6px 20px rgba(0,0,0,0.5); cursor:pointer; line-height:1.4; word-break:break-word;`;
223
- t.textContent = text;
224
- t.title = 'Кликни чтобы закрыть';
225
- t.addEventListener('click', () => t.remove());
226
- host.appendChild(t);
227
- const ttl = kind === 'error' ? 30000 : 10000;
228
- setTimeout(() => t.remove(), ttl);
229
- return t;
210
+ // Bottom-left toast убрансобытие напрямую идёт в notifyPanel,
211
+ // панель сама подсветится (см. _scheduleAutoFlash в notifyPanel.js).
212
+ // Если notifyPanel ещё не загружен (DOM-readiness race) — копим
213
+ // в очередь, она дренируется при создании панели.
214
+ try {
215
+ if (window.notifyPanel?.addEvent) {
216
+ window.notifyPanel.addEvent({ kind: kind || 'info', text, target: opts?.target || null });
217
+ } else {
218
+ (window.__pendingToastQueue = window.__pendingToastQueue || [])
219
+ .push({ kind: kind || 'info', text, target: opts?.target || null });
220
+ }
221
+ } catch {}
222
+ return { _stub: true };
230
223
  }
231
224
  window.showToast = showToast;
232
225
 
@@ -124,8 +124,11 @@
124
124
  display: flex; flex-direction: column; gap: 6px;
125
125
  font-size: 11px; color: #777;
126
126
  }
127
- .sidebar-footer .hint { color: #777; font-size: 11px; line-height: 1.4; }
128
- .sidebar-footer .jobs-info { color: #aaccdd; font-size: 11px; }
127
+ /* «В фоне: N» и hint «Перетаскивай файлы…» отступаем влево на ширину
128
+ 🔔-кнопки (32px + 12px gap слева от sidebar = 44px), чтобы кнопка
129
+ событий (position:fixed; left:12px; bottom:12px) их не перекрывала. */
130
+ .sidebar-footer .hint { color: #777; font-size: 11px; line-height: 1.4; padding-left: 44px; }
131
+ .sidebar-footer .jobs-info { color: #aaccdd; font-size: 11px; padding-left: 44px; }
129
132
  .sidebar-footer .balance-info {
130
133
  display: flex; align-items: center; gap: 6px; font-size: 11px;
131
134
  color: #c4c4c4; padding: 4px 8px; background: #2a2a2a;