kingkont 0.20.39 → 0.20.41
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/index.html +2 -0
- package/lib/providers.js +57 -0
- package/package.json +1 -1
- package/renderer/genLogger.js +152 -0
- package/renderer/state.js +10 -0
- package/server.js +33 -0
package/index.html
CHANGED
|
@@ -619,6 +619,8 @@
|
|
|
619
619
|
</div>
|
|
620
620
|
</div>
|
|
621
621
|
|
|
622
|
+
<!-- genLogger ПЕРЕД state.js — чтобы logJob() в state.js нашёл window.kkGenLogger. -->
|
|
623
|
+
<script src="renderer/genLogger.js"></script>
|
|
622
624
|
<script src="renderer/state.js"></script>
|
|
623
625
|
<!-- cloudFs.js даёт shim FileSystemDirectoryHandle для облачных проектов.
|
|
624
626
|
Грузим ДО board.js — board.js обращается к window.cloudFsShim в openFilm. -->
|
package/lib/providers.js
CHANGED
|
@@ -38,6 +38,11 @@ const CHATIUM_PATHS = {
|
|
|
38
38
|
// 502 на text/markdown; CDN-roundtrip оверхед для маленьких файлов;
|
|
39
39
|
// изменение текста = UPDATE существующей записи, без orphan'ов в storage.
|
|
40
40
|
projectTexts: '/app/spaces/server/api/project_texts',
|
|
41
|
+
// Gen-logs — append-only лента событий генерации (Electron+web).
|
|
42
|
+
// Юзер: «все логи генераций пользователя в электроне и в вебе должны
|
|
43
|
+
// писаться на сервер, чтобы я мог считать когда говорю 'у пользователя
|
|
44
|
+
// email проблема с последней генерацией'».
|
|
45
|
+
genLog: '/app/spaces/server/api/gen_log',
|
|
41
46
|
};
|
|
42
47
|
|
|
43
48
|
const CHATIUM_CDN = 'https://fs.chatium.ru/get';
|
|
@@ -1443,6 +1448,54 @@ async function batchSaveProjectTexts(body, s) {
|
|
|
1443
1448
|
return d;
|
|
1444
1449
|
}
|
|
1445
1450
|
|
|
1451
|
+
// =============================================================================
|
|
1452
|
+
// Gen-logs proxy (append entries / read by-email / read mine).
|
|
1453
|
+
// =============================================================================
|
|
1454
|
+
|
|
1455
|
+
async function appendGenLog(body, s) {
|
|
1456
|
+
// Тихо игнорим если нет Chatium-сессии — логи доступны только залогиненным.
|
|
1457
|
+
if (!s?.chatium?.token) return { saved: 0, skipped: 'no-session' };
|
|
1458
|
+
const headers = { ...chatiumAuthHeaders(s), 'Content-Type': 'application/json' };
|
|
1459
|
+
const url = `${chatiumBase(s)}${CHATIUM_PATHS.genLog}`;
|
|
1460
|
+
// Не логируем — иначе бесконечный цикл (наш собственный logCall сам бы
|
|
1461
|
+
// сгенерил event → flush → ... ).
|
|
1462
|
+
const r = await fetch(url, { method: 'POST', headers, body: JSON.stringify(body) });
|
|
1463
|
+
const text = await r.text();
|
|
1464
|
+
let d; try { d = JSON.parse(text); } catch { d = { raw: text }; }
|
|
1465
|
+
if (!r.ok) throw new Error(d?.error || d?.reason || `HTTP ${r.status}`);
|
|
1466
|
+
return d;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
async function getGenLogByEmail(email, opts, s) {
|
|
1470
|
+
if (!s?.chatium?.token) throw new Error('Нет Chatium-сессии');
|
|
1471
|
+
const headers = chatiumAuthHeaders(s);
|
|
1472
|
+
const qs = new URLSearchParams({ email });
|
|
1473
|
+
if (opts?.limit) qs.set('limit', String(opts.limit));
|
|
1474
|
+
if (opts?.nodeId) qs.set('nodeId', String(opts.nodeId));
|
|
1475
|
+
const url = `${chatiumBase(s)}${CHATIUM_PATHS.genLog}~by-email?${qs.toString()}`;
|
|
1476
|
+
logCall('GET ', 'Chatium', url);
|
|
1477
|
+
const r = await fetch(url, { headers });
|
|
1478
|
+
const text = await r.text();
|
|
1479
|
+
let d; try { d = JSON.parse(text); } catch { d = { raw: text }; }
|
|
1480
|
+
if (!r.ok) throw new Error(d?.error || d?.reason || `HTTP ${r.status}`);
|
|
1481
|
+
return d;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
async function getGenLogMine(opts, s) {
|
|
1485
|
+
if (!s?.chatium?.token) throw new Error('Нет Chatium-сессии');
|
|
1486
|
+
const headers = chatiumAuthHeaders(s);
|
|
1487
|
+
const qs = new URLSearchParams();
|
|
1488
|
+
if (opts?.limit) qs.set('limit', String(opts.limit));
|
|
1489
|
+
if (opts?.nodeId) qs.set('nodeId', String(opts.nodeId));
|
|
1490
|
+
const url = `${chatiumBase(s)}${CHATIUM_PATHS.genLog}~mine?${qs.toString()}`;
|
|
1491
|
+
logCall('GET ', 'Chatium', url);
|
|
1492
|
+
const r = await fetch(url, { headers });
|
|
1493
|
+
const text = await r.text();
|
|
1494
|
+
let d; try { d = JSON.parse(text); } catch { d = { raw: text }; }
|
|
1495
|
+
if (!r.ok) throw new Error(d?.error || d?.reason || `HTTP ${r.status}`);
|
|
1496
|
+
return d;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1446
1499
|
async function listElevenVoices() {
|
|
1447
1500
|
const key = process.env.ELEVENLABS_API_KEY;
|
|
1448
1501
|
if (!key) throw new Error('ELEVENLABS_API_KEY не задан');
|
|
@@ -1496,6 +1549,10 @@ module.exports = {
|
|
|
1496
1549
|
batchGetProjectTexts,
|
|
1497
1550
|
batchSaveProjectTexts,
|
|
1498
1551
|
deleteProjectText,
|
|
1552
|
+
// Gen-logs (append-only лента событий генерации)
|
|
1553
|
+
appendGenLog,
|
|
1554
|
+
getGenLogByEmail,
|
|
1555
|
+
getGenLogMine,
|
|
1499
1556
|
// Constants (для server.js / тестов)
|
|
1500
1557
|
KIE_IMAGE_MODELS,
|
|
1501
1558
|
KIE_VIDEO_MODELS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kingkont",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.41",
|
|
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": {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// renderer/genLogger.js — батч-логгер для отправки логов генерации на сервер.
|
|
2
|
+
//
|
|
3
|
+
// Юзер: «все логи генераций пользователя в электроне и в вебе должны
|
|
4
|
+
// писаться на сервер с указанием что делалось, в какой сцене, и т.п.».
|
|
5
|
+
//
|
|
6
|
+
// Архитектура:
|
|
7
|
+
// - logJob() в state.js дёргает window.kkGenLogger.append({nodeId, msg, level})
|
|
8
|
+
// - append() добавляет запись в буфер и помечает что нужен flush
|
|
9
|
+
// - flush() раз в FLUSH_INTERVAL_MS отправляет накопленный буфер на сервер
|
|
10
|
+
// POST'ом в /api/gen-log (Electron — server.js → chatium; web — web-shim
|
|
11
|
+
// перенаправляет на chatium-endpoint)
|
|
12
|
+
// - На критические события (gen start / done / error) flush триггерится
|
|
13
|
+
// немедленно — чтобы при крэше клиента не потерять важные строки
|
|
14
|
+
// - На beforeunload — best-effort через navigator.sendBeacon
|
|
15
|
+
//
|
|
16
|
+
// Контекст (projectKey, boardKey, kind, model, appVersion, platform)
|
|
17
|
+
// автоматически вставляется из state и window.appVersion/cloudFs.
|
|
18
|
+
|
|
19
|
+
(function () {
|
|
20
|
+
const FLUSH_INTERVAL_MS = 3000; // обычный таймер
|
|
21
|
+
const MAX_BUFFER = 200; // защита от пика — flush'нем форсом
|
|
22
|
+
const MAX_PER_REQUEST = 100; // server ограничивает до 100/batch
|
|
23
|
+
|
|
24
|
+
const PLATFORM = window.cloudFs ? 'electron' : 'web';
|
|
25
|
+
let _appVersion = null;
|
|
26
|
+
// Версию подсосём асинхронно (в Electron через kingkont-api, в web — берём
|
|
27
|
+
// из window.__KK_VERSION__ если когда-нибудь поставим).
|
|
28
|
+
try {
|
|
29
|
+
if (window.appUpdates?.check) {
|
|
30
|
+
window.appUpdates.check().then(r => { _appVersion = r?.current || null; }).catch(() => {});
|
|
31
|
+
}
|
|
32
|
+
} catch {}
|
|
33
|
+
|
|
34
|
+
const _buffer = [];
|
|
35
|
+
let _flushTimer = null;
|
|
36
|
+
let _flushInFlight = null;
|
|
37
|
+
|
|
38
|
+
// === context helpers =====================================================
|
|
39
|
+
function _currentContext() {
|
|
40
|
+
const s = (typeof state !== 'undefined') ? state : null;
|
|
41
|
+
if (!s) return {};
|
|
42
|
+
const ctx = {};
|
|
43
|
+
if (s.cloudProjectId) ctx.projectKey = 'cloud:' + s.cloudProjectId;
|
|
44
|
+
else if (s.filmHandle?.name) ctx.projectKey = 'folder:' + s.filmHandle.name;
|
|
45
|
+
const b = s.currentBoard;
|
|
46
|
+
if (b?.kind && b?.name) ctx.boardKey = b.kind + '/' + b.name;
|
|
47
|
+
return ctx;
|
|
48
|
+
}
|
|
49
|
+
function _nodeContext(nodeId) {
|
|
50
|
+
const s = (typeof state !== 'undefined') ? state : null;
|
|
51
|
+
if (!s?.currentBoard?.metadata?.nodes) return {};
|
|
52
|
+
const n = s.currentBoard.metadata.nodes.find(x => x.id === nodeId);
|
|
53
|
+
if (!n) return {};
|
|
54
|
+
const ctx = {};
|
|
55
|
+
if (n.type) ctx.kind = n.type;
|
|
56
|
+
if (n.generated?.modelKey) ctx.model = n.generated.modelKey;
|
|
57
|
+
else if (n.generated?.model) ctx.model = n.generated.model;
|
|
58
|
+
return ctx;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// === buffer + flush ======================================================
|
|
62
|
+
function append({ nodeId, msg, level, meta }) {
|
|
63
|
+
const entry = {
|
|
64
|
+
ts: Date.now(),
|
|
65
|
+
msg: String(msg || '').slice(0, 2000),
|
|
66
|
+
level: level || 'info',
|
|
67
|
+
..._currentContext(),
|
|
68
|
+
};
|
|
69
|
+
if (nodeId) {
|
|
70
|
+
entry.nodeId = nodeId;
|
|
71
|
+
Object.assign(entry, _nodeContext(nodeId));
|
|
72
|
+
}
|
|
73
|
+
if (meta) entry.meta = (typeof meta === 'string') ? meta.slice(0, 8000) : JSON.stringify(meta).slice(0, 8000);
|
|
74
|
+
if (_appVersion) entry.appVersion = _appVersion;
|
|
75
|
+
entry.platform = PLATFORM;
|
|
76
|
+
_buffer.push(entry);
|
|
77
|
+
if (_buffer.length >= MAX_BUFFER) {
|
|
78
|
+
flush().catch(() => {});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!_flushTimer) {
|
|
82
|
+
_flushTimer = setTimeout(() => {
|
|
83
|
+
_flushTimer = null;
|
|
84
|
+
flush().catch(() => {});
|
|
85
|
+
}, FLUSH_INTERVAL_MS);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// event() = high-priority append + немедленный flush.
|
|
90
|
+
// Юзер увидит важные события в server-логе без задержки в 3s.
|
|
91
|
+
function event({ nodeId, msg, kind, model, meta }) {
|
|
92
|
+
append({ nodeId, msg, level: 'event', meta });
|
|
93
|
+
// Не ждём flush'а — fire-and-forget.
|
|
94
|
+
flush().catch(() => {});
|
|
95
|
+
}
|
|
96
|
+
function error({ nodeId, msg, meta }) {
|
|
97
|
+
append({ nodeId, msg, level: 'error', meta });
|
|
98
|
+
flush().catch(() => {});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function flush() {
|
|
102
|
+
if (_flushInFlight) return _flushInFlight;
|
|
103
|
+
if (!_buffer.length) return;
|
|
104
|
+
if (_flushTimer) { clearTimeout(_flushTimer); _flushTimer = null; }
|
|
105
|
+
// Берём первые MAX_PER_REQUEST. Остаток оставляем — следующий тик flush'нет.
|
|
106
|
+
const batch = _buffer.splice(0, MAX_PER_REQUEST);
|
|
107
|
+
_flushInFlight = (async () => {
|
|
108
|
+
try {
|
|
109
|
+
const r = await fetch('/api/gen-log', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
credentials: 'include',
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
body: JSON.stringify({ entries: batch }),
|
|
114
|
+
});
|
|
115
|
+
// 401/403 — нет сессии: тихо drop, не возвращаем в буфер (иначе
|
|
116
|
+
// бесконечная попытка). На событиях которые важны — лог в console.
|
|
117
|
+
if (!r.ok && r.status !== 401 && r.status !== 403) {
|
|
118
|
+
console.warn('[genLogger] flush', r.status);
|
|
119
|
+
// Можно вернуть в буфер для ретрая, но рискуем зациклиться при
|
|
120
|
+
// постоянно битом сервере. Игнор.
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {
|
|
123
|
+
// Network error — лог. Тоже без ретрая, чтобы не накручивать.
|
|
124
|
+
console.warn('[genLogger] flush failed:', e?.message || e);
|
|
125
|
+
} finally {
|
|
126
|
+
_flushInFlight = null;
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
return _flushInFlight;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// На выгрузке страницы — best-effort через sendBeacon (не ждёт promise,
|
|
133
|
+
// браузер обещает отправить пока tab закрывается).
|
|
134
|
+
function _beacon() {
|
|
135
|
+
if (!_buffer.length) return;
|
|
136
|
+
try {
|
|
137
|
+
const batch = _buffer.splice(0, MAX_PER_REQUEST);
|
|
138
|
+
const data = JSON.stringify({ entries: batch });
|
|
139
|
+
// sendBeacon работает с relative URL.
|
|
140
|
+
if (navigator?.sendBeacon) {
|
|
141
|
+
// sendBeacon отправляет с credentials по умолчанию.
|
|
142
|
+
const blob = new Blob([data], { type: 'application/json' });
|
|
143
|
+
navigator.sendBeacon('/api/gen-log', blob);
|
|
144
|
+
}
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
window.addEventListener('pagehide', _beacon);
|
|
148
|
+
window.addEventListener('beforeunload', _beacon);
|
|
149
|
+
|
|
150
|
+
// === public API ==========================================================
|
|
151
|
+
window.kkGenLogger = { append, event, error, flush };
|
|
152
|
+
})();
|
package/renderer/state.js
CHANGED
|
@@ -686,6 +686,16 @@ function logJob(nodeId, msg) {
|
|
|
686
686
|
arr.push({ ts: Date.now(), msg });
|
|
687
687
|
if (arr.length > 1000) arr.splice(0, arr.length - 1000);
|
|
688
688
|
console.log(`[${nodeId.slice(0,8)}] ${msg}`);
|
|
689
|
+
// Сервер-логгер: батч-fwd на chatium для дебага (см. renderer/genLogger.js).
|
|
690
|
+
// Юзер: «все логи генераций должны писаться на сервер с указанием что
|
|
691
|
+
// делалось, в какой сцене — чтобы я мог считать когда говорю
|
|
692
|
+
// 'у пользователя email проблема с последней генерацией'».
|
|
693
|
+
try {
|
|
694
|
+
const lvl = /error|failed|fail|ошиб/i.test(msg) ? 'error'
|
|
695
|
+
: /✓|done|success|gen start|generate|готов/i.test(msg) ? 'event'
|
|
696
|
+
: 'info';
|
|
697
|
+
window.kkGenLogger?.append?.({ nodeId, msg, level: lvl });
|
|
698
|
+
} catch {}
|
|
689
699
|
}
|
|
690
700
|
|
|
691
701
|
// Зеркалит логику маршрутизации в server.js — для timeline-лога ноды,
|
package/server.js
CHANGED
|
@@ -496,6 +496,33 @@ async function handleChatiumProjProxy(req, res, action, url) {
|
|
|
496
496
|
} catch (e) { sendError(res, e, 502); }
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
// ---- Gen-logs (append-only лог событий генерации). -------------------------
|
|
500
|
+
|
|
501
|
+
async function handleGenLogAppend(req, res) {
|
|
502
|
+
try {
|
|
503
|
+
const body = await readJson(req); // { entries: [...] }
|
|
504
|
+
send(res, 200, await providers.appendGenLog(body, getSettings()));
|
|
505
|
+
} catch (e) { sendError(res, e, 502); }
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function handleGenLogMine(res, url) {
|
|
509
|
+
try {
|
|
510
|
+
const limit = url.searchParams.get('limit');
|
|
511
|
+
const nodeId = url.searchParams.get('nodeId');
|
|
512
|
+
send(res, 200, await providers.getGenLogMine({ limit, nodeId }, getSettings()));
|
|
513
|
+
} catch (e) { sendError(res, e, 502); }
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function handleGenLogByEmail(res, url) {
|
|
517
|
+
try {
|
|
518
|
+
const email = url.searchParams.get('email');
|
|
519
|
+
if (!email) return send(res, 400, { error: 'email обязателен' });
|
|
520
|
+
const limit = url.searchParams.get('limit');
|
|
521
|
+
const nodeId = url.searchParams.get('nodeId');
|
|
522
|
+
send(res, 200, await providers.getGenLogByEmail(email, { limit, nodeId }, getSettings()));
|
|
523
|
+
} catch (e) { sendError(res, e, 502); }
|
|
524
|
+
}
|
|
525
|
+
|
|
499
526
|
// ---- Project texts (.md контент text-нод). --------------------------------
|
|
500
527
|
// Зачем отдельный API: см. lib/providers.js → CHATIUM_PATHS.projectTexts.
|
|
501
528
|
|
|
@@ -773,6 +800,12 @@ async function _requestHandler(req, res) {
|
|
|
773
800
|
|
|
774
801
|
// Project texts (.md контент). Отдельная таблица на сервере; клиент
|
|
775
802
|
// ссылается на записи по id из manifest'а board.texts[relPath] = textId.
|
|
803
|
+
// Gen-logs (append-only). Юзер: «все логи генераций должны писаться на
|
|
804
|
+
// сервер с указанием что делалось, в какой сцене, и т.п.».
|
|
805
|
+
if (req.method === 'POST' && url.pathname === '/api/gen-log') return handleGenLogAppend(req, res);
|
|
806
|
+
if (req.method === 'GET' && url.pathname === '/api/gen-log/mine') return handleGenLogMine(res, url);
|
|
807
|
+
if (req.method === 'GET' && url.pathname === '/api/gen-log/by-email') return handleGenLogByEmail(res, url);
|
|
808
|
+
|
|
776
809
|
if (req.method === 'POST' && url.pathname === '/api/project-texts') return handleProjectTextCreate(req, res);
|
|
777
810
|
// Batch-операции (отдельные роуты с одним fetch для N items).
|
|
778
811
|
if (req.method === 'POST' && url.pathname === '/api/project-texts/batch-get') return handleProjectTextBatchGet(req, res);
|