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 +1 -1
- package/src/main.js +140 -0
- package/src/preload.js +4 -0
- package/src/renderer/i18n.js +8 -4
- package/src/renderer/index.html +2 -0
- package/src/renderer/renderer.js +107 -1
package/package.json
CHANGED
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'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -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',
|
package/src/renderer/index.html
CHANGED
|
@@ -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>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -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,
|