beast-agent 2.44.11 → 2.44.12
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/piper.js +39 -28
- package/src/main.js +14 -2
- package/src/preload.js +1 -0
- package/src/renderer/i18n.js +10 -0
- package/src/renderer/renderer.js +23 -0
package/package.json
CHANGED
package/src/agent/piper.js
CHANGED
|
@@ -195,41 +195,52 @@ async function install(voiceId) {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
/* ---------- sentez: metin → { audio: WAV buffer, mime } ----------
|
|
198
|
+
/* ---------- sentez: metin → { audio: WAV buffer, mime } ----------
|
|
199
|
+
DİKKAT: piper'ı ASLA stdout'a yazdırmıyoruz (--output_file -). Windows'ta
|
|
200
|
+
binary stdout akışı bozuluyor — çıktı gürültülü/cızırtılı geliyor
|
|
201
|
+
(rhasspy/piper#211: "piping raw output... choppy"). Geçici WAV dosyasına
|
|
202
|
+
yazıp okuyoruz; dosya çıktısı temiz. */
|
|
199
203
|
|
|
200
204
|
function synthesize(text, opts = {}) {
|
|
201
205
|
const vid = resolveVoice(opts.voice);
|
|
202
206
|
return (async () => {
|
|
203
207
|
await ensureRuntime();
|
|
204
208
|
await ensureVoice(vid);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
clearTimeout(timer);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return reject(new Error('piper başarısız (kod ' + code + '): ' + err.slice(0, 140)));
|
|
227
|
-
|
|
228
|
-
|
|
209
|
+
const tmpDir = path.join(rootDir(), 'tmp');
|
|
210
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
211
|
+
const outFile = path.join(tmpDir, 'out-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8) + '.wav');
|
|
212
|
+
try {
|
|
213
|
+
await new Promise((resolve, reject) => {
|
|
214
|
+
const args = [
|
|
215
|
+
'--model', voicePath(vid),
|
|
216
|
+
'--config', voiceJsonPath(vid),
|
|
217
|
+
'--output_file', outFile,
|
|
218
|
+
'-q',
|
|
219
|
+
];
|
|
220
|
+
const p = spawn(exePath(), args, { windowsHide: true, cwd: binDir() });
|
|
221
|
+
let err = '';
|
|
222
|
+
const timer = setTimeout(() => {
|
|
223
|
+
try { p.kill(); } catch {}
|
|
224
|
+
reject(new Error('piper zaman aşımı'));
|
|
225
|
+
}, 120000);
|
|
226
|
+
p.stderr.on('data', (d) => { err += d.toString(); });
|
|
227
|
+
p.on('error', (e) => { clearTimeout(timer); reject(e); });
|
|
228
|
+
p.on('close', (code) => {
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
if (code !== 0) return reject(new Error('piper başarısız (kod ' + code + '): ' + err.slice(0, 140)));
|
|
231
|
+
resolve();
|
|
232
|
+
});
|
|
233
|
+
p.stdin.write(String(text).slice(0, 4000));
|
|
234
|
+
p.stdin.end();
|
|
229
235
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
const buf = fs.readFileSync(outFile);
|
|
237
|
+
if (buf.length < 44 || buf.slice(0, 4).toString('latin1') !== 'RIFF') {
|
|
238
|
+
throw new Error('geçersiz WAV çıktısı');
|
|
239
|
+
}
|
|
240
|
+
return { audio: buf, mime: 'audio/wav' };
|
|
241
|
+
} finally {
|
|
242
|
+
try { fs.unlinkSync(outFile); } catch {}
|
|
243
|
+
}
|
|
233
244
|
})();
|
|
234
245
|
}
|
|
235
246
|
|
package/src/main.js
CHANGED
|
@@ -703,7 +703,10 @@ function sttBaseUrl() {
|
|
|
703
703
|
}
|
|
704
704
|
function sttProvider() {
|
|
705
705
|
const explicit = String(settings.sttProvider || '').trim().toLowerCase();
|
|
706
|
-
|
|
706
|
+
/* açık seçim: anahtar yoksa bulut seçimi yerel Whisper'a düşer (sessiz hata olmasın) */
|
|
707
|
+
if (explicit === 'local') return 'local';
|
|
708
|
+
if (explicit === 'groq' && sttGroqKey()) return 'groq';
|
|
709
|
+
if (explicit === 'openai' && (sttOpenaiKey() || sttBaseUrl())) return 'openai';
|
|
707
710
|
if (sttGroqKey()) return 'groq';
|
|
708
711
|
if (sttOpenaiKey() || sttBaseUrl()) return 'openai';
|
|
709
712
|
return 'local';
|
|
@@ -6639,12 +6642,21 @@ ipcMain.handle('stt:transcribe', async (_e, b64, lang, priority) => {
|
|
|
6639
6642
|
|
|
6640
6643
|
ipcMain.handle('stt:lang:get', () => settings.sttLang || 'tr');
|
|
6641
6644
|
|
|
6645
|
+
/* STT MOTORU SEÇİMİ: '' = otomatik (anahtar varsa bulut, yoksa yerel Whisper),
|
|
6646
|
+
'local' = yerel Whisper, 'groq' | 'openai' = bulut */
|
|
6647
|
+
ipcMain.handle('stt:provider:set', (_e, p) => {
|
|
6648
|
+
const v = String(p || '').trim().toLowerCase();
|
|
6649
|
+
settings.sttProvider = ['local', 'groq', 'openai'].includes(v) ? v : '';
|
|
6650
|
+
saveSettings();
|
|
6651
|
+
return { ok: true, pref: settings.sttProvider, provider: sttProvider(), engineLabel: sttEngineLabel() };
|
|
6652
|
+
});
|
|
6653
|
+
|
|
6642
6654
|
/* STT DURUMU: ayarlar ekranı için — model indirildi mi/in-progress mi,
|
|
6643
6655
|
hangi motor, ne kadar disk */
|
|
6644
6656
|
ipcMain.handle('stt:status', () => {
|
|
6645
6657
|
const provider = sttProvider();
|
|
6646
6658
|
const model = sttModelName();
|
|
6647
|
-
const out = { provider, engineLabel: sttEngineLabel(), model };
|
|
6659
|
+
const out = { provider, engineLabel: sttEngineLabel(), model, pref: String(settings.sttProvider || '') };
|
|
6648
6660
|
if (provider !== 'local') { out.state = 'cloud'; return out; }
|
|
6649
6661
|
const dir = path.join(APP_DIR, 'models', ...model.split('/'));
|
|
6650
6662
|
let files = [];
|
package/src/preload.js
CHANGED
|
@@ -67,6 +67,7 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
67
67
|
installStatus: () => ipcRenderer.invoke('install:status'),
|
|
68
68
|
embedPrefetch: () => ipcRenderer.invoke('embed:prefetch'),
|
|
69
69
|
sttLangSet: (lang) => ipcRenderer.invoke('stt:lang:set', lang),
|
|
70
|
+
sttProviderSet: (p) => ipcRenderer.invoke('stt:provider:set', p),
|
|
70
71
|
updateStatus: () => ipcRenderer.invoke('update:status'),
|
|
71
72
|
updateCheck: () => ipcRenderer.invoke('update:check'),
|
|
72
73
|
updateInstall: () => ipcRenderer.invoke('update:install'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -268,6 +268,11 @@
|
|
|
268
268
|
tts_save: 'TTS Kaydet',
|
|
269
269
|
tts_test: 'Test sesi çal',
|
|
270
270
|
stt_download_now: 'Şimdi indir / hazırla',
|
|
271
|
+
stt_engine: 'STT motoru',
|
|
272
|
+
stt_engine_auto: 'Otomatik — anahtar varsa bulut, yoksa yerel Whisper',
|
|
273
|
+
stt_engine_local: 'Whisper — yerel/offline (large-v3-turbo)',
|
|
274
|
+
stt_engine_groq: 'Groq Whisper — bulut (hızlı)',
|
|
275
|
+
stt_engine_openai: 'OpenAI-uyumlu Whisper — bulut',
|
|
271
276
|
tts_note: 'Edge TTS: ücretsiz bulut motoru — anahtar gerekmez (Ahmet/Emel vb.). Piper: tamamen yerel/offline neural motor (ilk kullanımda ~80MB indirir). OpenAI motoru: uyumlu speech API (tts-1, tts-1-hd; sesler alloy, echo, fable, onyx, nova, shimmer).',
|
|
272
277
|
tts_engine: 'Motor',
|
|
273
278
|
tts_edge_voice: 'Edge sesi',
|
|
@@ -994,6 +999,11 @@
|
|
|
994
999
|
tts_save: 'Save TTS',
|
|
995
1000
|
tts_test: 'Play test sound',
|
|
996
1001
|
stt_download_now: 'Download / prepare now',
|
|
1002
|
+
stt_engine: 'STT engine',
|
|
1003
|
+
stt_engine_auto: 'Auto — cloud if a key exists, otherwise local Whisper',
|
|
1004
|
+
stt_engine_local: 'Whisper — local/offline (large-v3-turbo)',
|
|
1005
|
+
stt_engine_groq: 'Groq Whisper — cloud (fast)',
|
|
1006
|
+
stt_engine_openai: 'OpenAI-compatible Whisper — cloud',
|
|
997
1007
|
tts_note: 'Edge TTS: free cloud engine — no key needed (Ahmet/Emel etc.). Piper: fully local/offline neural engine (downloads ~80MB on first use). OpenAI engine: compatible speech API (tts-1, tts-1-hd; voices alloy, echo, fable, onyx, nova, shimmer).',
|
|
998
1008
|
tts_engine: 'Engine',
|
|
999
1009
|
tts_edge_voice: 'Edge voice',
|
package/src/renderer/renderer.js
CHANGED
|
@@ -2592,6 +2592,15 @@ async function renderTtsPane() { const pane = $('#tab-tts');
|
|
|
2592
2592
|
'<h2>' + _t('tts_h2') + '</h2><div class="sub">' + _t('tts_sub') + '</div>' +
|
|
2593
2593
|
'<div class="sub" style="margin:10px 0 4px;font-weight:700;color:var(--accent)">STT (Ses → Yazı)</div>' +
|
|
2594
2594
|
`<div class="sub" id="sttStatusTxt" style="margin-bottom:8px">${sttLine}</div>` +
|
|
2595
|
+
`<div class="form-grid" style="grid-template-columns:auto 1fr;align-items:center;margin-bottom:8px">
|
|
2596
|
+
<label style="grid-column:1">${_t('stt_engine')}</label>
|
|
2597
|
+
<select id="sttEngine" class="inp" style="grid-column:2">
|
|
2598
|
+
<option value="">${_t('stt_engine_auto')}</option>
|
|
2599
|
+
<option value="local">${_t('stt_engine_local')}</option>
|
|
2600
|
+
<option value="groq">${_t('stt_engine_groq')}</option>
|
|
2601
|
+
<option value="openai">${_t('stt_engine_openai')}</option>
|
|
2602
|
+
</select>
|
|
2603
|
+
</div>` +
|
|
2595
2604
|
`<button id="sttDlBtn" class="btn ghost" style="margin-bottom:14px">${_t('stt_download_now')}</button>` +
|
|
2596
2605
|
'<div class="sub" style="margin:10px 0 4px;font-weight:700;color:var(--accent)">TTS (Yazı → Ses)</div>' +
|
|
2597
2606
|
`<div class="form-grid" style="grid-template-columns:auto 1fr 1fr;align-items:center;margin-top:10px">
|
|
@@ -2647,6 +2656,10 @@ async function renderTtsPane() { const pane = $('#tab-tts');
|
|
|
2647
2656
|
$('#ttsVoice').value = tts.voice || 'alloy';
|
|
2648
2657
|
} catch {}
|
|
2649
2658
|
|
|
2659
|
+
/* STT motoru seçimi: '' = otomatik (bulut anahtarı varsa bulut, yoksa yerel) */
|
|
2660
|
+
const sttEngineSel = $('#sttEngine');
|
|
2661
|
+
if (sttEngineSel) sttEngineSel.value = (stt && stt.pref) || '';
|
|
2662
|
+
|
|
2650
2663
|
/* Piper durum satırı: runtime/ses modeli hazır mı (indirilince güncellenir) */
|
|
2651
2664
|
const refreshPiperStatus = async () => {
|
|
2652
2665
|
const v = $('#ttsPiperVoice') ? $('#ttsPiperVoice').value : '';
|
|
@@ -2697,6 +2710,16 @@ async function renderTtsPane() { const pane = $('#tab-tts');
|
|
|
2697
2710
|
if (s && (s.state === 'ready' || s.state === 'cloud')) { clearInterval(poll); toast('STT hazır'); }
|
|
2698
2711
|
}, 3000);
|
|
2699
2712
|
});
|
|
2713
|
+
/* STT motoru: Whisper yerel / Groq / OpenAI-uyumlu — anında kaydedilir */
|
|
2714
|
+
if (sttEngineSel) sttEngineSel.addEventListener('change', async () => {
|
|
2715
|
+
const r = await beast.sttProviderSet(sttEngineSel.value).catch(() => null);
|
|
2716
|
+
if (r && r.ok) {
|
|
2717
|
+
await refreshSttStatus();
|
|
2718
|
+
toast('STT motoru: ' + (r.engineLabel || 'otomatik'));
|
|
2719
|
+
} else {
|
|
2720
|
+
toast('STT motoru değiştirilemedi');
|
|
2721
|
+
}
|
|
2722
|
+
});
|
|
2700
2723
|
$('#ttsSave').addEventListener('click', async () => {
|
|
2701
2724
|
await beast.waSetTts({
|
|
2702
2725
|
enabled: $('#ttsOn').checked,
|