kingkont 0.10.6 → 0.10.8
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 +57 -1
- package/renderer/cloudProjects.js +25 -0
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
|
@@ -243,6 +243,50 @@
|
|
|
243
243
|
return text.replace(/<tool>[\s\S]*?<\/tool>/g, '').trim();
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
// ============== PERSISTENCE ==============
|
|
247
|
+
// Чат-история живёт в `<filmHandle>/.kingkont-chat.json`. Работает для
|
|
248
|
+
// папочных проектов и cloud (cloudFs-shim — те же FSAH-методы).
|
|
249
|
+
// Загружается в loadHistoryFromCurrentProject() — board.js зовёт после openFilm.
|
|
250
|
+
// Сохраняется debounced на каждое изменение history (см. persistDebounced).
|
|
251
|
+
const CHAT_FILE = '.kingkont-chat.json';
|
|
252
|
+
let _persistTimer = null;
|
|
253
|
+
async function persistNow() {
|
|
254
|
+
if (!state.filmHandle) return;
|
|
255
|
+
try {
|
|
256
|
+
const fh = await state.filmHandle.getFileHandle(CHAT_FILE, { create: true });
|
|
257
|
+
const w = await fh.createWritable();
|
|
258
|
+
// Не сохраняем role==='system' (буфер) и tool_result-турны (промежуточные,
|
|
259
|
+
// нужны только модели). При re-load восстановим только pure user/assistant.
|
|
260
|
+
const persistable = history.filter(m =>
|
|
261
|
+
m.role !== 'system' &&
|
|
262
|
+
!(m.role === 'user' && m.content?.startsWith('<tool_result>')),
|
|
263
|
+
);
|
|
264
|
+
await w.write(JSON.stringify({ history: persistable, savedAt: Date.now() }, null, 2));
|
|
265
|
+
await w.close();
|
|
266
|
+
} catch (e) {
|
|
267
|
+
console.warn('[chat] persist failed:', e?.message || e);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function persistDebounced() {
|
|
271
|
+
clearTimeout(_persistTimer);
|
|
272
|
+
_persistTimer = setTimeout(persistNow, 600);
|
|
273
|
+
}
|
|
274
|
+
async function loadHistoryFromCurrentProject() {
|
|
275
|
+
if (!state.filmHandle) { history = []; renderHistory(); return; }
|
|
276
|
+
try {
|
|
277
|
+
const fh = await state.filmHandle.getFileHandle(CHAT_FILE);
|
|
278
|
+
const txt = await (await fh.getFile()).text();
|
|
279
|
+
const data = JSON.parse(txt);
|
|
280
|
+
if (Array.isArray(data?.history)) {
|
|
281
|
+
history = data.history;
|
|
282
|
+
renderHistory();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
} catch {} // нет файла или JSON — норма для нового проекта
|
|
286
|
+
history = [];
|
|
287
|
+
renderHistory();
|
|
288
|
+
}
|
|
289
|
+
|
|
246
290
|
// ============== UI ==============
|
|
247
291
|
let history = []; // [{role: 'user'|'assistant'|'system', content: string, tools?: [...], results?: [...]}]
|
|
248
292
|
let busy = false;
|
|
@@ -364,6 +408,7 @@
|
|
|
364
408
|
busy = true;
|
|
365
409
|
history.push({ role: 'user', content: userText });
|
|
366
410
|
renderHistory();
|
|
411
|
+
persistDebounced();
|
|
367
412
|
const status = appendStatus('Claude думает…');
|
|
368
413
|
const system = buildSystemPrompt();
|
|
369
414
|
try {
|
|
@@ -384,6 +429,7 @@
|
|
|
384
429
|
// Финальный ответ — больше нечего исполнять.
|
|
385
430
|
status.remove();
|
|
386
431
|
renderHistory();
|
|
432
|
+
persistDebounced();
|
|
387
433
|
break;
|
|
388
434
|
}
|
|
389
435
|
|
|
@@ -406,6 +452,7 @@
|
|
|
406
452
|
}
|
|
407
453
|
}
|
|
408
454
|
renderHistory();
|
|
455
|
+
persistDebounced(); // assistant-турн с tool-вызовами уже частично готов
|
|
409
456
|
|
|
410
457
|
// Шлём результаты обратно как user-message (чтобы Claude увидел и продолжил).
|
|
411
458
|
const resultsMsg = results.map(r => `<tool_result>${JSON.stringify(r)}</tool_result>`).join('\n');
|
|
@@ -515,6 +562,9 @@
|
|
|
515
562
|
const panel = $('chatPanel');
|
|
516
563
|
panel.classList.toggle('hidden');
|
|
517
564
|
if (!panel.classList.contains('hidden')) {
|
|
565
|
+
// Отрисовываем сохранённую историю при показе (на случай если она
|
|
566
|
+
// была подгружена в фоне через loadFromCurrentProject до первого toggle).
|
|
567
|
+
renderHistory();
|
|
518
568
|
setTimeout(() => $('chatInput')?.focus(), 50);
|
|
519
569
|
}
|
|
520
570
|
}
|
|
@@ -525,7 +575,13 @@
|
|
|
525
575
|
open: () => { ensureUI(); $('chatPanel').classList.remove('hidden'); setTimeout(() => $('chatInput')?.focus(), 50); },
|
|
526
576
|
close: () => $('chatPanel')?.classList.add('hidden'),
|
|
527
577
|
send,
|
|
528
|
-
clear:
|
|
578
|
+
// User-clear: чистит И на диске тоже (юзер явно нажал ⌫).
|
|
579
|
+
clear: () => { history = []; renderHistory(); persistNow().catch(() => {}); },
|
|
580
|
+
// Reset-on-close: только in-memory + UI, на диске НЕ трогает (история
|
|
581
|
+
// должна остаться чтобы при следующем открытии того же проекта подгрузилась).
|
|
582
|
+
resetInMemory: () => { history = []; renderHistory(); },
|
|
583
|
+
// board.js зовёт после openFilm чтобы подгрузить chat-историю проекта.
|
|
584
|
+
loadFromCurrentProject: () => loadHistoryFromCurrentProject(),
|
|
529
585
|
tools: TOOLS,
|
|
530
586
|
};
|
|
531
587
|
|
|
@@ -644,6 +644,31 @@
|
|
|
644
644
|
$('newCloudProject')?.addEventListener('click', createNewCloudProject);
|
|
645
645
|
$('openCloudProjects')?.addEventListener('click', openCloudProjectsModal);
|
|
646
646
|
$('saveProjectCloud')?.addEventListener('click', saveCloudProject);
|
|
647
|
+
// ПКМ на «☁ Сохранить на сервер» — расширенное меню: «Сохранить как
|
|
648
|
+
// шаблон» (доступно ВСЕМ типам проектов, включая cloud — реализация
|
|
649
|
+
// в templates.js работает через filmHandle, cloudFs-shim тоже подходит).
|
|
650
|
+
$('saveProjectCloud')?.addEventListener('contextmenu', e => {
|
|
651
|
+
e.preventDefault();
|
|
652
|
+
e.stopPropagation();
|
|
653
|
+
const menu = document.getElementById('nodeMenu');
|
|
654
|
+
if (!menu) return;
|
|
655
|
+
menu.innerHTML = '';
|
|
656
|
+
const add = (label, fn) => {
|
|
657
|
+
const b = document.createElement('button');
|
|
658
|
+
b.textContent = label;
|
|
659
|
+
b.addEventListener('click', () => { menu.classList.add('hidden'); fn(); });
|
|
660
|
+
menu.appendChild(b);
|
|
661
|
+
};
|
|
662
|
+
add('☁ Сохранить на сервер', saveCloudProject);
|
|
663
|
+
add('💾 Сохранить как шаблон…', () => {
|
|
664
|
+
if (typeof saveCurrentProjectAsTemplate === 'function') saveCurrentProjectAsTemplate();
|
|
665
|
+
else alert('saveCurrentProjectAsTemplate недоступен');
|
|
666
|
+
});
|
|
667
|
+
if (typeof positionFloatingMenu === 'function') {
|
|
668
|
+
positionFloatingMenu(menu, e.clientX, e.clientY);
|
|
669
|
+
}
|
|
670
|
+
setTimeout(() => document.addEventListener('mousedown', () => menu.classList.add('hidden'), { once: true }), 0);
|
|
671
|
+
});
|
|
647
672
|
setCloudButtonsVisibility();
|
|
648
673
|
// Переинициализируем видимость кнопок раз в 5 сек — для случая когда юзер
|
|
649
674
|
// login/logout в Chatium через настройки (нет других сигналов).
|