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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.44.11",
4
+ "version": "2.44.12",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -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
- return new Promise((resolve, reject) => {
206
- const args = [
207
- '--model', voicePath(vid),
208
- '--config', voiceJsonPath(vid),
209
- '--output_file', '-',
210
- '-q',
211
- ];
212
- const p = spawn(exePath(), args, { windowsHide: true, cwd: binDir() });
213
- const chunks = [];
214
- let err = '';
215
- const timer = setTimeout(() => {
216
- try { p.kill(); } catch {}
217
- reject(new Error('piper zaman aşımı'));
218
- }, 120000);
219
- p.stdout.on('data', (d) => chunks.push(d));
220
- p.stderr.on('data', (d) => { err += d.toString(); });
221
- p.on('error', (e) => { clearTimeout(timer); reject(e); });
222
- p.on('close', (code) => {
223
- clearTimeout(timer);
224
- const buf = Buffer.concat(chunks);
225
- if (code !== 0 || buf.length < 44) {
226
- return reject(new Error('piper başarısız (kod ' + code + '): ' + err.slice(0, 140)));
227
- }
228
- resolve({ audio: buf, mime: 'audio/wav' });
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
- p.stdin.write(String(text).slice(0, 4000));
231
- p.stdin.end();
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
- if (explicit === 'groq' || explicit === 'openai' || explicit === 'local') return explicit;
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'),
@@ -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',
@@ -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,