kingkont 0.10.7 → 0.10.9
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 +1 -1
- package/renderer/board.js +8 -4
- package/renderer/chat.js +69 -2
- package/renderer/cloudProjects.js +6 -1
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -913,6 +913,10 @@ async function openFilm(handle) {
|
|
|
913
913
|
showEmpty();
|
|
914
914
|
// Cloud-кнопки: показать «☁ Сохранить на сервер» когда проект открыт.
|
|
915
915
|
if (window.cloudProjects?.setVisibility) window.cloudProjects.setVisibility();
|
|
916
|
+
// Загружаем chat-историю проекта (если есть .kingkont-chat.json).
|
|
917
|
+
if (window.kingChat?.loadFromCurrentProject) {
|
|
918
|
+
window.kingChat.loadFromCurrentProject().catch(() => {});
|
|
919
|
+
}
|
|
916
920
|
|
|
917
921
|
const raw = localStorage.getItem(`lastBoard:${handle.name}`);
|
|
918
922
|
if (raw) {
|
|
@@ -956,10 +960,10 @@ async function closeProject() {
|
|
|
956
960
|
// Помечаем что юзер вышел явно — на следующем старте autoload пропускается.
|
|
957
961
|
// Cmd+R после close = welcome, а не реоткрытие.
|
|
958
962
|
try { localStorage.setItem('welcomeOnNextStart', '1'); } catch {}
|
|
959
|
-
// Чат привязан к одному проекту —
|
|
960
|
-
//
|
|
961
|
-
// на welcome
|
|
962
|
-
if (window.kingChat?.
|
|
963
|
+
// Чат привязан к одному проекту — сбрасываем in-memory (НЕ трогая
|
|
964
|
+
// .kingkont-chat.json на диске; при следующем открытии загрузится).
|
|
965
|
+
// Прячем панель — на welcome без проекта чат не имеет смысла.
|
|
966
|
+
if (window.kingChat?.resetInMemory) window.kingChat.resetInMemory();
|
|
963
967
|
if (window.kingChat?.close) window.kingChat.close();
|
|
964
968
|
stopExternalWatcher();
|
|
965
969
|
// Сбрасываем UI таймлайна/превью — иначе при возврате через welcome
|
package/renderer/chat.js
CHANGED
|
@@ -219,6 +219,10 @@
|
|
|
219
219
|
lines.push('- Если нужен новый id для add_node — оставь поле пустым, вернётся в результате.');
|
|
220
220
|
lines.push('- Для генерации используй generate_node ПОСЛЕ add_node с promptом.');
|
|
221
221
|
lines.push('- Отвечай по-русски, кратко. Объясняй что делаешь, без лишней воды.');
|
|
222
|
+
lines.push('');
|
|
223
|
+
lines.push('ВАЖНО: НИКОГДА не пиши <tool_result>...</tool_result> сам — это формат который Я');
|
|
224
|
+
lines.push('пришлю тебе с результатами выполнения. В своих сообщениях ты только зовёшь tools');
|
|
225
|
+
lines.push('через <tool>...</tool> и пишешь обычный текст для пользователя.');
|
|
222
226
|
return lines.join('\n');
|
|
223
227
|
}
|
|
224
228
|
|
|
@@ -240,7 +244,58 @@
|
|
|
240
244
|
}
|
|
241
245
|
function stripToolCalls(text) {
|
|
242
246
|
if (typeof text !== 'string') return '';
|
|
243
|
-
|
|
247
|
+
// Убираем оба тэга:
|
|
248
|
+
// <tool>JSON</tool> — наш command-protocol (модель так зовёт tools)
|
|
249
|
+
// <tool_result>JSON</tool_result> — модель иногда его галюцинирует
|
|
250
|
+
// в outputе, копируя format из user-msg.
|
|
251
|
+
return text
|
|
252
|
+
.replace(/<tool>[\s\S]*?<\/tool>/g, '')
|
|
253
|
+
.replace(/<tool_result>[\s\S]*?<\/tool_result>/g, '')
|
|
254
|
+
.trim();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============== PERSISTENCE ==============
|
|
258
|
+
// Чат-история живёт в `<filmHandle>/.kingkont-chat.json`. Работает для
|
|
259
|
+
// папочных проектов и cloud (cloudFs-shim — те же FSAH-методы).
|
|
260
|
+
// Загружается в loadHistoryFromCurrentProject() — board.js зовёт после openFilm.
|
|
261
|
+
// Сохраняется debounced на каждое изменение history (см. persistDebounced).
|
|
262
|
+
const CHAT_FILE = '.kingkont-chat.json';
|
|
263
|
+
let _persistTimer = null;
|
|
264
|
+
async function persistNow() {
|
|
265
|
+
if (!state.filmHandle) return;
|
|
266
|
+
try {
|
|
267
|
+
const fh = await state.filmHandle.getFileHandle(CHAT_FILE, { create: true });
|
|
268
|
+
const w = await fh.createWritable();
|
|
269
|
+
// Не сохраняем role==='system' (буфер) и tool_result-турны (промежуточные,
|
|
270
|
+
// нужны только модели). При re-load восстановим только pure user/assistant.
|
|
271
|
+
const persistable = history.filter(m =>
|
|
272
|
+
m.role !== 'system' &&
|
|
273
|
+
!(m.role === 'user' && m.content?.startsWith('<tool_result>')),
|
|
274
|
+
);
|
|
275
|
+
await w.write(JSON.stringify({ history: persistable, savedAt: Date.now() }, null, 2));
|
|
276
|
+
await w.close();
|
|
277
|
+
} catch (e) {
|
|
278
|
+
console.warn('[chat] persist failed:', e?.message || e);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function persistDebounced() {
|
|
282
|
+
clearTimeout(_persistTimer);
|
|
283
|
+
_persistTimer = setTimeout(persistNow, 600);
|
|
284
|
+
}
|
|
285
|
+
async function loadHistoryFromCurrentProject() {
|
|
286
|
+
if (!state.filmHandle) { history = []; renderHistory(); return; }
|
|
287
|
+
try {
|
|
288
|
+
const fh = await state.filmHandle.getFileHandle(CHAT_FILE);
|
|
289
|
+
const txt = await (await fh.getFile()).text();
|
|
290
|
+
const data = JSON.parse(txt);
|
|
291
|
+
if (Array.isArray(data?.history)) {
|
|
292
|
+
history = data.history;
|
|
293
|
+
renderHistory();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
} catch {} // нет файла или JSON — норма для нового проекта
|
|
297
|
+
history = [];
|
|
298
|
+
renderHistory();
|
|
244
299
|
}
|
|
245
300
|
|
|
246
301
|
// ============== UI ==============
|
|
@@ -364,6 +419,7 @@
|
|
|
364
419
|
busy = true;
|
|
365
420
|
history.push({ role: 'user', content: userText });
|
|
366
421
|
renderHistory();
|
|
422
|
+
persistDebounced();
|
|
367
423
|
const status = appendStatus('Claude думает…');
|
|
368
424
|
const system = buildSystemPrompt();
|
|
369
425
|
try {
|
|
@@ -384,6 +440,7 @@
|
|
|
384
440
|
// Финальный ответ — больше нечего исполнять.
|
|
385
441
|
status.remove();
|
|
386
442
|
renderHistory();
|
|
443
|
+
persistDebounced();
|
|
387
444
|
break;
|
|
388
445
|
}
|
|
389
446
|
|
|
@@ -406,6 +463,7 @@
|
|
|
406
463
|
}
|
|
407
464
|
}
|
|
408
465
|
renderHistory();
|
|
466
|
+
persistDebounced(); // assistant-турн с tool-вызовами уже частично готов
|
|
409
467
|
|
|
410
468
|
// Шлём результаты обратно как user-message (чтобы Claude увидел и продолжил).
|
|
411
469
|
const resultsMsg = results.map(r => `<tool_result>${JSON.stringify(r)}</tool_result>`).join('\n');
|
|
@@ -515,6 +573,9 @@
|
|
|
515
573
|
const panel = $('chatPanel');
|
|
516
574
|
panel.classList.toggle('hidden');
|
|
517
575
|
if (!panel.classList.contains('hidden')) {
|
|
576
|
+
// Отрисовываем сохранённую историю при показе (на случай если она
|
|
577
|
+
// была подгружена в фоне через loadFromCurrentProject до первого toggle).
|
|
578
|
+
renderHistory();
|
|
518
579
|
setTimeout(() => $('chatInput')?.focus(), 50);
|
|
519
580
|
}
|
|
520
581
|
}
|
|
@@ -525,7 +586,13 @@
|
|
|
525
586
|
open: () => { ensureUI(); $('chatPanel').classList.remove('hidden'); setTimeout(() => $('chatInput')?.focus(), 50); },
|
|
526
587
|
close: () => $('chatPanel')?.classList.add('hidden'),
|
|
527
588
|
send,
|
|
528
|
-
clear:
|
|
589
|
+
// User-clear: чистит И на диске тоже (юзер явно нажал ⌫).
|
|
590
|
+
clear: () => { history = []; renderHistory(); persistNow().catch(() => {}); },
|
|
591
|
+
// Reset-on-close: только in-memory + UI, на диске НЕ трогает (история
|
|
592
|
+
// должна остаться чтобы при следующем открытии того же проекта подгрузилась).
|
|
593
|
+
resetInMemory: () => { history = []; renderHistory(); },
|
|
594
|
+
// board.js зовёт после openFilm чтобы подгрузить chat-историю проекта.
|
|
595
|
+
loadFromCurrentProject: () => loadHistoryFromCurrentProject(),
|
|
529
596
|
tools: TOOLS,
|
|
530
597
|
};
|
|
531
598
|
|
|
@@ -666,8 +666,13 @@
|
|
|
666
666
|
});
|
|
667
667
|
if (typeof positionFloatingMenu === 'function') {
|
|
668
668
|
positionFloatingMenu(menu, e.clientX, e.clientY);
|
|
669
|
+
} else {
|
|
670
|
+
menu.style.cssText = `position:fixed; left:${e.clientX}px; top:${e.clientY}px; z-index:9999;`;
|
|
669
671
|
}
|
|
670
|
-
|
|
672
|
+
// Используем глобальный closeNodeMenu — он умеет НЕ закрываться на клик
|
|
673
|
+
// по активной кнопке внутри меню (даёт click-обработчику сработать).
|
|
674
|
+
// Без этого mousedown на кнопке закрывал меню до click → нажатие тонуло.
|
|
675
|
+
setTimeout(() => document.addEventListener('mousedown', closeNodeMenu, { once: true }), 0);
|
|
671
676
|
});
|
|
672
677
|
setCloudButtonsVisibility();
|
|
673
678
|
// Переинициализируем видимость кнопок раз в 5 сек — для случая когда юзер
|