kingkont 0.11.0 → 0.11.2

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.0",
3
+ "version": "0.11.2",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer/chat.js CHANGED
@@ -182,16 +182,35 @@
182
182
  },
183
183
 
184
184
  generate_node: {
185
- description: 'Запустить генерацию для draft-ноды (image/video/audio). Возвращает сразугенерация идёт в фоне.',
185
+ description: 'Запустить генерацию для draft-ноды (image/video/audio/text). Стартует напрямую в фоне БЕЗ показа диалога не интерактивная UI-форма как regenerateNode.',
186
186
  params: '{"id":"<node-id>"}',
187
187
  async handler({ id }) {
188
188
  const b = state.currentBoard;
189
189
  if (!b) throw new Error('доска не выбрана');
190
190
  const node = b.metadata.nodes.find(n => n.id === id);
191
191
  if (!node) throw new Error(`нода не найдена: ${id}`);
192
- if (typeof regenerateNode !== 'function') throw new Error('regenerateNode недоступен');
193
- regenerateNode(node);
194
- return { ok: true, note: 'генерация запущена в фоне' };
192
+ const prompt = node.generated?.prompt || node.generated?.rawPrompt;
193
+ if (!prompt) throw new Error('у ноды нет промпта');
194
+ const kind = node.type;
195
+ const refs = (node.generated?.refs || []).map(r => ({ name: r.name, type: r.type, file: r.file }));
196
+ const modelKey = node.generated?.modelKey;
197
+ const boardHandle = b.handle;
198
+ const bKey = b.key;
199
+ // Маршрутизация по kind — копия логики из resumeJob, чтобы запустить
200
+ // job напрямую без gen-modal'а.
201
+ if (kind === 'audio') {
202
+ if (typeof runTTSJob !== 'function') throw new Error('runTTSJob недоступен');
203
+ runTTSJob(node, prompt, boardHandle, bKey, node.generated?.voiceId).catch(e => console.error('TTS job failed:', e));
204
+ } else if (kind === 'text') {
205
+ if (typeof runTextJob !== 'function') throw new Error('runTextJob недоступен');
206
+ const imageRefs = refs.filter(r => r.type === 'image' && r.file);
207
+ runTextJob(node, prompt, modelKey || 'anthropic/claude-sonnet-4', boardHandle, bKey, imageRefs).catch(e => console.error('text job failed:', e));
208
+ } else {
209
+ // image / video — через KIE/Chatium.
210
+ if (typeof startGenerationJob !== 'function') throw new Error('startGenerationJob недоступен');
211
+ startGenerationJob(node, kind, prompt, refs, boardHandle, bKey, modelKey).catch(e => console.error('gen job failed:', e));
212
+ }
213
+ return { ok: true, note: 'генерация стартовала в фоне (без gen-modal)' };
195
214
  },
196
215
  },
197
216
  };
@@ -214,10 +233,12 @@
214
233
  }
215
234
  lines.push('');
216
235
  lines.push('Правила:');
217
- lines.push('- Не выдумывай id нод — сначала read_scene/list_scenes.');
218
- lines.push('- Не выдумывай имена сценlist_scenes покажет реальные.');
219
- lines.push('- Если нужен новый id для add_node оставь поле пустым, вернётся в результате.');
220
- lines.push('- Для генерации используй generate_node ПОСЛЕ add_node с promptом.');
236
+ lines.push('- Не выдумывай id нод — id это случайные UUID, угадать нельзя.');
237
+ lines.push(' - Если нужен id существующей ноды сначала read_scene (вернёт массив с реальными id).');
238
+ lines.push(' - После add_node id новой ноды лежит в result.id ответа этого вызова.');
239
+ lines.push('- Не выдумывай имена сцен сначала list_scenes.');
240
+ lines.push('- Для генерации сразу после add_node — ВОЗЬМИ id из result.id ПРЕДЫДУЩЕГО вызова и передай в generate_node.');
241
+ lines.push('- Когда нужно создать несколько нод сразу — выдавай add_node + generate_node чередуя, или сначала все add_node, потом все generate_node (используя id из их result-ов).');
221
242
  lines.push('- Отвечай по-русски, кратко. Объясняй что делаешь, без лишней воды.');
222
243
  lines.push('');
223
244
  lines.push('ВАЖНО: НИКОГДА не пиши <tool_result>...</tool_result> сам — это формат который Я');
@@ -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);