beast-agent 0.21.0 → 0.23.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/README.md +1 -1
- package/bin/beast-agent.js +16 -6
- package/package.json +3 -2
- package/src/agent/bots.js +17 -3
- package/src/agent/engine.js +35 -17
- package/src/agent/telegram.js +155 -0
- package/src/main.js +554 -17
- package/src/preload.js +8 -0
- package/src/renderer/i18n.js +34 -2
- package/src/renderer/index.html +1 -0
- package/src/renderer/renderer.js +370 -14
- package/src/renderer/style.css +28 -0
package/src/preload.js
CHANGED
|
@@ -115,6 +115,14 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
115
115
|
waSetTts: (cfg) => ipcRenderer.invoke('wa:tts:set', cfg),
|
|
116
116
|
waGetGroups: () => ipcRenderer.invoke('wa:groups:get'),
|
|
117
117
|
waSetGroups: (cfg) => ipcRenderer.invoke('wa:groups:set', cfg),
|
|
118
|
+
tgGetStatus: () => ipcRenderer.invoke('tg:status:get'),
|
|
119
|
+
tgSetToken: (token) => ipcRenderer.invoke('tg:set', token),
|
|
120
|
+
tgStart: () => ipcRenderer.invoke('tg:start'),
|
|
121
|
+
tgStop: () => ipcRenderer.invoke('tg:stop'),
|
|
122
|
+
tgGetAllow: () => ipcRenderer.invoke('tg:allow:get'),
|
|
123
|
+
tgSetAllow: (list) => ipcRenderer.invoke('tg:allow:set', list),
|
|
124
|
+
tgListSessions: () => ipcRenderer.invoke('tg:sessions'),
|
|
125
|
+
onTgEvent: (cb) => ipcRenderer.on('tg:event', (_e, ev) => cb(ev)),
|
|
118
126
|
getUsage: () => ipcRenderer.invoke('usage:get'),
|
|
119
127
|
resetUsage: () => ipcRenderer.invoke('usage:reset'),
|
|
120
128
|
createBackup: () => ipcRenderer.invoke('backup:create'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
up_downloading: 'İndiriliyor…',
|
|
74
74
|
up_downloaded: 'İndirildi — kurulum için hazır',
|
|
75
75
|
up_npm_mode: 'npm kurulumu — güncellemeler otomatik kontrol edilir',
|
|
76
|
-
up_npm_note: 'Yeni sürüm algılanınca buton aktifleşir: uygulama kapanır, güncellenir ve kendiliğinden yeniden açılır. Terminal komutu: beast
|
|
76
|
+
up_npm_note: 'Yeni sürüm algılanınca buton aktifleşir: uygulama kapanır, güncellenir ve kendiliğinden yeniden açılır. Terminal komutu: beast update',
|
|
77
|
+
up_npm_started: 'Güncelleme başladı — uygulama kapanacak, en son sürüm kurulup kendiliğinden açılacak…',
|
|
77
78
|
up_dev_note: 'Geliştirme modu — güncelleme buradan yapılmaz; dağıtım npm ve GitHub Releases üzerinden. Kaynak kod için: git pull',
|
|
78
79
|
up_auto_check: 'Otomatik sürüm kontrolü (açılış + 6 saatte bir)',
|
|
79
80
|
up_auto_dl: 'Yeni sürümü otomatik indir (kapanışta kurulur)',
|
|
@@ -199,6 +200,13 @@
|
|
|
199
200
|
it_num_ph: 'Numara 905xxxxxxxxx',
|
|
200
201
|
it_add: 'Ekle',
|
|
201
202
|
it_allow_note: 'İsim + numara zorunlu. Her kişinin yanındaki serbest/web/okuma/kısıtlı seçimiyle kişiye özel yetki ver.',
|
|
203
|
+
it_tg_sub: 'Bot tokenı ile çalışır — izin listendeki kişilere Telegram\u2019dan cevap verir',
|
|
204
|
+
it_tg_token_ph: 'Bot tokenı (örn. 123456789:AAH…)',
|
|
205
|
+
it_tg_save: 'Kaydet & Bağlan',
|
|
206
|
+
it_tg_connecting: 'Telegram\u2019a bağlanıyor…',
|
|
207
|
+
it_tg_token_bad: 'Token doğrulanamadı — @BotFather\u2019dan aldığın tokenı kontrol et',
|
|
208
|
+
it_tg_id_ph: 'Kullanıcı ID veya @kullanıcı_adı',
|
|
209
|
+
it_tg_note: 'İzin listesi boşsa kimse cevap almaz. Bot oluşturma: @BotFather → /newbot. Kendi ID\u2019ni öğrenmek için @userinfobot\u2019a mesaj at.',
|
|
202
210
|
ev_h2: 'Olay Merkezi',
|
|
203
211
|
ev_sub: 'Cron\u2019suz canlı olaylar — kaynakları aç, abonelikleri agent kendisi kurar (event_subscribe). Tetiklenen olay ilgili sohbete düşer, cevap WhatsApp\u2019a gider.',
|
|
204
212
|
ev_on: 'Olay merkezi açık',
|
|
@@ -374,6 +382,7 @@
|
|
|
374
382
|
bot_tab_plugin: 'Plugin',
|
|
375
383
|
bot_tab_watcher: 'Watcher',
|
|
376
384
|
bot_tab_stats: 'İstatistik',
|
|
385
|
+
bot_tab_notes: 'Notlar',
|
|
377
386
|
bot_name: 'Bot adı',
|
|
378
387
|
bot_icon: 'İkon',
|
|
379
388
|
bot_prompt: 'System prompt (kişilik/görev)',
|
|
@@ -396,6 +405,7 @@
|
|
|
396
405
|
bot_see: 'Görebilir (diğer botlar)',
|
|
397
406
|
bot_no_other: 'Başka bot yok.',
|
|
398
407
|
bot_perm: 'Yetki seviyesi',
|
|
408
|
+
bot_perm_note: 'all tek başına tüm araçları açar (özel). Web / Okuma / Sohbet birden fazla seçilebilir — seçilenlerin araçları birleşir.',
|
|
399
409
|
bot_skills: 'Skill erişimi',
|
|
400
410
|
bot_browser: 'Tarayıcı ayarları',
|
|
401
411
|
bot_ext_browser: 'Dış tarayıcı yetkisi (kullanıcı isterse)',
|
|
@@ -449,6 +459,12 @@
|
|
|
449
459
|
p_zen_ok: '${n} free model yüklendi',
|
|
450
460
|
p_zen_skipped: '${n} model yanıt vermedi, listeye alınmadı',
|
|
451
461
|
p_zen_fail: 'Model kurulumu başarısız',
|
|
462
|
+
net_offline: 'İnternet bağlantısı yok — mesajların kuyruğa alındı',
|
|
463
|
+
net_online: 'Bağlantı geri geldi',
|
|
464
|
+
net_pill_offline: 'İnternet yok — ${n} mesaj kuyrukta',
|
|
465
|
+
net_pill_online: 'İnternet yok',
|
|
466
|
+
q_pending: 'kuyrukta — internet bekleniyor',
|
|
467
|
+
q_flushed: '${n} kuyruktaki mesaj gönderiliyor',
|
|
452
468
|
bot_num_ro_hint: 'Numara ekleme/kişi adı Ayarlar → Entegrasyonlar (WhatsApp izin listesi) üzerinden yapılır; burada yalnızca bağlı numaralar görünür.',
|
|
453
469
|
tipStore: 'Skills Store',
|
|
454
470
|
tipIde: 'IDE Modu — dosyalar + chat + preview',
|
|
@@ -571,7 +587,8 @@
|
|
|
571
587
|
up_downloading: 'Downloading…',
|
|
572
588
|
up_downloaded: 'Downloaded — ready to install',
|
|
573
589
|
up_npm_mode: 'npm install — updates are checked automatically',
|
|
574
|
-
up_npm_note: 'The button activates when a new version is detected: the app closes, updates and relaunches by itself. Terminal command: beast
|
|
590
|
+
up_npm_note: 'The button activates when a new version is detected: the app closes, updates and relaunches by itself. Terminal command: beast update',
|
|
591
|
+
up_npm_started: 'Update started — the app will close, install the latest version and relaunch…',
|
|
575
592
|
up_dev_note: 'Development mode — updates are distributed via npm and GitHub Releases. For source code: git pull',
|
|
576
593
|
up_auto_check: 'Automatic version check (on startup + every 6 hours)',
|
|
577
594
|
up_auto_dl: 'Auto-download new versions (installed on quit)',
|
|
@@ -697,6 +714,13 @@
|
|
|
697
714
|
it_num_ph: 'Number 905xxxxxxxxx',
|
|
698
715
|
it_add: 'Add',
|
|
699
716
|
it_allow_note: 'Name + number required. Grant per-person permission (free/web/read/restricted) via the selector next to each.',
|
|
717
|
+
it_tg_sub: 'Works with a bot token — replies on Telegram to people in your allow list',
|
|
718
|
+
it_tg_token_ph: 'Bot token (e.g. 123456789:AAH…)',
|
|
719
|
+
it_tg_save: 'Save & Connect',
|
|
720
|
+
it_tg_connecting: 'Connecting to Telegram…',
|
|
721
|
+
it_tg_token_bad: 'Token could not be verified — check the token from @BotFather',
|
|
722
|
+
it_tg_id_ph: 'User ID or @username',
|
|
723
|
+
it_tg_note: 'Empty allow list = nobody gets a reply. Create a bot: @BotFather → /newbot. To learn your ID, message @userinfobot.',
|
|
700
724
|
ev_h2: 'Event Center',
|
|
701
725
|
ev_sub: 'Live events without cron — open sources, the agent sets up subscriptions itself (event_subscribe). The triggered event drops into the relevant chat, the reply goes to WhatsApp.',
|
|
702
726
|
ev_on: 'Event center on',
|
|
@@ -930,6 +954,7 @@
|
|
|
930
954
|
bot_tab_plugin: 'Plugin',
|
|
931
955
|
bot_tab_watcher: 'Watcher',
|
|
932
956
|
bot_tab_stats: 'Stats',
|
|
957
|
+
bot_tab_notes: 'Notes',
|
|
933
958
|
bot_name: 'Bot name',
|
|
934
959
|
bot_icon: 'Icon',
|
|
935
960
|
bot_prompt: 'System prompt (personality/task)',
|
|
@@ -952,6 +977,7 @@
|
|
|
952
977
|
bot_see: 'Can see (other bots)',
|
|
953
978
|
bot_no_other: 'No other bots.',
|
|
954
979
|
bot_perm: 'Permission level',
|
|
980
|
+
bot_perm_note: 'all alone grants every tool (exclusive). Web / Read / Chat can be multi-selected — their tool sets are combined.',
|
|
955
981
|
bot_skills: 'Skill access',
|
|
956
982
|
bot_browser: 'Browser settings',
|
|
957
983
|
bot_ext_browser: 'External browser permission (when user asks)',
|
|
@@ -1005,6 +1031,12 @@
|
|
|
1005
1031
|
p_zen_ok: '${n} free models installed',
|
|
1006
1032
|
p_zen_skipped: '${n} models did not respond, excluded',
|
|
1007
1033
|
p_zen_fail: 'Model setup failed',
|
|
1034
|
+
net_offline: 'No internet connection — messages queued',
|
|
1035
|
+
net_online: 'Connection restored',
|
|
1036
|
+
net_pill_offline: 'No internet — ${n} message(s) queued',
|
|
1037
|
+
net_pill_online: 'No internet',
|
|
1038
|
+
q_pending: 'queued — waiting for internet',
|
|
1039
|
+
q_flushed: '${n} queued message(s) being sent',
|
|
1008
1040
|
bot_num_ro_hint: 'Add numbers / person names via Settings → Integrations (WhatsApp allow list); only linked numbers are shown here.',
|
|
1009
1041
|
tipStore: 'Skills Store',
|
|
1010
1042
|
tipIde: 'IDE Mode — files + chat + preview',
|
package/src/renderer/index.html
CHANGED
|
@@ -240,6 +240,7 @@
|
|
|
240
240
|
<button class="btab" data-btab="log" data-i18n="bot_tab_log">Log</button>
|
|
241
241
|
<button class="btab" data-btab="watcher" data-i18n="bot_tab_watcher">Watcher</button>
|
|
242
242
|
<button class="btab" data-btab="stats" data-i18n="bot_tab_stats">İstatistik</button>
|
|
243
|
+
<button class="btab" data-btab="notes" data-i18n="bot_tab_notes">Notlar</button>
|
|
243
244
|
</nav>
|
|
244
245
|
<div id="botPane"></div>
|
|
245
246
|
</div>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -193,6 +193,72 @@ function setStatus(text) {
|
|
|
193
193
|
els.statusPill.textContent = text;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/* ---------------- OFFLINE MESAJ KUYRUĞU (chat) ----------------
|
|
197
|
+
İnternet yokken gönderilen mesajlar main'deki diske yazılan kuyruğa düşer;
|
|
198
|
+
burada ⏳ "kuyrukta" balonu gösterilir. Bağlantı gelince kuyruk otomatik
|
|
199
|
+
boşaltılır ('netQueue flushed') ve balonlar gerçek mesajla değişir. */
|
|
200
|
+
let netOnline = true;
|
|
201
|
+
let netQueueCount = 0;
|
|
202
|
+
const netPending = []; // { key, el } — bekleyen mesaj balonları
|
|
203
|
+
|
|
204
|
+
function setNetBadge(el, text) {
|
|
205
|
+
if (!el || !el.isConnected) return;
|
|
206
|
+
let badge = el.querySelector('.q-badge');
|
|
207
|
+
if (!badge) {
|
|
208
|
+
badge = document.createElement('span');
|
|
209
|
+
badge.className = 'q-badge';
|
|
210
|
+
el.appendChild(badge);
|
|
211
|
+
}
|
|
212
|
+
badge.textContent = '⏳ ' + text;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function updateNetQueueUi() {
|
|
216
|
+
if (busy) return;
|
|
217
|
+
if (!netOnline) {
|
|
218
|
+
setStatus(netQueueCount > 0 ? _ti('net_pill_offline', netQueueCount) : _t('net_pill_online'));
|
|
219
|
+
} else if (!netQueueCount) {
|
|
220
|
+
setStatus('');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function onNetEvent(ev) {
|
|
225
|
+
if (ev.type === 'net') {
|
|
226
|
+
const was = netOnline;
|
|
227
|
+
netOnline = ev.online !== false;
|
|
228
|
+
if (was !== netOnline) {
|
|
229
|
+
if (netOnline) {
|
|
230
|
+
toast(_t('net_online'));
|
|
231
|
+
if (!netQueueCount) setStatus('');
|
|
232
|
+
} else {
|
|
233
|
+
toast(_t('net_offline'));
|
|
234
|
+
updateNetQueueUi();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (ev.type === 'netQueue') {
|
|
240
|
+
netQueueCount = typeof ev.count === 'number' ? ev.count : netQueueCount;
|
|
241
|
+
if (ev.queued) {
|
|
242
|
+
if (ev.sessionId === activeId) {
|
|
243
|
+
addUserBubble(ev.text || (ev.attCount ? `[${ev.attCount} ek]` : ''));
|
|
244
|
+
const el = els.msgs.querySelector('.msg-user:last-of-type');
|
|
245
|
+
if (el && ev.key) {
|
|
246
|
+
setNetBadge(el, _t('q_pending'));
|
|
247
|
+
netPending.push({ key: ev.key, el });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} else if (ev.flushed) {
|
|
251
|
+
/* kuyruk boşaltılıyor: ⏳ balonlarını kaldır — gerçek mesaj echo ile gelir */
|
|
252
|
+
for (const p of netPending) {
|
|
253
|
+
if (p.el && p.el.isConnected) p.el.remove();
|
|
254
|
+
}
|
|
255
|
+
netPending.length = 0;
|
|
256
|
+
toast(_ti('q_flushed', ev.flushed));
|
|
257
|
+
}
|
|
258
|
+
updateNetQueueUi();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
196
262
|
const TODO_GLYPH = { done: '✓', active: '▸', pending: '○' };
|
|
197
263
|
|
|
198
264
|
function renderTodos(items) {
|
|
@@ -878,7 +944,7 @@ async function renderProviderPane() {
|
|
|
878
944
|
form.innerHTML =
|
|
879
945
|
/* TEK TİKLA MODEL (OpenCode Zen Free) */
|
|
880
946
|
`<div class="zen-hero">` +
|
|
881
|
-
`<button id="zenOneClick" class="btn">⚡ ${_t('p_zen_btn')}</button>` +
|
|
947
|
+
`<button id="zenOneClick" class="btn"><span class="zen-load" hidden></span><span class="zen-label">⚡ ${_t('p_zen_btn')}</span></button>` +
|
|
882
948
|
`<div class="sub" style="margin-top:6px">${_t('p_zen_hint')}</div>` +
|
|
883
949
|
`</div>` +
|
|
884
950
|
`<label class="mem-label">${_t('p_preset')}</label>` +
|
|
@@ -928,13 +994,19 @@ async function renderProviderPane() {
|
|
|
928
994
|
/* TEK TİKLA MODEL: OpenCode Zen free kurulumu */
|
|
929
995
|
$('#zenOneClick').addEventListener('click', async () => {
|
|
930
996
|
const btn = $('#zenOneClick');
|
|
931
|
-
const
|
|
997
|
+
const label = btn.querySelector('.zen-label');
|
|
998
|
+
const spinner = btn.querySelector('.zen-load');
|
|
999
|
+
const old = label.textContent;
|
|
932
1000
|
btn.disabled = true;
|
|
933
|
-
btn.
|
|
1001
|
+
btn.classList.add('loading');
|
|
1002
|
+
spinner.hidden = false;
|
|
1003
|
+
label.textContent = _t('p_zen_busy');
|
|
934
1004
|
let r;
|
|
935
1005
|
try { r = await beast.zenOneClick(); } catch (e) { r = { ok: false, error: String((e && e.message) || e) }; }
|
|
936
1006
|
btn.disabled = false;
|
|
937
|
-
btn.
|
|
1007
|
+
btn.classList.remove('loading');
|
|
1008
|
+
spinner.hidden = true;
|
|
1009
|
+
label.textContent = old;
|
|
938
1010
|
if (r && r.ok) {
|
|
939
1011
|
let msg = _ti('p_zen_ok', (r.models || []).length) + ' (OpenCode Zen)';
|
|
940
1012
|
if (r.failed && r.failed.length) msg += ' — ' + _ti('p_zen_skipped', r.failed.length);
|
|
@@ -1414,6 +1486,35 @@ async function renderIntegrationsPane() {
|
|
|
1414
1486
|
<button id="waAllowAdd" class="btn ghost" style="margin-top:6px">${_t('it_add')}</button>
|
|
1415
1487
|
</div>
|
|
1416
1488
|
<div class="sub" style="margin-top:8px">${_t('it_allow_note')}</div>
|
|
1489
|
+
</div>
|
|
1490
|
+
|
|
1491
|
+
<div class="wa-card" style="margin-top:14px">
|
|
1492
|
+
<div class="wa-head">
|
|
1493
|
+
<div class="wa-logo tg">T</div>
|
|
1494
|
+
<div>
|
|
1495
|
+
<div class="wa-title">Telegram</div>
|
|
1496
|
+
<div class="wa-sub">${_t('it_tg_sub')}</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
</div>
|
|
1499
|
+
<div class="wa-status"><span id="tgDot" class="wa-dot"></span><span id="tgStatText">—</span></div>
|
|
1500
|
+
<div id="tgUser" class="wa-user" hidden></div>
|
|
1501
|
+
<div class="form-grid" style="grid-template-columns:1fr auto;align-items:center">
|
|
1502
|
+
<input id="tgTokenInp" class="inp" type="password" style="margin:6px 0 0" placeholder="${_t('it_tg_token_ph')}" autocomplete="off" />
|
|
1503
|
+
<button id="tgSaveBtn" class="btn" style="margin-top:6px">${_t('it_tg_save')}</button>
|
|
1504
|
+
</div>
|
|
1505
|
+
<div class="wa-actions" style="margin-top:6px">
|
|
1506
|
+
<button id="tgStopBtn" class="btn ghost">${_t('it_disconnect')}</button>
|
|
1507
|
+
</div>
|
|
1508
|
+
<div class="divider"></div>
|
|
1509
|
+
<label class="mem-label" style="margin-top:0">${_t('it_allow_label')} — Telegram</label>
|
|
1510
|
+
<div id="tgAllowChips" class="chips-inline"></div>
|
|
1511
|
+
<div class="form-grid" style="grid-template-columns:1.2fr 1fr 1fr auto;align-items:center">
|
|
1512
|
+
<input id="tgAllowNameInp" class="inp" style="margin:6px 0 0" placeholder="${_t('it_name_ph')}" autocomplete="off" />
|
|
1513
|
+
<input id="tgAllowIdInp" class="inp" style="margin:6px 0 0" placeholder="${_t('it_tg_id_ph')}" autocomplete="off" />
|
|
1514
|
+
<select id="tgAllowBotSel" class="perm-select" style="margin:6px 0 0;min-width:105px" title="${_t('bot_bind_title')}"></select>
|
|
1515
|
+
<button id="tgAllowAdd" class="btn ghost" style="margin-top:6px">${_t('it_add')}</button>
|
|
1516
|
+
</div>
|
|
1517
|
+
<div class="sub" style="margin-top:8px">${_t('it_tg_note')}</div>
|
|
1417
1518
|
</div>`;
|
|
1418
1519
|
|
|
1419
1520
|
const waGroupsOn = $('#waGroupsOn');
|
|
@@ -1453,6 +1554,29 @@ async function renderIntegrationsPane() {
|
|
|
1453
1554
|
waUI.status = snap.status || 'disconnected';
|
|
1454
1555
|
if (snap.user) waUI.user = snap.user;
|
|
1455
1556
|
updateWaPane();
|
|
1557
|
+
|
|
1558
|
+
/* TELEGRAM (FEATURE 3): token + allow list + durum */
|
|
1559
|
+
$('#tgSaveBtn').addEventListener('click', async () => {
|
|
1560
|
+
const tok = $('#tgTokenInp').value.trim();
|
|
1561
|
+
if (!tok) { toast(_t('it_tg_token_ph')); return; }
|
|
1562
|
+
toast(_t('it_tg_connecting'));
|
|
1563
|
+
const r = await beast.tgSetToken(tok).catch((e) => ({ status: 'error', error: String(e) }));
|
|
1564
|
+
$('#tgTokenInp').value = '';
|
|
1565
|
+
updateTgPane();
|
|
1566
|
+
toast(r.status === 'connected' ? 'Telegram bağlı: ' + (r.user || '') : r.status === 'error' ? _t('it_tg_token_bad') : _t('it_tg_connecting'));
|
|
1567
|
+
});
|
|
1568
|
+
$('#tgStopBtn').addEventListener('click', async () => {
|
|
1569
|
+
await beast.tgStop().catch(() => {});
|
|
1570
|
+
updateTgPane();
|
|
1571
|
+
toast('Kesildi');
|
|
1572
|
+
});
|
|
1573
|
+
await renderTgAllow();
|
|
1574
|
+
try {
|
|
1575
|
+
const ts = await beast.tgGetStatus();
|
|
1576
|
+
tgUI.status = ts.status || 'disconnected';
|
|
1577
|
+
tgUI.user = ts.user || null;
|
|
1578
|
+
updateTgPane();
|
|
1579
|
+
} catch {}
|
|
1456
1580
|
}
|
|
1457
1581
|
|
|
1458
1582
|
/* ---------------- Olay Merkezi (ayrı sekme) ---------------- */
|
|
@@ -1719,7 +1843,8 @@ async function renderUpdatePane(autoCheck) {
|
|
|
1719
1843
|
const btnInstall = pane.querySelector('#upInstall');
|
|
1720
1844
|
if (btnInstall) btnInstall.addEventListener('click', async () => {
|
|
1721
1845
|
const r = await beast.updateInstall().catch(() => ({ ok: false, error: 'ipc' }));
|
|
1722
|
-
if (
|
|
1846
|
+
if (r.ok && r.npm) toast(_t('up_npm_started'));
|
|
1847
|
+
else if (!r.ok && r.error) toast(r.error);
|
|
1723
1848
|
});
|
|
1724
1849
|
|
|
1725
1850
|
/* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
|
|
@@ -2085,6 +2210,172 @@ function updateWaPane() {
|
|
|
2085
2210
|
u.textContent = waUI.user ? '👤 ' + waUI.user : '';
|
|
2086
2211
|
}
|
|
2087
2212
|
|
|
2213
|
+
/* ---------------- integrations (Telegram — FEATURE 3) ---------------- */
|
|
2214
|
+
|
|
2215
|
+
const tgUI = { status: 'disconnected', user: null };
|
|
2216
|
+
const TG_STATUS_TEXT = {
|
|
2217
|
+
disconnected: 'Bağlı değil',
|
|
2218
|
+
connecting: 'Bağlanıyor…',
|
|
2219
|
+
connected: 'Bağlı',
|
|
2220
|
+
error: 'Hata — tokenı kontrol et',
|
|
2221
|
+
};
|
|
2222
|
+
|
|
2223
|
+
function onTgEvent(ev) {
|
|
2224
|
+
if (ev.type !== 'status') return;
|
|
2225
|
+
tgUI.status = ev.status;
|
|
2226
|
+
if (ev.user) tgUI.user = ev.user;
|
|
2227
|
+
updateTgPane();
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
function updateTgPane() {
|
|
2231
|
+
const pane = $('#tab-integrations');
|
|
2232
|
+
const dot = pane && pane.querySelector('#tgDot');
|
|
2233
|
+
if (!dot) return;
|
|
2234
|
+
dot.className = 'wa-dot' + (tgUI.status === 'connected' ? ' on' : tgUI.status === 'error' ? ' qr' : '');
|
|
2235
|
+
pane.querySelector('#tgStatText').textContent = TG_STATUS_TEXT[tgUI.status] || tgUI.status;
|
|
2236
|
+
const u = pane.querySelector('#tgUser');
|
|
2237
|
+
u.hidden = !(tgUI.status === 'connected' && tgUI.user);
|
|
2238
|
+
u.textContent = tgUI.user ? '🤖 ' + tgUI.user : '';
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
/* Telegram izin listesi — WA ile aynı mantık: isim zorunlu, kişi bazlı izin,
|
|
2242
|
+
bot eşleme. Chip × ile silinir; düzenlemek için silip yeniden ekle. */
|
|
2243
|
+
async function renderTgAllow() {
|
|
2244
|
+
const wrap = $('#tgAllowChips');
|
|
2245
|
+
if (!wrap) return;
|
|
2246
|
+
const list = await beast.tgGetAllow();
|
|
2247
|
+
let botChoices = [];
|
|
2248
|
+
try { botChoices = (await beast.botsList()) || []; } catch {}
|
|
2249
|
+
const multiBot = botChoices.length >= 2;
|
|
2250
|
+
const tgLabel = (e) => {
|
|
2251
|
+
if (e === '*') return '* herkes';
|
|
2252
|
+
if (typeof e === 'string') return e;
|
|
2253
|
+
const name = String((e && e.name) || '').trim();
|
|
2254
|
+
const id = String((e && e.id) || '');
|
|
2255
|
+
return (name ? name + ' ' : '') + id;
|
|
2256
|
+
};
|
|
2257
|
+
wrap.innerHTML = '';
|
|
2258
|
+
if (!list.length) wrap.innerHTML = '<span class="sub">— boş —</span>';
|
|
2259
|
+
list.forEach((entry, idx) => {
|
|
2260
|
+
const curPerm = (typeof entry === 'object' && entry.perm) || (entry && entry.lockdown ? 'chat' : 'all');
|
|
2261
|
+
const c = document.createElement('span');
|
|
2262
|
+
c.className = 'chip';
|
|
2263
|
+
c.style.margin = '0 6px 6px 0';
|
|
2264
|
+
const txt = document.createElement('span');
|
|
2265
|
+
txt.className = 'chip-txt';
|
|
2266
|
+
txt.textContent = tgLabel(entry);
|
|
2267
|
+
c.appendChild(txt);
|
|
2268
|
+
if (entry !== '*') {
|
|
2269
|
+
/* kişi bazlı granül izin: serbest/web/okuma/kısıtlı (WA ile aynı) */
|
|
2270
|
+
const selP = document.createElement('select');
|
|
2271
|
+
selP.className = 'perm-select';
|
|
2272
|
+
selP.title = _t('wa_perm_title');
|
|
2273
|
+
const PERMS = [
|
|
2274
|
+
['all', _t('wa_perm_all')],
|
|
2275
|
+
['web', 'web'],
|
|
2276
|
+
['read', _t('wa_perm_read')],
|
|
2277
|
+
['chat', _t('wa_perm_chat')],
|
|
2278
|
+
];
|
|
2279
|
+
for (const [v, lbl] of PERMS) {
|
|
2280
|
+
const o = document.createElement('option');
|
|
2281
|
+
o.value = v;
|
|
2282
|
+
o.textContent = lbl;
|
|
2283
|
+
if (curPerm === v) o.selected = true;
|
|
2284
|
+
selP.appendChild(o);
|
|
2285
|
+
}
|
|
2286
|
+
selP.addEventListener('change', async () => {
|
|
2287
|
+
const cur = await beast.tgGetAllow();
|
|
2288
|
+
const next = cur.map((e, i) => {
|
|
2289
|
+
if (i !== idx) return e;
|
|
2290
|
+
const base = typeof e === 'string' ? { id: e, name: '' } : { ...e };
|
|
2291
|
+
base.lockdown = selP.value === 'chat';
|
|
2292
|
+
base.perm = selP.value;
|
|
2293
|
+
return base;
|
|
2294
|
+
});
|
|
2295
|
+
await beast.tgSetAllow(next);
|
|
2296
|
+
renderTgAllow();
|
|
2297
|
+
toast(tgLabel(entry) + ': ' + selP.selectedOptions[0].textContent);
|
|
2298
|
+
});
|
|
2299
|
+
c.appendChild(selP);
|
|
2300
|
+
|
|
2301
|
+
/* BOT SİSTEMİ: 2+ bot varsa kaydı hangi botun karşılayacağını seç */
|
|
2302
|
+
if (multiBot) {
|
|
2303
|
+
const sel = document.createElement('select');
|
|
2304
|
+
sel.className = 'perm-select';
|
|
2305
|
+
sel.title = _t('bot_bind_title');
|
|
2306
|
+
for (const bb of botChoices) {
|
|
2307
|
+
const o = document.createElement('option');
|
|
2308
|
+
o.value = bb.id;
|
|
2309
|
+
o.textContent = `${bb.icon} ${bb.name}`;
|
|
2310
|
+
if ((typeof entry === 'object' && entry.bot_id) === bb.id) o.selected = true;
|
|
2311
|
+
sel.appendChild(o);
|
|
2312
|
+
}
|
|
2313
|
+
sel.addEventListener('change', async () => {
|
|
2314
|
+
const cur = await beast.tgGetAllow();
|
|
2315
|
+
const next = cur.map((e, i) => {
|
|
2316
|
+
if (i !== idx) return e;
|
|
2317
|
+
const base = typeof e === 'string' ? { id: e, name: '' } : { ...e };
|
|
2318
|
+
base.bot_id = sel.value || undefined;
|
|
2319
|
+
return base;
|
|
2320
|
+
});
|
|
2321
|
+
await beast.tgSetAllow(next);
|
|
2322
|
+
renderTgAllow();
|
|
2323
|
+
const bt = botChoices.find((bb) => bb.id === sel.value);
|
|
2324
|
+
toast(tgLabel(entry) + ' → ' + (bt ? bt.name : '?'));
|
|
2325
|
+
});
|
|
2326
|
+
c.appendChild(sel);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
const x = document.createElement('span');
|
|
2330
|
+
x.className = 'x';
|
|
2331
|
+
x.textContent = '×';
|
|
2332
|
+
x.addEventListener('click', async () => {
|
|
2333
|
+
const next = (await beast.tgGetAllow()).filter((_v, i) => i !== idx);
|
|
2334
|
+
await beast.tgSetAllow(next);
|
|
2335
|
+
renderTgAllow();
|
|
2336
|
+
toast('Kaldırıldı: ' + tgLabel(entry));
|
|
2337
|
+
});
|
|
2338
|
+
c.appendChild(x);
|
|
2339
|
+
wrap.appendChild(c);
|
|
2340
|
+
});
|
|
2341
|
+
|
|
2342
|
+
/* ekleme formu: isim + (ID veya @username) + bot seçici */
|
|
2343
|
+
const inp = $('#tgAllowIdInp');
|
|
2344
|
+
const nameInp = $('#tgAllowNameInp');
|
|
2345
|
+
const add = $('#tgAllowAdd');
|
|
2346
|
+
const botSel = $('#tgAllowBotSel');
|
|
2347
|
+
if (botSel) {
|
|
2348
|
+
const prev = botSel.value;
|
|
2349
|
+
botSel.innerHTML =
|
|
2350
|
+
`<option value="">${_t('bot_sel_empty')}</option>` +
|
|
2351
|
+
botChoices
|
|
2352
|
+
.map((bb) => `<option value="${bb.id}">${bb.icon} ${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
|
|
2353
|
+
.join('');
|
|
2354
|
+
if (prev) botSel.value = prev;
|
|
2355
|
+
}
|
|
2356
|
+
if (!add.dataset.bound) {
|
|
2357
|
+
add.dataset.bound = '1';
|
|
2358
|
+
const addEntry = async () => {
|
|
2359
|
+
let v = inp.value.trim();
|
|
2360
|
+
if (!v) return;
|
|
2361
|
+
const name = nameInp.value.trim().slice(0, 40);
|
|
2362
|
+
if (!name) { toast('İsim zorunlu — kimin yazdığını bilmek için'); nameInp.focus(); return; }
|
|
2363
|
+
/* @username olduğu gibi; sayısal ID'den boşluk/nokta temizle */
|
|
2364
|
+
v = v.startsWith('@') ? '@' + v.slice(1).replace(/[^\w]/g, '') : v.replace(/[^\d]/g, '');
|
|
2365
|
+
if (!v) { toast(_t('it_tg_id_ph')); return; }
|
|
2366
|
+
const botId = botSel && botSel.value ? { bot_id: botSel.value } : {};
|
|
2367
|
+
await beast.tgSetAllow([...(await beast.tgGetAllow()), { id: v, name, ...botId }]);
|
|
2368
|
+
inp.value = '';
|
|
2369
|
+
nameInp.value = '';
|
|
2370
|
+
renderTgAllow();
|
|
2371
|
+
toast('Eklendi: ' + name);
|
|
2372
|
+
};
|
|
2373
|
+
add.addEventListener('click', addEntry);
|
|
2374
|
+
inp.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addEntry(); } });
|
|
2375
|
+
nameInp.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addEntry(); } });
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2088
2379
|
/* ---------------- BOT SİSTEMİ (BÖLÜM 2-3-4) ----------------
|
|
2089
2380
|
Sol panel altında bot kartları; her bot için sohbet geçmişi + sekmeli yönetim.
|
|
2090
2381
|
İlk bot hep Beast (admin, silinemez). Max 5 bot. */
|
|
@@ -2117,6 +2408,9 @@ async function refreshBots() {
|
|
|
2117
2408
|
} catch { botsCache = []; }
|
|
2118
2409
|
try { botPageStats = (await beast.botsStats()) || []; } catch { botPageStats = []; }
|
|
2119
2410
|
renderBotCards();
|
|
2411
|
+
/* BOT PICKER SENKRONU: liste her yenilendiğinde üstteki rozet de aktif bota
|
|
2412
|
+
ayarlanır — startup yarışında (liste boşken çizilen) eski rozet düzelir */
|
|
2413
|
+
updateBotChip();
|
|
2120
2414
|
if (!$('#botOverlay').hidden) renderBotPage();
|
|
2121
2415
|
}
|
|
2122
2416
|
|
|
@@ -2159,11 +2453,20 @@ async function switchBot(id) {
|
|
|
2159
2453
|
const b = botsCache.find((x) => x.id === id);
|
|
2160
2454
|
if (!b) return;
|
|
2161
2455
|
if (id === activeBotId) return;
|
|
2456
|
+
/* UI hemen dönsün: rozet + kartlar IPC beklemeden aktif bota geçer */
|
|
2162
2457
|
activeBotId = id;
|
|
2163
|
-
try { await beast.botsActivate(id); } catch {}
|
|
2164
|
-
try { localStorage.setItem('beast.activeBot', id); } catch {}
|
|
2165
2458
|
updateBotChip();
|
|
2166
2459
|
renderBotCards();
|
|
2460
|
+
try {
|
|
2461
|
+
const r = await beast.botsActivate(id);
|
|
2462
|
+
/* main'inkiyle eşleş: normalizasyon ('beast' fallback) varsa düzelt */
|
|
2463
|
+
if (r && r.activeBotId && r.activeBotId !== activeBotId) {
|
|
2464
|
+
activeBotId = r.activeBotId;
|
|
2465
|
+
updateBotChip();
|
|
2466
|
+
renderBotCards();
|
|
2467
|
+
}
|
|
2468
|
+
} catch {}
|
|
2469
|
+
try { localStorage.setItem('beast.activeBot', id); } catch {}
|
|
2167
2470
|
/* o botun en son oturumuna geç; hiç yoksa o bot için yeni sohbet aç */
|
|
2168
2471
|
try {
|
|
2169
2472
|
const list = (await beast.listSessions()).filter((s) => (s.botId || 'beast') === id);
|
|
@@ -2224,9 +2527,44 @@ function renderBotPage() {
|
|
|
2224
2527
|
else if (tab === 'log') renderBotLog(pane, b);
|
|
2225
2528
|
else if (tab === 'watcher') renderBotWatcher(pane, b);
|
|
2226
2529
|
else if (tab === 'stats') renderBotStats(pane, b);
|
|
2530
|
+
else if (tab === 'notes') renderBotNotes(pane, b);
|
|
2227
2531
|
renderBotChats(b);
|
|
2228
2532
|
}
|
|
2229
2533
|
|
|
2534
|
+
/* --- Notlar sekmesi: oturum notları — konuşma kodu, başlık, tarih ve özet metni.
|
|
2535
|
+
Admin bot (beast) tüm oturumları görür; müşteri botu yalnız kendi oturumlarını. --- */
|
|
2536
|
+
async function renderBotNotes(pane, b) {
|
|
2537
|
+
pane.innerHTML = `<h2>${_t('notes_h2')}</h2><div class="sub">${_t('notes_sub')}</div>`;
|
|
2538
|
+
let all = [];
|
|
2539
|
+
try { all = await beast.listNotes(); } catch {}
|
|
2540
|
+
const list = (b.admin ? all : all.filter((n) => (n.botId || 'beast') === b.id))
|
|
2541
|
+
.slice()
|
|
2542
|
+
.sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt)));
|
|
2543
|
+
if (!list.length) {
|
|
2544
|
+
pane.insertAdjacentHTML('beforeend', `<div class="mini-empty">${_t('notes_empty')}</div>`);
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
const f = document.createElement('div');
|
|
2548
|
+
f.className = 'bot-form';
|
|
2549
|
+
for (const n of list) {
|
|
2550
|
+
const when = n.updatedAt ? new Date(n.updatedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
|
|
2551
|
+
const card = document.createElement('div');
|
|
2552
|
+
card.className = 'notes-card';
|
|
2553
|
+
card.innerHTML =
|
|
2554
|
+
`<div class="notes-head"><div class="skill-name">${escapeHtml((b.icon || '') + ' ' + (n.code || '?'))} — ${escapeHtml(n.title || '')}</div>` +
|
|
2555
|
+
`<div class="notes-meta">${escapeHtml(when)} · ${n.count || 0} ${_t('bot_stat_msgs')}</div></div>` +
|
|
2556
|
+
`<div class="notes-body">${escapeHtml(String(n.notes || '').slice(0, 4000))}</div>` +
|
|
2557
|
+
`<div style="margin-top:8px"><button class="btn ghost notes-del">${_t('notes_del')}</button></div>`;
|
|
2558
|
+
card.querySelector('.notes-del').addEventListener('click', async () => {
|
|
2559
|
+
await beast.clearNotes(n.id).catch(() => {});
|
|
2560
|
+
toast(_t('deleted'));
|
|
2561
|
+
renderBotNotes(pane, b);
|
|
2562
|
+
});
|
|
2563
|
+
f.appendChild(card);
|
|
2564
|
+
}
|
|
2565
|
+
pane.appendChild(f);
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2230
2568
|
function renderBotOverview(pane) {
|
|
2231
2569
|
pane.innerHTML = `<h2>${_t('bot_overview')}</h2><div class="sub">${_t('bot_overview_sub')}</div>`;
|
|
2232
2570
|
const totals = { numbers: 0, sessions: 0, msgs: 0 };
|
|
@@ -2313,6 +2651,12 @@ function renderBotSettings(pane, b) {
|
|
|
2313
2651
|
const skillChecks = BOT_SKILLS
|
|
2314
2652
|
.map(([k, lbl]) => `<label><input type="checkbox" data-skill="${k}" ${(b.skills || {})[k] ? 'checked' : ''}/> ${lbl}</label>`)
|
|
2315
2653
|
.join('');
|
|
2654
|
+
/* Yetki: 'all' tek başına tüm araçları verir (özel); web/read/chat çoklu seçilebilir */
|
|
2655
|
+
const PERM_OPTS = [['all', _t('wa_perm_all')], ['web', 'Web'], ['read', _t('wa_perm_read')], ['chat', _t('wa_perm_chat')]];
|
|
2656
|
+
const curPerms = Array.isArray(b.perm) ? b.perm : [b.perm || 'all'];
|
|
2657
|
+
const permChecks = PERM_OPTS
|
|
2658
|
+
.map(([k, lbl]) => `<label><input type="checkbox" data-perm="${k}" ${curPerms.includes(k) ? 'checked' : ''}/> ${lbl}</label>`)
|
|
2659
|
+
.join('');
|
|
2316
2660
|
const numRows = (b.numbers || [])
|
|
2317
2661
|
.map((n) => `<div class="bot-num-row" data-num="${n.num}"><span class="n">+${escapeHtml(n.num)}${n.name ? ' · ' + escapeHtml(n.name) : ''}</span><span class="x" title="${_t('bot_num_del')}">×</span></div>`)
|
|
2318
2662
|
.join('');
|
|
@@ -2334,12 +2678,8 @@ function renderBotSettings(pane, b) {
|
|
|
2334
2678
|
<div class="bot-checks" id="bSee">${seeChecks}</div>
|
|
2335
2679
|
${b.admin ? '' : `
|
|
2336
2680
|
<label class="mem-label">${_t('bot_perm')}</label>
|
|
2337
|
-
<
|
|
2338
|
-
|
|
2339
|
-
<option value="web" ${b.perm === 'web' ? 'selected' : ''}>web</option>
|
|
2340
|
-
<option value="read" ${b.perm === 'read' ? 'selected' : ''}>${_t('wa_perm_read')}</option>
|
|
2341
|
-
<option value="chat" ${b.perm === 'chat' ? 'selected' : ''}>${_t('wa_perm_chat')}</option>
|
|
2342
|
-
</select>
|
|
2681
|
+
<div class="bot-checks" id="bPerm">${permChecks}</div>
|
|
2682
|
+
<div class="sub" style="margin-top:4px">${_t('bot_perm_note')}</div>
|
|
2343
2683
|
<label class="mem-label">${_t('bot_skills')}</label>
|
|
2344
2684
|
<div class="bot-checks" id="bSkills">${skillChecks}</div>`}
|
|
2345
2685
|
<label class="mem-label">${_t('bot_browser')}</label>
|
|
@@ -2387,6 +2727,18 @@ function renderBotSettings(pane, b) {
|
|
|
2387
2727
|
if (bb) renderBotSettings(pane, bb);
|
|
2388
2728
|
})
|
|
2389
2729
|
);
|
|
2730
|
+
/* 'all' hariç diğer izinler çoklu seçilebilir; 'all' işaretlenince diğerleri kapanır */
|
|
2731
|
+
f.querySelectorAll('#bPerm input').forEach((c) =>
|
|
2732
|
+
c.addEventListener('change', () => {
|
|
2733
|
+
if (c.dataset.perm === 'all' && c.checked) {
|
|
2734
|
+
f.querySelectorAll('#bPerm input').forEach((x) => { if (x !== c) x.checked = false; });
|
|
2735
|
+
} else if (c.checked) {
|
|
2736
|
+
const all = f.querySelector('#bPerm input[data-perm="all"]');
|
|
2737
|
+
if (all) all.checked = false;
|
|
2738
|
+
}
|
|
2739
|
+
if (![...f.querySelectorAll('#bPerm input')].some((x) => x.checked)) c.checked = true; // en az biri seçili kalsın
|
|
2740
|
+
})
|
|
2741
|
+
);
|
|
2390
2742
|
$('#bSave').addEventListener('click', async () => {
|
|
2391
2743
|
const patch = {
|
|
2392
2744
|
name: $('#bName').value.trim(),
|
|
@@ -2399,7 +2751,8 @@ function renderBotSettings(pane, b) {
|
|
|
2399
2751
|
numbers: (b.numbers || []).map((n) => n.num),
|
|
2400
2752
|
};
|
|
2401
2753
|
if (!b.admin) {
|
|
2402
|
-
|
|
2754
|
+
const selPerms = [...f.querySelectorAll('#bPerm input:checked')].map((c) => c.dataset.perm);
|
|
2755
|
+
patch.perm = selPerms.includes('all') ? 'all' : selPerms;
|
|
2403
2756
|
const sk = {};
|
|
2404
2757
|
f.querySelectorAll('#bSkills input').forEach((c) => { sk[c.dataset.skill] = c.checked; });
|
|
2405
2758
|
patch.skills = sk;
|
|
@@ -2847,6 +3200,8 @@ function onEvent(ev) {
|
|
|
2847
3200
|
scheduleAgentsRender();
|
|
2848
3201
|
return;
|
|
2849
3202
|
}
|
|
3203
|
+
/* OFFLINE MESAJ KUYRUĞU: bağlantı + kuyruk olayları (sessionId filtresinden önce) */
|
|
3204
|
+
if (ev.type === 'net' || ev.type === 'netQueue') { onNetEvent(ev); return; }
|
|
2850
3205
|
/* Beast Code oturumu (IDE modu ortasındaki panel): olayları panele akıt,
|
|
2851
3206
|
ana sohbeti kirletme */
|
|
2852
3207
|
if (bcSessionId && ev.sessionId === bcSessionId) {
|
|
@@ -3775,6 +4130,7 @@ async function init() {
|
|
|
3775
4130
|
}
|
|
3776
4131
|
|
|
3777
4132
|
beast.onWaEvent(onWaEvent);
|
|
4133
|
+
beast.onTgEvent(onTgEvent);
|
|
3778
4134
|
|
|
3779
4135
|
els.gearBtn.addEventListener('click', openSettings);
|
|
3780
4136
|
els.setClose.addEventListener('click', closeSettings);
|