beast-agent 2.4.0 → 2.4.5
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/src/agent/engine.js +41 -5
- package/src/agent/perception.js +177 -8
- package/src/main.js +122 -11
- package/src/preload.js +2 -0
- package/src/renderer/i18n.js +32 -12
- package/src/renderer/renderer.js +91 -44
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -987,9 +987,11 @@ class Engine {
|
|
|
987
987
|
if (session.code) this._codeIndex.set(session.code, session.id);
|
|
988
988
|
}
|
|
989
989
|
|
|
990
|
-
/* Oturum dosyasındaki son 'msg' satırını
|
|
991
|
-
yanıtsız kalmış user mesajıyla yeni mesaj birleştirildiğinde
|
|
992
|
-
|
|
990
|
+
/* Oturum dosyasındaki son 'msg' satırını güncel içerikle değiştir —
|
|
991
|
+
yanıtsız kalmış user mesajıyla yeni mesaj birleştirildiğinde (role:'user')
|
|
992
|
+
ya da proaktif not son asistan mesajına birleştirildiğinde (role:'assistant') */
|
|
993
|
+
_rewriteLastMsg(id, content, attachments, role) {
|
|
994
|
+
const wantRole = role === 'assistant' ? 'assistant' : 'user';
|
|
993
995
|
try {
|
|
994
996
|
const file = this._file(String(id));
|
|
995
997
|
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
@@ -997,8 +999,8 @@ class Engine {
|
|
|
997
999
|
if (!lines[i].trim()) continue;
|
|
998
1000
|
let r;
|
|
999
1001
|
try { r = JSON.parse(lines[i]); } catch { continue; }
|
|
1000
|
-
if (r.t === 'msg' && r.role ===
|
|
1001
|
-
const out = { t: 'msg', role:
|
|
1002
|
+
if (r.t === 'msg' && r.role === wantRole) {
|
|
1003
|
+
const out = { t: 'msg', role: wantRole, content: String(content || '') };
|
|
1002
1004
|
if (Array.isArray(attachments) && attachments.length) out.attachments = attachments;
|
|
1003
1005
|
lines[i] = JSON.stringify(out);
|
|
1004
1006
|
break;
|
|
@@ -2284,6 +2286,40 @@ class Engine {
|
|
|
2284
2286
|
return true;
|
|
2285
2287
|
}
|
|
2286
2288
|
|
|
2289
|
+
/* PROAKTİF MESAJ ENJEKSİYONU (Empati Loop): ajanın az önce KULLANICIYA
|
|
2290
|
+
bildirdiği metni, yeni bir tur BAŞLATMADAN geçmişe kendi asistan
|
|
2291
|
+
mesajı olarak işler. Model bir sonraki turda bu mesajı bağlamında
|
|
2292
|
+
"benim söylediğim söz" olarak görür — kullanıcı cevap verdiğinde
|
|
2293
|
+
sohbet aynı oturumda kopmadan devam eder. Son mesaj araçsız bir
|
|
2294
|
+
asistan mesajıysa metin ona BİRLEŞTİRİLİR — katı sağlayıcılarda
|
|
2295
|
+
art arda asistan mesajı gitmez. */
|
|
2296
|
+
injectAssistant(sessionId, text) {
|
|
2297
|
+
const s = this._load(String(sessionId));
|
|
2298
|
+
if (!s) return false;
|
|
2299
|
+
const body = String(text || '').slice(0, USER_MAX);
|
|
2300
|
+
if (!body.trim()) return false;
|
|
2301
|
+
const lastMsg = s.messages[s.messages.length - 1];
|
|
2302
|
+
if (
|
|
2303
|
+
lastMsg &&
|
|
2304
|
+
lastMsg.role === 'assistant' &&
|
|
2305
|
+
typeof lastMsg.content === 'string' &&
|
|
2306
|
+
!(Array.isArray(lastMsg.tool_calls) && lastMsg.tool_calls.length)
|
|
2307
|
+
) {
|
|
2308
|
+
lastMsg.content = (lastMsg.content + '\n\n' + body).slice(0, USER_MAX);
|
|
2309
|
+
this._rewriteLastMsg(s.id, lastMsg.content, null, 'assistant');
|
|
2310
|
+
this.emit({ type: 'sessions' });
|
|
2311
|
+
return true;
|
|
2312
|
+
}
|
|
2313
|
+
const msg = { role: 'assistant', content: body };
|
|
2314
|
+
s.messages.push(msg);
|
|
2315
|
+
try {
|
|
2316
|
+
this._append(s, msg);
|
|
2317
|
+
} catch {}
|
|
2318
|
+
this.emit({ type: 'message', sessionId: s.id, message: msg });
|
|
2319
|
+
this.emit({ type: 'sessions' });
|
|
2320
|
+
return true;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2287
2323
|
/* Paralel ajan GEÇMİŞİNİ topluca sil: çalışanlar/ bekleyenler iptal edilir,
|
|
2288
2324
|
tüm bg oturum dosyaları + kalıcı kayıt temizlenir. Döndürür: silinen iş sayısı */
|
|
2289
2325
|
clearAllBgJobs() {
|
package/src/agent/perception.js
CHANGED
|
@@ -93,6 +93,161 @@ function saveState(st) {
|
|
|
93
93
|
} catch {}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/* ---------- empati hafızası: sohbet kaydı + öğrenilen ilgi alanları ----------
|
|
97
|
+
Sohbet akışı (chat/WA/TG/DC) main tarafından buraya düşürülür; kullanıcı
|
|
98
|
+
mesajlarından anlamlı kelimeler toplanır, sık geçenler "öğrenilen ilgi
|
|
99
|
+
alanı" olur. Tarama filtresi ve bildirim mesajı bu hafızayla kişiselleşir:
|
|
100
|
+
elle girilen ilgi alanları + öğrenilenler birlikte prompta girer. */
|
|
101
|
+
|
|
102
|
+
const CHAT_CAP = 120; // son sohbet kaydı tavanı
|
|
103
|
+
const INTEREST_CAP = 24; // öğrenilen ilgi etiketi tavanı
|
|
104
|
+
const CHAT_SNIPPET = 220;
|
|
105
|
+
|
|
106
|
+
const TR_FOLD_MEM = { ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u', â: 'a', î: 'i', û: 'u' };
|
|
107
|
+
|
|
108
|
+
function memFold(s) {
|
|
109
|
+
return String(s || '').toLowerCase().replace(/[çğıöşüâîû]/g, (ch) => TR_FOLD_MEM[ch] || ch);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* gereksiz kelimeler — ilgi alanı sayılmaz. Set FOLD'LU kurulur: kelimeler
|
|
113
|
+
zaten memFold ile ASCII'ye indirgendikten sonra arandığı için 'bugün' gibi
|
|
114
|
+
stopword'ler fold'lu haliyle ('bugun') listede olmalı. */
|
|
115
|
+
const MEM_STOP_RAW =
|
|
116
|
+
'the and for with this that have from was are were been will would could should ' +
|
|
117
|
+
'bir bu şu o ve ile için gibi ama fakat çok daha en ne mi mı mu mü de da ki ise ya ' +
|
|
118
|
+
'veya yani bana bize sana ona beni bizi seni onun benim bizim senin onları var yok ' +
|
|
119
|
+
'olur olan olarak nasıl neden niye şey şeyi her hem acaba lazım gerek evet hayır ' +
|
|
120
|
+
'tamam tmm kanka abi hocam merhaba selam hey lütfen teşekkür sağol eyvallah bugün ' +
|
|
121
|
+
'yarın dün şimdi sonra önce hala henüz yine tekrar biraz az kadar çünkü eğer ' +
|
|
122
|
+
'yapabilir misin musun isterim istiyorum lazım gerekiyor beast asistan önemli ' +
|
|
123
|
+
'hakkında üzerine konuştuk konuşuyoruz konuşmak kendi aynı başka diğer belki ' +
|
|
124
|
+
'sanırım galiba gerekli olsun olmuyor oluyor bakalım biriyle hangi kim nerede';
|
|
125
|
+
|
|
126
|
+
const MEM_STOP = new Set(MEM_STOP_RAW.split(/\s+/).map((w) => memFold(w)).filter(Boolean));
|
|
127
|
+
|
|
128
|
+
function chatFile() {
|
|
129
|
+
return path.join(beastRoot(), 'perception', 'chatmem.json');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function loadChatMem() {
|
|
133
|
+
try {
|
|
134
|
+
const r = JSON.parse(fs.readFileSync(chatFile(), 'utf8'));
|
|
135
|
+
return {
|
|
136
|
+
chats: Array.isArray(r.chats) ? r.chats : [],
|
|
137
|
+
learned: r.learned && typeof r.learned === 'object' ? r.learned : {},
|
|
138
|
+
};
|
|
139
|
+
} catch {
|
|
140
|
+
return { chats: [], learned: {} };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function saveChatMem(cm) {
|
|
145
|
+
try {
|
|
146
|
+
fs.mkdirSync(path.dirname(chatFile()), { recursive: true });
|
|
147
|
+
fs.writeFileSync(chatFile(), JSON.stringify(cm));
|
|
148
|
+
} catch {}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Sohbet parçasını hafızaya yaz (kullanıcı + beast cevapları + proaktif mesajlar) */
|
|
152
|
+
function rememberConversation(text, channel) {
|
|
153
|
+
try {
|
|
154
|
+
const t = String(text || '').replace(/\s+/g, ' ').trim().slice(0, CHAT_SNIPPET);
|
|
155
|
+
if (t.length < 4) return false;
|
|
156
|
+
const cm = loadChatMem();
|
|
157
|
+
/* birebir tekrar yut */
|
|
158
|
+
if (cm.chats.length && cm.chats[cm.chats.length - 1].t === t) return false;
|
|
159
|
+
cm.chats.push({ ts: new Date().toISOString(), ch: String(channel || 'sohbet').slice(0, 12), t });
|
|
160
|
+
while (cm.chats.length > CHAT_CAP) cm.chats.shift();
|
|
161
|
+
saveChatMem(cm);
|
|
162
|
+
return true;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Kullanıcı mesajından ilgi alanı öğren: fold + stopword temizliği, sayaca yaz */
|
|
169
|
+
function learnInterests(text) {
|
|
170
|
+
try {
|
|
171
|
+
const toks = (memFold(text).match(/[a-z0-9_+#.]{3,}/g) || [])
|
|
172
|
+
.filter((w) => !MEM_STOP.has(w) && !/^\d+$/.test(w))
|
|
173
|
+
.slice(0, 12);
|
|
174
|
+
if (!toks.length) return false;
|
|
175
|
+
const cm = loadChatMem();
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
for (const w of toks) {
|
|
178
|
+
const cur = cm.learned[w] || { c: 0, at: 0 };
|
|
179
|
+
cur.c++;
|
|
180
|
+
cur.at = now;
|
|
181
|
+
cm.learned[w] = cur;
|
|
182
|
+
}
|
|
183
|
+
/* tavana in: en sık + en taze etiketler kalır */
|
|
184
|
+
const keys = Object.keys(cm.learned);
|
|
185
|
+
if (keys.length > INTEREST_CAP) {
|
|
186
|
+
keys.sort((a, b) => (cm.learned[b].c - cm.learned[a].c) || (cm.learned[b].at - cm.learned[a].at));
|
|
187
|
+
for (const k of keys.slice(INTEREST_CAP)) delete cm.learned[k];
|
|
188
|
+
}
|
|
189
|
+
saveChatMem(cm);
|
|
190
|
+
return true;
|
|
191
|
+
} catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Elle girilen + öğrenilen ilgi alanları birleşik (promptlar bunu kullanır) */
|
|
197
|
+
function combinedInterests(cfg) {
|
|
198
|
+
const manual = String((cfg && cfg.interests) || '').trim();
|
|
199
|
+
try {
|
|
200
|
+
const cm = loadChatMem();
|
|
201
|
+
const learned = Object.keys(cm.learned)
|
|
202
|
+
.sort((a, b) => cm.learned[b].c - cm.learned[a].c)
|
|
203
|
+
.slice(0, 12);
|
|
204
|
+
return [manual, learned.join(', ')].filter(Boolean).join(', ');
|
|
205
|
+
} catch {
|
|
206
|
+
return manual;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Son konuşma satırları — compose/filtre promptuna bağlam olarak girer */
|
|
211
|
+
function chatContextLine(max = 8) {
|
|
212
|
+
try {
|
|
213
|
+
const cm = loadChatMem();
|
|
214
|
+
const items = cm.chats.slice(-Math.max(1, Number(max) || 8));
|
|
215
|
+
if (!items.length) return '';
|
|
216
|
+
const when = (iso) => {
|
|
217
|
+
try {
|
|
218
|
+
const d = new Date(iso);
|
|
219
|
+
return ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2);
|
|
220
|
+
} catch { return ''; }
|
|
221
|
+
};
|
|
222
|
+
return (
|
|
223
|
+
'SON KONUŞMALAR (kullanıcıyla yakın zamanda konuşulanlar):\n' +
|
|
224
|
+
items.map((c) => '- [' + when(c.ts) + ' ' + (c.ch || 'sohbet') + '] ' + c.t).join('\n')
|
|
225
|
+
);
|
|
226
|
+
} catch {
|
|
227
|
+
return '';
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* UI için hafıza görünümü: öğrenilen etiketler + son konuşmalar */
|
|
232
|
+
function memSnapshot(limit = 40) {
|
|
233
|
+
const cm = loadChatMem();
|
|
234
|
+
const learned = Object.keys(cm.learned)
|
|
235
|
+
.sort((a, b) => (cm.learned[b].c - cm.learned[a].c) || (cm.learned[b].at - cm.learned[a].at))
|
|
236
|
+
.slice(0, 30)
|
|
237
|
+
.map((w) => ({ w, c: cm.learned[w].c || 0 }));
|
|
238
|
+
const n = Math.max(1, Math.min(80, Number(limit) || 40));
|
|
239
|
+
return { learned, chats: cm.chats.slice(-n).reverse(), total: cm.chats.length };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function memClear() {
|
|
243
|
+
try {
|
|
244
|
+
fs.rmSync(chatFile(), { force: true });
|
|
245
|
+
return { ok: true };
|
|
246
|
+
} catch (e) {
|
|
247
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
96
251
|
/* ---------- normalize / dedup ---------- */
|
|
97
252
|
|
|
98
253
|
function normTitle(t) {
|
|
@@ -174,9 +329,11 @@ function heuristicScores(ev, cfg) {
|
|
|
174
329
|
const URGENT = URGENT_RE.test(t);
|
|
175
330
|
let importance = ev.type === 'news' ? 40 : 65;
|
|
176
331
|
if (URGENT) importance += 20;
|
|
177
|
-
|
|
332
|
+
/* ilgi kelimeleri fold'lanır (öğrenilen etiketler zaten fold'lu) */
|
|
333
|
+
const words = String(cfg.interests || '').split(/[,\s]+/).map((w) => memFold(w).trim()).filter((w) => w.length > 2);
|
|
178
334
|
let hits = 0;
|
|
179
|
-
|
|
335
|
+
const hay = memFold(t);
|
|
336
|
+
for (const w of words) if (hay.includes(w)) hits++;
|
|
180
337
|
const relevance = hits ? Math.min(100, 55 + hits * 15) : (ev.type === 'news' ? 30 : 60);
|
|
181
338
|
const urgency = URGENT ? 75 : (ev.type === 'news' ? 35 : 45);
|
|
182
339
|
const novelty = ev.type === 'news' ? 80 : 60;
|
|
@@ -203,7 +360,7 @@ const FILTER_SYSTEM =
|
|
|
203
360
|
'"notify": kullanıcıya bildirmeye gerçekten değer mi (önemsiz/spam/klasik haberlerde false). ' +
|
|
204
361
|
'"reason": en fazla 60 karakter kısa gerekçe.';
|
|
205
362
|
|
|
206
|
-
function filterPrompt(events, interests) {
|
|
363
|
+
function filterPrompt(events, interests, chatCtx) {
|
|
207
364
|
const list = events.map((e) => ({
|
|
208
365
|
id: e.id,
|
|
209
366
|
type: e.type,
|
|
@@ -212,6 +369,7 @@ function filterPrompt(events, interests) {
|
|
|
212
369
|
}));
|
|
213
370
|
return (
|
|
214
371
|
'KULLANICI İLGİLERİ: ' + (String(interests || '').trim() || '(belirtilmemiş)') + '\n' +
|
|
372
|
+
(String(chatCtx || '').trim() ? String(chatCtx).trim() + '\n' : '') +
|
|
215
373
|
'OLAYLAR:\n' + JSON.stringify(list) + '\n\n' +
|
|
216
374
|
'Her olay için {"id","relevant","importance","urgency","novelty","notify","reason"} içeren ' +
|
|
217
375
|
'TEK bir JSON dizisi döndür. Sadece JSON, başka metin yok.'
|
|
@@ -244,13 +402,15 @@ const COMPOSE_SYSTEM =
|
|
|
244
402
|
'Alarm spam\'i yok; başlık/emoji/Markdown ekleme, yalnız düz metin yaz.';
|
|
245
403
|
|
|
246
404
|
function composePrompt(ev, cfg) {
|
|
405
|
+
const ctx = chatContextLine(5);
|
|
247
406
|
return (
|
|
248
407
|
'OLAY: ' + ev.title + '\n' +
|
|
249
408
|
(ev.detail ? 'DETAY: ' + ev.detail + '\n' : '') +
|
|
250
409
|
'KAYNAK: ' + ev.source + '\n' +
|
|
251
410
|
'ÖNCELİK: ' + ev.priority + '/100 · ' + (ev.reason || '') + '\n' +
|
|
252
|
-
'KULLANICI İLGİLERİ: ' + (
|
|
253
|
-
|
|
411
|
+
'KULLANICI İLGİLERİ: ' + (combinedInterests(cfg) || '(belirtilmemiş)') + '\n' +
|
|
412
|
+
(ctx ? ctx + '\n' : '') +
|
|
413
|
+
'\nBu olay için kullanıcıya gönderilecek proaktif kısa mesajı yaz.'
|
|
254
414
|
);
|
|
255
415
|
}
|
|
256
416
|
|
|
@@ -310,11 +470,14 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
|
|
|
310
470
|
for (const k of seenKeys.slice(0, seenKeys.length - SEEN_CAP)) delete st.seen[k];
|
|
311
471
|
}
|
|
312
472
|
|
|
313
|
-
/* UCUZ FİLTRE: tüm adaylar TEK toplu çağrıda (maliyet freni). Çökerse deterministik.
|
|
473
|
+
/* UCUZ FİLTRE: tüm adaylar TEK toplu çağrıda (maliyet freni). Çökerse deterministik.
|
|
474
|
+
İlgi alanları: elle girilen + sohbetlerden öğrenilenler; son konuşmalar bağlam. */
|
|
475
|
+
const interests = combinedInterests(cfg);
|
|
476
|
+
const chatCtx = chatContextLine(8);
|
|
314
477
|
let graded = null;
|
|
315
478
|
if (typeof llmFilter === 'function' && pending.length) {
|
|
316
479
|
try {
|
|
317
|
-
const out = await llmFilter(filterPrompt(pending.slice(0, FILTER_MAX),
|
|
480
|
+
const out = await llmFilter(filterPrompt(pending.slice(0, FILTER_MAX), interests, chatCtx));
|
|
318
481
|
graded = parseFilterJson(out);
|
|
319
482
|
if (graded.size) log('filtre: LLM ' + graded.size + ' olayı puanladı');
|
|
320
483
|
} catch {
|
|
@@ -334,7 +497,7 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
|
|
|
334
497
|
urgency: Math.max(0, Math.min(100, Math.round(Number(g.urgency) || 0))),
|
|
335
498
|
novelty: Math.max(0, Math.min(100, Math.round(Number(g.novelty) || 0))),
|
|
336
499
|
}
|
|
337
|
-
: heuristicScores(ev, cfg);
|
|
500
|
+
: heuristicScores(ev, { ...cfg, interests });
|
|
338
501
|
ev.priority = compositePriority(ev.scores, cfg);
|
|
339
502
|
ev.reason = g ? String(g.reason || '').slice(0, 80) : 'deterministik puan';
|
|
340
503
|
if (g && g.notify === false && ev.priority < cfg.minNotifyPriority) {
|
|
@@ -438,6 +601,12 @@ module.exports = {
|
|
|
438
601
|
markNotified,
|
|
439
602
|
listEvents,
|
|
440
603
|
lastRunAt,
|
|
604
|
+
rememberConversation,
|
|
605
|
+
learnInterests,
|
|
606
|
+
combinedInterests,
|
|
607
|
+
chatContextLine,
|
|
608
|
+
memSnapshot,
|
|
609
|
+
memClear,
|
|
441
610
|
FILTER_SYSTEM,
|
|
442
611
|
COMPOSE_SYSTEM,
|
|
443
612
|
filterPrompt,
|
package/src/main.js
CHANGED
|
@@ -2937,6 +2937,7 @@ function reloadBackend() {
|
|
|
2937
2937
|
},
|
|
2938
2938
|
emit: (ev) => {
|
|
2939
2939
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
2940
|
+
try { empatiRememberFromEvent(ev); } catch {} /* empati hafızası: sohbet → kayıt + ilgi öğrenme */
|
|
2940
2941
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
2941
2942
|
bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
|
|
2942
2943
|
stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
|
|
@@ -6200,6 +6201,33 @@ function empatiCfg() {
|
|
|
6200
6201
|
return empati.mergeCfg(settings.empati || {});
|
|
6201
6202
|
}
|
|
6202
6203
|
|
|
6204
|
+
/* EMPATİ HAFIZASI: sohbet akışını kaydet + kullanıcı ilgi alanlarını öğren.
|
|
6205
|
+
- user mesajı → hafızaya yaz + sık kelimelerden ilgi etiketi öğren
|
|
6206
|
+
- assistant cevabı → hafızaya yaz (konuşmanın iki yanı da kalsın)
|
|
6207
|
+
Beast Code/Studio iş mesajları, /komutlar ve bağlam enjeksiyonları kayda girmez. */
|
|
6208
|
+
function empatiRememberFromEvent(ev) {
|
|
6209
|
+
if (!ev || ev.type !== 'message' || !ev.message || !ev.message.role) return;
|
|
6210
|
+
const m = ev.message;
|
|
6211
|
+
if (m.role !== 'user' && m.role !== 'assistant') return;
|
|
6212
|
+
const txt = typeof m.content === 'string' ? m.content : '';
|
|
6213
|
+
if (!txt.trim()) return;
|
|
6214
|
+
if (txt.startsWith((engine && engine.OBSERVE_MARK) || '[BAĞLAM')) return; /* sessiz bağlam — sohbet değil */
|
|
6215
|
+
if (txt.startsWith('🫡 *Beast proaktif:') || txt.startsWith('🫡 **Beast proaktif:')) return; /* proaktif not — döngü '[proaktif]' etiketiyle zaten hafızaya yazdı */
|
|
6216
|
+
if (m.role === 'user' && txt.startsWith('/')) return; /* slash komut gürültüsü */
|
|
6217
|
+
let bc = false;
|
|
6218
|
+
try {
|
|
6219
|
+
const s = engine && engine.cache && engine.cache.get(String(ev.sessionId || ''));
|
|
6220
|
+
bc = !!(s && (s.bcCode || s.bcMode));
|
|
6221
|
+
} catch {}
|
|
6222
|
+
if (bc) return; /* kod işi — ilgi alanını bulanıklaştırır */
|
|
6223
|
+
if (m.role === 'user') {
|
|
6224
|
+
empati.rememberConversation(txt, 'sohbet');
|
|
6225
|
+
empati.learnInterests(txt);
|
|
6226
|
+
} else {
|
|
6227
|
+
empati.rememberConversation(txt, 'beast');
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
|
|
6203
6231
|
function empatiLog(line) {
|
|
6204
6232
|
try { waLog('[EMPATİ] ' + line); } catch {}
|
|
6205
6233
|
}
|
|
@@ -6275,6 +6303,53 @@ function empatiLlmCompose(prompt) {
|
|
|
6275
6303
|
.finally(() => clearTimeout(kill));
|
|
6276
6304
|
}
|
|
6277
6305
|
|
|
6306
|
+
/* PROAKTİF NOT → OTURUM BAĞLAMI: bildirim hangi kanala gittiyse AYNI metin
|
|
6307
|
+
o kanalın sohbet oturumuna asistan mesajı olarak da işlenir (yeni tur
|
|
6308
|
+
başlamaz). Böylece kullanıcı bildirime cevap verdiğinde model o mesajı
|
|
6309
|
+
KENDİSİNİN attığını bilir — aynı oturum, kesintisiz bağlam. */
|
|
6310
|
+
const PROACTIVE_MARK = '🫡 *Beast proaktif:*';
|
|
6311
|
+
|
|
6312
|
+
function empatiInjectToSession(sid, out) {
|
|
6313
|
+
try {
|
|
6314
|
+
if (!sid || engine.isBusy(sid)) return; /* tur ortasında geçmişi karıştırma — bildirim kanala zaten gitti */
|
|
6315
|
+
engine.injectAssistant(sid, out);
|
|
6316
|
+
} catch {}
|
|
6317
|
+
}
|
|
6318
|
+
|
|
6319
|
+
/* Oturum yoksa oluştur (processTgMessage/processDcMessage ile aynı kalıp) */
|
|
6320
|
+
function ensureTgSession(chatId) {
|
|
6321
|
+
let sid = tgChats.get(chatId);
|
|
6322
|
+
if (!sid) {
|
|
6323
|
+
const v = engine.createSession();
|
|
6324
|
+
sid = v.id;
|
|
6325
|
+
tgChats.set(chatId, sid);
|
|
6326
|
+
tgRememberSession(chatId, sid);
|
|
6327
|
+
saveTgChats();
|
|
6328
|
+
}
|
|
6329
|
+
return sid;
|
|
6330
|
+
}
|
|
6331
|
+
function ensureDcSession(channelId) {
|
|
6332
|
+
let sid = dcChats.get(channelId);
|
|
6333
|
+
if (!sid) {
|
|
6334
|
+
const v = engine.createSession();
|
|
6335
|
+
sid = v.id;
|
|
6336
|
+
dcChats.set(channelId, sid);
|
|
6337
|
+
dcRememberSession(channelId, sid);
|
|
6338
|
+
saveDcChats();
|
|
6339
|
+
}
|
|
6340
|
+
return sid;
|
|
6341
|
+
}
|
|
6342
|
+
|
|
6343
|
+
/* Masaüstü yedeği: en güncel (bg/bot-DM'siz, meşgul olmayan) sohbet — yoksa yeni */
|
|
6344
|
+
function empatiDesktopSid() {
|
|
6345
|
+
try {
|
|
6346
|
+
for (const v of engine.listSessions()) {
|
|
6347
|
+
if (!engine.isBusy(v.id)) return String(v.id);
|
|
6348
|
+
}
|
|
6349
|
+
} catch {}
|
|
6350
|
+
return engine.createSession().id;
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6278
6353
|
/* bildirim hedefi: sekmeden seçilen entegrasyon; seçilmemişse bağlı olanlar.
|
|
6279
6354
|
Hiçbir entegrasyon yazılamazsa masaüstü chat UI (toast) kalır. */
|
|
6280
6355
|
function empatiNotify(text, ev) {
|
|
@@ -6283,17 +6358,43 @@ function empatiNotify(text, ev) {
|
|
|
6283
6358
|
const tryWa = () => {
|
|
6284
6359
|
try {
|
|
6285
6360
|
const own = waOwnerNum();
|
|
6286
|
-
if (own && wa && wa.connected)
|
|
6361
|
+
if (own && wa && wa.connected) {
|
|
6362
|
+
const jid = own + '@s.whatsapp.net';
|
|
6363
|
+
const out = PROACTIVE_MARK + '\n' + text;
|
|
6364
|
+
senders.push(() =>
|
|
6365
|
+
Promise.resolve(sendWaSafe(jid, out))
|
|
6366
|
+
.then(() => empatiInjectToSession(ensureWaSession(jid), out))
|
|
6367
|
+
.catch(() => {})
|
|
6368
|
+
);
|
|
6369
|
+
}
|
|
6287
6370
|
} catch {}
|
|
6288
6371
|
};
|
|
6289
6372
|
const tryTg = () => {
|
|
6290
6373
|
try {
|
|
6291
|
-
if (tg && tg.connected)
|
|
6374
|
+
if (tg && tg.connected) {
|
|
6375
|
+
const out = PROACTIVE_MARK + '\n' + text;
|
|
6376
|
+
for (const id of tgOwnerIds()) {
|
|
6377
|
+
senders.push(() =>
|
|
6378
|
+
Promise.resolve(sendTgSafe(id, out))
|
|
6379
|
+
.then(() => empatiInjectToSession(ensureTgSession(String(id)), out))
|
|
6380
|
+
.catch(() => {})
|
|
6381
|
+
);
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6292
6384
|
} catch {}
|
|
6293
6385
|
};
|
|
6294
6386
|
const tryDc = () => {
|
|
6295
6387
|
try {
|
|
6296
|
-
if (dc && dc.connected)
|
|
6388
|
+
if (dc && dc.connected) {
|
|
6389
|
+
const out = '🫡 **Beast proaktif:**\n' + text;
|
|
6390
|
+
for (const id of dcOwnerIds()) {
|
|
6391
|
+
senders.push(() =>
|
|
6392
|
+
Promise.resolve(sendDcSafe(id, out))
|
|
6393
|
+
.then(() => empatiInjectToSession(ensureDcSession(String(id)), out))
|
|
6394
|
+
.catch(() => {})
|
|
6395
|
+
);
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6297
6398
|
} catch {}
|
|
6298
6399
|
};
|
|
6299
6400
|
if (cfg.notifyTarget === 'whatsapp') tryWa();
|
|
@@ -6304,9 +6405,11 @@ function empatiNotify(text, ev) {
|
|
|
6304
6405
|
for (const fn of senders) {
|
|
6305
6406
|
try { fn(); sent++; } catch {}
|
|
6306
6407
|
}
|
|
6307
|
-
/* hiçbir entegrasyona yazılamadıysa yalnız masaüstü chat UI'a düş
|
|
6408
|
+
/* hiçbir entegrasyona yazılamadıysa yalnız masaüstü chat UI'a düş —
|
|
6409
|
+
metin güncel sohbete de asistan mesajı olarak işlenir */
|
|
6308
6410
|
try {
|
|
6309
6411
|
if (!sent && win && !win.isDestroyed()) {
|
|
6412
|
+
empatiInjectToSession(empatiDesktopSid(), PROACTIVE_MARK + '\n' + text);
|
|
6310
6413
|
win.webContents.send('agent:event', { type: 'proactive', id: ev.id, level: ev.level, title: ev.title, text });
|
|
6311
6414
|
}
|
|
6312
6415
|
} catch {}
|
|
@@ -6318,13 +6421,18 @@ async function empatiCycle(manual) {
|
|
|
6318
6421
|
if (!cfg.enabled && !manual) return { ok: false, error: 'kapalı' };
|
|
6319
6422
|
empatiRuntime.running = true;
|
|
6320
6423
|
try {
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6424
|
+
/* zaman aşımı emniyeti: sinyal/LLM takılırsa `running` bayrağı sonsuz kilitlenmesin
|
|
6425
|
+
(yoksa "Şimdi Tara" sürekli 'döngü zaten çalışıyor' der) */
|
|
6426
|
+
const r = await Promise.race([
|
|
6427
|
+
empati.runCycle({
|
|
6428
|
+
cfg,
|
|
6429
|
+
signals: { self: empatiSignalSelf, news: empatiSignalNews },
|
|
6430
|
+
llmFilter: empatiLlmFilter,
|
|
6431
|
+
now: new Date(),
|
|
6432
|
+
log: empatiLog,
|
|
6433
|
+
}),
|
|
6434
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error('döngü zaman aşımı (2 dk)')), 120000).unref()),
|
|
6435
|
+
]);
|
|
6328
6436
|
let notified = 0;
|
|
6329
6437
|
for (const a of r.actions) {
|
|
6330
6438
|
let text = '';
|
|
@@ -6332,6 +6440,7 @@ async function empatiCycle(manual) {
|
|
|
6332
6440
|
if (!text) text = empati.composeFallback(a.event);
|
|
6333
6441
|
empatiNotify(text, a.event);
|
|
6334
6442
|
empati.markNotified(a.event.id, a.level, text, cfg.cooldownMin);
|
|
6443
|
+
try { empati.rememberConversation('[proaktif] ' + text, 'empati'); } catch {}
|
|
6335
6444
|
notified++;
|
|
6336
6445
|
}
|
|
6337
6446
|
if (notified && win && !win.isDestroyed()) {
|
|
@@ -6374,6 +6483,8 @@ ipcMain.handle('empati:scan', async () => {
|
|
|
6374
6483
|
try { return await empatiCycle(true); } catch (e) { return { ok: false, error: String((e && e.message) || e).slice(0, 200) }; }
|
|
6375
6484
|
});
|
|
6376
6485
|
ipcMain.handle('empati:events', () => empati.listEvents(80));
|
|
6486
|
+
ipcMain.handle('empati:memory', () => empati.memSnapshot(40));
|
|
6487
|
+
ipcMain.handle('empati:memclear', () => empati.memClear());
|
|
6377
6488
|
|
|
6378
6489
|
ipcMain.handle('cron:list', () => cron.list());
|
|
6379
6490
|
/* #23 Fallout: provider → kayıtlı API key haritası.
|
package/src/preload.js
CHANGED
|
@@ -211,4 +211,6 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
211
211
|
empatiSet: (patch) => ipcRenderer.invoke('empati:set', patch),
|
|
212
212
|
empatiScan: () => ipcRenderer.invoke('empati:scan'),
|
|
213
213
|
empatiEvents: () => ipcRenderer.invoke('empati:events'),
|
|
214
|
+
empatiMemory: () => ipcRenderer.invoke('empati:memory'),
|
|
215
|
+
empatiMemClear: () => ipcRenderer.invoke('empati:memclear'),
|
|
214
216
|
});
|
package/src/renderer/i18n.js
CHANGED
|
@@ -148,6 +148,16 @@
|
|
|
148
148
|
em_st_ignored: 'yok sayıldı',
|
|
149
149
|
em_lv_high: 'YÜKSEK',
|
|
150
150
|
em_lv_medium: 'ORTA',
|
|
151
|
+
em_interests_auto: 'Bunlara ek olarak sohbetlerinden öğrenilen ilgi alanları da otomatik devreye girer.',
|
|
152
|
+
em_scan_busy: 'Tarama hâlâ çalışıyor — birkaç saniye sonra tekrar dene.',
|
|
153
|
+
em_mem_h2: 'Empati Hafızası',
|
|
154
|
+
em_mem_sub: 'Konuşmalarınız burada birikir; sohbetlerinden ilgi alanların öğrenilir. Tarama ve bildirim mesajları bu hafızayla sana göre kişiselleşir.',
|
|
155
|
+
em_mem_learned: 'Sohbetlerden öğrenilen ilgi alanları',
|
|
156
|
+
em_mem_no_learned: 'Henüz öğrenilen ilgi yok — sohbet ettikçe burada birikir.',
|
|
157
|
+
em_mem_recent: 'Son konuşma kayıtları',
|
|
158
|
+
em_mem_no_recent: 'Henüz kayıt yok.',
|
|
159
|
+
em_mem_clear: 'Hafızayı Temizle',
|
|
160
|
+
em_mem_cleared: 'Empati hafızası temizlendi',
|
|
151
161
|
tab_cron: 'Cron',
|
|
152
162
|
tab_usage: 'Maliyet · Limit',
|
|
153
163
|
tab_logs: 'Log',
|
|
@@ -187,7 +197,6 @@
|
|
|
187
197
|
btn_close: 'Kapat',
|
|
188
198
|
tip_win_min: 'Simge durumunda küçült',
|
|
189
199
|
tip_win_max: 'Pencereyi büyüt/küçült',
|
|
190
|
-
em_pass_masked: 'Mevcut şifre korunuyor — değiştirmek için yeniden gir',
|
|
191
200
|
p_h2: 'Provider',
|
|
192
201
|
p_sub: 'Beast config.yaml\u2019dan gelen modeller — aktif olanı seç',
|
|
193
202
|
p_del_title: 'Modeli listeden sil',
|
|
@@ -257,11 +266,12 @@
|
|
|
257
266
|
tts_chat_auto: 'Chat\'te ajanın yazdıklarını otomatik seslendir',
|
|
258
267
|
tts_on: 'TTS açık — cevaplar sesli de gider',
|
|
259
268
|
tts_off: 'TTS kapalı',
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
269
|
+
mail_h2: 'E-posta (Gmail)',
|
|
270
|
+
mail_sub: 'IMAP/SMTP — Gmail hesabında "Uygulama Şifresi" oluşturup buraya gir. Agent mailleri okur, özetler ve senin adına gönderir.',
|
|
271
|
+
mail_save: 'E-posta Kaydet',
|
|
272
|
+
mail_note: 'Kaydettikten sonra agent\u2019a "gelen mailleri özetle", "X\u2019e mail at" gibi komutlar verebilirsin.',
|
|
273
|
+
mail_saved: 'E-posta kaydedildi — agent artık maillere erişebilir',
|
|
274
|
+
mail_pass_masked: 'Mevcut şifre korunuyor — değiştirmek için yeniden gir',
|
|
265
275
|
it_h2: 'Entegrasyonlar',
|
|
266
276
|
it_sub: 'Mesajlaşma köprüleri — gelen mesajlar Beast\u2019e gider, cevap geri döner',
|
|
267
277
|
it_wa_sub: 'Özel mesajlar · gruplar (@mention ile) · slash komutları (/help)',
|
|
@@ -759,6 +769,16 @@
|
|
|
759
769
|
em_st_ignored: 'ignored',
|
|
760
770
|
em_lv_high: 'HIGH',
|
|
761
771
|
em_lv_medium: 'MEDIUM',
|
|
772
|
+
em_interests_auto: 'On top of these, interest areas learned from your chats kick in automatically.',
|
|
773
|
+
em_scan_busy: 'A scan is still running — try again in a few seconds.',
|
|
774
|
+
em_mem_h2: 'Empathy Memory',
|
|
775
|
+
em_mem_sub: 'Your conversations accumulate here; interest areas are learned from your chats. Scans and notification texts are personalized with this memory.',
|
|
776
|
+
em_mem_learned: 'Interest areas learned from chats',
|
|
777
|
+
em_mem_no_learned: 'No learned interests yet — they accumulate as you chat.',
|
|
778
|
+
em_mem_recent: 'Recent conversation records',
|
|
779
|
+
em_mem_no_recent: 'No records yet.',
|
|
780
|
+
em_mem_clear: 'Clear Memory',
|
|
781
|
+
em_mem_cleared: 'Empathy memory cleared',
|
|
762
782
|
tab_cron: 'Cron',
|
|
763
783
|
tab_usage: 'Cost · Limit',
|
|
764
784
|
tab_logs: 'Logs',
|
|
@@ -798,7 +818,6 @@
|
|
|
798
818
|
btn_close: 'Close',
|
|
799
819
|
tip_win_min: 'Minimize',
|
|
800
820
|
tip_win_max: 'Maximize/Restore',
|
|
801
|
-
em_pass_masked: 'Current password kept — type a new one to change it',
|
|
802
821
|
p_h2: 'Provider',
|
|
803
822
|
p_sub: 'Models from Beast config.yaml — pick the active one',
|
|
804
823
|
p_del_title: 'Remove model from list',
|
|
@@ -868,11 +887,12 @@
|
|
|
868
887
|
tts_chat_auto: 'Auto-speak agent replies in chat',
|
|
869
888
|
tts_on: 'TTS on — replies also go as voice',
|
|
870
889
|
tts_off: 'TTS off',
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
890
|
+
mail_h2: 'E-mail (Gmail)',
|
|
891
|
+
mail_sub: 'IMAP/SMTP — create an "App Password" in your Gmail account and enter it here. The agent reads, summarizes and sends mail on your behalf.',
|
|
892
|
+
mail_save: 'Save E-mail',
|
|
893
|
+
mail_note: 'After saving you can give the agent commands like "summarize incoming mail", "send mail to X".',
|
|
894
|
+
mail_saved: 'E-mail saved — the agent can now access mail',
|
|
895
|
+
mail_pass_masked: 'Current password kept — type a new one to change it',
|
|
876
896
|
it_h2: 'Integrations',
|
|
877
897
|
it_sub: 'Messaging bridges — incoming messages go to Beast, replies come back',
|
|
878
898
|
it_wa_sub: 'Private messages · groups (@mention) · slash commands (/help)',
|
package/src/renderer/renderer.js
CHANGED
|
@@ -2333,47 +2333,49 @@ async function renderEmailPane() {
|
|
|
2333
2333
|
const PASS_MASK = '••••••••';
|
|
2334
2334
|
let savedPass = '';
|
|
2335
2335
|
let passChanged = false;
|
|
2336
|
+
/* ID'ler mail* önekiyle: empati sekmesindeki em* ID'leriyle ÇAKIŞMASIN —
|
|
2337
|
+
aynı ID iki panelde olursa $() ilkini bulur, empati butonları ölü kalırdı */
|
|
2336
2338
|
pane.innerHTML =
|
|
2337
|
-
'<h2>' + _t('
|
|
2339
|
+
'<h2>' + _t('mail_h2') + '</h2><div class="sub">' + _t('mail_sub') + '</div>' +
|
|
2338
2340
|
`<div class="form-grid" style="grid-template-columns:1fr 1fr;align-items:center;margin-top:10px">
|
|
2339
|
-
<input id="
|
|
2340
|
-
<input id="
|
|
2341
|
-
<input id="
|
|
2342
|
-
<input id="
|
|
2343
|
-
<input id="
|
|
2344
|
-
<button id="
|
|
2341
|
+
<input id="mailHost" class="inp" placeholder="imap.gmail.com" autocomplete="off" />
|
|
2342
|
+
<input id="mailUser" class="inp" placeholder="adres@gmail.com" autocomplete="off" />
|
|
2343
|
+
<input id="mailPass" class="inp" type="password" placeholder="Uygulama Şifresi" autocomplete="off" />
|
|
2344
|
+
<input id="mailSmtpHost" class="inp" placeholder="smtp.gmail.com" autocomplete="off" />
|
|
2345
|
+
<input id="mailSmtpPort" class="inp" placeholder="465" autocomplete="off" />
|
|
2346
|
+
<button id="mailSave" class="btn ghost">${_t('mail_save')}</button>
|
|
2345
2347
|
</div>` +
|
|
2346
|
-
'<div class="sub" style="margin-top:8px">' + _t('
|
|
2348
|
+
'<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>';
|
|
2347
2349
|
|
|
2348
2350
|
try {
|
|
2349
2351
|
const em = await beast.getEmail();
|
|
2350
|
-
$('#
|
|
2351
|
-
$('#
|
|
2352
|
-
$('#
|
|
2353
|
-
$('#
|
|
2352
|
+
$('#mailHost').value = em.host || 'imap.gmail.com';
|
|
2353
|
+
$('#mailUser').value = em.user || '';
|
|
2354
|
+
$('#mailSmtpHost').value = em.smtpHost || 'smtp.gmail.com';
|
|
2355
|
+
$('#mailSmtpPort').value = em.smtpPort || 465;
|
|
2354
2356
|
savedPass = em.pass || '';
|
|
2355
2357
|
} catch {}
|
|
2356
|
-
const
|
|
2358
|
+
const mailPass = $('#mailPass');
|
|
2357
2359
|
if (savedPass) {
|
|
2358
2360
|
/* kayıtlı şifre maskelenir; sadece kullanıcı değiştirirse yenisi gönderilir */
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
+
mailPass.value = PASS_MASK;
|
|
2362
|
+
mailPass.placeholder = window.I18N ? window.I18N.t('mail_pass_masked') : 'Mevcut şifre korunuyor — değiştirmek için yeniden gir';
|
|
2361
2363
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
$('#
|
|
2365
|
-
const entered =
|
|
2364
|
+
mailPass.addEventListener('focus', () => { if (mailPass.value === PASS_MASK) mailPass.value = ''; });
|
|
2365
|
+
mailPass.addEventListener('input', () => { passChanged = true; });
|
|
2366
|
+
$('#mailSave').addEventListener('click', async () => {
|
|
2367
|
+
const entered = mailPass.value;
|
|
2366
2368
|
const pass = passChanged ? entered : savedPass;
|
|
2367
2369
|
await beast.setEmail({
|
|
2368
|
-
host: $('#
|
|
2370
|
+
host: $('#mailHost').value.trim() || 'imap.gmail.com',
|
|
2369
2371
|
port: 993,
|
|
2370
|
-
user: $('#
|
|
2372
|
+
user: $('#mailUser').value.trim(),
|
|
2371
2373
|
pass,
|
|
2372
|
-
smtpHost: $('#
|
|
2373
|
-
smtpPort: Number($('#
|
|
2374
|
+
smtpHost: $('#mailSmtpHost').value.trim() || 'smtp.gmail.com',
|
|
2375
|
+
smtpPort: Number($('#mailSmtpPort').value) || 465,
|
|
2374
2376
|
});
|
|
2375
|
-
if (pass && pass !== PASS_MASK) { savedPass = pass;
|
|
2376
|
-
toast(_t('
|
|
2377
|
+
if (pass && pass !== PASS_MASK) { savedPass = pass; mailPass.value = PASS_MASK; passChanged = false; }
|
|
2378
|
+
toast(_t('mail_saved'));
|
|
2377
2379
|
});
|
|
2378
2380
|
}
|
|
2379
2381
|
|
|
@@ -3028,9 +3030,14 @@ async function renderEventsPane() {
|
|
|
3028
3030
|
async function renderEmpatiPane() {
|
|
3029
3031
|
const pane = $('#tab-empati');
|
|
3030
3032
|
if (!pane) return;
|
|
3033
|
+
/* pane-scope: e-posta sekmesinde eskiden aynı ID'ler vardı; $() global arar
|
|
3034
|
+
ve YANLIŞ butona bağlardı — tüm aramalar bu panelin içinde kalır */
|
|
3035
|
+
const q = (sel) => pane.querySelector(sel);
|
|
3031
3036
|
const cfg = await beast.empatiGet().catch(() => null);
|
|
3032
3037
|
let events = [];
|
|
3033
3038
|
try { events = await beast.empatiEvents(); } catch {}
|
|
3039
|
+
let mem = null;
|
|
3040
|
+
try { mem = await beast.empatiMemory(); } catch {}
|
|
3034
3041
|
let models = [];
|
|
3035
3042
|
try { models = (await beast.getState()).models || []; } catch {}
|
|
3036
3043
|
if (!cfg) {
|
|
@@ -3101,6 +3108,7 @@ async function renderEmpatiPane() {
|
|
|
3101
3108
|
<div style="margin-top:8px">
|
|
3102
3109
|
<label class="sub">${_t('em_interests')}</label>
|
|
3103
3110
|
<textarea id="emInterests" class="inp" rows="2" placeholder="${_t('em_interests_ph')}">${escapeHtml(cfg.interests || '')}</textarea>
|
|
3111
|
+
<div class="sub">${_t('em_interests_auto')}</div>
|
|
3104
3112
|
</div>
|
|
3105
3113
|
<label class="lock-row" style="margin-top:8px"><input type="checkbox" id="emNews" ${cfg.newsTopics ? 'checked' : ''}/><span>${_t('em_news')}</span></label>
|
|
3106
3114
|
<input id="emTopics" class="inp" style="${cfg.newsTopics ? '' : 'opacity:.5'}" placeholder="${_t('em_news_topics_ph')}" value="${escapeHtml(cfg.newsTopics || '')}" spellcheck="false"/>
|
|
@@ -3111,46 +3119,85 @@ async function renderEmpatiPane() {
|
|
|
3111
3119
|
</div>
|
|
3112
3120
|
<div class="sub" style="margin-top:6px">${_t('em_note')}</div>
|
|
3113
3121
|
</div>
|
|
3122
|
+
<h3 style="margin-top:16px;color:var(--muted)">${_t('em_mem_h2')}</h3>
|
|
3123
|
+
<div class="sub">${_t('em_mem_sub')}</div>
|
|
3124
|
+
<div style="margin-top:6px"><label class="sub">${_t('em_mem_learned')}</label>${(() => {
|
|
3125
|
+
const learned = (mem && Array.isArray(mem.learned)) ? mem.learned : [];
|
|
3126
|
+
if (!learned.length) return '<div class="sub" style="margin-top:2px">' + _t('em_mem_no_learned') + '</div>';
|
|
3127
|
+
return '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px">' +
|
|
3128
|
+
learned.map((x) =>
|
|
3129
|
+
'<span style="font-size:11px;padding:2px 8px;border:1px solid var(--border);border-radius:10px">' +
|
|
3130
|
+
escapeHtml(x.w) + ' <span style="color:var(--muted)">×' + (x.c || 0) + '</span></span>'
|
|
3131
|
+
).join('') + '</div>';
|
|
3132
|
+
})()}</div>
|
|
3133
|
+
<div style="margin-top:8px"><label class="sub">${_t('em_mem_recent')}</label>${(() => {
|
|
3134
|
+
const chats = (mem && Array.isArray(mem.chats)) ? mem.chats : [];
|
|
3135
|
+
if (!chats.length) return '<div class="sub" style="margin-top:2px">' + _t('em_mem_no_recent') + '</div>';
|
|
3136
|
+
return '<div style="margin-top:4px">' + chats.map((c) =>
|
|
3137
|
+
'<div class="usage-row" style="align-items:flex-start">' +
|
|
3138
|
+
'<div style="min-width:0;flex:1">' +
|
|
3139
|
+
'<div style="font-size:12px">' + escapeHtml(c.t || '') + '</div>' +
|
|
3140
|
+
'<div class="ur-meta">' + escapeHtml(c.ch || '') + ' · ' + when(c.ts) + '</div>' +
|
|
3141
|
+
'</div></div>').join('') + '</div>';
|
|
3142
|
+
})()}
|
|
3143
|
+
<button id="emMemClear" class="btn ghost" style="margin-top:8px;font-size:12px">${_t('em_mem_clear')}</button>
|
|
3144
|
+
</div>
|
|
3114
3145
|
<h3 style="margin-top:16px;color:var(--muted)">${_t('em_events')}</h3>`;
|
|
3115
3146
|
const wrap = document.createElement('div');
|
|
3116
3147
|
if (!rows) wrap.innerHTML = '<p class="sub">' + _t('em_no_events') + '</p>';
|
|
3117
3148
|
else wrap.innerHTML = rows;
|
|
3118
3149
|
pane.appendChild(wrap);
|
|
3119
3150
|
|
|
3120
|
-
const emOn =
|
|
3121
|
-
const emDetail =
|
|
3122
|
-
const emNews =
|
|
3123
|
-
const emTopics =
|
|
3124
|
-
emOn.addEventListener('change', () => { emDetail.style.opacity = emOn.checked ? '' : '.5'; });
|
|
3125
|
-
emNews.addEventListener('change', () => { emTopics.style.opacity = emNews.checked ? '' : '.5'; });
|
|
3126
|
-
|
|
3151
|
+
const emOn = q('#emOn');
|
|
3152
|
+
const emDetail = q('#emDetail');
|
|
3153
|
+
const emNews = q('#emNews');
|
|
3154
|
+
const emTopics = q('#emTopics');
|
|
3155
|
+
if (emOn && emDetail) emOn.addEventListener('change', () => { emDetail.style.opacity = emOn.checked ? '' : '.5'; });
|
|
3156
|
+
if (emNews && emTopics) emNews.addEventListener('change', () => { emTopics.style.opacity = emNews.checked ? '' : '.5'; });
|
|
3157
|
+
const emSaveBtn = q('#emSave');
|
|
3158
|
+
if (emSaveBtn) emSaveBtn.addEventListener('click', async () => {
|
|
3127
3159
|
const patch = {
|
|
3128
3160
|
enabled: emOn.checked,
|
|
3129
|
-
intervalMin: Number(
|
|
3130
|
-
minNotifyPriority: Number(
|
|
3131
|
-
cooldownMin: Number(
|
|
3132
|
-
notifyTarget:
|
|
3133
|
-
filterModel:
|
|
3134
|
-
interests:
|
|
3161
|
+
intervalMin: Number(q('#emInterval').value) || cfg.intervalMin,
|
|
3162
|
+
minNotifyPriority: Number(q('#emMin').value),
|
|
3163
|
+
cooldownMin: Number(q('#emCd').value),
|
|
3164
|
+
notifyTarget: q('#emTarget') ? q('#emTarget').value : '',
|
|
3165
|
+
filterModel: q('#emModel') ? q('#emModel').value : '',
|
|
3166
|
+
interests: q('#emInterests').value.trim(),
|
|
3135
3167
|
newsTopics: emNews.checked ? emTopics.value.trim() : '',
|
|
3136
3168
|
};
|
|
3137
3169
|
const r = await beast.empatiSet(patch).catch(() => null);
|
|
3138
3170
|
toast(r ? _t('em_saved') : 'Hata');
|
|
3139
3171
|
if (r) renderEmpatiPane();
|
|
3140
3172
|
});
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3173
|
+
const emScanBtn = q('#emScan');
|
|
3174
|
+
if (emScanBtn) emScanBtn.addEventListener('click', async () => {
|
|
3175
|
+
emScanBtn.disabled = true; /* çift tık → 'döngü zaten çalışıyor' kilitlenmesin */
|
|
3176
|
+
const msg = q('#emMsg');
|
|
3177
|
+
if (msg) msg.textContent = '⏳';
|
|
3144
3178
|
const r = await beast.empatiScan().catch(() => null);
|
|
3145
3179
|
if (r && r.ok) {
|
|
3146
|
-
|
|
3180
|
+
const txt = _t('em_scan_ok')
|
|
3147
3181
|
.replace('{raw}', String(r.raw || 0))
|
|
3148
3182
|
.replace('{queued}', String(r.queued || 0))
|
|
3149
3183
|
.replace('{stored}', String(r.stored || 0));
|
|
3150
|
-
|
|
3184
|
+
toast(txt);
|
|
3185
|
+
await renderEmpatiPane(); /* olay + hafıza listeleri tazelensin */
|
|
3186
|
+
const m2 = q('#emMsg');
|
|
3187
|
+
if (m2) m2.textContent = txt; /* yeniden çizim mesajı silmesin */
|
|
3151
3188
|
} else {
|
|
3152
|
-
|
|
3189
|
+
const err = (r && r.error) || 'hata';
|
|
3190
|
+
if (msg) msg.textContent = /zaten çalışıyor/.test(err) ? _t('em_scan_busy') : err;
|
|
3191
|
+
toast('Tarama hatası: ' + err);
|
|
3153
3192
|
}
|
|
3193
|
+
const b2 = q('#emScan');
|
|
3194
|
+
if (b2) b2.disabled = false;
|
|
3195
|
+
});
|
|
3196
|
+
const emMemClearBtn = q('#emMemClear');
|
|
3197
|
+
if (emMemClearBtn) emMemClearBtn.addEventListener('click', async () => {
|
|
3198
|
+
const r = await beast.empatiMemClear().catch(() => null);
|
|
3199
|
+
toast(r && r.ok ? _t('em_mem_cleared') : 'Hata');
|
|
3200
|
+
if (r && r.ok) renderEmpatiPane();
|
|
3154
3201
|
});
|
|
3155
3202
|
}
|
|
3156
3203
|
|