kingkont 0.7.48 → 0.7.50

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 CHANGED
@@ -216,8 +216,27 @@ async function startGeneration(args) {
216
216
  if (kind !== 'image' && kind !== 'video') throw new Error(`unknown kind: ${kind}`);
217
217
  if (!prompt && kind !== 'video') throw new Error('prompt обязателен');
218
218
 
219
+ // Приоритет провайдеров:
220
+ // 1) KIE — если useKie=true И KIE_API_KEY И модель поддерживается KIE
221
+ // (юзер явно включил прямой коннектор → используем его, экономим
222
+ // Chatium-кредиты).
223
+ // 2) Chatium — fallback, либо если модель не входит в KIE (kling-3.0,
224
+ // veo-3.1, runway и т.д.).
225
+ // 3) Если ни один не доступен — ошибка.
226
+ const kieMap = kind === 'video' ? KIE_VIDEO_MODELS : KIE_IMAGE_MODELS;
227
+ const kieKey = modelKey || (kind === 'video' ? 'seedance-2' : 'nano-banana-2');
228
+ const kieAvailable = s.useKie && process.env.KIE_API_KEY;
229
+ const kieSupportsModel = !!kieMap[kieKey];
230
+
231
+ if (kieAvailable && kieSupportsModel) {
232
+ return await _startGenerationViaKie({ kind, prompt, key: kieKey, imageInputs, videoInputs, aspectRatio, resolution, duration });
233
+ }
234
+
219
235
  // Chatium-путь.
220
236
  if (s.useChatium && s.chatium?.token && s.chatium?.base) {
237
+ if (kieAvailable && !kieSupportsModel) {
238
+ console.warn(`[providers] KIE включен, но не поддерживает "${kieKey}" → fallback на KingKont`);
239
+ }
221
240
  const map = kind === 'video' ? CHATIUM_VIDEO_MODELS : CHATIUM_IMAGE_MODELS;
222
241
  const fullModel = resolveModel(map, modelKey, kind === 'video' ? 'bytedance/seedance-2-fast' : 'nano-banana-2');
223
242
  if (!fullModel) {
@@ -241,14 +260,19 @@ async function startGeneration(args) {
241
260
  return { taskId: 'chatium:' + taskId, provider: 'kingkont' };
242
261
  }
243
262
 
244
- // KIE-путь.
245
- if (!s.useKie) {
246
- throw new Error(kind === 'video'
247
- ? 'Войдите в KingKont или KIE для видео.'
248
- : 'Войдите в KingKont или KIE для картинок.');
263
+ // Ни один провайдер не доступен.
264
+ if (s.useKie && !process.env.KIE_API_KEY) {
265
+ throw new Error('KIE включён, но KIE_API_KEY не задан в настройках.');
249
266
  }
267
+ throw new Error(kind === 'video'
268
+ ? 'Войдите в KingKont или KIE для видео.'
269
+ : 'Войдите в KingKont или KIE для картинок.');
270
+ }
271
+
272
+ // Внутренний helper: KIE-путь startGeneration. Вынесен чтобы не дублировать
273
+ // логику между «KIE первичный» и старым «KIE fallback».
274
+ async function _startGenerationViaKie({ kind, prompt, key, imageInputs, videoInputs, aspectRatio, resolution, duration }) {
250
275
  const map = kind === 'video' ? KIE_VIDEO_MODELS : KIE_IMAGE_MODELS;
251
- const key = modelKey || (kind === 'video' ? 'seedance-2' : 'nano-banana-2');
252
276
  const fullModel = map[key];
253
277
  if (!fullModel) throw new Error(`unknown ${kind} model: ${key}`);
254
278
 
@@ -364,7 +388,13 @@ async function waitForGeneration(taskId, settings, opts = {}) {
364
388
  async function generateText({ prompt, model = 'anthropic/claude-sonnet-4', system, images, settings: s }) {
365
389
  if (!prompt) throw new Error('нужен prompt');
366
390
 
367
- if (s.useChatium && s.chatium?.token && s.chatium?.base) {
391
+ // Приоритет: OpenRouter direct (если useOpenrouter+key) → Chatium → ошибка.
392
+ // Юзер явно включил прямой коннектор → используем его (Chatium-кредиты
393
+ // не тратим). OpenRouter принимает все те же model-slugs что Chatium-text
394
+ // (anthropic/claude-sonnet-4, openai/gpt-4o, google/gemini-..., etc.).
395
+ const directOpenrouter = s.useOpenrouter && process.env.OPENROUTER_API_KEY;
396
+
397
+ if (s.useChatium && s.chatium?.token && s.chatium?.base && !directOpenrouter) {
368
398
  const body = {
369
399
  prompt, model, system,
370
400
  images: Array.isArray(images) ? images.filter(i => i?.url) : undefined,
@@ -379,9 +409,13 @@ async function generateText({ prompt, model = 'anthropic/claude-sonnet-4', syste
379
409
  };
380
410
  }
381
411
 
382
- if (!s.useOpenrouter) throw new Error('Войдите в KingKont или OpenRouter для генерации текста.');
412
+ if (!directOpenrouter) {
413
+ if (s.useOpenrouter && !process.env.OPENROUTER_API_KEY) {
414
+ throw new Error('OpenRouter включён, но OPENROUTER_API_KEY не задан в настройках.');
415
+ }
416
+ throw new Error('Войдите в KingKont или OpenRouter для генерации текста.');
417
+ }
383
418
  const key = process.env.OPENROUTER_API_KEY;
384
- if (!key) throw new Error('OPENROUTER_API_KEY не задан');
385
419
 
386
420
  const messages = [];
387
421
  if (system) messages.push({ role: 'system', content: system });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.7.48",
3
+ "version": "0.7.50",
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/renderer/board.js CHANGED
@@ -1136,14 +1136,19 @@ async function selectBoard(board) {
1136
1136
 
1137
1137
  // =============================================================================
1138
1138
  // External file watcher (FSAH polling).
1139
- // FSAH не имеет нативного file-watch API — поллим scene.json's lastModified
1140
- // раз в EXTERNAL_POLL_MS. Если mtime новее чем lastDiskMtime (который мы
1141
- // обновляем при каждом своём write) — кто-то правил scene.json извне.
1139
+ // FSAH не имеет нативного file-watch API — поллим scene.json's lastModified.
1140
+ // Если mtime новее чем lastDiskMtime (который мы обновляем при каждом своём
1141
+ // write) — кто-то правил scene.json извне.
1142
1142
  // • dirty=false → тихо перечитать и перерендерить
1143
1143
  // • dirty=true → confirm() «перечитать с диска? текущие правки утеряются»
1144
+ //
1145
+ // Интервал адаптивный: когда окно в фокусе — 300мс (юзер видит изменения
1146
+ // от skill/CLI почти моментально, прогрессивно: добавил ноду — увидел).
1147
+ // Когда вкладка скрыта — 3 сек (экономим CPU, юзер всё равно не смотрит).
1144
1148
  // =============================================================================
1145
1149
 
1146
- const EXTERNAL_POLL_MS = 2000;
1150
+ const EXTERNAL_POLL_MS_ACTIVE = 300;
1151
+ const EXTERNAL_POLL_MS_HIDDEN = 3000;
1147
1152
  let _externalWatchTimer = null;
1148
1153
  let _externalReloadInFlight = false;
1149
1154
 
@@ -1158,11 +1163,23 @@ function stopExternalWatcher() {
1158
1163
  if (_externalWatchTimer) { clearInterval(_externalWatchTimer); _externalWatchTimer = null; }
1159
1164
  }
1160
1165
 
1166
+ function _currentPollInterval() {
1167
+ return document.hidden ? EXTERNAL_POLL_MS_HIDDEN : EXTERNAL_POLL_MS_ACTIVE;
1168
+ }
1169
+
1161
1170
  function startExternalWatcher() {
1162
1171
  stopExternalWatcher();
1163
- _externalWatchTimer = setInterval(checkExternalChanges, EXTERNAL_POLL_MS);
1172
+ _externalWatchTimer = setInterval(checkExternalChanges, _currentPollInterval());
1164
1173
  }
1165
1174
 
1175
+ // При смене visibility пересоздаём timer с новым интервалом.
1176
+ document.addEventListener('visibilitychange', () => {
1177
+ if (_externalWatchTimer) {
1178
+ stopExternalWatcher();
1179
+ _externalWatchTimer = setInterval(checkExternalChanges, _currentPollInterval());
1180
+ }
1181
+ });
1182
+
1166
1183
  async function checkExternalChanges() {
1167
1184
  if (_externalReloadInFlight) return;
1168
1185
  if (!state.currentBoard?.handle) { stopExternalWatcher(); return; }
package/skill/SKILL.md CHANGED
@@ -150,6 +150,39 @@ kingkont gen <project> <board> --kind=text --name=name \
150
150
 
151
151
  В обычной просьбе «напиши X» — **пиши сам**.
152
152
 
153
+ ## ⚠️ Изменения визуально — добавляй ноды по одной
154
+
155
+ Если открыто приложение KingKont, оно поллит `scene.json` каждые 300мс
156
+ и автоматически перерисовывает холст когда обнаруживает внешнюю правку.
157
+ Это значит **юзер видит каждую новую ноду как только ты её добавил**.
158
+
159
+ Поэтому когда нужно добавить несколько нод — **не батчи их в один скрипт**,
160
+ запускай `kingkont add-node` (или `gen`) по одной команде за раз. Между
161
+ командами юзер видит как ноды появляются прогрессивно.
162
+
163
+ ✅ Хорошо (юзер видит появление каждой ноды):
164
+ ```bash
165
+ kingkont add-node <project> <board> --kind=text --name=intro --text="..."
166
+ # (юзер видит ноду на холсте через ~300мс)
167
+ kingkont add-node <project> <board> --kind=text --name=dialog --text="..."
168
+ # (видит вторую)
169
+ kingkont add-node <project> <board> --kind=text --name=outro --text="..."
170
+ # (видит третью)
171
+ ```
172
+
173
+ ❌ Плохо (юзер видит «бах» — все ноды одним кадром):
174
+ ```bash
175
+ # Все три node'а пишутся в scene.json одним save'ом — на холсте они
176
+ # появятся одновременно.
177
+ kingkont add-node ... --kind=text --name=intro && \
178
+ kingkont add-node ... --kind=text --name=dialog && \
179
+ kingkont add-node ... --kind=text --name=outro
180
+ ```
181
+ (Это технически работает, но менее «живое» ощущение.)
182
+
183
+ Для длинных операций (десяток+ нод) можно объединять — юзер не различит
184
+ батчи быстрее ~1 сек. Но от 3 до 5-7 нод — добавляй по одной.
185
+
153
186
  ## Как открыть UI редактора
154
187
 
155
188
  ```bash