kingkont 0.10.8 → 0.11.0
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/chat.js +12 -1
- package/renderer/cloudProjects.js +6 -1
- package/renderer/templates.js +66 -97
package/package.json
CHANGED
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,14 @@
|
|
|
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();
|
|
244
255
|
}
|
|
245
256
|
|
|
246
257
|
// ============== PERSISTENCE ==============
|
|
@@ -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 сек — для случая когда юзер
|
package/renderer/templates.js
CHANGED
|
@@ -572,40 +572,41 @@ async function writeProjectMeta(filmHandle, meta) {
|
|
|
572
572
|
}
|
|
573
573
|
|
|
574
574
|
// =================== Open project template ===================
|
|
575
|
-
//
|
|
576
|
-
//
|
|
575
|
+
// Открываем шаблон проекта как НОВЫЙ облачный проект:
|
|
576
|
+
// 1) GET /api/templates/<id> — получаем manifest + files (cdnUrls)
|
|
577
|
+
// 2) POST /api/projects — создаём cloud-проект, передавая
|
|
578
|
+
// manifest+files КАК ЕСТЬ (файлы уже на CDN, не перезаливаем).
|
|
579
|
+
// 3) cloudProjects.open(newId, name) — синкаемся локально в
|
|
580
|
+
// userData/cloud-projects/<id>/ через стандартный flow.
|
|
581
|
+
//
|
|
582
|
+
// Юзер НЕ выбирает папку — облачный проект автоматически попадает в
|
|
583
|
+
// userData. Без логина в KingKont — ошибка (нет куда class).
|
|
577
584
|
async function openProjectTemplate(templateId, suggestedName) {
|
|
578
585
|
if (_openProjectTplInFlight) return;
|
|
579
|
-
if (typeof window.showDirectoryPicker !== 'function') {
|
|
580
|
-
alert('Браузер не поддерживает выбор папки. Открой проект через File-System-Access.');
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
586
|
_openProjectTplInFlight = true;
|
|
585
|
-
|
|
586
|
-
let parentHandle;
|
|
587
|
+
|
|
587
588
|
try {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
589
|
+
// 0. Проверяем логин (нужен для POST /api/projects).
|
|
590
|
+
let settings = {};
|
|
591
|
+
try { settings = await window.appSettings?.get(); } catch {}
|
|
592
|
+
if (!(settings?.useChatium && settings?.chatium?.token)) {
|
|
593
|
+
alert('Войдите в KingKont, чтобы развернуть шаблон как облачный проект.');
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
595
596
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
597
|
+
// 1. Имя нового проекта.
|
|
598
|
+
const projectName = await askName(
|
|
599
|
+
'Имя нового проекта:',
|
|
600
|
+
suggestedName || 'Из шаблона',
|
|
601
|
+
suggestedName || '',
|
|
602
|
+
{ okText: 'Создать в облаке' },
|
|
603
|
+
);
|
|
604
|
+
if (!projectName) return;
|
|
604
605
|
|
|
605
|
-
|
|
606
|
-
|
|
606
|
+
TPL_PROGRESS.show('Получение шаблона…');
|
|
607
|
+
tplStatus('');
|
|
607
608
|
|
|
608
|
-
|
|
609
|
+
// 2. Получаем шаблон.
|
|
609
610
|
const r = await fetch(`/api/templates/${encodeURIComponent(templateId)}`);
|
|
610
611
|
if (!r.ok) {
|
|
611
612
|
const err = await r.json().catch(() => ({}));
|
|
@@ -615,88 +616,56 @@ async function openProjectTemplate(templateId, suggestedName) {
|
|
|
615
616
|
const boards = tpl.manifest?.boards || [];
|
|
616
617
|
if (!boards.length) throw new Error('В шаблоне нет board\'ов');
|
|
617
618
|
|
|
618
|
-
// 3.
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
let parent = filmHandle;
|
|
632
|
-
if (b.kind === 'character') {
|
|
633
|
-
parent = await filmHandle.getDirectoryHandle(CHAR_DIR, { create: true });
|
|
634
|
-
} else if (b.kind === 'location') {
|
|
635
|
-
parent = await filmHandle.getDirectoryHandle(LOC_DIR, { create: true });
|
|
619
|
+
// 3. Перепаковываем boards в формат cloud-projects manifest:
|
|
620
|
+
// шаблон: { kind, name, manifest, files: {relPath: cdnUrl} }
|
|
621
|
+
// cloud: { kind, name, scene, files: {relPath: cdnUrl} }
|
|
622
|
+
const cloudBoards = boards.map(b => ({
|
|
623
|
+
kind: b.kind, name: b.name,
|
|
624
|
+
scene: b.manifest || {},
|
|
625
|
+
files: b.files || {},
|
|
626
|
+
}));
|
|
627
|
+
// Плоский files index — sum по boards.
|
|
628
|
+
const allFiles = {};
|
|
629
|
+
for (const b of cloudBoards) {
|
|
630
|
+
for (const [relPath, url] of Object.entries(b.files || {})) {
|
|
631
|
+
allFiles[`${b.kind}/${b.name}/${relPath}`] = url;
|
|
636
632
|
}
|
|
637
|
-
const boardHandle = await parent.getDirectoryHandle(b.name, { create: true });
|
|
638
|
-
|
|
639
|
-
const fileEntries = Object.entries(b.files || {});
|
|
640
|
-
for (const [relPath, cdnUrl] of fileEntries) {
|
|
641
|
-
TPL_PROGRESS.update(downloadedTotal, totalFiles,
|
|
642
|
-
`[${b.kind}/${b.name}] ${relPath} (${downloadedTotal + 1}/${totalFiles})`);
|
|
643
|
-
const proxyUrl = '/api/proxy?url=' + encodeURIComponent(cdnUrl);
|
|
644
|
-
const fr = await fetch(proxyUrl);
|
|
645
|
-
if (!fr.ok) throw new Error(`download ${b.name}/${relPath}: HTTP ${fr.status}`);
|
|
646
|
-
const buf = await fr.arrayBuffer();
|
|
647
|
-
await writeBoardFile(boardHandle, relPath, new Uint8Array(buf));
|
|
648
|
-
// Запомним size+mtime для dedup'а при следующем save.
|
|
649
|
-
try {
|
|
650
|
-
const parts = relPath.split('/');
|
|
651
|
-
let dh = boardHandle;
|
|
652
|
-
for (let i = 0; i < parts.length - 1; i++) dh = await dh.getDirectoryHandle(parts[i]);
|
|
653
|
-
const fh = await dh.getFileHandle(parts[parts.length - 1]);
|
|
654
|
-
const f = await fh.getFile();
|
|
655
|
-
const key = `${b.kind}/${b.name}/${relPath}`;
|
|
656
|
-
fileHashes[key] = { url: cdnUrl, size: f.size, mtime: f.lastModified };
|
|
657
|
-
} catch {}
|
|
658
|
-
downloadedTotal++;
|
|
659
|
-
}
|
|
660
|
-
// scene.json — manifest board'а.
|
|
661
|
-
await writeBoardFile(boardHandle, 'scene.json', JSON.stringify(b.manifest || {}, null, 2));
|
|
662
633
|
}
|
|
663
634
|
|
|
664
|
-
//
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
635
|
+
// 4. Создаём cloud-проект на сервере (без re-upload — файлы уже на CDN).
|
|
636
|
+
TPL_PROGRESS.update(0, 1, 'Создание облачного проекта…');
|
|
637
|
+
const cR = await fetch('/api/projects', {
|
|
638
|
+
method: 'POST',
|
|
639
|
+
headers: { 'Content-Type': 'application/json' },
|
|
640
|
+
body: JSON.stringify({
|
|
641
|
+
name: projectName,
|
|
642
|
+
manifest: { boards: cloudBoards },
|
|
643
|
+
files: allFiles,
|
|
644
|
+
coverUrl: tpl.manifest?.coverUrl || tpl.coverUrl || '',
|
|
645
|
+
}),
|
|
671
646
|
});
|
|
647
|
+
if (!cR.ok) {
|
|
648
|
+
const err = await cR.json().catch(() => ({}));
|
|
649
|
+
throw new Error('Не удалось создать облачный проект: ' + (err.error || cR.status));
|
|
650
|
+
}
|
|
651
|
+
const created = await cR.json();
|
|
672
652
|
|
|
673
|
-
//
|
|
674
|
-
|
|
675
|
-
//
|
|
676
|
-
// проект с таким же именем, openFilm попытается auto-select board'а
|
|
677
|
-
// которого нет в свежей структуре, что иногда блокирует загрузку.
|
|
678
|
-
try { localStorage.removeItem(`lastBoard:${projectName}`); } catch {}
|
|
679
|
-
// Закрываем модалку ДО openFilm — иначе её прогресс-бар остаётся
|
|
680
|
-
// на экране пока юзер кликает в sidebar и блокирует UX.
|
|
653
|
+
// 5. Закрываем templates-modal и его прогресс — дальше синк ведёт
|
|
654
|
+
// cloudProjects.open() со своим прогрессом (download файлов в
|
|
655
|
+
// userData/cloud-projects/<id>/).
|
|
681
656
|
TPL_PROGRESS.hide();
|
|
682
|
-
document.getElementById('templatesModal')
|
|
657
|
+
document.getElementById('templatesModal')?.classList.add('hidden');
|
|
683
658
|
tplStatus('');
|
|
684
659
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (boards.length) {
|
|
690
|
-
const first = boards[0];
|
|
691
|
-
const list = first.kind === 'character' ? await listCharacters(filmHandle)
|
|
692
|
-
: first.kind === 'location' ? await listLocations(filmHandle)
|
|
693
|
-
: await listEpisodes(filmHandle);
|
|
694
|
-
const found = list.find(x => x.name === first.name);
|
|
695
|
-
if (found) await selectBoard({ kind: first.kind, ...found });
|
|
660
|
+
if (window.cloudProjects?.open) {
|
|
661
|
+
await window.cloudProjects.open(created.id, created.name || projectName);
|
|
662
|
+
} else {
|
|
663
|
+
throw new Error('cloudProjects.open недоступен');
|
|
696
664
|
}
|
|
697
665
|
} catch (e) {
|
|
698
666
|
TPL_PROGRESS.hide();
|
|
699
667
|
tplStatus('Ошибка открытия проекта: ' + e.message, true);
|
|
668
|
+
alert('Ошибка: ' + (e?.message || e));
|
|
700
669
|
console.error('open project template failed', e);
|
|
701
670
|
} finally {
|
|
702
671
|
_openProjectTplInFlight = false;
|