beast-agent 2.3.1 → 2.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.3.1",
4
+ "version": "2.3.3",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/main.js CHANGED
@@ -3248,6 +3248,15 @@ app.whenReady().then(() => {
3248
3248
  waLog('STT aktif: ' + sttEngineLabel() + ' — yerel model kullanılmayacak');
3249
3249
  }
3250
3250
 
3251
+ /* EMBEDDING modeli (hafıza semantik arama) — açılışta otomatik indir */
3252
+ setTimeout(() => {
3253
+ try {
3254
+ require('./agent/mem0').search('beast', 'warmup')
3255
+ .then(() => waLog('embedding modeli hazır (all-MiniLM-L6-v2)'))
3256
+ .catch(() => {});
3257
+ } catch {}
3258
+ }, 20000);
3259
+
3251
3260
  // WhatsApp köprüsünü otomatik başlat (eşleme varsa direkt bağlanır)
3252
3261
  ensureWa().start().catch((e) => waLog('autostart failed: ' + (e && e.message)));
3253
3262
 
@@ -5778,6 +5787,137 @@ ipcMain.handle('stt:transcribe', async (_e, b64, lang, priority) => {
5778
5787
  });
5779
5788
 
5780
5789
  ipcMain.handle('stt:lang:get', () => settings.sttLang || 'tr');
5790
+
5791
+ /* STT DURUMU: ayarlar ekranı için — model indirildi mi/in-progress mi,
5792
+ hangi motor, ne kadar disk */
5793
+ ipcMain.handle('stt:status', () => {
5794
+ const provider = sttProvider();
5795
+ const model = sttModelName();
5796
+ const out = { provider, engineLabel: sttEngineLabel(), model };
5797
+ if (provider !== 'local') { out.state = 'cloud'; return out; }
5798
+ const dir = path.join(APP_DIR, 'models', ...model.split('/'));
5799
+ let files = [];
5800
+ try {
5801
+ files = fs.readdirSync(dir, { recursive: true, withFileTypes: true })
5802
+ .filter((d) => d.isFile())
5803
+ .map((d) => path.join(d.parentPath || d.path, d.name));
5804
+ } catch {}
5805
+ let bytes = 0;
5806
+ let tmp = 0;
5807
+ let onnx = 0;
5808
+ let hasConfig = false;
5809
+ for (const p of files) {
5810
+ try {
5811
+ bytes += fs.statSync(p).size;
5812
+ if (/\.tmp/i.test(p)) tmp++;
5813
+ else if (/\.onnx$/i.test(p)) onnx++;
5814
+ else if (/config\.json$/i.test(p)) hasConfig = true;
5815
+ } catch {}
5816
+ }
5817
+ let state;
5818
+ if (sttPipeline) state = 'ready';
5819
+ else if (sttLoading) state = 'loading';
5820
+ else if (hasConfig && onnx >= 2 && tmp === 0) state = 'downloaded';
5821
+ else if (files.length) state = 'partial';
5822
+ else state = 'missing';
5823
+ out.state = state;
5824
+ out.mb = Math.round(bytes / (1024 * 1024));
5825
+ out.onnx = onnx;
5826
+ out.tmp = tmp;
5827
+ return out;
5828
+ });
5829
+
5830
+ /* istenirse modeli şimdi indir/yükle (bloklamaz — arka planda) */
5831
+ ipcMain.handle('stt:prefetch', () => {
5832
+ ensureStt()
5833
+ .then(() => waLog('STT prefetch: hazır'))
5834
+ .catch((e) => waLog('STT prefetch hata: ' + String((e && e.message) || e)));
5835
+ return { ok: true, loading: true };
5836
+ });
5837
+
5838
+ /* KURULUM SEKMESİ: gerekli bileşenlerin durum taraması */
5839
+ ipcMain.handle('install:status', async () => {
5840
+ const rows = [];
5841
+ const pkgOk = (id) => { try { require.resolve(id); return true; } catch { return false; } };
5842
+ const scanModel = (rel) => {
5843
+ const dir = path.join(APP_DIR, 'models', ...rel.split('/'));
5844
+ let files = [];
5845
+ try {
5846
+ files = fs.readdirSync(dir, { recursive: true, withFileTypes: true })
5847
+ .filter((d) => d.isFile())
5848
+ .map((d) => path.join(d.parentPath || d.path, d.name));
5849
+ } catch {}
5850
+ let bytes = 0, tmp = 0, onnx = 0, hasCfg = false;
5851
+ for (const p of files) {
5852
+ try {
5853
+ bytes += fs.statSync(p).size;
5854
+ if (/\.tmp/i.test(p)) tmp++;
5855
+ else if (/\.onnx$/i.test(p)) onnx++;
5856
+ else if (/config\.json$/i.test(p)) hasCfg = true;
5857
+ } catch {}
5858
+ }
5859
+ return { files, bytes, tmp, onnx, hasCfg };
5860
+ };
5861
+
5862
+ /* 1) STT modeli */
5863
+ if (sttProvider() !== 'local') {
5864
+ rows.push({ id: 'stt', name: 'STT modeli — ' + sttModelName(), state: 'cloud', detail: 'bulut motoru — indirme gerekmez' });
5865
+ } else {
5866
+ const s = scanModel(sttModelName());
5867
+ let state;
5868
+ if (sttPipeline) state = 'ok';
5869
+ else if (sttLoading) state = 'loading';
5870
+ else if (s.hasCfg && s.onnx >= 2 && s.tmp === 0) state = 'downloaded';
5871
+ else if (s.files.length) state = 'partial';
5872
+ else state = 'missing';
5873
+ rows.push({ id: 'stt', name: 'STT modeli — whisper-large-v3-turbo', state, detail: sttEngineLabel(), mb: Math.round(s.bytes / 1048576) });
5874
+ }
5875
+
5876
+ /* 2) Embedding modeli (hafıza semantik arama) */
5877
+ {
5878
+ const s = scanModel('Xenova/all-MiniLM-L6-v2');
5879
+ const state = s.hasCfg && s.onnx >= 1 && s.tmp === 0 ? 'ok' : (s.files.length ? 'partial' : 'missing');
5880
+ rows.push({ id: 'emb', name: 'Embedding modeli — all-MiniLM-L6-v2', state, detail: 'hafıza semantik arama', mb: Math.round(s.bytes / 1048576) });
5881
+ }
5882
+
5883
+ /* 3) ffmpeg */
5884
+ let ffOk = false;
5885
+ try { const p = require('ffmpeg-static'); ffOk = !!p && fs.existsSync(p); } catch {}
5886
+ rows.push({ id: 'ffmpeg', name: 'ffmpeg — ses/video dönüşüm', state: ffOk ? 'ok' : 'missing', detail: ffOk ? 'kurulu' : 'npm paketi eksik' });
5887
+
5888
+ /* 4) çalışma zamanı npm paketleri */
5889
+ rows.push({ id: 'hf', name: 'Transformers.js — STT çalışma zamanı', state: pkgOk('@huggingface/transformers') ? 'ok' : 'missing', detail: 'npm paketi' });
5890
+ rows.push({ id: 'ort', name: 'ONNX Runtime — model motoru', state: pkgOk('onnxruntime-node') ? 'ok' : 'missing', detail: 'npm paketi' });
5891
+
5892
+ /* 5) OCR */
5893
+ rows.push({ id: 'ocr', name: 'OCR — Tesseract (ekran okuma)', state: pkgOk('tesseract.js') ? 'ok' : 'missing', detail: pkgOk('tesseract.js') ? 'kurulu — dil verisi ilk kullanımda iner' : 'npm paketi eksik' });
5894
+
5895
+ /* 6) Python (opsiyonel — betikler) */
5896
+ let pyVer = '';
5897
+ for (const cmd of ['python -c "import sys;print(sys.version.split()[0])"', 'py -3 -c "import sys;print(sys.version.split()[0])"']) {
5898
+ try {
5899
+ pyVer = String(require('child_process').execSync(cmd, { timeout: 4000, stdio: 'pipe', encoding: 'utf8' })).trim();
5900
+ if (pyVer) break;
5901
+ } catch {}
5902
+ }
5903
+ rows.push({ id: 'python', name: 'Python — betikler / web arama', state: pyVer ? 'ok' : 'optional', detail: pyVer ? 'v' + pyVer : 'sistemde bulunamadı — opsiyonel' });
5904
+
5905
+ /* 7) Edge TTS (bulut) */
5906
+ rows.push({ id: 'edge', name: 'Edge TTS — seslendirme', state: 'cloud', detail: 'bulut — kurulum gerekmez' });
5907
+
5908
+ return rows;
5909
+ });
5910
+
5911
+ /* embedding modelini şimdi indir (mem0 arama yolu ısıtılır) */
5912
+ ipcMain.handle('embed:prefetch', () => {
5913
+ try {
5914
+ const mem0 = require('./agent/mem0');
5915
+ mem0.search('beast', 'warmup')
5916
+ .then(() => waLog('embedding modeli hazır'))
5917
+ .catch(() => {});
5918
+ } catch {}
5919
+ return { ok: true, loading: true };
5920
+ });
5781
5921
  ipcMain.handle('stt:lang:set', (_e, lang) => {
5782
5922
  const v = ['auto', 'tr', 'en'].includes(String(lang)) ? String(lang) : 'tr';
5783
5923
  settings.sttLang = v;
package/src/preload.js CHANGED
@@ -53,6 +53,10 @@ contextBridge.exposeInMainWorld('beast', {
53
53
  approvalRespond: (id, ok, always) => ipcRenderer.invoke('approval:respond', { id, ok, always }),
54
54
  sttTranscribe: (b64, lang, priority) => ipcRenderer.invoke('stt:transcribe', b64, lang, priority),
55
55
  sttLangGet: () => ipcRenderer.invoke('stt:lang:get'),
56
+ sttStatus: () => ipcRenderer.invoke('stt:status'),
57
+ sttPrefetchNow: () => ipcRenderer.invoke('stt:prefetch'),
58
+ installStatus: () => ipcRenderer.invoke('install:status'),
59
+ embedPrefetch: () => ipcRenderer.invoke('embed:prefetch'),
56
60
  sttLangSet: (lang) => ipcRenderer.invoke('stt:lang:set', lang),
57
61
  updateStatus: () => ipcRenderer.invoke('update:status'),
58
62
  updateCheck: () => ipcRenderer.invoke('update:check'),
@@ -69,7 +69,8 @@
69
69
  tab_notes: 'Oturum Notları',
70
70
  tab_history: 'Oturum Geçmişi',
71
71
  tab_agents: 'Paralel Ajanlar',
72
- tab_tts: 'Sesli Yanıt',
72
+ tab_tts: 'Sesli Yanıt',
73
+ tab_install: 'Kurulum',
73
74
  tab_email: 'E-posta',
74
75
  tab_integrations: 'Entegrasyonlar',
75
76
  tab_websearch: 'Web Arama',
@@ -214,7 +215,8 @@
214
215
  tts_sub: 'Açık olursa WhatsApp cevapları metnin yanı sıra sesli not olarak da gönderilir.',
215
216
  tts_active: 'Aktif',
216
217
  tts_save: 'TTS Kaydet',
217
- tts_test: 'Test sesi çal',
218
+ tts_test: 'Test sesi çal',
219
+ stt_download_now: 'Şimdi indir / hazırla',
218
220
  tts_note: 'Edge TTS: ücretsiz yerel motor — anahtar gerekmez (Ahmet/Emel vb.). OpenAI motoru: uyumlu speech API (tts-1, tts-1-hd; sesler alloy, echo, fable, onyx, nova, shimmer).',
219
221
  tts_engine: 'Motor',
220
222
  tts_edge_voice: 'Edge sesi',
@@ -644,7 +646,8 @@
644
646
  tab_notes: 'Session Notes',
645
647
  tab_history: 'Session History',
646
648
  tab_agents: 'Parallel Agents',
647
- tab_tts: 'Voice Reply',
649
+ tab_tts: 'Voice Reply',
650
+ tab_install: 'Setup',
648
651
  tab_email: 'E-mail',
649
652
  tab_integrations: 'Integrations',
650
653
  tab_websearch: 'Web Search',
@@ -789,7 +792,8 @@
789
792
  tts_sub: 'When on, WhatsApp replies are also sent as voice notes alongside text.',
790
793
  tts_active: 'Active',
791
794
  tts_save: 'Save TTS',
792
- tts_test: 'Play test sound',
795
+ tts_test: 'Play test sound',
796
+ stt_download_now: 'Download / prepare now',
793
797
  tts_note: 'Edge TTS: free local engine — no key needed (Ahmet/Emel etc.). OpenAI engine: compatible speech API (tts-1, tts-1-hd; voices alloy, echo, fable, onyx, nova, shimmer).',
794
798
  tts_engine: 'Engine',
795
799
  tts_edge_voice: 'Edge voice',
@@ -233,6 +233,7 @@
233
233
  <button class="tab" data-tab="skills" data-i18n="tab_skills">Skills</button>
234
234
  <button class="tab" data-tab="agents" data-i18n="tab_agents">Paralel Ajanlar</button>
235
235
  <button class="tab" data-tab="tts" data-i18n="tab_tts">Sesli Yanıt</button>
236
+ <button class="tab" data-tab="install" data-i18n="tab_install">Kurulum</button>
236
237
  <button class="tab" data-tab="email" data-i18n="tab_email">E-posta</button>
237
238
  <button class="tab" data-tab="integrations" data-i18n="tab_integrations">Entegrasyonlar</button>
238
239
  <button class="tab" data-tab="websearch" data-i18n="tab_websearch">Web Arama</button>
@@ -257,6 +258,7 @@
257
258
  <div id="tab-skills" class="pane" hidden></div>
258
259
  <div id="tab-agents" class="pane" hidden></div>
259
260
  <div id="tab-tts" class="pane" hidden></div>
261
+ <div id="tab-install" class="pane" hidden></div>
260
262
  <div id="tab-email" class="pane" hidden></div>
261
263
  <div id="tab-integrations" class="pane" hidden></div>
262
264
  <div id="tab-websearch" class="pane" hidden></div>
@@ -1060,6 +1060,7 @@ async function renderActiveSettingsTab() {
1060
1060
  case 'fallout': await refreshFalloutPane(); break;
1061
1061
  case 'skills': await renderSkillsPane(); break;
1062
1062
  case 'tts': await renderTtsPane(); break;
1063
+ case 'install': await renderInstallPane(); break;
1063
1064
  case 'email': await renderEmailPane(); break;
1064
1065
  case 'integrations': await renderIntegrationsPane(); break;
1065
1066
  case 'websearch': await renderWebSearchPane(); break;
@@ -1150,13 +1151,14 @@ function switchTab(name) {
1150
1151
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1151
1152
  b.classList.toggle('active', b.dataset.tab === name)
1152
1153
  );
1153
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'mcp', 'events', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
1154
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
1154
1155
  const el = $('#tab-' + p);
1155
1156
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1156
1157
  }
1157
1158
  if (name === 'lang') renderLangPane();
1158
1159
  if (name === 'cron') openCron();
1159
1160
  if (name === 'usage') renderUsagePane();
1161
+ if (name === 'install') renderInstallPane();
1160
1162
  if (name === 'events') renderEventsPane();
1161
1163
  if (name === 'logs') renderLogPane();
1162
1164
  if (name === 'dash') renderDashboardPane();
@@ -2097,9 +2099,91 @@ function ttsFlushTail() {
2097
2099
  if (rest) ttsEnqueueSentence(rest);
2098
2100
  else ttsKick();
2099
2101
  }
2102
+ /* ---------------- KURULUM SEKMESİ: bileşenler OTOMATİK kurulur ----------------
2103
+ Eksik/kısmen inmiş bileşen sekme açılınca kendiliğinden indirilir; kullanıcı
2104
+ hiçbir düğmeye basmaz. Durum canlı güncellenir (4 sn'de bir). */
2105
+ async function renderInstallPane() {
2106
+ const pane = $('#tab-install');
2107
+ if (!pane) return;
2108
+ pane.innerHTML =
2109
+ '<h2>Kurulum</h2><div class="sub">Gerekli bileşenler otomatik indirilir ve kurulur — eksik varsa aşağıda İNİYOR olarak görürsün.</div>' +
2110
+ '<div id="instRows" style="margin-top:10px">Taranıyor…</div>';
2111
+
2112
+ const autoStarted = new Set(); // bu pane oturumunda otomatik başlatılanlar
2113
+ let refreshTimer = null;
2114
+
2115
+ const badge = (st) => {
2116
+ const map = {
2117
+ ok: ['KURULU', 'var(--ok)'],
2118
+ downloaded: ['İNDİRİLDİ', 'var(--ok)'],
2119
+ loading: ['İNİYOR…', '#d9a441'],
2120
+ partial: ['İNİYOR… (devam)', '#d9a441'],
2121
+ missing: ['İNDİRİLİYOR…', '#d9a441'],
2122
+ cloud: ['BULUT', 'var(--muted)'],
2123
+ optional: ['OPSİYONEL', 'var(--muted)'],
2124
+ };
2125
+ const [txt, color] = map[st] || [st, 'var(--muted)'];
2126
+ return '<span style="color:' + color + ';font-weight:700;font-size:11px;flex:none">' + txt + '</span>';
2127
+ };
2128
+
2129
+ /* eksik bileşenleri OTOMATİK başlat (bileşen başına bir kez) */
2130
+ const autoStart = (rows) => {
2131
+ for (const r of rows) {
2132
+ if (autoStarted.has(r.id)) continue;
2133
+ if (r.id === 'stt' && ['missing', 'partial', 'loading'].includes(r.state)) {
2134
+ autoStarted.add('stt');
2135
+ beast.sttPrefetchNow().catch(() => {});
2136
+ }
2137
+ if (r.id === 'emb' && ['missing', 'partial'].includes(r.state)) {
2138
+ autoStarted.add('emb');
2139
+ beast.embedPrefetch().catch(() => {});
2140
+ }
2141
+ }
2142
+ };
2143
+
2144
+ const render = (rows) => {
2145
+ const el = $('#instRows');
2146
+ if (!el) return;
2147
+ if (!rows || !rows.length) { el.innerHTML = '<div class="sub">Durum alınamadı</div>'; return; }
2148
+ autoStart(rows);
2149
+ el.innerHTML = rows.map((r) => (
2150
+ '<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--border)">' +
2151
+ '<div style="flex:1;min-width:0">' +
2152
+ '<div style="font-size:13px">' + escapeHtml(r.name) + '</div>' +
2153
+ '<div class="sub" style="font-size:11px">' + escapeHtml(r.detail || '') + (r.mb ? ' · ' + r.mb + ' MB' : '') + '</div>' +
2154
+ '</div>' +
2155
+ badge(r.state) +
2156
+ '</div>'
2157
+ )).join('');
2158
+ };
2159
+
2160
+ const refresh = async () => {
2161
+ if (pane.hidden) return;
2162
+ const rows = await beast.installStatus().catch(() => []);
2163
+ render(rows);
2164
+ };
2165
+
2166
+ if (refreshTimer) clearInterval(refreshTimer);
2167
+ await refresh();
2168
+ refreshTimer = setInterval(refresh, 4000);
2169
+ }
2100
2170
 
2101
2171
  async function renderTtsPane() { const pane = $('#tab-tts');
2102
2172
  if (!pane) return;
2173
+ /* STT DURUMU: model indirildi mi / iniyor mu / hangi motor */
2174
+ let stt = null;
2175
+ try { stt = await beast.sttStatus(); } catch {}
2176
+ const stateText = {
2177
+ ready: 'HAZIR — model yüklü',
2178
+ loading: 'İNİYOR / yükleniyor…',
2179
+ downloaded: 'indirildi — ilk kullanımda yüklenir',
2180
+ partial: 'kısmen indi — devam edecek',
2181
+ missing: 'henüz indirilmedi',
2182
+ cloud: 'bulut motoru — indirme gerekmez',
2183
+ };
2184
+ const sttLine = stt
2185
+ ? stt.engineLabel + ' · ' + (stateText[stt.state] || stt.state) + (stt.mb ? ' · ' + stt.mb + ' MB' : '')
2186
+ : (window.beast ? 'bilinmiyor' : '');
2103
2187
  const edgeOpts = [
2104
2188
  ['tr-TR-AhmetNeural', 'Ahmet — Türkçe (erkek)'],
2105
2189
  ['tr-TR-EmelNeural', 'Emel — Türkçe (kadın)'],
@@ -2112,6 +2196,10 @@ async function renderTtsPane() { const pane = $('#tab-tts');
2112
2196
  .join('');
2113
2197
  pane.innerHTML =
2114
2198
  '<h2>' + _t('tts_h2') + '</h2><div class="sub">' + _t('tts_sub') + '</div>' +
2199
+ '<div class="sub" style="margin:10px 0 4px;font-weight:700;color:var(--accent)">STT (Ses → Yazı)</div>' +
2200
+ `<div class="sub" id="sttStatusTxt" style="margin-bottom:8px">${sttLine}</div>` +
2201
+ `<button id="sttDlBtn" class="btn ghost" style="margin-bottom:14px">${_t('stt_download_now')}</button>` +
2202
+ '<div class="sub" style="margin:10px 0 4px;font-weight:700;color:var(--accent)">TTS (Yazı → Ses)</div>' +
2115
2203
  `<div class="form-grid" style="grid-template-columns:auto 1fr 1fr;align-items:center;margin-top:10px">
2116
2204
  <label class="lock-row"><input type="checkbox" id="ttsOn" /><span>${_t('tts_active')}</span></label>
2117
2205
  <label style="grid-column:1">${_t('tts_engine')}</label>
@@ -2153,6 +2241,24 @@ async function renderTtsPane() { const pane = $('#tab-tts');
2153
2241
  } catch {}
2154
2242
  syncEngineUi();
2155
2243
  $('#ttsEngine').addEventListener('change', syncEngineUi);
2244
+
2245
+ /* STT durum satırı: yenile + şimdi indir (yüklenirken 3 sn'de bir güncellenir) */
2246
+ const refreshSttStatus = async () => {
2247
+ try {
2248
+ stt = await beast.sttStatus();
2249
+ const el = $('#sttStatusTxt');
2250
+ if (el && stt) el.textContent = stt.engineLabel + ' · ' + (stateText[stt.state] || stt.state) + (stt.mb ? ' · ' + stt.mb + ' MB' : '');
2251
+ return stt;
2252
+ } catch { return null; }
2253
+ };
2254
+ $('#sttDlBtn').addEventListener('click', async () => {
2255
+ await beast.sttPrefetchNow().catch(() => {});
2256
+ toast('STT modeli indiriliyor/yükleniyor…');
2257
+ const poll = setInterval(async () => {
2258
+ const s = await refreshSttStatus();
2259
+ if (s && (s.state === 'ready' || s.state === 'cloud')) { clearInterval(poll); toast('STT hazır'); }
2260
+ }, 3000);
2261
+ });
2156
2262
  $('#ttsSave').addEventListener('click', async () => {
2157
2263
  await beast.waSetTts({
2158
2264
  enabled: $('#ttsOn').checked,