kingkont 0.20.18 → 0.20.19

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.20.18",
3
+ "version": "0.20.19",
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
@@ -4099,10 +4099,21 @@ function showNodeContextMenu(node, clientX, clientY) {
4099
4099
  });
4100
4100
  }
4101
4101
  else if (node.status === 'draft') {
4102
- add('▶ Запустить генерацию', () => regenerateNode(node));
4102
+ // Раньше обе кнопки звали regenerateNode → обе открывали modal,
4103
+ // юзер не понимал чем они отличаются. Теперь:
4104
+ // ▶ Запустить генерацию — direct-run без модалки (сохранённые
4105
+ // параметры + incoming-edge рефы).
4106
+ // ✎ Изменить и запустить — открыть modal для правки промпта/
4107
+ // модели/настроек, потом запустить.
4108
+ add('▶ Запустить генерацию', () => runNodeJobDirectly(node));
4103
4109
  add('✎ Изменить и запустить', () => regenerateNode(node));
4104
4110
  }
4105
4111
  else if (node.generated) {
4112
+ // Готовая нода: даём оба варианта симметрично с draft-кейсом.
4113
+ // «↻ Перегенерировать» — без модалки (быстрый ре-ран), «(правка)»
4114
+ // — с модалкой. Файл текущей версии уйдёт в node.history[] (см.
4115
+ // regenerateInto / startGenerationJob — там делается syncHistorySlot).
4116
+ add('↻ Перегенерировать', () => runNodeJobDirectly(node));
4106
4117
  add('↻ Перегенерировать (правка)', () => regenerateNode(node));
4107
4118
  }
4108
4119
  if (node.type === 'image' || node.type === 'video' || node.type === 'audio') {
@@ -2128,6 +2128,97 @@ async function startGenerationJob(node, kind, prompt, mediaRefs, boardHandle, bK
2128
2128
  }
2129
2129
  }
2130
2130
 
2131
+ // Запустить генерацию ноды НАПРЯМУЮ (без gen-modal'а), с сохранёнными
2132
+ // параметрами в node.generated. Юзер: «неясно чем отличаются кнопки в ПКМ
2133
+ // 'Запустить генерацию' и 'Изменить и запустить' — обе открывают модалку».
2134
+ // Теперь:
2135
+ // • «▶ Запустить генерацию» → runNodeJobDirectly (без модалки).
2136
+ // • «✎ Изменить и запустить» → regenerateNode (открывает модалку с
2137
+ // предзаполненными полями для правки).
2138
+ // Логика маршрутизации совпадает с resumeJob (kind/subKind → конкретная
2139
+ // job-функция). Дополнительно — собираем incoming-edge refs (см.
2140
+ // gatherIncomingMediaRefs), чтобы юзер не терял авто-рефы при direct-run.
2141
+ async function runNodeJobDirectly(node) {
2142
+ if (!node) return;
2143
+ if (node.status === 'generating') return; // защита от повторного клика
2144
+ const g = node.generated;
2145
+ if (!g || !g.prompt) {
2146
+ alert('У ноды нет промпта — открой ПКМ → «Изменить и запустить».');
2147
+ return;
2148
+ }
2149
+ const board = state.currentBoard;
2150
+ if (!board) return;
2151
+ const bKey = board.key;
2152
+ const boardHandle = board.handle;
2153
+ const kind = g.kind || node.type;
2154
+ const subKind = g.subKind || (kind === 'audio' ? 'voice' : null);
2155
+
2156
+ // Готовим refs: сохранённые в node.generated.refs + incoming-edges (если
2157
+ // граф расширили с момента прошлой генерации). Дедуп по file+boardHandle.
2158
+ // boardHandle re-attach через текущий — refs из других досок (sheet'ы)
2159
+ // здесь не поддерживаем при direct-run, для них юзер должен открыть modal.
2160
+ let refs = (g.refs || []).map(r => ({ ...r, boardHandle: r.boardHandle || boardHandle }));
2161
+ if (typeof gatherIncomingMediaRefs === 'function') {
2162
+ for (const ir of gatherIncomingMediaRefs(node.id)) {
2163
+ if (refs.some(r => r.file === ir.file && r.boardHandle === ir.boardHandle)) continue;
2164
+ refs.push(ir);
2165
+ }
2166
+ }
2167
+
2168
+ // Если у ноды есть готовый файл (готовая нода, direct-rerun) — seedим
2169
+ // history-слот ТЕКУЩЕЙ версией перед запуском, чтобы юзер мог откатиться
2170
+ // на старый файл через ←/→ в footer'е. Без этого syncHistorySlot (внутри
2171
+ // mutateNode) ничего не делает на пустом node.history (см. media.js:531).
2172
+ // regenerateInto делает это руками — копируем сюда тот же приём.
2173
+ if (node.file && (!Array.isArray(node.history) || !node.history.length)) {
2174
+ node.history = [{
2175
+ file: node.file,
2176
+ generated: { ...g },
2177
+ status: undefined,
2178
+ error: undefined,
2179
+ }];
2180
+ node.historyIndex = 0;
2181
+ }
2182
+
2183
+ // Помечаем как generating ДО запуска — UI сразу показывает spinner.
2184
+ node.status = 'generating';
2185
+ node.error = undefined;
2186
+ node.generated = { ...g, refs: refs.map(r => ({ name: r.name, key: r.key, type: r.type, file: r.file })) };
2187
+ scheduleSave();
2188
+ // Перерисовываем ноду — body уйдёт в spinner-state.
2189
+ const el = canvas.querySelector(`.node[data-id="${node.id}"]`);
2190
+ if (el) {
2191
+ const body = el.querySelector('.node-body');
2192
+ if (body && typeof renderNodeBody === 'function') await renderNodeBody(node, body);
2193
+ }
2194
+
2195
+ // Маршрутизация: повторяет resumeJob + chat.js generate_node tool.
2196
+ try {
2197
+ if (kind === 'audio') {
2198
+ if (subKind === 'music' && typeof runMusicJob === 'function') {
2199
+ await runMusicJob(node, g.prompt, g.durationMs || null, boardHandle, bKey);
2200
+ } else if (subKind === 'sfx' && typeof runSfxJob === 'function') {
2201
+ await runSfxJob(node, g.prompt, g.durationMs ? g.durationMs / 1000 : null, boardHandle, bKey);
2202
+ } else {
2203
+ await runTTSJob(node, g.prompt, boardHandle, bKey, g.voiceId);
2204
+ }
2205
+ } else if (kind === 'text') {
2206
+ const imageRefs = refs.filter(r => r.type === 'image' && r.file);
2207
+ const model = g.model || g.modelKey || 'anthropic/claude-sonnet-4';
2208
+ await runTextJob(node, g.prompt, model, boardHandle, bKey, imageRefs);
2209
+ } else {
2210
+ // image / video
2211
+ await startGenerationJob(node, kind, g.prompt, refs, boardHandle, bKey, g.modelKey);
2212
+ }
2213
+ } catch (e) {
2214
+ console.error('runNodeJobDirectly failed', e);
2215
+ node.status = 'error';
2216
+ node.error = e?.message || String(e);
2217
+ scheduleSave();
2218
+ }
2219
+ }
2220
+ window.runNodeJobDirectly = runNodeJobDirectly;
2221
+
2131
2222
  async function resumeJob(node, bKey, boardHandle) {
2132
2223
  if (state.jobs.has(node.id)) return;
2133
2224
  if (!node.generated || node.status !== 'generating') return;