kingkont 0.19.1 → 0.19.2
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/lib/eventStore.js +95 -9
- package/package.json +1 -1
- package/renderer/generate.js +6 -1
- package/renderer/notifyPanel.js +6 -2
package/lib/eventStore.js
CHANGED
|
@@ -41,6 +41,8 @@ function init({ userDataDir }) {
|
|
|
41
41
|
if (_userDataDir) {
|
|
42
42
|
const dir = path.join(_userDataDir, 'events');
|
|
43
43
|
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
44
|
+
// Lazy migration на первом list/listAll — не блокируем init.
|
|
45
|
+
setImmediate(_migrateGlobalEvents);
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -68,6 +70,65 @@ function _ensureLoaded(projectKey) {
|
|
|
68
70
|
return list;
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
// Одноразовая миграция: legacy events в `_global` без target.projectKey,
|
|
74
|
+
// но с target.nodeId — пытаемся найти их nodeId в других bucket'ах и
|
|
75
|
+
// переместить event в правильный bucket. Без неё клик по таким events
|
|
76
|
+
// делает navigateToTarget early-return (`if (!target.projectKey) return`).
|
|
77
|
+
let _migrationDone = false;
|
|
78
|
+
function _migrateGlobalEvents() {
|
|
79
|
+
if (_migrationDone || !_userDataDir) return;
|
|
80
|
+
_migrationDone = true;
|
|
81
|
+
try {
|
|
82
|
+
// Загрузим все bucket'ы с диска для поиска nodeId.
|
|
83
|
+
const dir = path.join(_userDataDir, 'events');
|
|
84
|
+
let names;
|
|
85
|
+
try { names = fs.readdirSync(dir); } catch { return; }
|
|
86
|
+
const allNodeMap = new Map(); // nodeId → projectKey
|
|
87
|
+
for (const n of names) {
|
|
88
|
+
if (!n.endsWith('.json')) continue;
|
|
89
|
+
const pk = decodeURIComponent(n.replace(/\.json$/, ''));
|
|
90
|
+
if (pk === '_global') continue;
|
|
91
|
+
const evts = _ensureLoaded(pk);
|
|
92
|
+
for (const e of evts) {
|
|
93
|
+
const nid = e.target?.nodeId;
|
|
94
|
+
if (nid && !allNodeMap.has(nid)) allNodeMap.set(nid, pk);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const global = _ensureLoaded('_global');
|
|
98
|
+
if (!global.length) return;
|
|
99
|
+
const stays = [];
|
|
100
|
+
const moveBuckets = new Map(); // pk → events[]
|
|
101
|
+
for (const e of global) {
|
|
102
|
+
const nid = e.target?.nodeId;
|
|
103
|
+
const pk = nid ? allNodeMap.get(nid) : null;
|
|
104
|
+
if (pk) {
|
|
105
|
+
if (!moveBuckets.has(pk)) moveBuckets.set(pk, []);
|
|
106
|
+
moveBuckets.get(pk).push({ ...e, target: { ...e.target, projectKey: pk } });
|
|
107
|
+
} else {
|
|
108
|
+
stays.push(e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (moveBuckets.size === 0) return;
|
|
112
|
+
// Применяем перемещение.
|
|
113
|
+
for (const [pk, evts] of moveBuckets.entries()) {
|
|
114
|
+
const target = _ensureLoaded(pk);
|
|
115
|
+
// Дедуп по (nodeId, ts) — может уже был добавлен.
|
|
116
|
+
for (const e of evts) {
|
|
117
|
+
if (!target.some(t => t.target?.nodeId === e.target.nodeId && t.ts === e.ts)) {
|
|
118
|
+
target.push(e);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
target.sort((a, b) => a.ts - b.ts);
|
|
122
|
+
_schedulePersist(pk);
|
|
123
|
+
}
|
|
124
|
+
_cache.set('_global', stays);
|
|
125
|
+
_schedulePersist('_global');
|
|
126
|
+
console.log(`[eventStore] migrated ${global.length - stays.length} events из _global в правильные bucket'ы`);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.warn('[eventStore] migration failed:', e?.message);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
71
132
|
function _schedulePersist(projectKey) {
|
|
72
133
|
if (!_userDataDir) return;
|
|
73
134
|
if (_persistTimers.has(projectKey)) clearTimeout(_persistTimers.get(projectKey));
|
|
@@ -89,22 +150,26 @@ function append(projectKey, event) {
|
|
|
89
150
|
const pk = projectKey || '_global';
|
|
90
151
|
const list = _ensureLoaded(pk);
|
|
91
152
|
const ts = event.ts || Date.now();
|
|
92
|
-
//
|
|
93
|
-
// - completion
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
153
|
+
// Дедуп:
|
|
154
|
+
// - node:<id> completion (ok/error/info/warn) — окно 24h (gen
|
|
155
|
+
// каждой ноды завершается ОДИН раз; повторное событие — точно
|
|
156
|
+
// дубль, разнос времени бывает 20+ мин если client поллит после
|
|
157
|
+
// долгой паузы и повторно генерит showToast).
|
|
158
|
+
// - chat-final — окно 24h тоже.
|
|
159
|
+
// - всё остальное — 30s.
|
|
98
160
|
let dk = null;
|
|
161
|
+
let windowMs = 30 * 1000;
|
|
99
162
|
if (event.target?.nodeId && (event.kind === 'ok' || event.kind === 'error' || event.kind === 'info' || event.kind === 'warn')) {
|
|
100
163
|
dk = `node:${event.target.nodeId}`;
|
|
164
|
+
windowMs = 24 * 3600 * 1000;
|
|
101
165
|
} else if (event.target?.chat) {
|
|
102
166
|
dk = `chat:${String(event.text).slice(0, 60)}`;
|
|
167
|
+
windowMs = 24 * 3600 * 1000;
|
|
103
168
|
} else {
|
|
104
169
|
dk = `text:${String(event.text).slice(0, 80)}`;
|
|
105
170
|
}
|
|
106
171
|
if (dk) {
|
|
107
|
-
const cutoff = ts -
|
|
172
|
+
const cutoff = ts - windowMs;
|
|
108
173
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
109
174
|
if (list[i].ts < cutoff) break;
|
|
110
175
|
if (list[i]._dk === dk) return; // already have it
|
|
@@ -124,9 +189,23 @@ function append(projectKey, event) {
|
|
|
124
189
|
_schedulePersist(pk);
|
|
125
190
|
}
|
|
126
191
|
|
|
192
|
+
// Backward-compat helper: гарантируем что у каждого event'а в target есть
|
|
193
|
+
// projectKey (равный bucket-key где он лежит). Старые client-persisted
|
|
194
|
+
// события писались БЕЗ target.projectKey — `navigateToTarget` для них
|
|
195
|
+
// молча возвращал ('if (!target.projectKey) return') → клик не работал.
|
|
196
|
+
function _injectProjectKey(events, bucketKey) {
|
|
197
|
+
if (bucketKey === '_global') return events;
|
|
198
|
+
return events.map(e => {
|
|
199
|
+
if (e.target && !e.target.projectKey) {
|
|
200
|
+
return { ...e, target: { ...e.target, projectKey: bucketKey } };
|
|
201
|
+
}
|
|
202
|
+
return e;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
127
206
|
function list(projectKey, { limit = MAX_PER_PROJECT } = {}) {
|
|
128
207
|
const pk = projectKey || '_global';
|
|
129
|
-
|
|
208
|
+
let own = _injectProjectKey(_ensureLoaded(pk).slice(), pk);
|
|
130
209
|
// Backward-compat: события могли быть mis-filed в _global (когда юзер
|
|
131
210
|
// на welcome генерил, _persistEvent.projectKey был null). Извлекаем
|
|
132
211
|
// те у которых target.projectKey совпадает с запрошенным.
|
|
@@ -155,7 +234,14 @@ function listAll({ limit = 200 } = {}) {
|
|
|
155
234
|
}
|
|
156
235
|
const merged = [];
|
|
157
236
|
for (const [pk, evts] of _cache.entries()) {
|
|
158
|
-
|
|
237
|
+
// Inject projectKey в target если отсутствует (backward-compat).
|
|
238
|
+
for (const e of evts) {
|
|
239
|
+
const ev = { ...e, projectKey: pk };
|
|
240
|
+
if (e.target && !e.target.projectKey && pk !== '_global') {
|
|
241
|
+
ev.target = { ...e.target, projectKey: pk };
|
|
242
|
+
}
|
|
243
|
+
merged.push(ev);
|
|
244
|
+
}
|
|
159
245
|
}
|
|
160
246
|
merged.sort((a, b) => a.ts - b.ts);
|
|
161
247
|
return merged.slice(-limit);
|
package/package.json
CHANGED
package/renderer/generate.js
CHANGED
|
@@ -1969,7 +1969,12 @@ async function resumeJob(node, bKey, boardHandle) {
|
|
|
1969
1969
|
|
|
1970
1970
|
if (node.generated.taskId) {
|
|
1971
1971
|
// Уже зарегистрировано в KIE — просто опрашиваем.
|
|
1972
|
-
|
|
1972
|
+
// КРИТИЧНО: projectKey обязателен — без него на completion showToast
|
|
1973
|
+
// создаст event с target БЕЗ projectKey → клик по уведомлению ведёт
|
|
1974
|
+
// в никуда (navigateToTarget рано return'ит). Берём из текущего state.
|
|
1975
|
+
const projectKey = state.cloudProjectId ? 'cloud:' + state.cloudProjectId
|
|
1976
|
+
: state.filmHandle?.name ? 'folder:' + state.filmHandle.name : null;
|
|
1977
|
+
const job = { boardKey: bKey, boardHandle, kind, taskId: node.generated.taskId, nodeId: node.id, projectKey };
|
|
1973
1978
|
state.jobs.set(node.id, job);
|
|
1974
1979
|
updateJobsBadge();
|
|
1975
1980
|
try {
|
package/renderer/notifyPanel.js
CHANGED
|
@@ -251,7 +251,8 @@
|
|
|
251
251
|
// не ловился (юзер видел два одинаковых «✓ image «name» готов»,
|
|
252
252
|
// один синий, второй зелёный). Сейчас одинаковый nodeId-completion
|
|
253
253
|
// в окне 30s — один event.
|
|
254
|
-
const
|
|
254
|
+
const DEDUP_DEFAULT_WINDOW_MS = 30 * 1000;
|
|
255
|
+
const DEDUP_NODE_WINDOW_MS = 24 * 3600 * 1000; // node-completion: дубль через 20+ мин — всё равно дубль
|
|
255
256
|
// События с этими kind считаются «completion» — для них дедупим по nodeId.
|
|
256
257
|
const _COMPLETION_KINDS = new Set(['ok', 'error', 'info', 'warn']);
|
|
257
258
|
function _dedupKey(kind, text, target) {
|
|
@@ -265,7 +266,10 @@
|
|
|
265
266
|
const k = kind || 'info';
|
|
266
267
|
const dk = _dedupKey(k, text, target);
|
|
267
268
|
if (dk) {
|
|
268
|
-
|
|
269
|
+
// Для node-events окно длинное (gen завершается ОДИН раз), для всего
|
|
270
|
+
// остального — короткое (могут быть несколько похожих info-toast'ов).
|
|
271
|
+
const windowMs = dk.startsWith('node:') ? DEDUP_NODE_WINDOW_MS : DEDUP_DEFAULT_WINDOW_MS;
|
|
272
|
+
const cutoff = Date.now() - windowMs;
|
|
269
273
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
270
274
|
if (events[i].ts < cutoff) break;
|
|
271
275
|
if (events[i]._dk === dk) {
|